diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 334c054a8fb0e..fadd1bb42dde2 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2703,12 +2703,12 @@ namespace ts { function createDiagnosticForReference(index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) { const referencesSyntax = getProjectReferencesSyntax(); - if (referencesSyntax) { - if (createOptionDiagnosticInArrayLiteralSyntax(referencesSyntax, index, message, arg0, arg1)) { - return; - } + if (referencesSyntax && referencesSyntax.elements.length > index) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, referencesSyntax.elements[index], message, arg0, arg1)); + } + else { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1)); } - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1)); } function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number) { @@ -2761,15 +2761,6 @@ namespace ts { return !!props.length; } - function createOptionDiagnosticInArrayLiteralSyntax(arrayLiteral: ArrayLiteralExpression, index: number, message: DiagnosticMessage, arg0: string | number | undefined, arg1?: string | number, arg2?: string | number): boolean { - if (arrayLiteral.elements.length <= index) { - // Out-of-bounds - return false; - } - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, arrayLiteral.elements[index], message, arg0, arg1, arg2)); - return false; // TODO: GH#18217 This function always returns `false`!` - } - function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); programDiagnostics.add(diag); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 56b81731b6102..012735c52f8fa 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1674,10 +1674,12 @@ namespace ts.server { if (!this.eventHandler || this.suppressDiagnosticEvents) { return; } + const diagnostics = project.getLanguageService().getCompilerOptionsDiagnostics(); + diagnostics.push(...project.getAllProjectErrors()); this.eventHandler({ eventName: ConfigFileDiagEvent, - data: { configFileName: project.getConfigFilePath(), diagnostics: project.getAllProjectErrors(), triggerFile } + data: { configFileName: project.getConfigFilePath(), diagnostics, triggerFile } }); } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index ff5ee24bf5d27..995833cc0369a 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -176,6 +176,18 @@ namespace ts.projectSystem { return { ts: 0, tsx: 0, dts: 0, js: 0, jsx: 0, deferred: 0, ...nonZeroStats }; } + export interface ConfigFileDiagnostic { + fileName: string | undefined; + start: number | undefined; + length: number | undefined; + messageText: string; + category: DiagnosticCategory; + code: number; + reportsUnnecessary?: {}; + source?: string; + relatedInformation?: DiagnosticRelatedInformation[]; + } + export class TestServerEventManager { private events: server.ProjectServiceEvent[] = []; readonly session: TestSession; @@ -216,10 +228,14 @@ namespace ts.projectSystem { this.events.forEach(event => assert.notEqual(event.eventName, eventName)); } - checkSingleConfigFileDiagEvent(configFileName: string, triggerFile: string) { + checkSingleConfigFileDiagEvent(configFileName: string, triggerFile: string, errors: ReadonlyArray) { const eventData = this.getEvent(server.ConfigFileDiagEvent); assert.equal(eventData.configFileName, configFileName); assert.equal(eventData.triggerFile, triggerFile); + const actual = eventData.diagnostics.map(({ file, messageText, ...rest }) => ({ fileName: file && file.fileName, messageText: isString(messageText) ? messageText : "", ...rest })); + if (errors) { + assert.deepEqual(actual, errors); + } } assertProjectInfoTelemetryEvent(partial: Partial, configFile = "/tsconfig.json"): void { @@ -4698,13 +4714,41 @@ namespace ts.projectSystem { }); describe("tsserverProjectSystem Configure file diagnostics events", () => { + function getUnknownCompilerOptionDiagnostic(configFile: File, prop: string): ConfigFileDiagnostic { + const d = Diagnostics.Unknown_compiler_option_0; + const start = configFile.content.indexOf(prop) - 1; // start at "prop" + return { + fileName: configFile.path, + start, + length: prop.length + 2, + messageText: formatStringFromArgs(d.message, [prop]), + category: d.category, + code: d.code, + reportsUnnecessary: undefined + }; + } + + function getFileNotFoundDiagnostic(configFile: File, relativeFileName: string): ConfigFileDiagnostic { + const findString = `{"path":"./${relativeFileName}"}`; + const d = Diagnostics.File_0_does_not_exist; + const start = configFile.content.indexOf(findString); + return { + fileName: configFile.path, + start, + length: findString.length, + messageText: formatStringFromArgs(d.message, [`${getDirectoryPath(configFile.path)}/${relativeFileName}`]), + category: d.category, + code: d.code, + reportsUnnecessary: undefined + }; + } it("are generated when the config file has errors", () => { - const file = { + const file: File = { path: "/a/b/app.ts", content: "let x = 10" }; - const configFile = { + const configFile: File = { path: "/a/b/tsconfig.json", content: `{ "compilerOptions": { @@ -4713,29 +4757,32 @@ namespace ts.projectSystem { } }` }; - const serverEventManager = new TestServerEventManager([file, configFile]); + const serverEventManager = new TestServerEventManager([file, libFile, configFile]); openFilesForSession([file], serverEventManager.session); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, [ + getUnknownCompilerOptionDiagnostic(configFile, "foo"), + getUnknownCompilerOptionDiagnostic(configFile, "allowJS") + ]); }); it("are generated when the config file doesn't have errors", () => { - const file = { + const file: File = { path: "/a/b/app.ts", content: "let x = 10" }; - const configFile = { + const configFile: File = { path: "/a/b/tsconfig.json", content: `{ "compilerOptions": {} }` }; - const serverEventManager = new TestServerEventManager([file, configFile]); + const serverEventManager = new TestServerEventManager([file, libFile, configFile]); openFilesForSession([file], serverEventManager.session); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, emptyArray); }); it("are generated when the config file changes", () => { - const file = { + const file: File = { path: "/a/b/app.ts", content: "let x = 10" }; @@ -4746,37 +4793,40 @@ namespace ts.projectSystem { }` }; - const serverEventManager = new TestServerEventManager([file, configFile]); + const files = [file, libFile, configFile]; + const serverEventManager = new TestServerEventManager(files); openFilesForSession([file], serverEventManager.session); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, emptyArray); configFile.content = `{ "compilerOptions": { "haha": 123 } }`; - serverEventManager.host.reloadFS([file, configFile]); + serverEventManager.host.reloadFS(files); serverEventManager.host.runQueuedTimeoutCallbacks(); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path, [ + getUnknownCompilerOptionDiagnostic(configFile, "haha") + ]); configFile.content = `{ "compilerOptions": {} }`; - serverEventManager.host.reloadFS([file, configFile]); + serverEventManager.host.reloadFS(files); serverEventManager.host.runQueuedTimeoutCallbacks(); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path, emptyArray); }); it("are not generated when the config file does not include file opened and config file has errors", () => { - const file = { + const file: File = { path: "/a/b/app.ts", content: "let x = 10" }; - const file2 = { + const file2: File = { path: "/a/b/test.ts", content: "let x = 10" }; - const configFile = { + const configFile: File = { path: "/a/b/tsconfig.json", content: `{ "compilerOptions": { @@ -4792,11 +4842,11 @@ namespace ts.projectSystem { }); it("are not generated when the config file has errors but suppressDiagnosticEvents is true", () => { - const file = { + const file: File = { path: "/a/b/app.ts", content: "let x = 10" }; - const configFile = { + const configFile: File = { path: "/a/b/tsconfig.json", content: `{ "compilerOptions": { @@ -4805,21 +4855,21 @@ namespace ts.projectSystem { } }` }; - const serverEventManager = new TestServerEventManager([file, configFile], /*suppressDiagnosticEvents*/ true); + const serverEventManager = new TestServerEventManager([file, libFile, configFile], /*suppressDiagnosticEvents*/ true); openFilesForSession([file], serverEventManager.session); serverEventManager.hasZeroEvent("configFileDiag"); }); it("are not generated when the config file does not include file opened and doesnt contain any errors", () => { - const file = { + const file: File = { path: "/a/b/app.ts", content: "let x = 10" }; - const file2 = { + const file2: File = { path: "/a/b/test.ts", content: "let x = 10" }; - const configFile = { + const configFile: File = { path: "/a/b/tsconfig.json", content: `{ "files": ["app.ts"] @@ -4830,6 +4880,27 @@ namespace ts.projectSystem { openFilesForSession([file2], serverEventManager.session); serverEventManager.hasZeroEvent("configFileDiag"); }); + + it("contains the project reference errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const noSuchTsconfig = "no-such-tsconfig.json"; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "files": ["app.ts"], + "references": [{"path":"./${noSuchTsconfig}"}] + }` + }; + + const serverEventManager = new TestServerEventManager([file, libFile, configFile]); + openFilesForSession([file], serverEventManager.session); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, [ + getFileNotFoundDiagnostic(configFile, noSuchTsconfig) + ]); + }); }); describe("tsserverProjectSystem skipLibCheck", () => {