From a9d497a008c8f2f423fd0186d4128401abb997c1 Mon Sep 17 00:00:00 2001
From: Sheetal Nandi <shkamat@microsoft.com>
Date: Fri, 20 Jul 2018 17:22:25 -0700
Subject: [PATCH] Report compiler options errors as well as part of
 configFileDiag event Fixes #25741

---
 src/compiler/program.ts                       |  19 +--
 src/server/editorServices.ts                  |   4 +-
 .../unittests/tsserverProjectSystem.ts        | 121 ++++++++++++++----
 3 files changed, 104 insertions(+), 40 deletions(-)

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(<ConfigFileDiagEvent>{
                 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<ConfigFileDiagnostic>) {
             const eventData = this.getEvent<server.ConfigFileDiagEvent>(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<server.ProjectInfoTelemetryEventData>, 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", () => {