diff --git a/.chronus/changes/unused-using-2024-11-26-19-12-40.md b/.chronus/changes/unused-using-2024-11-26-19-12-40.md new file mode 100644 index 0000000000..bc22025918 --- /dev/null +++ b/.chronus/changes/unused-using-2024-11-26-19-12-40.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/compiler" +--- + +Support diagnostics for unused using statement \ No newline at end of file diff --git a/.chronus/changes/unused-using-2024-11-27-14-37-20.md b/.chronus/changes/unused-using-2024-11-27-14-37-20.md new file mode 100644 index 0000000000..029d3ae02b --- /dev/null +++ b/.chronus/changes/unused-using-2024-11-27-14-37-20.md @@ -0,0 +1,14 @@ +--- +changeKind: internal +packages: + - "@typespec/http-specs" + - "@typespec/http" + - "@typespec/openapi" + - "@typespec/openapi3" + - "@typespec/rest" + - "@typespec/streams" + - "@typespec/xml" + - "@typespec/http-server-csharp" +--- + +Fix test for diagnostics of unused using statement \ No newline at end of file diff --git a/packages/compiler/src/core/compiler-code-fixes/remove-unused-code.codefix.ts b/packages/compiler/src/core/compiler-code-fixes/remove-unused-code.codefix.ts new file mode 100644 index 0000000000..8fa20b4fea --- /dev/null +++ b/packages/compiler/src/core/compiler-code-fixes/remove-unused-code.codefix.ts @@ -0,0 +1,16 @@ +import { defineCodeFix, getSourceLocation } from "../diagnostics.js"; +import { type ImportStatementNode, type UsingStatementNode } from "../types.js"; + +/** + * Quick fix that remove unused code. + */ +export function removeUnusedCodeCodeFix(node: ImportStatementNode | UsingStatementNode) { + return defineCodeFix({ + id: "remove-unused-code", + label: `Remove unused code`, + fix: (context) => { + const location = getSourceLocation(node); + return context.replaceText(location, ""); + }, + }); +} diff --git a/packages/compiler/src/core/diagnostics.ts b/packages/compiler/src/core/diagnostics.ts index f234a97512..eb4033ae22 100644 --- a/packages/compiler/src/core/diagnostics.ts +++ b/packages/compiler/src/core/diagnostics.ts @@ -33,7 +33,7 @@ export type DiagnosticHandler = (diagnostic: Diagnostic) => void; export function logDiagnostics(diagnostics: readonly Diagnostic[], logger: LogSink) { for (const diagnostic of diagnostics) { logger.log({ - level: diagnostic.severity, + level: diagnostic.severity === "hint" ? "trace" : diagnostic.severity, message: diagnostic.message, code: diagnostic.code, url: diagnostic.url, @@ -52,7 +52,7 @@ export function formatDiagnostic(diagnostic: Diagnostic, options: FormatDiagnost return formatLog( { code: diagnostic.code, - level: diagnostic.severity, + level: diagnostic.severity === "hint" ? "trace" : diagnostic.severity, message: diagnostic.message, url: diagnostic.url, sourceLocation: getSourceLocation(diagnostic.target, { locateId: true }), diff --git a/packages/compiler/src/core/messages.ts b/packages/compiler/src/core/messages.ts index a0b74d2d58..a999b25528 100644 --- a/packages/compiler/src/core/messages.ts +++ b/packages/compiler/src/core/messages.ts @@ -576,6 +576,12 @@ const diagnostics = { default: "The #deprecated directive cannot be used more than once on the same declaration.", }, }, + "unused-using": { + severity: "hint", + messages: { + default: paramMessage`Unused using: ${"code"}`, + }, + }, /** * Configuration diff --git a/packages/compiler/src/core/name-resolver.ts b/packages/compiler/src/core/name-resolver.ts index 3641bdb78b..7057c220cd 100644 --- a/packages/compiler/src/core/name-resolver.ts +++ b/packages/compiler/src/core/name-resolver.ts @@ -60,7 +60,7 @@ import { Mutable, mutate } from "../utils/misc.js"; import { createSymbol, createSymbolTable, getSymNode } from "./binder.js"; import { compilerAssert } from "./diagnostics.js"; -import { visitChildren } from "./parser.js"; +import { getFirstAncestor, visitChildren } from "./parser.js"; import { Program } from "./program.js"; import { AliasStatementNode, @@ -94,6 +94,7 @@ import { TypeReferenceNode, TypeSpecScriptNode, UnionStatementNode, + UsingStatementNode, } from "./types.js"; export interface NameResolver { @@ -142,6 +143,9 @@ export interface NameResolver { node: TypeReferenceNode | IdentifierNode | MemberExpressionNode, ): ResolutionResult; + /** Get the using statement nodes which is not used in resolving yet */ + getUnusedUsings(): UsingStatementNode[]; + /** Built-in symbols. */ readonly symbols: { /** Symbol for the global namespace */ @@ -175,6 +179,11 @@ export function createResolver(program: Program): NameResolver { mutate(globalNamespaceNode).symbol = globalNamespaceSym; mutate(globalNamespaceSym.exports).set(globalNamespaceNode.id.sv, globalNamespaceSym); + /** + * Tracking the symbols that are used through using. + */ + const usedUsingSym = new Map>(); + const metaTypePrototypes = createMetaTypePrototypes(); const nullSym = createSymbol(undefined, "null", SymbolFlags.None); @@ -222,8 +231,33 @@ export function createResolver(program: Program): NameResolver { resolveTypeReference, getAugmentDecoratorsForSym, + getUnusedUsings, }; + function getUnusedUsings(): UsingStatementNode[] { + const unusedUsings: Set = new Set(); + for (const file of program.sourceFiles.values()) { + const lc = program.getSourceFileLocationContext(file.file); + if (lc.type === "project") { + const usedSym = usedUsingSym.get(file) ?? new Set(); + for (const using of file.usings) { + const table = getNodeLinks(using.name).resolvedSymbol; + let used = false; + for (const [_, sym] of table?.exports ?? new Map()) { + if (usedSym.has(getMergedSymbol(sym))) { + used = true; + break; + } + } + if (used === false) { + unusedUsings.add(using); + } + } + } + } + return [...unusedUsings]; + } + function getAugmentDecoratorsForSym(sym: Sym) { return augmentDecoratorsForSym.get(sym) ?? []; } @@ -960,6 +994,15 @@ export function createResolver(program: Program): NameResolver { if ("locals" in scope && scope.locals !== undefined) { binding = tableLookup(scope.locals, node, options.resolveDecorators); if (binding) { + if (binding.flags & SymbolFlags.Using && binding.symbolSource) { + const fileNode = getFirstAncestor(node, (n) => n.kind === SyntaxKind.TypeSpecScript) as + | TypeSpecScriptNode + | undefined; + if (fileNode) { + usedUsingSym.get(fileNode)?.add(binding.symbolSource) ?? + usedUsingSym.set(fileNode, new Set([binding.symbolSource])); + } + } return resolvedResult(binding); } } @@ -997,6 +1040,10 @@ export function createResolver(program: Program): NameResolver { []), ]); } + if (usingBinding.flags & SymbolFlags.Using && usingBinding.symbolSource) { + usedUsingSym.get(scope)?.add(usingBinding.symbolSource) ?? + usedUsingSym.set(scope, new Set([usingBinding.symbolSource])); + } return resolvedResult(usingBinding.symbolSource!); } } diff --git a/packages/compiler/src/core/program.ts b/packages/compiler/src/core/program.ts index ff73599e6d..980a3b2ad2 100644 --- a/packages/compiler/src/core/program.ts +++ b/packages/compiler/src/core/program.ts @@ -15,6 +15,7 @@ import { PackageJson } from "../types/package-json.js"; import { deepEquals, findProjectRoot, isDefined, mapEquals, mutate } from "../utils/misc.js"; import { createBinder } from "./binder.js"; import { Checker, createChecker } from "./checker.js"; +import { removeUnusedCodeCodeFix } from "./compiler-code-fixes/remove-unused-code.codefix.js"; import { createSuppressCodeFix } from "./compiler-code-fixes/suppress.codefix.js"; import { compilerAssert } from "./diagnostics.js"; import { resolveTypeSpecEntrypoint } from "./entrypoint-resolution.js"; @@ -45,11 +46,13 @@ import { EmitContext, EmitterFunc, Entity, + IdentifierNode, JsSourceFileNode, LibraryInstance, LibraryMetadata, LiteralType, LocationContext, + MemberExpressionNode, ModuleLibraryMetadata, Namespace, NoTarget, @@ -250,6 +253,8 @@ export async function compile( return program; } + validateUnusedCode(); + // Linter stage program.reportDiagnostics(linter.lint()); @@ -260,6 +265,29 @@ export async function compile( return program; + function validateUnusedCode() { + const getUsingName = (node: MemberExpressionNode | IdentifierNode): string => { + if (node.kind === SyntaxKind.MemberExpression) { + return `${getUsingName(node.base)}${node.selector}${node.id.sv}`; + } else { + // identifier node + return node.sv; + } + }; + resolver.getUnusedUsings().forEach((target) => { + reportDiagnostic( + createDiagnostic({ + code: "unused-using", + target: target, + format: { + code: `using ${getUsingName(target.name)}`, + }, + codefixes: [removeUnusedCodeCodeFix(target)], + }), + ); + }); + } + /** * Validate the libraries loaded during the compilation process are compatible. */ diff --git a/packages/compiler/src/core/types.ts b/packages/compiler/src/core/types.ts index ec31817968..93803cbdb7 100644 --- a/packages/compiler/src/core/types.ts +++ b/packages/compiler/src/core/types.ts @@ -2300,7 +2300,7 @@ export interface TemplateInstanceTarget { export type DiagnosticTarget = TypeSpecDiagnosticTarget | SourceLocation; -export type DiagnosticSeverity = "error" | "warning"; +export type DiagnosticSeverity = "error" | "warning" | "hint"; export interface Diagnostic { code: string; @@ -2529,8 +2529,9 @@ export interface DiagnosticDefinition { * Diagnostic severity. * - `warning` - Suppressable, should be used to represent potential issues but not blocking. * - `error` - Non-suppressable, should be used to represent failure to move forward. + * - `hint` - Something to hint to a better way of doing it, like proposing a refactoring. */ - readonly severity: "warning" | "error"; + readonly severity: "warning" | "error" | "hint"; /** Messages that can be reported with the diagnostic. */ readonly messages: M; /** Short description of the diagnostic */ diff --git a/packages/compiler/src/server/diagnostics.ts b/packages/compiler/src/server/diagnostics.ts index f9dbe4718a..5c5f687676 100644 --- a/packages/compiler/src/server/diagnostics.ts +++ b/packages/compiler/src/server/diagnostics.ts @@ -141,11 +141,13 @@ function getVSLocationWithTypeInfo( }; } -function convertSeverity(severity: "warning" | "error"): DiagnosticSeverity { +function convertSeverity(severity: "warning" | "error" | "hint"): DiagnosticSeverity { switch (severity) { case "warning": return DiagnosticSeverity.Warning; case "error": return DiagnosticSeverity.Error; + case "hint": + return DiagnosticSeverity.Hint; } } diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index eb484b0da8..74bc95afbd 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -317,7 +317,7 @@ export function createServer(host: ServerHost): Server { if (!validationResult.valid) { for (const diag of validationResult.diagnostics) { log({ - level: diag.severity, + level: diag.severity === "hint" ? "info" : diag.severity, message: diag.message, detail: { code: diag.code, @@ -490,6 +490,11 @@ export function createServer(host: ServerHost): Server { } if (each.code === "deprecated") { diagnostic.tags = [DiagnosticTag.Deprecated]; + } else if (each.code === "unused-using") { + // Unused or unnecessary code. Diagnostics with this tag are rendered faded out, so no extra work needed from IDE side + // https://vscode-api.js.org/enums/vscode.DiagnosticTag.html#google_vignette + // https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.languageserver.protocol.diagnostictag?view=visualstudiosdk-2022 + diagnostic.tags = [DiagnosticTag.Unnecessary]; } diagnostic.data = { id: diagnosticIdCounter++ }; const diagnostics = diagnosticMap.get(diagDocument); diff --git a/packages/compiler/src/testing/expect.ts b/packages/compiler/src/testing/expect.ts index 4732daddf3..c710c6ca42 100644 --- a/packages/compiler/src/testing/expect.ts +++ b/packages/compiler/src/testing/expect.ts @@ -33,7 +33,7 @@ export interface DiagnosticMatch { /** * Match the severity. */ - severity?: "error" | "warning"; + severity?: "error" | "warning" | "hint"; /** * Name of the file for this diagnostic. diff --git a/packages/compiler/src/testing/test-host.ts b/packages/compiler/src/testing/test-host.ts index 9b38cf0867..4d31bb841b 100644 --- a/packages/compiler/src/testing/test-host.ts +++ b/packages/compiler/src/testing/test-host.ts @@ -242,7 +242,7 @@ export const StandardTestLibrary: TypeSpecTestLibrary = { }; export async function createTestHost(config: TestHostConfig = {}): Promise { - const testHost = await createTestHostInternal(); + const testHost = await createTestHostInternal(config); await testHost.addTypeSpecLibrary(StandardTestLibrary); if (config.libraries) { for (const library of config.libraries) { @@ -257,7 +257,7 @@ export async function createTestRunner(host?: TestHost): Promise { +async function createTestHostInternal(config: TestHostConfig): Promise { let program: Program | undefined; const libraries: TypeSpecTestLibrary[] = []; const testTypes: Record = {}; @@ -315,7 +315,10 @@ async function createTestHostInternal(): Promise { async function compile(main: string, options: CompilerOptions = {}) { const [testTypes, diagnostics] = await compileAndDiagnose(main, options); - expectDiagnosticEmpty(diagnostics); + const filteredDiagnostics = config.diagnosticFilter + ? diagnostics.filter(config.diagnosticFilter) + : diagnostics; + expectDiagnosticEmpty(filteredDiagnostics); return testTypes; } @@ -334,10 +337,13 @@ async function createTestHostInternal(): Promise { } const p = await compileProgram(fileSystem.compilerHost, resolveVirtualPath(mainFile), options); program = p; + const filteredDiagnostics = config.diagnosticFilter + ? p.diagnostics.filter(config.diagnosticFilter) + : p.diagnostics; logVerboseTestOutput((log) => - logDiagnostics(p.diagnostics, createLogger({ sink: fileSystem.compilerHost.logSink })), + logDiagnostics(filteredDiagnostics, createLogger({ sink: fileSystem.compilerHost.logSink })), ); - return [testTypes, p.diagnostics]; + return [testTypes, filteredDiagnostics]; } } diff --git a/packages/compiler/src/testing/types.ts b/packages/compiler/src/testing/types.ts index 833c052097..99204b6060 100644 --- a/packages/compiler/src/testing/types.ts +++ b/packages/compiler/src/testing/types.ts @@ -54,6 +54,8 @@ export interface TypeSpecTestLibrary { export interface TestHostConfig { libraries?: TypeSpecTestLibrary[]; + /** the diagnostics will be ignored if this return false */ + diagnosticFilter?: (diag: Diagnostic) => boolean; } export class TestHostError extends Error { diff --git a/packages/compiler/test/checker/decorators.test.ts b/packages/compiler/test/checker/decorators.test.ts index 41e05c83f1..72e6ff1128 100644 --- a/packages/compiler/test/checker/decorators.test.ts +++ b/packages/compiler/test/checker/decorators.test.ts @@ -11,6 +11,7 @@ import { numericRanges } from "../../src/core/numeric-ranges.js"; import { Numeric } from "../../src/core/numeric.js"; import { BasicTestRunner, + DiagnosticMatch, TestHost, createTestHost, createTestWrapper, @@ -42,6 +43,18 @@ describe("compiler: checker: decorators", () => { }); }); + const unnecessaryDiags: DiagnosticMatch[] = [ + { + code: "unused-using", + message: `Unused using: using TypeSpec.Reflection`, + severity: "hint", + }, + ]; + const compileAndCheckDiags = async (code: string) => { + const [_, diags] = await runner.compileAndDiagnose(code); + expectDiagnostics(diags, [...unnecessaryDiags]); + }; + describe("bind implementation to declaration", () => { let $otherDec: DecoratorFunction; function expectDecorator(ns: Namespace) { @@ -57,7 +70,7 @@ describe("compiler: checker: decorators", () => { }); it("defined at root", async () => { - await runner.compile(` + await compileAndCheckDiags(` extern dec otherDec(target: unknown); `); @@ -67,7 +80,7 @@ describe("compiler: checker: decorators", () => { it("in a namespace", async () => { setTypeSpecNamespace("Foo.Bar", $otherDec); - await runner.compile(` + await compileAndCheckDiags(` namespace Foo.Bar { extern dec otherDec(target: unknown); } @@ -86,7 +99,7 @@ describe("compiler: checker: decorators", () => { it("defined at root", async () => { testJs.$decorators = { "": { otherDec: $otherDec } }; - await runner.compile(` + await compileAndCheckDiags(` extern dec otherDec(target: unknown); `); @@ -96,7 +109,7 @@ describe("compiler: checker: decorators", () => { it("in a namespace", async () => { testJs.$decorators = { "Foo.Bar": { otherDec: $otherDec } }; - await runner.compile(` + await compileAndCheckDiags(` namespace Foo.Bar { extern dec otherDec(target: unknown); } @@ -116,30 +129,36 @@ describe("compiler: checker: decorators", () => { const diagnostics = await runner.diagnose(` dec testDec(target: unknown); `); - expectDiagnostics(diagnostics, { - code: "decorator-extern", - message: "A decorator declaration must be prefixed with the 'extern' modifier.", - }); + expectDiagnostics(diagnostics, [ + { + code: "decorator-extern", + message: "A decorator declaration must be prefixed with the 'extern' modifier.", + }, + ]); }); it("errors if rest parameter type is not an array expression", async () => { const diagnostics = await runner.diagnose(` extern dec testDec(target: unknown, ...rest: string); `); - expectDiagnostics(diagnostics, { - code: "rest-parameter-array", - message: "A rest parameter must be of an array type.", - }); + expectDiagnostics(diagnostics, [ + { + code: "rest-parameter-array", + message: "A rest parameter must be of an array type.", + }, + ]); }); it("errors if extern decorator is missing implementation", async () => { const diagnostics = await runner.diagnose(` extern dec notImplemented(target: unknown); `); - expectDiagnostics(diagnostics, { - code: "missing-implementation", - message: "Extern declaration must have an implementation in JS file.", - }); + expectDiagnostics(diagnostics, [ + { + code: "missing-implementation", + message: "Extern declaration must have an implementation in JS file.", + }, + ]); }); }); @@ -160,6 +179,19 @@ describe("compiler: checker: decorators", () => { }); }); + const unnecessaryDiags: DiagnosticMatch[] = [ + { + code: "unused-using", + message: `Unused using: using TypeSpec.Reflection`, + severity: "hint", + }, + ]; + const compileAndCheckDiags = async (code: string) => { + const [r, diags] = await runner.compileAndDiagnose(code); + expectDiagnostics(diags, [...unnecessaryDiags]); + return r; + }; + function expectDecoratorCalledWith(target: unknown, ...args: unknown[]) { ok(calledArgs, "Decorator was not called."); strictEqual(calledArgs.length, 2 + args.length); @@ -175,7 +207,7 @@ describe("compiler: checker: decorators", () => { } it("calls a decorator with no argument", async () => { - const { Foo } = await runner.compile(` + const { Foo } = await compileAndCheckDiags(` extern dec testDec(target: unknown); @testDec @@ -187,7 +219,7 @@ describe("compiler: checker: decorators", () => { }); it("calls a decorator with arguments", async () => { - const { Foo } = await runner.compile(` + const { Foo } = await compileAndCheckDiags(` extern dec testDec(target: unknown, arg1: valueof string, arg2: valueof string); @testDec("one", "two") @@ -199,7 +231,7 @@ describe("compiler: checker: decorators", () => { }); it("calls a decorator with optional arguments", async () => { - const { Foo } = await runner.compile(` + const { Foo } = await compileAndCheckDiags(` extern dec testDec(target: unknown, arg1: valueof string, arg2?: valueof string); @testDec("one") @@ -211,7 +243,7 @@ describe("compiler: checker: decorators", () => { }); it("calls a decorator with rest arguments", async () => { - const { Foo } = await runner.compile(` + const { Foo } = await compileAndCheckDiags(` extern dec testDec(target: unknown, arg1: valueof string, ...args: valueof string[]); @testDec("one", "two", "three", "four") @@ -230,10 +262,12 @@ describe("compiler: checker: decorators", () => { model Foo {} `); - expectDiagnostics(diagnostics, { - code: "invalid-argument-count", - message: "Expected 2 arguments, but got 1.", - }); + expectDiagnostics(diagnostics, [ + { + code: "invalid-argument-count", + message: "Expected 2 arguments, but got 1.", + }, + ]); expectDecoratorNotCalled(); }); @@ -245,10 +279,12 @@ describe("compiler: checker: decorators", () => { @test model Foo {} `); - expectDiagnostics(diagnostics, { - code: "invalid-argument-count", - message: "Expected 1-2 arguments, but got 3.", - }); + expectDiagnostics(diagnostics, [ + { + code: "invalid-argument-count", + message: "Expected 1-2 arguments, but got 3.", + }, + ]); expectDecoratorCalledWith(Foo, "one", "two"); }); @@ -260,10 +296,12 @@ describe("compiler: checker: decorators", () => { model Foo {} `); - expectDiagnostics(diagnostics, { - code: "invalid-argument-count", - message: "Expected 0 arguments, but got 1.", - }); + expectDiagnostics(diagnostics, [ + { + code: "invalid-argument-count", + message: "Expected 0 arguments, but got 1.", + }, + ]); }); it("errors if not calling with too few arguments with rest", async () => { @@ -274,10 +312,12 @@ describe("compiler: checker: decorators", () => { model Foo {} `); - expectDiagnostics(diagnostics, { - code: "invalid-argument-count", - message: "Expected at least 1 arguments, but got 0.", - }); + expectDiagnostics(diagnostics, [ + { + code: "invalid-argument-count", + message: "Expected at least 1 arguments, but got 0.", + }, + ]); expectDecoratorNotCalled(); }); @@ -304,10 +344,12 @@ describe("compiler: checker: decorators", () => { model Foo {} `); - expectDiagnostics(diagnostics, { - code: "invalid-argument", - message: "Argument of type '123' is not assignable to parameter of type 'string'", - }); + expectDiagnostics(diagnostics, [ + { + code: "invalid-argument", + message: "Argument of type '123' is not assignable to parameter of type 'string'", + }, + ]); expectDecoratorNotCalled(); }); @@ -334,7 +376,7 @@ describe("compiler: checker: decorators", () => { // Regresssion test for https://github.com/microsoft/typespec/issues/3211 it("augmenting a template model property before a decorator declaration resolve the declaration correctly", async () => { - await runner.compile(` + await compileAndCheckDiags(` model Foo { prop: T; } @@ -353,7 +395,7 @@ describe("compiler: checker: decorators", () => { value: string, suppress?: boolean, ): Promise { - await runner.compile(` + await compileAndCheckDiags(` extern dec testDec(target: unknown, arg1: ${type}); ${suppress ? `#suppress "deprecated" "for testing"` : ""} @@ -527,7 +569,9 @@ describe("compiler: checker: decorators", () => { @test model Foo {} `); - expectDiagnosticEmpty(diagnostics.filter((x) => x.code !== "deprecated")); + expectDiagnosticEmpty( + diagnostics.filter((x) => x.code !== "deprecated" && x.code !== "unused-using"), + ); return calledArgs![2]; } diff --git a/packages/compiler/test/checker/unused-using.test.ts b/packages/compiler/test/checker/unused-using.test.ts new file mode 100644 index 0000000000..d64163427d --- /dev/null +++ b/packages/compiler/test/checker/unused-using.test.ts @@ -0,0 +1,885 @@ +import { beforeEach, describe, it } from "vitest"; +import { + TestHost, + createTestHost, + expectDiagnosticEmpty, + expectDiagnostics, +} from "../../src/testing/index.js"; + +describe("compiler: unused using statements", () => { + let testHost: TestHost; + + beforeEach(async () => { + testHost = await createTestHost(); + }); + + it("no unused diagnostic when using is used", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + + using N; + using M; + model Z { a: X, b: Y} + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N; + model X { x: int32 } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + namespace M; + model Y { y: int32 } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnosticEmpty(diagnostics); + }); + + it("report for unused using", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + + model Z { a: N.X, b: Y} + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N; + model X { x: int32 } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + using N; + model Y { y: int32 } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using N", + severity: "hint", + }, + ]); + }); + + it("report for same unused using from different file", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + + using N; + model Z { a: Y, b: Y} + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N; + model X { x: int32 } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + using N; + model Y { y: int32 } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using N", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using N", + severity: "hint", + }, + ]); + }); + + it("report for multiple unused using in one file", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + + using N; + using M; + model Z { a: N.X, b: M.Y} + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N; + model X { x: int32 } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + namespace M; + model Y { y: int32 } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using N", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using M", + severity: "hint", + }, + ]); + }); + + it("report for unused using when there is used using", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + + using N; + using M; + model Z { a: X, b: M.Y} + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N; + model X { x: int32 } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + namespace M; + model Y { y: int32 } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using M", + severity: "hint", + }, + ]); + }); + + it("using in namespaces", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + namespace N{ + model X { x: int32 } + } + namespace M{ + model XX {xx: Z.Y } + } + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace Z; + using N; + using M; + @test model Y { ... X } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using M", + severity: "hint", + }, + ]); + }); + + it("using in the same file", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + namespace N { + using M; + model X { x: XX } + } + namespace M { + using N; + model XX {xx: N.X } + } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using N", + severity: "hint", + }, + ]); + }); + + it("works with dotted namespaces", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + using N.M; + namespace Z { + alias test = Y; + } + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N.M; + model X { x: int32 } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + using N.M; + @test model Y { ...N.M.X } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using N.M", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using N.M", + severity: "hint", + }, + ]); + }); + + it("TypeSpec.Xyz namespace doesn't need TypeSpec prefix in using", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + import "./c.tsp"; + using TypeSpec.Xyz; + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace TypeSpec.Xyz; + model X { x: Y } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + using Xyz; + @test model Y { ... Xyz.X, ... Z } + `, + ); + testHost.addTypeSpecFile( + "c.tsp", + ` + using Xyz; + @test model Z { ... X } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using TypeSpec.Xyz", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using Xyz", + severity: "hint", + }, + ]); + }); + + it("2 namespace with the same last name", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + using N.A; + using M.A; + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N.A { + model B { } + } + namespace M.A { + model B { } + } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using N.A", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using M.A", + severity: "hint", + }, + ]); + }); + + it("one namespace from two file, no unused using when just refering to one of them", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + using N.M; + model Z { b: B2} + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N.M { + model B2 { } + } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + namespace N.M { + model B { } + } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnosticEmpty(diagnostics); + }); + + it("one namespace from two file, show unused using when none is referred", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + using N.M; + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N.M { + model B2 { } + } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + namespace N.M { + model B { } + } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using N.M", + severity: "hint", + }, + ]); + }); + + it("unused invalid using, no unnecessary diagnostic when there is other error", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + using N.M2; + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N.M; + model X { x: int32 } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "invalid-ref", + severity: "error", + }, + ]); + }); + + it("unused using along with duplicate usings, no unnecessary diagnostic when there is other error", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + using N.M; + using N.M; + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N.M; + model X { x: int32 } + `, + ); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "duplicate-using", + message: 'duplicate using of "N.M" namespace', + }, + { + code: "duplicate-using", + message: 'duplicate using of "N.M" namespace', + }, + ]); + }); + + it("does not throws errors for different usings with the same bindings if not used", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + + namespace Ns { + using N; + namespace Ns2 { + using M; + namespace Ns3 { + using L; + alias a2 = A2; + } + } + alias a = A3; + } + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N { + model A1 { } + } + namespace M { + model A { } + } + namespace L { + model A2 { } + } + namespace Ns.N { + model A3 { } + } + `, + ); + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using M", + severity: "hint", + }, + ]); + }); + + it("using multi-level namespace", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + import "./b.tsp"; + import "./c.tsp"; + import "./d.tsp"; + + namespace Ns1 { + model A1 { } + namespace Ns2 { + model A2 { } + namespace Ns3 { + model A3 { } + } + } + } + model Test { + a: A; + b: B; + c: C; + d: D; + } + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + using Ns1; + using Ns1.Ns2; + using Ns1.Ns2.Ns3; + model A { } + `, + ); + testHost.addTypeSpecFile( + "b.tsp", + ` + using Ns1; + using Ns1.Ns2; + using Ns1.Ns2.Ns3; + model B { a: A1 } + `, + ); + testHost.addTypeSpecFile( + "c.tsp", + ` + using Ns1; + using Ns1.Ns2; + using Ns1.Ns2.Ns3; + model C { a: A2 } + `, + ); + testHost.addTypeSpecFile( + "d.tsp", + ` + using Ns1; + using Ns1.Ns2; + using Ns1.Ns2.Ns3; + model D { a: A3 } + `, + ); + const diagnostics = await testHost.diagnose("./"); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using Ns1", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using Ns1.Ns2", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using Ns1.Ns2.Ns3", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using Ns1.Ns2", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using Ns1.Ns2.Ns3", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using Ns1", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using Ns1.Ns2.Ns3", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using Ns1", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using Ns1.Ns2", + severity: "hint", + }, + ]); + }); + + it("no report unused using when the ref is ambiguous (error) while others not impacted", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./a.tsp"; + using N; + using M; + model B extends A {}; + model B2 extends C {}; + `, + ); + testHost.addTypeSpecFile( + "a.tsp", + ` + namespace N { + model A { } + } + namespace M { + model A { } + model C { } + } + `, + ); + + const diagnostics = await testHost.diagnose("./", { nostdlib: true }); + expectDiagnostics(diagnostics, [ + { + code: "ambiguous-symbol", + message: + '"A" is an ambiguous name between N.A, M.A. Try using fully qualified name instead: N.A, M.A', + }, + ]); + }); + + it("no not-used using for decorator", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./doc.js"; + namespace Test; + using A; + @dec1 + namespace Foo {} + `, + ); + + testHost.addJsFile("doc.js", { + namespace: "Test.A", + $dec1() {}, + }); + + const diagnostics = await testHost.diagnose("./"); + expectDiagnosticEmpty(diagnostics); + }); + + it("unused using for TypeSpec", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + namespace Foo; + using TypeSpec; + model Bar { a : TypeSpec.int32 } + `, + ); + + const diagnostics = await testHost.diagnose("./", { nostdlib: true }); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using TypeSpec", + severity: "hint", + }, + ]); + }); + + it("works same name in different namespace", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./other.tsp"; + namespace Main { + using Other; + model OtherModel { + } + model MainModel { + a: OtherModel; + } + } + `, + ); + testHost.addTypeSpecFile( + "other.tsp", + ` + namespace Other { + model OtherModel { + } + } + `, + ); + const diagnostics = await testHost.diagnose("./", { nostdlib: true }); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using Other", + severity: "hint", + }, + ]); + }); + + it("not used using for lib", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "my-lib"; + using LibNs; + model A { x: int16; } + `, + ); + testHost.addTypeSpecFile( + "node_modules/my-lib/package.json", + JSON.stringify({ + name: "my-test-lib", + exports: { ".": { typespec: "./main.tsp" } }, + }), + ); + testHost.addTypeSpecFile( + "node_modules/my-lib/main.tsp", + ` + import "./lib-a.tsp"; + namespace LibNs { + model LibMainModel{ } + } + `, + ); + testHost.addTypeSpecFile( + "node_modules/my-lib/lib-a.tsp", + ` + namespace LibNs; + model LibAModel { } + `, + ); + + const diagnostics = await testHost.diagnose("./", { nostdlib: true }); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using LibNs", + severity: "hint", + }, + ]); + }); + + it("no not-used using for lib", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "my-lib"; + using LibNs; + model A { x: LibAModel; } + `, + ); + testHost.addTypeSpecFile( + "node_modules/my-lib/package.json", + JSON.stringify({ + name: "my-test-lib", + exports: { ".": { typespec: "./main.tsp" } }, + }), + ); + testHost.addTypeSpecFile( + "node_modules/my-lib/main.tsp", + ` + import "./lib-a.tsp"; + namespace LibNs { + model LibMainModel{ } + } + `, + ); + testHost.addTypeSpecFile( + "node_modules/my-lib/lib-a.tsp", + ` + namespace LibNs; + model LibAModel { } + `, + ); + + const diagnostics = await testHost.diagnose("./", { nostdlib: true }); + expectDiagnosticEmpty(diagnostics); + }); + + it("unused using when type referenced directly", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + import "./other.tsp"; + namespace Main { + using Other; + + model MainModel { + a: Other.OtherModel; + } + } + `, + ); + testHost.addTypeSpecFile( + "other.tsp", + ` + namespace Other { + model OtherModel { + } + } + `, + ); + const diagnostics = await testHost.diagnose("./", { nostdlib: true }); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using Other", + severity: "hint", + }, + ]); + }); +}); diff --git a/packages/compiler/test/checker/using.test.ts b/packages/compiler/test/checker/using.test.ts index 736e4b5052..13aac35a4e 100644 --- a/packages/compiler/test/checker/using.test.ts +++ b/packages/compiler/test/checker/using.test.ts @@ -1,12 +1,7 @@ import { rejects, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; import { Model } from "../../src/core/types.js"; -import { - TestHost, - createTestHost, - expectDiagnosticEmpty, - expectDiagnostics, -} from "../../src/testing/index.js"; +import { TestHost, createTestHost, expectDiagnostics } from "../../src/testing/index.js"; describe("compiler: using statements", () => { let testHost: TestHost; @@ -21,6 +16,7 @@ describe("compiler: using statements", () => { ` import "./a.tsp"; import "./b.tsp"; + alias foo = Y; `, ); testHost.addTypeSpecFile( @@ -51,6 +47,7 @@ describe("compiler: using statements", () => { ` import "./a.tsp"; import "./b.tsp"; + alias foo = Z.Y; `, ); testHost.addTypeSpecFile( @@ -82,6 +79,7 @@ describe("compiler: using statements", () => { ` import "./a.tsp"; import "./b.tsp"; + alias foo = Y; `, ); testHost.addTypeSpecFile( @@ -125,8 +123,14 @@ describe("compiler: using statements", () => { `, ); testHost.addTypeSpecFile("b.tsp", `namespace B { model BModel {} }`); - - expectDiagnosticEmpty(await testHost.diagnose("./")); + const diags = await testHost.diagnose("./"); + expectDiagnostics(diags, [ + { + code: "unused-using", + message: "Unused using: using A", + severity: "hint", + }, + ]); }); it("TypeSpec.Xyz namespace doesn't need TypeSpec prefix in using", async () => { @@ -135,6 +139,7 @@ describe("compiler: using statements", () => { ` import "./a.tsp"; import "./b.tsp"; + alias foo = Y; `, ); testHost.addTypeSpecFile( @@ -189,7 +194,18 @@ describe("compiler: using statements", () => { ); const diagnostics = await testHost.diagnose("./"); - expectDiagnosticEmpty(diagnostics); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using N.A", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using M.A", + severity: "hint", + }, + ]); }); describe("duplicate usings", () => { @@ -211,7 +227,23 @@ describe("compiler: using statements", () => { testHost.addTypeSpecFile("a.tsp", `namespace A { model AModel {} }`); const diagnostics = await testHost.diagnose("./"); - expectDiagnosticEmpty(diagnostics); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using A", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using A", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using A", + severity: "hint", + }, + ]); }); it("throws errors for duplicate imported usings", async () => { @@ -276,7 +308,18 @@ describe("compiler: using statements", () => { ); const diagnostics = await testHost.diagnose("./"); - expectDiagnosticEmpty(diagnostics); + expectDiagnostics(diagnostics, [ + { + code: "unused-using", + message: "Unused using: using N", + severity: "hint", + }, + { + code: "unused-using", + message: "Unused using: using M", + severity: "hint", + }, + ]); }); it("report ambiguous diagnostics when using name present in multiple using", async () => { @@ -310,13 +353,16 @@ describe("compiler: using statements", () => { `, ); const diagnostics = await testHost.diagnose("./", { nostdlib: true }); - expectDiagnostics(diagnostics, [ - { - code: "ambiguous-symbol", - message: - '"A" is an ambiguous name between N.A, M.A. Try using fully qualified name instead: N.A, M.A', - }, - ]); + expectDiagnostics( + diagnostics.filter((d) => d.code !== "unused-using"), + [ + { + code: "ambiguous-symbol", + message: + '"A" is an ambiguous name between N.A, M.A. Try using fully qualified name instead: N.A, M.A', + }, + ], + ); }); it("report ambiguous diagnostics when symbol exists in using namespace and global namespace", async () => { @@ -347,13 +393,16 @@ describe("compiler: using statements", () => { ); const diagnostics = await testHost.diagnose("./", { nostdlib: true }); - expectDiagnostics(diagnostics, [ - { - code: "ambiguous-symbol", - message: - '"M" is an ambiguous name between global.M, B.M. Try using fully qualified name instead: global.M, B.M', - }, - ]); + expectDiagnostics( + diagnostics.filter((d) => d.code !== "unused-using"), + [ + { + code: "ambiguous-symbol", + message: + '"M" is an ambiguous name between global.M, B.M. Try using fully qualified name instead: global.M, B.M', + }, + ], + ); }); it("reports ambiguous symbol for decorator", async () => { @@ -380,12 +429,15 @@ describe("compiler: using statements", () => { }); const diagnostics = await testHost.diagnose("./"); - expectDiagnostics(diagnostics, [ - { - code: "ambiguous-symbol", - message: `"doc" is an ambiguous name between TypeSpec.doc, Test.A.doc. Try using fully qualified name instead: TypeSpec.doc, Test.A.doc`, - }, - ]); + expectDiagnostics( + diagnostics.filter((d) => d.code !== "unused-using"), + [ + { + code: "ambiguous-symbol", + message: `"doc" is an ambiguous name between TypeSpec.doc, Test.A.doc. Try using fully qualified name instead: TypeSpec.doc, Test.A.doc`, + }, + ], + ); }); it("reports ambiguous symbol for decorator with missing implementation", async () => { @@ -406,13 +458,16 @@ describe("compiler: using statements", () => { ); const diagnostics = await testHost.diagnose("./"); - expectDiagnostics(diagnostics, [ - { - code: "ambiguous-symbol", - message: `"doc" is an ambiguous name between TypeSpec.doc, Test.A.doc. Try using fully qualified name instead: TypeSpec.doc, Test.A.doc`, - }, - { code: "missing-implementation" }, - ]); + expectDiagnostics( + diagnostics.filter((d) => d.code !== "unused-using"), + [ + { + code: "ambiguous-symbol", + message: `"doc" is an ambiguous name between TypeSpec.doc, Test.A.doc. Try using fully qualified name instead: TypeSpec.doc, Test.A.doc`, + }, + { code: "missing-implementation" }, + ], + ); }); it("ambiguous use doesn't affect other files", async () => { @@ -456,14 +511,17 @@ describe("compiler: using statements", () => { `, ); const diagnostics = await testHost.diagnose("./"); - expectDiagnostics(diagnostics, [ - { - code: "ambiguous-symbol", - message: - '"A" is an ambiguous name between N.A, M.A. Try using fully qualified name instead: N.A, M.A', - file: /ambiguous\.tsp$/, - }, - ]); + expectDiagnostics( + diagnostics.filter((d) => d.code !== "unused-using"), + [ + { + code: "ambiguous-symbol", + message: + '"A" is an ambiguous name between N.A, M.A. Try using fully qualified name instead: N.A, M.A', + file: /ambiguous\.tsp$/, + }, + ], + ); }); it("resolves 'local' decls over usings", async () => { @@ -493,11 +551,19 @@ describe("compiler: using statements", () => { `, ); - const { B } = (await testHost.compile("./")) as { + const [result, diags] = await testHost.compileAndDiagnose("./"); + const { B } = result as { B: Model; }; strictEqual(B.properties.size, 1); strictEqual(B.properties.get("a")!.type.kind, "Union"); + expectDiagnostics(diags, [ + { + code: "unused-using", + message: "Unused using: using N", + severity: "hint", + }, + ]); }); it("usings are local to a file", async () => { @@ -585,10 +651,12 @@ describe("emit diagnostics", () => { const diagnostics = await diagnose(` using NotDefined; `); - expectDiagnostics(diagnostics, { - code: "invalid-ref", - message: "Unknown identifier NotDefined", - }); + expectDiagnostics(diagnostics, [ + { + code: "invalid-ref", + message: "Unknown identifier NotDefined", + }, + ]); }); describe("when using non-namespace types", () => { @@ -605,10 +673,12 @@ describe("emit diagnostics", () => { using Target; ${code} `); - expectDiagnostics(diagnostics, { - code: "using-invalid-ref", - message: "Using must refer to a namespace", - }); + expectDiagnostics(diagnostics, [ + { + code: "using-invalid-ref", + message: "Using must refer to a namespace", + }, + ]); }); }); }); diff --git a/packages/http-server-csharp/test/test-host.ts b/packages/http-server-csharp/test/test-host.ts index 92ccc01168..31fdf67f7b 100644 --- a/packages/http-server-csharp/test/test-host.ts +++ b/packages/http-server-csharp/test/test-host.ts @@ -13,6 +13,7 @@ export async function createCSharpServiceEmitterTestHost() { VersioningTestLibrary, CSharpServiceEmitterTestLibrary, ], + diagnosticFilter: (diag) => diag.severity !== "hint", }); return result; diff --git a/packages/http-specs/specs/server/path/multiple/main.tsp b/packages/http-specs/specs/server/path/multiple/main.tsp index 868684aeb3..3535d7c585 100644 --- a/packages/http-specs/specs/server/path/multiple/main.tsp +++ b/packages/http-specs/specs/server/path/multiple/main.tsp @@ -5,7 +5,6 @@ import "@typespec/versioning"; using Http; using Spector; using TypeSpec.Versioning; -using TypeSpec.Rest; @versioned(Versions) @service({ diff --git a/packages/http-specs/specs/special-headers/conditional-request/main.tsp b/packages/http-specs/specs/special-headers/conditional-request/main.tsp index 3f6b6d8979..820f2427f5 100644 --- a/packages/http-specs/specs/special-headers/conditional-request/main.tsp +++ b/packages/http-specs/specs/special-headers/conditional-request/main.tsp @@ -4,7 +4,6 @@ import "@typespec/spector"; using Http; using Spector; -using TypeSpec.Versioning; @doc("Illustrates conditional request headers") @scenarioService("/special-headers/conditional-request") diff --git a/packages/http-specs/specs/special-headers/repeatability/main.tsp b/packages/http-specs/specs/special-headers/repeatability/main.tsp index e9bb0067ef..3c7a851137 100644 --- a/packages/http-specs/specs/special-headers/repeatability/main.tsp +++ b/packages/http-specs/specs/special-headers/repeatability/main.tsp @@ -4,7 +4,6 @@ import "@typespec/spector"; using Http; using Spector; -using TypeSpec.Versioning; @doc("Illustrates OASIS repeatability headers") @scenarioService("/special-headers/repeatability") diff --git a/packages/http/test/test-host.ts b/packages/http/test/test-host.ts index e3306f1eef..e8f4d7d013 100644 --- a/packages/http/test/test-host.ts +++ b/packages/http/test/test-host.ts @@ -18,6 +18,7 @@ import { HttpTestLibrary } from "../src/testing/index.js"; export async function createHttpTestHost(): Promise { return createTestHost({ libraries: [HttpTestLibrary], + diagnosticFilter: (diag) => diag.severity !== "hint", }); } export async function createHttpTestRunner(): Promise { @@ -102,7 +103,7 @@ export async function getOperationsWithServiceNamespace( }, ); const [services] = getAllHttpServices(runner.program, routeOptions); - return [services[0].operations, runner.program.diagnostics]; + return [services[0].operations, runner.program.diagnostics.filter((d) => d.severity !== "hint")]; } export async function getOperations(code: string): Promise { diff --git a/packages/openapi/test/test-host.ts b/packages/openapi/test/test-host.ts index c32f07e16b..ccdff53f62 100644 --- a/packages/openapi/test/test-host.ts +++ b/packages/openapi/test/test-host.ts @@ -6,6 +6,7 @@ import { OpenAPITestLibrary } from "../src/testing/index.js"; export async function createOpenAPITestHost() { return createTestHost({ libraries: [HttpTestLibrary, RestTestLibrary, OpenAPITestLibrary], + diagnosticFilter: (diag) => diag.severity !== "hint", }); } export async function createOpenAPITestRunner() { diff --git a/packages/openapi3/test/test-host.ts b/packages/openapi3/test/test-host.ts index 2be1f42e0f..5538f50ff6 100644 --- a/packages/openapi3/test/test-host.ts +++ b/packages/openapi3/test/test-host.ts @@ -25,6 +25,7 @@ export async function createOpenAPITestHost() { OpenAPITestLibrary, OpenAPI3TestLibrary, ], + diagnosticFilter: (diag) => diag.severity !== "hint", }); } diff --git a/packages/openapi3/test/tsp-openapi3/utils/tsp-for-openapi3.ts b/packages/openapi3/test/tsp-openapi3/utils/tsp-for-openapi3.ts index f746fe549e..43c7031337 100644 --- a/packages/openapi3/test/tsp-openapi3/utils/tsp-for-openapi3.ts +++ b/packages/openapi3/test/tsp-openapi3/utils/tsp-for-openapi3.ts @@ -57,6 +57,7 @@ export async function compileForOpenAPI3({ parameters, paths, schemas }: OpenAPI const testableCode = wrapCodeInTest(code); const host = await createTestHost({ libraries: [HttpTestLibrary, OpenAPITestLibrary, OpenAPI3TestLibrary], + diagnosticFilter: (diag) => diag.severity !== "hint", }); host.addTypeSpecFile("main.tsp", testableCode); diff --git a/packages/playground-website/samples/unions.tsp b/packages/playground-website/samples/unions.tsp index 6a6746d1af..5e07c65fe4 100644 --- a/packages/playground-website/samples/unions.tsp +++ b/packages/playground-website/samples/unions.tsp @@ -6,7 +6,6 @@ import "@typespec/openapi3"; title: "Widget Service", }) namespace DemoService; -using TypeSpec.Rest; using TypeSpec.Http; using TypeSpec.OpenAPI; diff --git a/packages/rest/test/test-host.ts b/packages/rest/test/test-host.ts index 564e048506..1315142947 100644 --- a/packages/rest/test/test-host.ts +++ b/packages/rest/test/test-host.ts @@ -19,6 +19,7 @@ import { RestTestLibrary } from "../src/testing/index.js"; export async function createRestTestHost(): Promise { return createTestHost({ libraries: [HttpTestLibrary, RestTestLibrary], + diagnosticFilter: (diag) => diag.severity !== "hint", }); } export async function createRestTestRunner(): Promise { @@ -95,7 +96,7 @@ export async function getOperationsWithServiceNamespace( }, ); const [services] = getAllHttpServices(runner.program, routeOptions); - return [services[0].operations, runner.program.diagnostics]; + return [services[0].operations, runner.program.diagnostics.filter((d) => d.severity !== "hint")]; } export async function getOperations(code: string): Promise { diff --git a/packages/samples/specs/grpc-kiosk-example/types.tsp b/packages/samples/specs/grpc-kiosk-example/types.tsp index 2c8af4f05a..6ac5262597 100644 --- a/packages/samples/specs/grpc-kiosk-example/types.tsp +++ b/packages/samples/specs/grpc-kiosk-example/types.tsp @@ -1,7 +1,5 @@ import "@typespec/openapi"; -using TypeSpec.Http; - // // types.tsp // diff --git a/packages/samples/specs/polymorphism/polymorphism.tsp b/packages/samples/specs/polymorphism/polymorphism.tsp index 494bf24852..f1d8372d31 100644 --- a/packages/samples/specs/polymorphism/polymorphism.tsp +++ b/packages/samples/specs/polymorphism/polymorphism.tsp @@ -1,7 +1,6 @@ import "@typespec/rest"; using TypeSpec.Http; -using TypeSpec.Rest; @service({ title: "Polymorphism sample", diff --git a/packages/samples/specs/use-versioned-lib/main.tsp b/packages/samples/specs/use-versioned-lib/main.tsp index d52b8b38e4..2f7aea72db 100644 --- a/packages/samples/specs/use-versioned-lib/main.tsp +++ b/packages/samples/specs/use-versioned-lib/main.tsp @@ -10,6 +10,5 @@ using TypeSpec.Versioning; }) @useDependency(Library.Versions.`1.0`) namespace VersionedApi; -using TypeSpec.Http; op read(): Library.PetToy; diff --git a/packages/samples/specs/versioning/main.tsp b/packages/samples/specs/versioning/main.tsp index ffff9cc5d0..7ba711d041 100644 --- a/packages/samples/specs/versioning/main.tsp +++ b/packages/samples/specs/versioning/main.tsp @@ -3,7 +3,6 @@ import "@typespec/rest"; import "./library.tsp"; using TypeSpec.Versioning; -using TypeSpec.Rest; @service({ title: "Pet Store Service", diff --git a/packages/samples/specs/visibility/visibility.tsp b/packages/samples/specs/visibility/visibility.tsp index 03f519c3b8..6f0afcb42e 100644 --- a/packages/samples/specs/visibility/visibility.tsp +++ b/packages/samples/specs/visibility/visibility.tsp @@ -2,7 +2,6 @@ import "@typespec/http"; import "@typespec/rest"; using TypeSpec.Http; -using TypeSpec.Rest; @service({ title: "Visibility sample", diff --git a/packages/streams/test/test-host.ts b/packages/streams/test/test-host.ts index 1bb51f7ac3..31907bec5e 100644 --- a/packages/streams/test/test-host.ts +++ b/packages/streams/test/test-host.ts @@ -4,6 +4,7 @@ import { StreamsTestLibrary } from "../src/testing/index.js"; export async function createStreamsTestHost() { return createTestHost({ libraries: [StreamsTestLibrary], + diagnosticFilter: (diag) => diag.severity !== "hint", }); } diff --git a/packages/xml/test/test-host.ts b/packages/xml/test/test-host.ts index ada33dbc4c..f0285cd5d7 100644 --- a/packages/xml/test/test-host.ts +++ b/packages/xml/test/test-host.ts @@ -4,6 +4,7 @@ import { XmlTestLibrary } from "../src/testing/index.js"; export async function createXmlTestHost() { return createTestHost({ libraries: [XmlTestLibrary], + diagnosticFilter: (diag) => diag.severity !== "hint", }); } export async function createXmlTestRunner() {