diff --git a/.changeset/heavy-radios-lose.md b/.changeset/heavy-radios-lose.md new file mode 100644 index 000000000..8eeb92d47 --- /dev/null +++ b/.changeset/heavy-radios-lose.md @@ -0,0 +1,5 @@ +--- +'svelte-check': patch +--- + +fix: report diagnostics in tsconfig.json diff --git a/.changeset/two-cups-argue.md b/.changeset/two-cups-argue.md new file mode 100644 index 000000000..46b5180f6 --- /dev/null +++ b/.changeset/two-cups-argue.md @@ -0,0 +1,5 @@ +--- +'svelte-language-server': patch +--- + +fix: provide tsconfig.json diagnostics for svelte-check diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index d37c6f511..2defeb3cf 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -23,7 +23,7 @@ import { import { isInGeneratedCode } from './plugins/typescript/features/utils'; import { mapAndFilterDiagnostics } from './plugins/typescript/features/DiagnosticsProvider'; import { convertRange, getDiagnosticTag, mapSeverity } from './plugins/typescript/utils'; -import { pathToUrl, urlToPath } from './utils'; +import { normalizePath, pathToUrl, urlToPath } from './utils'; import { groupBy } from 'lodash'; export function mapSvelteCheckDiagnostics( @@ -226,6 +226,7 @@ export class SvelteCheck { private async getDiagnosticsForTsconfig(tsconfigPath: string) { const lsContainer = await this.getLSContainer(tsconfigPath); + const normalizedTsconfigPath = normalizePath(tsconfigPath); const map = (diagnostic: ts.Diagnostic, range?: Range): Diagnostic => { const file = diagnostic.file; range ??= file @@ -241,23 +242,34 @@ export class SvelteCheck { source: diagnostic.source, message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'), code: diagnostic.code, - tags: getDiagnosticTag(diagnostic) + tags: getDiagnosticTag(diagnostic), + data: { + positionUnknown: !diagnostic.start || !diagnostic.length + } }; }; - if ( - lsContainer.configErrors.some((error) => error.category === ts.DiagnosticCategory.Error) - ) { - return reportConfigError(); + const isErrorCategory = (diagnostic: ts.Diagnostic) => + diagnostic.category === ts.DiagnosticCategory.Error; + + if (lsContainer.configErrors.some(isErrorCategory)) { + return reportConfigError(lsContainer.configErrors); } const lang = lsContainer.getService(); - if ( - lsContainer.configErrors.some((error) => error.category === ts.DiagnosticCategory.Error) - ) { - return reportConfigError(); + if (lsContainer.configErrors.some(isErrorCategory)) { + return reportConfigError(lsContainer.configErrors); } + const program = lang.getProgram(); + const globalOrConfigFileDiagnostics = program + ? [...program.getGlobalDiagnostics(), ...program.getOptionsDiagnostics()] + : []; + // TODO: enable this in svelte-check v5. For now, we report these as warnings along with other diagnostics. + // if (globalOrConfigFileDiagnostics.some(isErrorCategory)) { + // return reportConfigError(globalOrConfigFileDiagnostics); + // } + const files = lang.getProgram()?.getSourceFiles() || []; const options = lang.getProgram()?.getCompilerOptions() || {}; @@ -356,22 +368,36 @@ export class SvelteCheck { }) ); - if (lsContainer.configErrors.length) { - diagnostics.push(...reportConfigError()); + const configErrors = lsContainer.configErrors + // TODO: remove this in svelte-check v5. + .concat( + globalOrConfigFileDiagnostics.map((diagnostic) => ({ + ...diagnostic, + category: + diagnostic.category === ts.DiagnosticCategory.Error + ? ts.DiagnosticCategory.Warning + : diagnostic.category + })) + ); + if (configErrors.length) { + diagnostics.push(...reportConfigError(configErrors)); } return diagnostics; - function reportConfigError() { + function reportConfigError(errors: readonly ts.Diagnostic[]) { const grouped = groupBy( - lsContainer.configErrors, - (error) => error.file?.fileName ?? tsconfigPath + errors, + (error) => error.file?.fileName ?? normalizedTsconfigPath ); + const lspDiagnostics = errors.map((diagnostic) => map(diagnostic)); return Object.entries(grouped).map(([filePath, errors]) => ({ filePath, - text: '', - diagnostics: errors.map((diagnostic) => map(diagnostic)) + text: lspDiagnostics.some((diagnostic) => diagnostic.data?.positionUnknown) + ? (ts.sys?.readFile(filePath) ?? '') + : '', + diagnostics: lspDiagnostics })); } } diff --git a/packages/svelte-check/src/incremental.ts b/packages/svelte-check/src/incremental.ts index 205328ffa..9d125bbbf 100644 --- a/packages/svelte-check/src/incremental.ts +++ b/packages/svelte-check/src/incremental.ts @@ -45,7 +45,7 @@ export type EmitResult = { }; export type ParsedDiagnostic = { - filePath: string; + filePath: string | null; line: number; character: number; /** Span length in characters, parsed from tsc pretty-output ~~ underlines */ @@ -548,7 +548,8 @@ export function runTypeScriptDiagnostics( */ export function mapCliDiagnosticsToLsp( diagnostics: ParsedDiagnostic[], - emitResult: EmitResult + emitResult: EmitResult, + tsconfigPath: string ): Array<{ filePath: string; text: string; diagnostics: Diagnostic[] }> { const entryByOutPath = new Map( emitResult.entries.map((entry) => [path.normalize(entry.outPath), entry]) @@ -561,7 +562,8 @@ export function mapCliDiagnosticsToLsp( const diagnosticsByFile = new Map(); for (const diagnostic of diagnostics) { - const key = path.normalize(diagnostic.filePath); + const filePath = diagnostic.filePath ?? tsconfigPath; + const key = filePath ? path.normalize(filePath) : ''; // Even though we try to exclude +page.js etc files that had code inserted (due to SvelteKit's zero types feature) // we might still have them included through code in .svelte-kit/types importing them. So we exclude the diagnostics for these. if (excludedSourcePaths.has(key)) { @@ -662,7 +664,10 @@ export function mapCliDiagnosticsToLsp( severity: diag.severity, code: diag.code, message: diag.message, - source + source, + data: { + positionUnknown: diag.filePath === null + } })); results.set(filePath, { @@ -704,7 +709,7 @@ function parseDiagnostics(output: string, baseDir: string): ParsedDiagnostic[] { const diagnostics: ParsedDiagnostic[] = []; const lines = clean.split(/\r?\n/); // Pretty format: file.ts:5:10 - error TS2322: message - const headerRegex = /^(.+):(\d+):(\d+) - (error|warning) TS(\d+): (.*)$/; + const headerRegex = /^((.+):(\d+):(\d+) - )?(error|warning) TS(\d+): (.*)$/; // Tilde underline: optional leading whitespace followed by one or more tildes const tildeRegex = /^(\s*)(~+)\s*$/; @@ -713,23 +718,31 @@ function parseDiagnostics(output: string, baseDir: string): ParsedDiagnostic[] { if (!match) { continue; } - const [, filePath, lineStr, colStr, severity, codeStr, message] = match; - const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(baseDir, filePath); + const [, , filePath, lineStr = '0', colStr = '0', severity, codeStr, message] = match; + const resolvedPath = filePath + ? path.isAbsolute(filePath) + ? filePath + : path.resolve(baseDir, filePath) + : null; const lineNum = Math.max(0, Number(lineStr) - 1); const colNum = Math.max(0, Number(colStr) - 1); // Look ahead (up to 4 lines) for a ~~ underline to determine span length. // The underline appears after the source context line in pretty output. let length = 1; - for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) { - const tildeMatch = tildeRegex.exec(lines[j]); - if (tildeMatch) { - length = tildeMatch[2].length; - break; - } - // Stop looking if we hit another diagnostic header - if (headerRegex.test(lines[j].trim())) { - break; + // No file path, so no source context line. + if (filePath) { + for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) { + const tildeMatch = tildeRegex.exec(lines[j]); + if (tildeMatch) { + length = tildeMatch[2].length; + break; + } + + // Stop looking if we hit another diagnostic header + if (headerRegex.test(lines[j].trim())) { + break; + } } } diff --git a/packages/svelte-check/src/index.ts b/packages/svelte-check/src/index.ts index 23e4d0212..403bd2d2b 100644 --- a/packages/svelte-check/src/index.ts +++ b/packages/svelte-check/src/index.ts @@ -460,7 +460,8 @@ async function runWithVirtualFiles( opts.incremental, opts.workspaceUri.fsPath ), - emitResult + emitResult, + opts.tsconfig ); const { diff --git a/packages/svelte-check/src/writers.ts b/packages/svelte-check/src/writers.ts index c8e5a6582..fb4027340 100644 --- a/packages/svelte-check/src/writers.ts +++ b/packages/svelte-check/src/writers.ts @@ -73,7 +73,7 @@ export class HumanFriendlyWriter implements Writer { } private formatRelatedCode(diagnostic: Diagnostic, text: string) { - if (!text) { + if (!text || diagnostic.data?.positionUnknown) { return ''; }