diff --git a/.chronus/changes/show-notarget-dialog-in-vscode-2025-9-31-6-57-48.md b/.chronus/changes/show-notarget-dialog-in-vscode-2025-9-31-6-57-48.md new file mode 100644 index 00000000000..abf6118f391 --- /dev/null +++ b/.chronus/changes/show-notarget-dialog-in-vscode-2025-9-31-6-57-48.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Show NoTarget diagnostic message in vscode diff --git a/packages/compiler/src/server/client-config-provider.ts b/packages/compiler/src/server/client-config-provider.ts index a9d8594ef03..3383d3db3a9 100644 --- a/packages/compiler/src/server/client-config-provider.ts +++ b/packages/compiler/src/server/client-config-provider.ts @@ -6,7 +6,7 @@ interface LSPConfig { emit?: string[]; } -interface Config { +export interface Config { lsp?: LSPConfig; entrypoint?: string[]; } diff --git a/packages/compiler/src/server/compile-service.ts b/packages/compiler/src/server/compile-service.ts index 78b8efbde70..3d52c4c71fb 100644 --- a/packages/compiler/src/server/compile-service.ts +++ b/packages/compiler/src/server/compile-service.ts @@ -16,10 +16,9 @@ import { parse } from "../core/parser.js"; import { getBaseFileName, getDirectoryPath } from "../core/path-utils.js"; import type { CompilerHost, TypeSpecScriptNode } from "../core/types.js"; import { distinctArray } from "../utils/misc.js"; -import { getLocationInYamlScript } from "../yaml/diagnostics.js"; -import { parseYaml } from "../yaml/parser.js"; import { ClientConfigProvider } from "./client-config-provider.js"; import { serverOptions } from "./constants.js"; +import { getDiagnosticRangeInTspConfig } from "./diagnostics.js"; import { resolveEntrypointFile } from "./entrypoint-resolver.js"; import { FileService } from "./file-service.js"; import { FileSystemCache } from "./file-system-cache.js"; @@ -238,25 +237,22 @@ export function createCompileService({ } let uri = document.uri; - let range = Range.create(0, 0, 0, 0); + let range: Range | undefined; if (err.name === "ExternalError" && err.info.kind === "emitter" && configFilePath) { const emitterName = err.info.metadata.name; - const [yamlScript] = parseYaml(await serverHost.compilerHost.readFile(configFilePath)); - const target = getLocationInYamlScript(yamlScript, ["emit", emitterName], "key"); - if (target.pos === 0) { + range = await getDiagnosticRangeInTspConfig( + configFilePath, + serverHost.compilerHost.readFile, + emitterName, + ); + + uri = fileService.getURL(configFilePath); + if (range === undefined) { log({ level: "debug", message: `Unexpected situation, can't find emitter '${emitterName}' in config file '${configFilePath}'`, }); } - uri = fileService.getURL(configFilePath); - const lineAndChar = target.file.getLineAndCharacterOfPosition(target.pos); - range = Range.create( - lineAndChar.line, - lineAndChar.character, - lineAndChar.line, - lineAndChar.character + emitterName.length, - ); } serverHost.sendDiagnostics({ @@ -264,7 +260,7 @@ export function createCompileService({ diagnostics: [ { severity: DiagnosticSeverity.Error, - range, + range: range ?? Range.create(0, 0, 0, 0), message: (err.name === "ExternalError" ? "External compiler error!\n" diff --git a/packages/compiler/src/server/diagnostics.ts b/packages/compiler/src/server/diagnostics.ts index 712201e3d5b..e0447ec04d9 100644 --- a/packages/compiler/src/server/diagnostics.ts +++ b/packages/compiler/src/server/diagnostics.ts @@ -3,6 +3,7 @@ import type { Range, Diagnostic as VSDiagnostic, } from "vscode-languageserver"; +import { Range as VSRange } from "vscode-languageserver"; import type { TextDocument } from "vscode-languageserver-textdocument"; import { DiagnosticSeverity } from "vscode-languageserver/node.js"; import { @@ -11,24 +12,49 @@ import { } from "../core/diagnostics.js"; import { getTypeName } from "../core/helpers/type-name-utils.js"; import type { Program } from "../core/program.js"; -import type { Node, SourceLocation } from "../core/types.js"; -import { Diagnostic } from "../core/types.js"; +import type { Node, SourceFile, SourceLocation } from "../core/types.js"; +import { Diagnostic, NoTarget } from "../core/types.js"; import { isDefined } from "../utils/misc.js"; +import { getLocationInYamlScript } from "../yaml/diagnostics.js"; +import { parseYaml } from "../yaml/parser.js"; +import { Config } from "./client-config-provider.js"; import type { FileService } from "./file-service.js"; import type { ServerSourceFile } from "./types.js"; /** Convert TypeSpec Diagnostic to Lsp diagnostic. Each TypeSpec diagnostic could produce multiple lsp ones when it involve multiple locations. */ -export function convertDiagnosticToLsp( +export async function convertDiagnosticToLsp( fileService: FileService, program: Program, document: TextDocument, diagnostic: Diagnostic, -): [VSDiagnostic, TextDocument][] { - const root = getVSLocation( + clientConfig?: Config | undefined, + readFile: ((path: string) => Promise) | undefined = undefined, +): Promise<[VSDiagnostic, TextDocument][]> { + let root = getVSLocation( fileService, getSourceLocation(diagnostic.target, { locateId: true }), document, ); + + if (root === undefined) { + const emitterName = program.compilerOptions.emit?.find((emitName) => + diagnostic.message.includes(emitName), + ); + const result = await getDiagnosticInTspConfig( + fileService, + program.compilerOptions.config, + readFile, + emitterName, + ); + if (result === NoTarget) { + return getDiagnosticInVsCodeSettings(diagnostic, document, clientConfig, emitterName); + } else if (result !== undefined) { + root = result; + } else { + return []; + } + } + if (root === undefined || !fileService.upToDate(root.document)) return []; const instantiationNodes = getDiagnosticTemplateInstantitationTrace(diagnostic.target); @@ -175,3 +201,75 @@ function convertSeverity(severity: "warning" | "error"): DiagnosticSeverity { return DiagnosticSeverity.Error; } } + +async function getDiagnosticInTspConfig( + fileService: FileService, + configFilePath: string | undefined, + readFile: ((path: string) => Promise) | undefined, + emitterName: string | undefined, +): Promise { + if (configFilePath && readFile && emitterName && emitterName.length > 0) { + const docTspConfig = fileService.getOpenDocument(configFilePath); + if (!docTspConfig) { + return NoTarget; + } + + const range = await getDiagnosticRangeInTspConfig(configFilePath, readFile, emitterName); + if (range === undefined) { + return NoTarget; + } + return { + range, + document: docTspConfig, + }; + } + return undefined; +} + +export async function getDiagnosticRangeInTspConfig( + configFilePath: string, + readFile: (path: string) => Promise, + emitterName: string, +): Promise { + const [yamlScript] = parseYaml(await readFile(configFilePath)); + const target = getLocationInYamlScript(yamlScript, ["emit", emitterName], "key"); + if (target.pos === 0) { + return undefined; + } + + const lineAndChar = target.file.getLineAndCharacterOfPosition(target.pos); + return VSRange.create( + lineAndChar.line, + lineAndChar.character, + lineAndChar.line, + lineAndChar.character + emitterName.length, + ); +} + +function getDiagnosticInVsCodeSettings( + diagnostic: Diagnostic, + document: TextDocument, + clientConfig?: Config | undefined, + emitterName?: string | undefined, +): [VSDiagnostic, TextDocument][] { + let customMsg = ""; + if (clientConfig?.lsp?.emit && clientConfig.lsp.emit.includes(emitterName || "")) { + customMsg = " [It's configured in vscode settings]"; + } else { + customMsg = " [No target source location reported for this Error/Warning]"; + } + + const relatedInformation: DiagnosticRelatedInformation[] = []; + return [ + [ + createLspDiagnostic({ + range: VSRange.create(0, 0, 0, 0), + message: diagnostic.message + customMsg, + severity: convertSeverity(diagnostic.severity), + code: diagnostic.code, + relatedInformation, + }), + document, + ], + ]; +} diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 7eff5cf2f06..cf745b2ab1a 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -739,8 +739,21 @@ export function createServer( } } + // Add a diagnostic slot to tspconfig.yaml + const configDocument = fileService.getOpenDocument(program.compilerOptions.config ?? ""); + if (configDocument && !diagnosticMap.has(configDocument)) { + diagnosticMap.set(configDocument, []); + } + for (const each of program.diagnostics) { - const results = convertDiagnosticToLsp(fileService, program, document, each); + const results = await convertDiagnosticToLsp( + fileService, + program, + document, + each, + clientConfigsProvider?.config, + compilerHost.readFile, + ); for (const result of results) { const [diagnostic, diagDocument] = result; if (each.url) {