diff --git a/.chronus/changes/lsp-diag-url-2024-7-12-14-53-19.md b/.chronus/changes/lsp-diag-url-2024-7-12-14-53-19.md new file mode 100644 index 00000000000..7e26cde6e90 --- /dev/null +++ b/.chronus/changes/lsp-diag-url-2024-7-12-14-53-19.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/compiler" +--- + +Diagnostic code in IDE now link to the linter rule documentation url if applicable diff --git a/packages/compiler/src/core/linter.ts b/packages/compiler/src/core/linter.ts index 93ebaa045cb..568a833f1a5 100644 --- a/packages/compiler/src/core/linter.ts +++ b/packages/compiler/src/core/linter.ts @@ -21,6 +21,9 @@ import { export interface Linter { extendRuleSet(ruleSet: LinterRuleSet): Promise; lint(): readonly Diagnostic[]; + + /** @internal */ + getRuleUrl(ruleId: string): string | undefined; } /** @@ -64,8 +67,13 @@ export function createLinter( return { extendRuleSet, lint, + getRuleUrl, }; + function getRuleUrl(ruleId: string): string | undefined { + return ruleMap.get(ruleId)?.url; + } + async function extendRuleSet(ruleSet: LinterRuleSet): Promise { tracer.trace("extend-rule-set.start", JSON.stringify(ruleSet, null, 2)); const diagnostics = createDiagnosticCollector(); diff --git a/packages/compiler/src/core/program.ts b/packages/compiler/src/core/program.ts index 769a259bda0..526a705275b 100644 --- a/packages/compiler/src/core/program.ts +++ b/packages/compiler/src/core/program.ts @@ -120,6 +120,9 @@ export interface Program { * Project root. If a tsconfig was found/specified this is the directory for the tsconfig.json. Otherwise directory where the entrypoint is located. */ readonly projectRoot: string; + + /** @internal */ + getDiagnosticUrl(diagnostic: Diagnostic): string | undefined; } interface EmitterRef { @@ -192,6 +195,7 @@ export async function compile( resolveTypeReference, getSourceFileLocationContext, projectRoot: getDirectoryPath(options.config ?? resolvedMain ?? ""), + getDiagnosticUrl, }; trace("compiler.options", JSON.stringify(options, null, 2)); @@ -206,6 +210,10 @@ export async function compile( await loadStandardLibrary(); } + function getDiagnosticUrl(diagnostic: Diagnostic): string | undefined { + return linter.getRuleUrl(diagnostic.code); + } + // Load additional imports prior to compilation if (resolvedMain && options.additionalImports) { const importScript = options.additionalImports.map((i) => `import "${i}";`).join("\n"); diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 4dc89f625e7..c059bac5ec2 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -397,6 +397,13 @@ export function createServer(host: ServerHost): Server { const range = Range.create(start, end); const severity = convertSeverity(each.severity); const diagnostic = VSDiagnostic.create(range, each.message, severity, each.code, "TypeSpec"); + + const url = program.getDiagnosticUrl(each); + if (url) { + diagnostic.codeDescription = { + href: url, + }; + } if (each.code === "deprecated") { diagnostic.tags = [DiagnosticTag.Deprecated]; }