From 071689874cf496a999fcb566fe3ec4165e7deb9f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 4 Mar 2020 12:07:06 -0800 Subject: [PATCH 01/11] Add test where file from referenced project of solution belongs to inferred project instead of referenced project --- .../unittests/tsserver/projectReferences.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 8baa3a1f0cf63..8b42d34e2f3af 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1839,5 +1839,61 @@ bar(); // No new solutions/projects loaded checkNumberOfProjects(service, { configuredProjects: 1 }); }); + + it("when default project is solution project", () => { + const tsconfigSrc: File = { + path: `${tscWatch.projectRoot}/tsconfig-src.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "./target/", + baseUrl: "./src/" + }, + include: ["./src/**/*"] + }) + }; + const tsconfig: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + references: [{ path: "./tsconfig-src.json" }], + files: [] + }) + }; + const main: File = { + path: `${tscWatch.projectRoot}/src/main.ts`, + content: `import { foo } from 'helpers/functions'; +foo;` + }; + const helper: File = { + path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, + content: `export const foo = 1;` + }; + const host = createServerHost([tsconfigSrc, tsconfig, main, helper, libFile]); + const session = createSession(host, { canUseEvents: true }); + const service = session.getProjectService(); + service.openClientFile(main.path); + checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); + checkProjectActualFiles(service.inferredProjects[0], [main.path, libFile.path]); + checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, [tsconfig.path]); + + const location = protocolTextSpanFromSubstring(main.content, `'helpers/functions'`); + verifyGetErrRequest({ + session, + host, + expected: [ + { + file: main, + syntax: [], + semantic: [createDiagnostic( + location.start, + location.end, + Diagnostics.Cannot_find_module_0, + ["helpers/functions"] + )], + suggestion: [] + }, + ] + }); + }); }); } From 47946fff82714a4dedaefa8b27041b2f6f36f8eb Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 4 Mar 2020 12:59:28 -0800 Subject: [PATCH 02/11] Try to find project from project references if the default config project is solution Fixes #36708 --- src/server/editorServices.ts | 54 +++++++++++++++---- .../unittests/tsserver/projectReferences.ts | 17 ++---- 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a79070dde80f7..86c59eaac5fa6 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -308,7 +308,7 @@ namespace ts.server { } interface AssignProjectResult extends OpenConfiguredProjectResult { - defaultConfigProject: ConfiguredProject | undefined; + retainProjects: readonly ConfiguredProject[] | ConfiguredProject | undefined; } interface FilePropertyReader { @@ -2860,6 +2860,7 @@ namespace ts.server { let configFileErrors: readonly Diagnostic[] | undefined; let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info); let defaultConfigProject: ConfiguredProject | undefined; + let retainProjects: ConfiguredProject[] | ConfiguredProject | undefined; if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization configFileName = this.getConfigFileNameForFile(info); if (configFileName) { @@ -2880,7 +2881,42 @@ namespace ts.server { // Ensure project is ready to check if it contains opened script info updateProjectIfDirty(project); } + defaultConfigProject = project; + retainProjects = defaultConfigProject; + + // If this configured project doesnt contain script info but + // it is solution with project references, try those project references + if (!project.containsScriptInfo(info) && + project.getRootFilesMap().size === 0 && + !project.canConfigFileJsonReportNoInputFiles) { + + // try to load project from the tree + forEachResolvedProjectReference( + project, + ref => { + if (!ref) return; + + // Try to load the project of resolvedRef + const configFileName = toNormalizedPath(ref.sourceFile.fileName); + const child = this.findConfiguredProjectByProjectName(configFileName) || + this.createAndLoadConfiguredProject(configFileName, `Creating project referenced in solution ${defaultConfigProject!.projectName} to find possible configured project for ${info.fileName} to open`); + // Retain these projects + if (!isArray(retainProjects)) { + retainProjects = [defaultConfigProject!, child]; + } + else { + retainProjects.push(child); + } + + if (child.containsScriptInfo(info)) { + defaultConfigProject = child; + return true; + } + } + ); + } + // Create ancestor configured project this.createAncestorProjects(info, defaultConfigProject); } @@ -2902,7 +2938,7 @@ namespace ts.server { this.assignOrphanScriptInfoToInferredProject(info, this.openFiles.get(info.path)); } Debug.assert(!info.isOrphan()); - return { configFileName, configFileErrors, defaultConfigProject }; + return { configFileName, configFileErrors, retainProjects }; } private createAncestorProjects(info: ScriptInfo, project: ConfiguredProject) { @@ -2978,7 +3014,7 @@ namespace ts.server { ); } - private cleanupAfterOpeningFile(toRetainConfigProjects: ConfiguredProject[] | ConfiguredProject | undefined) { + private cleanupAfterOpeningFile(toRetainConfigProjects: readonly ConfiguredProject[] | ConfiguredProject | undefined) { // This was postponed from closeOpenFile to after opening next file, // so that we can reuse the project if we need to right away this.removeOrphanConfiguredProjects(toRetainConfigProjects); @@ -3000,14 +3036,14 @@ namespace ts.server { openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult { const info = this.getOrCreateOpenScriptInfo(fileName, fileContent, scriptKind, hasMixedContent, projectRootPath); - const { defaultConfigProject, ...result } = this.assignProjectToOpenedScriptInfo(info); - this.cleanupAfterOpeningFile(defaultConfigProject); + const { retainProjects, ...result } = this.assignProjectToOpenedScriptInfo(info); + this.cleanupAfterOpeningFile(retainProjects); this.telemetryOnOpenFile(info); this.printProjects(); return result; } - private removeOrphanConfiguredProjects(toRetainConfiguredProjects: ConfiguredProject[] | ConfiguredProject | undefined) { + private removeOrphanConfiguredProjects(toRetainConfiguredProjects: readonly ConfiguredProject[] | ConfiguredProject | undefined) { const toRemoveConfiguredProjects = cloneMap(this.configuredProjects); const markOriginalProjectsAsUsed = (project: Project) => { if (!project.isOrphan() && project.originalConfiguredProjects) { @@ -3206,9 +3242,9 @@ namespace ts.server { } // All the script infos now exist, so ok to go update projects for open files - let defaultConfigProjects: ConfiguredProject[] | undefined; + let retainProjects: readonly ConfiguredProject[] | undefined; if (openScriptInfos) { - defaultConfigProjects = mapDefined(openScriptInfos, info => this.assignProjectToOpenedScriptInfo(info).defaultConfigProject); + retainProjects = flatMap(openScriptInfos, info => this.assignProjectToOpenedScriptInfo(info).retainProjects); } // While closing files there could be open files that needed assigning new inferred projects, do it now @@ -3218,7 +3254,7 @@ namespace ts.server { if (openScriptInfos) { // Cleanup projects - this.cleanupAfterOpeningFile(defaultConfigProjects); + this.cleanupAfterOpeningFile(retainProjects); // Telemetry openScriptInfos.forEach(info => this.telemetryOnOpenFile(info)); this.printProjects(); diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 8b42d34e2f3af..27715a168253c 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1872,26 +1872,15 @@ foo;` const session = createSession(host, { canUseEvents: true }); const service = session.getProjectService(); service.openClientFile(main.path); - checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [main.path, libFile.path]); + checkNumberOfProjects(service, { configuredProjects: 2 }); + checkProjectActualFiles(service.configuredProjects.get(tsconfigSrc.path)!, [tsconfigSrc.path, main.path, helper.path, libFile.path]); checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, [tsconfig.path]); - const location = protocolTextSpanFromSubstring(main.content, `'helpers/functions'`); verifyGetErrRequest({ session, host, expected: [ - { - file: main, - syntax: [], - semantic: [createDiagnostic( - location.start, - location.end, - Diagnostics.Cannot_find_module_0, - ["helpers/functions"] - )], - suggestion: [] - }, + { file: main, syntax: [], semantic: [], suggestion: [] }, ] }); }); From 8b9c1f95709959637b43ca985b9247be05e01720 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 4 Mar 2020 13:59:33 -0800 Subject: [PATCH 03/11] Add test to verify the correct collection of projects --- src/server/editorServices.ts | 12 ++++--- src/server/project.ts | 19 ++++++++++- .../unittests/tsserver/projectReferences.ts | 32 ++++++++++++++++--- .../reference/api/tsserverlibrary.d.ts | 1 - 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 86c59eaac5fa6..6e5c88142c7bf 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -424,7 +424,8 @@ namespace ts.server { return !!(infoOrFileNameOrConfig as AncestorConfigFileInfo).configFileInfo; } - function forEachResolvedProjectReference( + /*@internal*/ + export function forEachResolvedProjectReference( project: ConfiguredProject, cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined ): T | undefined { @@ -1737,7 +1738,8 @@ namespace ts.server { this.logger.endGroup(); } - private findConfiguredProjectByProjectName(configFileName: NormalizedPath): ConfiguredProject | undefined { + /*@internal*/ + findConfiguredProjectByProjectName(configFileName: NormalizedPath): ConfiguredProject | undefined { // make sure that casing of config file name is consistent const canonicalConfigFilePath = asNormalizedPath(this.toCanonicalFileName(configFileName)); return this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath); @@ -2888,8 +2890,7 @@ namespace ts.server { // If this configured project doesnt contain script info but // it is solution with project references, try those project references if (!project.containsScriptInfo(info) && - project.getRootFilesMap().size === 0 && - !project.canConfigFileJsonReportNoInputFiles) { + project.isSolution()) { // try to load project from the tree forEachResolvedProjectReference( @@ -2909,7 +2910,8 @@ namespace ts.server { retainProjects.push(child); } - if (child.containsScriptInfo(info)) { + if (child.containsScriptInfo(info) && + !child.isSourceOfProjectReferenceRedirect(info.fileName)) { defaultConfigProject = child; return true; } diff --git a/src/server/project.ts b/src/server/project.ts index 05f73e4f07ce0..18d4a5416e830 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -2164,6 +2164,12 @@ namespace ts.server { this.externalProjectRefCount--; } + /* @internal */ + isSolution() { + return this.getRootFilesMap().size === 0 && + !this.canConfigFileJsonReportNoInputFiles; + } + /** Returns true if the project is needed by any of the open script info/external project */ /* @internal */ hasOpenRef() { @@ -2184,12 +2190,23 @@ namespace ts.server { return !!configFileExistenceInfo.openFilesImpactedByConfigFile.size; } + const isSolution = this.isSolution(); + // If there is no pending update for this project, // We know exact set of open files that get impacted by this configured project as the files in the project // The project is referenced only if open files impacted by this project are present in this project return forEachEntry( configFileExistenceInfo.openFilesImpactedByConfigFile, - (_value, infoPath) => this.containsScriptInfo(this.projectService.getScriptInfoForPath(infoPath as Path)!) + (_value, infoPath) => isSolution ? + forEachResolvedProjectReference(this, ref => { + if (!ref) return false; + const configFileName = toNormalizedPath(ref.sourceFile.fileName); + const child = this.projectService.findConfiguredProjectByProjectName(configFileName); + return child && + child.containsScriptInfo(this.projectService.getScriptInfoForPath(infoPath as Path)!) && + !child.isSourceOfProjectReferenceRedirect(infoPath); + }) : + this.containsScriptInfo(this.projectService.getScriptInfoForPath(infoPath as Path)!) ) || false; } diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 27715a168253c..e1c1741efe50b 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1868,14 +1868,15 @@ foo;` path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, content: `export const foo = 1;` }; - const host = createServerHost([tsconfigSrc, tsconfig, main, helper, libFile]); + const dummyFile: File = { + path: "/dummy/dummy.ts", + content: "let a = 10;" + }; + const host = createServerHost([tsconfigSrc, tsconfig, main, helper, libFile, dummyFile]); const session = createSession(host, { canUseEvents: true }); const service = session.getProjectService(); service.openClientFile(main.path); - checkNumberOfProjects(service, { configuredProjects: 2 }); - checkProjectActualFiles(service.configuredProjects.get(tsconfigSrc.path)!, [tsconfigSrc.path, main.path, helper.path, libFile.path]); - checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, [tsconfig.path]); - + verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false); verifyGetErrRequest({ session, host, @@ -1883,6 +1884,27 @@ foo;` { file: main, syntax: [], semantic: [], suggestion: [] }, ] }); + + service.openClientFile(dummyFile.path); + verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); + + service.closeClientFile(main.path); + service.closeClientFile(dummyFile.path); + service.openClientFile(dummyFile.path); + verifyProjects(/*includeConfigured*/ false, /*includeDummy*/ true); + + function verifyProjects(includeConfigured: boolean, includeDummy: boolean) { + const inferredProjects = includeDummy ? 1 : 0; + const configuredProjects = includeConfigured ? 2 : 0; + checkNumberOfProjects(service, { configuredProjects, inferredProjects }); + if (includeConfigured) { + checkProjectActualFiles(service.configuredProjects.get(tsconfigSrc.path)!, [tsconfigSrc.path, main.path, helper.path, libFile.path]); + checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, [tsconfig.path]); + } + if (includeDummy) { + checkProjectActualFiles(service.inferredProjects[0], [dummyFile.path, libFile.path]); + } + } }); }); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2cdbe454e68d2..438e677610ac7 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9310,7 +9310,6 @@ declare namespace ts.server { */ private getConfigFileNameForFile; private printProjects; - private findConfiguredProjectByProjectName; private getConfiguredProjectByCanonicalConfigFilePath; private findExternalProjectByProjectName; /** Get a filename if the language service exceeds the maximum allowed program size; otherwise returns undefined. */ From 1e41cd833b12620495730a105ace4a19b96cd8ce Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 4 Mar 2020 15:22:08 -0800 Subject: [PATCH 04/11] Handle when default config project is indirectly referenced in the solution --- src/server/editorServices.ts | 3 + src/testRunner/unittests/tsserver/helpers.ts | 63 ++++++ .../unittests/tsserver/projectReferences.ts | 199 ++++++++++++------ 3 files changed, 203 insertions(+), 62 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6e5c88142c7bf..669f75b2cf658 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2902,6 +2902,7 @@ namespace ts.server { const configFileName = toNormalizedPath(ref.sourceFile.fileName); const child = this.findConfiguredProjectByProjectName(configFileName) || this.createAndLoadConfiguredProject(configFileName, `Creating project referenced in solution ${defaultConfigProject!.projectName} to find possible configured project for ${info.fileName} to open`); + updateProjectIfDirty(child); // Retain these projects if (!isArray(retainProjects)) { retainProjects = [defaultConfigProject!, child]; @@ -2913,6 +2914,8 @@ namespace ts.server { if (child.containsScriptInfo(info) && !child.isSourceOfProjectReferenceRedirect(info.fileName)) { defaultConfigProject = child; + configFileErrors = child.getAllProjectErrors(); + this.sendConfigFileDiagEvent(child, info.fileName); return true; } } diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index ce776323879a4..ab24d9edc2faf 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -716,6 +716,69 @@ namespace ts.projectSystem { assert.isFalse(event.event.endsWith("Diag"), JSON.stringify(event)); } } + export function projectLoadingStartEvent(projectName: string, reason: string, seq?: number): protocol.ProjectLoadingStartEvent { + return { + seq: seq || 0, + type: "event", + event: server.ProjectLoadingStartEvent, + body: { projectName, reason } + }; + } + + export function projectLoadingFinishEvent(projectName: string, seq?: number): protocol.ProjectLoadingFinishEvent { + return { + seq: seq || 0, + type: "event", + event: server.ProjectLoadingFinishEvent, + body: { projectName } + }; + } + + export function projectInfoTelemetryEvent(seq?: number): protocol.TelemetryEvent { + return telemetryEvent(server.ProjectInfoTelemetryEvent, "", seq); + } + + function telemetryEvent(telemetryEventName: string, payload: any, seq?: number): protocol.TelemetryEvent { + return { + seq: seq || 0, + type: "event", + event: "telemetry", + body: { + telemetryEventName, + payload + } + }; + } + + export function configFileDiagEvent(triggerFile: string, configFile: string, diagnostics: protocol.DiagnosticWithFileName[], seq?: number): protocol.ConfigFileDiagnosticEvent { + return { + seq: seq || 0, + type: "event", + event: server.ConfigFileDiagEvent, + body: { + triggerFile, + configFile, + diagnostics + } + }; + } + + export function checkEvents(session: TestSession, expectedEvents: protocol.Event[]) { + const events = session.events; + assert.equal(events.length, expectedEvents.length, `Actual:: ${JSON.stringify(session.events, /*replacer*/ undefined, " ")}`); + expectedEvents.forEach((expectedEvent, index) => { + if (expectedEvent.event === "telemetry") { + // Ignore payload + const { body, ...actual } = events[index] as protocol.TelemetryEvent; + const { body: expectedBody, ...expected } = expectedEvent as protocol.TelemetryEvent; + assert.deepEqual(actual, expected, `Expected ${JSON.stringify(expectedEvent)} at ${index} in ${JSON.stringify(events)}`); + assert.equal(body.telemetryEventName, expectedBody.telemetryEventName, `Expected ${JSON.stringify(expectedEvent)} at ${index} in ${JSON.stringify(events)}`); + } + else { + checkNthEvent(session, expectedEvent, index, index === expectedEvents.length); + } + }); + } export function checkNthEvent(session: TestSession, expectedEvent: protocol.Event, index: number, isMostRecent: boolean) { const events = session.events; diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index e1c1741efe50b..2459ec3bd5fed 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1840,71 +1840,146 @@ bar(); checkNumberOfProjects(service, { configuredProjects: 1 }); }); - it("when default project is solution project", () => { - const tsconfigSrc: File = { - path: `${tscWatch.projectRoot}/tsconfig-src.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "./target/", - baseUrl: "./src/" - }, - include: ["./src/**/*"] - }) - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - references: [{ path: "./tsconfig-src.json" }], - files: [] - }) - }; - const main: File = { - path: `${tscWatch.projectRoot}/src/main.ts`, - content: `import { foo } from 'helpers/functions'; -foo;` - }; - const helper: File = { - path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, - content: `export const foo = 1;` - }; - const dummyFile: File = { - path: "/dummy/dummy.ts", - content: "let a = 10;" - }; - const host = createServerHost([tsconfigSrc, tsconfig, main, helper, libFile, dummyFile]); - const session = createSession(host, { canUseEvents: true }); - const service = session.getProjectService(); - service.openClientFile(main.path); - verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false); - verifyGetErrRequest({ - session, - host, - expected: [ - { file: main, syntax: [], semantic: [], suggestion: [] }, - ] - }); + describe("when default project is solution project", () => { + interface VerifySolutionScenario { + configRefs: string[]; + additionalFiles: readonly File[]; + additionalProjects: readonly { projectName: string, files: readonly string[] }[]; + expectedOpenEvents: protocol.Event[]; + } + const mainPath = `${tscWatch.projectRoot}/src/main.ts`; + const helperPath = `${tscWatch.projectRoot}/src/helpers/functions.ts`; + const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`; + const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`; + function verifySolutionScenario({ + configRefs, additionalFiles, additionalProjects, expectedOpenEvents + }: VerifySolutionScenario) { + const tsconfigSrc: File = { + path: tsconfigSrcPath, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "./target/", + baseUrl: "./src/" + }, + include: ["./src/**/*"] + }) + }; + const tsconfig: File = { + path: tsconfigPath, + content: JSON.stringify({ + references: configRefs.map(path => ({ path })), + files: [] + }) + }; + const main: File = { + path: mainPath, + content: `import { foo } from 'helpers/functions'; +export { foo };` + }; + const helper: File = { + path: helperPath, + content: `export const foo = 1;` + }; + const dummyFile: File = { + path: "/dummy/dummy.ts", + content: "let a = 10;" + }; + const host = createServerHost([tsconfigSrc, tsconfig, main, helper, libFile, dummyFile, ...additionalFiles]); + const session = createSession(host, { canUseEvents: true }); + const service = session.getProjectService(); + service.openClientFile(main.path); + verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false); + checkEvents(session, expectedOpenEvents); - service.openClientFile(dummyFile.path); - verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); - - service.closeClientFile(main.path); - service.closeClientFile(dummyFile.path); - service.openClientFile(dummyFile.path); - verifyProjects(/*includeConfigured*/ false, /*includeDummy*/ true); - - function verifyProjects(includeConfigured: boolean, includeDummy: boolean) { - const inferredProjects = includeDummy ? 1 : 0; - const configuredProjects = includeConfigured ? 2 : 0; - checkNumberOfProjects(service, { configuredProjects, inferredProjects }); - if (includeConfigured) { - checkProjectActualFiles(service.configuredProjects.get(tsconfigSrc.path)!, [tsconfigSrc.path, main.path, helper.path, libFile.path]); - checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, [tsconfig.path]); - } - if (includeDummy) { - checkProjectActualFiles(service.inferredProjects[0], [dummyFile.path, libFile.path]); + verifyGetErrRequest({ + session, + host, + expected: [ + { file: main, syntax: [], semantic: [], suggestion: [] }, + ] + }); + + service.openClientFile(dummyFile.path); + verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); + + service.closeClientFile(main.path); + service.closeClientFile(dummyFile.path); + service.openClientFile(dummyFile.path); + verifyProjects(/*includeConfigured*/ false, /*includeDummy*/ true); + + function verifyProjects(includeConfigured: boolean, includeDummy: boolean) { + const inferredProjects = includeDummy ? 1 : 0; + const configuredProjects = includeConfigured ? additionalProjects.length + 2 : 0; + checkNumberOfProjects(service, { configuredProjects, inferredProjects }); + if (includeConfigured) { + checkProjectActualFiles(service.configuredProjects.get(tsconfigSrc.path)!, [tsconfigSrc.path, main.path, helper.path, libFile.path]); + checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, [tsconfig.path]); + additionalProjects.forEach(({ projectName, files }) => + checkProjectActualFiles(service.configuredProjects.get(projectName)!, files)); + } + if (includeDummy) { + checkProjectActualFiles(service.inferredProjects[0], [dummyFile.path, libFile.path]); + } } } + + it("when project is directly referenced by solution", () => { + verifySolutionScenario({ + configRefs: ["./tsconfig-src.json"], + additionalFiles: emptyArray, + additionalProjects: emptyArray, + expectedOpenEvents: [ + projectLoadingStartEvent(tsconfigPath, `Creating possible configured project for ${mainPath} to open`), + projectLoadingFinishEvent(tsconfigPath), + projectInfoTelemetryEvent(), + projectLoadingStartEvent(tsconfigSrcPath, `Creating project referenced in solution ${tsconfigPath} to find possible configured project for ${mainPath} to open`), + projectLoadingFinishEvent(tsconfigSrcPath), + projectInfoTelemetryEvent(), + configFileDiagEvent(mainPath, tsconfigSrcPath, []) + ] + }); + }); + + it("when project is indirectly referenced by solution", () => { + const tsconfigIndirect: File = { + path: `${tscWatch.projectRoot}/tsconfig-indirect.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "./target/", + baseUrl: "./src/" + }, + files: ["./indirect/main.ts"], + references: [{ path: "./tsconfig-src.json" }] + }) + }; + const indirect: File = { + path: `${tscWatch.projectRoot}/indirect/main.ts`, + content: `import { foo } from 'main'; +foo;` + }; + verifySolutionScenario({ + configRefs: ["./tsconfig-indirect.json"], + additionalFiles: [tsconfigIndirect, indirect], + additionalProjects: [{ + projectName: tsconfigIndirect.path, + files: [tsconfigIndirect.path, mainPath, helperPath, indirect.path, libFile.path] + }], + expectedOpenEvents: [ + projectLoadingStartEvent(tsconfigPath, `Creating possible configured project for ${mainPath} to open`), + projectLoadingFinishEvent(tsconfigPath), + projectInfoTelemetryEvent(), + projectLoadingStartEvent(tsconfigIndirect.path, `Creating project referenced in solution ${tsconfigPath} to find possible configured project for ${mainPath} to open`), + projectLoadingFinishEvent(tsconfigIndirect.path), + projectInfoTelemetryEvent(), + projectLoadingStartEvent(tsconfigSrcPath, `Creating project referenced in solution ${tsconfigPath} to find possible configured project for ${mainPath} to open`), + projectLoadingFinishEvent(tsconfigSrcPath), + projectInfoTelemetryEvent(), + configFileDiagEvent(mainPath, tsconfigSrcPath, []) + ] + }); + }); }); }); } From 2e7ae2bdb93bb7147bcea850bc3f6e58a5079ce0 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 4 Mar 2020 15:23:41 -0800 Subject: [PATCH 05/11] Include public API tests in unittests --- src/testRunner/unittests/publicApi.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/testRunner/unittests/publicApi.ts b/src/testRunner/unittests/publicApi.ts index 917fbe590efe4..7f141f16c2705 100644 --- a/src/testRunner/unittests/publicApi.ts +++ b/src/testRunner/unittests/publicApi.ts @@ -1,4 +1,4 @@ -describe("Public APIs", () => { +describe("unittests:: Public APIs", () => { function verifyApi(fileName: string) { const builtFile = `built/local/${fileName}`; const api = `api/${fileName}`; @@ -32,7 +32,7 @@ describe("Public APIs", () => { }); }); -describe("Public APIs:: token to string", () => { +describe("unittests:: Public APIs:: token to string", () => { function assertDefinedTokenToString(initial: ts.SyntaxKind, last: ts.SyntaxKind) { for (let t = initial; t <= last; t++) { assert.isDefined(ts.tokenToString(t), `Expected tokenToString defined for ${ts.Debug.formatSyntaxKind(t)}`); @@ -47,13 +47,13 @@ describe("Public APIs:: token to string", () => { }); }); -describe("Public APIs:: createPrivateIdentifier", () => { +describe("unittests:: Public APIs:: createPrivateIdentifier", () => { it("throws when name doesn't start with #", () => { assert.throw(() => ts.createPrivateIdentifier("not"), "Debug Failure. First character of private identifier must be #: not"); }); }); -describe("Public APIs:: isPropertyName", () => { +describe("unittests:: Public APIs:: isPropertyName", () => { it("checks if a PrivateIdentifier is a valid property name", () => { const prop = ts.createPrivateIdentifier("#foo"); assert.isTrue(ts.isPropertyName(prop), "PrivateIdentifier must be a valid property name."); From 57bad74ce510aafc9bd0967d9f3345c0d5c9a12f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 4 Mar 2020 15:29:57 -0800 Subject: [PATCH 06/11] Make sure default project for script info is calculated correctly --- src/server/editorServices.ts | 6 ++++- src/server/project.ts | 24 ++++++++++++------ .../unittests/tsserver/projectReferences.ts | 25 +++++++++++++++++-- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 669f75b2cf658..f8a0a4674ff54 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1687,8 +1687,12 @@ namespace ts.server { findDefaultConfiguredProject(info: ScriptInfo) { if (!info.isScriptOpen()) return undefined; const configFileName = this.getConfigFileNameForFile(info); - return configFileName && + const project = configFileName && this.findConfiguredProjectByProjectName(configFileName); + + return project?.isSolution() ? + project.getDefaultChildProjectFromSolution(info) : + project; } /** diff --git a/src/server/project.ts b/src/server/project.ts index 18d4a5416e830..f0bd05aac2903 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -2170,6 +2170,21 @@ namespace ts.server { !this.canConfigFileJsonReportNoInputFiles; } + /* @internal */ + getDefaultChildProjectFromSolution(info: ScriptInfo) { + Debug.assert(this.isSolution()); + return forEachResolvedProjectReference(this, ref => { + if (!ref) return undefined; + const configFileName = toNormalizedPath(ref.sourceFile.fileName); + const child = this.projectService.findConfiguredProjectByProjectName(configFileName); + return child && + child.containsScriptInfo(info) && + !child.isSourceOfProjectReferenceRedirect(info.path) ? + child : + undefined; + }); + } + /** Returns true if the project is needed by any of the open script info/external project */ /* @internal */ hasOpenRef() { @@ -2198,14 +2213,7 @@ namespace ts.server { return forEachEntry( configFileExistenceInfo.openFilesImpactedByConfigFile, (_value, infoPath) => isSolution ? - forEachResolvedProjectReference(this, ref => { - if (!ref) return false; - const configFileName = toNormalizedPath(ref.sourceFile.fileName); - const child = this.projectService.findConfiguredProjectByProjectName(configFileName); - return child && - child.containsScriptInfo(this.projectService.getScriptInfoForPath(infoPath as Path)!) && - !child.isSourceOfProjectReferenceRedirect(infoPath); - }) : + !!this.getDefaultChildProjectFromSolution(this.projectService.getScriptInfoForPath(infoPath as Path)!) : this.containsScriptInfo(this.projectService.getScriptInfoForPath(infoPath as Path)!) ) || false; } diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 2459ec3bd5fed..b37c292ebca69 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1891,6 +1891,10 @@ export { foo };` service.openClientFile(main.path); verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false); checkEvents(session, expectedOpenEvents); + const info = service.getScriptInfoForPath(mainPath as Path)!; + const project = service.configuredProjects.get(tsconfigSrc.path)!; + assert.equal(info.getDefaultProject(), project); + assert.equal(service.findDefaultConfiguredProject(info), project); verifyGetErrRequest({ session, @@ -1957,11 +1961,28 @@ export { foo };` const indirect: File = { path: `${tscWatch.projectRoot}/indirect/main.ts`, content: `import { foo } from 'main'; +foo;` + }; + const tsconfigIndirect2: File = { + path: `${tscWatch.projectRoot}/tsconfig-indirect2.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "./target/", + baseUrl: "./src/" + }, + files: ["./indirect2/main2.ts"], + references: [{ path: "./tsconfig-src.json" }] + }) + }; + const indirect2: File = { + path: `${tscWatch.projectRoot}/indirect2/main2.ts`, + content: `import { foo } from 'main'; foo;` }; verifySolutionScenario({ - configRefs: ["./tsconfig-indirect.json"], - additionalFiles: [tsconfigIndirect, indirect], + configRefs: ["./tsconfig-indirect.json", "./tsconfig-indirect2.json"], + additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], additionalProjects: [{ projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, mainPath, helperPath, indirect.path, libFile.path] From 01bfca9a3d416ebf44ebf3c977044433a964ffc0 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 4 Mar 2020 16:15:13 -0800 Subject: [PATCH 07/11] Handle reload projects --- src/server/editorServices.ts | 116 +++++++++++++----- src/server/project.ts | 15 +-- .../unittests/tsserver/projectReferences.ts | 33 ++++- .../reference/api/tsserverlibrary.d.ts | 1 - 4 files changed, 121 insertions(+), 44 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index f8a0a4674ff54..d64d03b69991f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -424,6 +424,46 @@ namespace ts.server { return !!(infoOrFileNameOrConfig as AncestorConfigFileInfo).configFileInfo; } + /*@internal*/ + export enum ProjectReferenceProjectLoadKind { + Find, + FindCreate, + FindCreateLoad + } + + /*@internal*/ + export function forEachResolvedProjectReferenceProject( + project: ConfiguredProject, + cb: (child: ConfiguredProject, configFileName: NormalizedPath) => T | undefined, + projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind.Find | ProjectReferenceProjectLoadKind.FindCreate + ): T | undefined; + /*@internal*/ + export function forEachResolvedProjectReferenceProject( + project: ConfiguredProject, + cb: (child: ConfiguredProject, configFileName: NormalizedPath) => T | undefined, + projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind.FindCreateLoad, + reason: string + ): T | undefined; + export function forEachResolvedProjectReferenceProject( + project: ConfiguredProject, + cb: (child: ConfiguredProject, configFileName: NormalizedPath) => T | undefined, + projectReferenceProjectLoadKind: ProjectReferenceProjectLoadKind, + reason?: string + ): T | undefined { + return forEachResolvedProjectReference(project, ref => { + if (!ref) return undefined; + const configFileName = toNormalizedPath(ref.sourceFile.fileName); + const child = project.projectService.findConfiguredProjectByProjectName(configFileName) || ( + projectReferenceProjectLoadKind === ProjectReferenceProjectLoadKind.FindCreate ? + project.projectService.createConfiguredProject(configFileName) : + projectReferenceProjectLoadKind === ProjectReferenceProjectLoadKind.FindCreateLoad ? + project.projectService.createAndLoadConfiguredProject(configFileName, reason!) : + undefined + ); + return child && cb(child, configFileName); + }); + } + /*@internal*/ export function forEachResolvedProjectReference( project: ConfiguredProject, @@ -487,6 +527,12 @@ namespace ts.server { return !info.isScriptOpen() && info.mTime !== undefined; } + /*@internal*/ + export function projectContainsInfoDirectly(project: Project, info: ScriptInfo) { + return project.containsScriptInfo(info) && + !project.isSourceOfProjectReferenceRedirect(info.path); + } + /*@internal*/ export function updateProjectIfDirty(project: Project) { return project.dirty && project.updateGraph(); @@ -1877,7 +1923,8 @@ namespace ts.server { project.setTypeAcquisition(typeAcquisition); } - private createConfiguredProject(configFileName: NormalizedPath) { + /* @internal */ + createConfiguredProject(configFileName: NormalizedPath) { const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames)!; // TODO: GH#18217 this.logger.info(`Opened configuration file ${configFileName}`); const project = new ConfiguredProject( @@ -1901,7 +1948,7 @@ namespace ts.server { } /* @internal */ - private createAndLoadConfiguredProject(configFileName: NormalizedPath, reason: string) { + createAndLoadConfiguredProject(configFileName: NormalizedPath, reason: string) { const project = this.createConfiguredProject(configFileName); this.loadConfiguredProject(project, reason); return project; @@ -2719,7 +2766,8 @@ namespace ts.server { const configFileName = this.getConfigFileNameForFile(info); if (configFileName) { const project = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName); - if (!updatedProjects.has(configFileName)) { + if (!updatedProjects.has(project.canonicalConfigFilePath)) { + updatedProjects.set(project.canonicalConfigFilePath, true); if (delayReload) { project.pendingReload = ConfigFileProgramReloadLevel.Full; project.pendingReloadReason = reason; @@ -2728,8 +2776,20 @@ namespace ts.server { else { // reload from the disk this.reloadConfiguredProject(project, reason); + if (!project.containsScriptInfo(info) && project.isSolution()) { + forEachResolvedProjectReferenceProject( + project, + child => { + if (!updatedProjects.has(child.canonicalConfigFilePath)) { + updatedProjects.set(child.canonicalConfigFilePath, true); + this.reloadConfiguredProject(child, reason); + } + return projectContainsInfoDirectly(child, info); + }, + ProjectReferenceProjectLoadKind.FindCreate + ); + } } - updatedProjects.set(configFileName, true); } } }); @@ -2893,41 +2953,35 @@ namespace ts.server { // If this configured project doesnt contain script info but // it is solution with project references, try those project references - if (!project.containsScriptInfo(info) && - project.isSolution()) { - - // try to load project from the tree - forEachResolvedProjectReference( + if (!project.containsScriptInfo(info) && project.isSolution()) { + forEachResolvedProjectReferenceProject( project, - ref => { - if (!ref) return; - - // Try to load the project of resolvedRef - const configFileName = toNormalizedPath(ref.sourceFile.fileName); - const child = this.findConfiguredProjectByProjectName(configFileName) || - this.createAndLoadConfiguredProject(configFileName, `Creating project referenced in solution ${defaultConfigProject!.projectName} to find possible configured project for ${info.fileName} to open`); + (child, childConfigFileName) => { updateProjectIfDirty(child); // Retain these projects if (!isArray(retainProjects)) { - retainProjects = [defaultConfigProject!, child]; + retainProjects = [project as ConfiguredProject, child]; } else { retainProjects.push(child); } - if (child.containsScriptInfo(info) && - !child.isSourceOfProjectReferenceRedirect(info.fileName)) { - defaultConfigProject = child; + // If script info belongs to this child project, use this as default config project + if (projectContainsInfoDirectly(child, info)) { + configFileName = childConfigFileName; configFileErrors = child.getAllProjectErrors(); this.sendConfigFileDiagEvent(child, info.fileName); - return true; + return child; } - } + }, + ProjectReferenceProjectLoadKind.FindCreateLoad, + `Creating project referenced in solution ${project.projectName} to find possible configured project for ${info.fileName} to open` ); } - - // Create ancestor configured project - this.createAncestorProjects(info, defaultConfigProject); + else { + // Create ancestor configured project + this.createAncestorProjects(info, defaultConfigProject || project); + } } } @@ -3011,15 +3065,11 @@ namespace ts.server { updateProjectIfDirty(project); // Create tree because project is uptodate we only care of resolved references - forEachResolvedProjectReference( + forEachResolvedProjectReferenceProject( project, - ref => { - if (!ref) return; - const configFileName = toNormalizedPath(ref.sourceFile.fileName); - const child = this.findConfiguredProjectByProjectName(configFileName) || - this.createAndLoadConfiguredProject(configFileName, `Creating project for reference of project: ${project.projectName}`); - this.ensureProjectChildren(child, seenProjects); - } + child => this.ensureProjectChildren(child, seenProjects), + ProjectReferenceProjectLoadKind.FindCreateLoad, + `Creating project for reference of project: ${project.projectName}` ); } diff --git a/src/server/project.ts b/src/server/project.ts index f0bd05aac2903..e7f1bd0675136 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -2173,16 +2173,13 @@ namespace ts.server { /* @internal */ getDefaultChildProjectFromSolution(info: ScriptInfo) { Debug.assert(this.isSolution()); - return forEachResolvedProjectReference(this, ref => { - if (!ref) return undefined; - const configFileName = toNormalizedPath(ref.sourceFile.fileName); - const child = this.projectService.findConfiguredProjectByProjectName(configFileName); - return child && - child.containsScriptInfo(info) && - !child.isSourceOfProjectReferenceRedirect(info.path) ? + return forEachResolvedProjectReferenceProject( + this, + child => projectContainsInfoDirectly(child, info) ? child : - undefined; - }); + undefined, + ProjectReferenceProjectLoadKind.Find + ); } /** Returns true if the project is needed by any of the open script info/external project */ diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index b37c292ebca69..f1ced5b8b3494 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1846,13 +1846,15 @@ bar(); additionalFiles: readonly File[]; additionalProjects: readonly { projectName: string, files: readonly string[] }[]; expectedOpenEvents: protocol.Event[]; + expectedReloadEvents: protocol.Event[]; } const mainPath = `${tscWatch.projectRoot}/src/main.ts`; const helperPath = `${tscWatch.projectRoot}/src/helpers/functions.ts`; const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`; const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`; function verifySolutionScenario({ - configRefs, additionalFiles, additionalProjects, expectedOpenEvents + configRefs, additionalFiles, additionalProjects, + expectedOpenEvents, expectedReloadEvents }: VerifySolutionScenario) { const tsconfigSrc: File = { path: tsconfigSrcPath, @@ -1912,6 +1914,16 @@ export { foo };` service.openClientFile(dummyFile.path); verifyProjects(/*includeConfigured*/ false, /*includeDummy*/ true); + service.openClientFile(main.path); + service.closeClientFile(dummyFile.path); + service.openClientFile(dummyFile.path); + verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); + + session.clearMessages(); + service.reloadProjects(); + checkEvents(session, expectedReloadEvents); + verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); + function verifyProjects(includeConfigured: boolean, includeDummy: boolean) { const inferredProjects = includeDummy ? 1 : 0; const configuredProjects = includeConfigured ? additionalProjects.length + 2 : 0; @@ -1941,6 +1953,14 @@ export { foo };` projectLoadingFinishEvent(tsconfigSrcPath), projectInfoTelemetryEvent(), configFileDiagEvent(mainPath, tsconfigSrcPath, []) + ], + expectedReloadEvents: [ + projectLoadingStartEvent(tsconfigPath, `User requested reload projects`), + projectLoadingFinishEvent(tsconfigPath), + configFileDiagEvent(tsconfigPath, tsconfigPath, []), + projectLoadingStartEvent(tsconfigSrcPath, `User requested reload projects`), + projectLoadingFinishEvent(tsconfigSrcPath), + configFileDiagEvent(tsconfigSrcPath, tsconfigSrcPath, []) ] }); }); @@ -1998,6 +2018,17 @@ foo;` projectLoadingFinishEvent(tsconfigSrcPath), projectInfoTelemetryEvent(), configFileDiagEvent(mainPath, tsconfigSrcPath, []) + ], + expectedReloadEvents: [ + projectLoadingStartEvent(tsconfigPath, `User requested reload projects`), + projectLoadingFinishEvent(tsconfigPath), + configFileDiagEvent(tsconfigPath, tsconfigPath, []), + projectLoadingStartEvent(tsconfigIndirect.path, `User requested reload projects`), + projectLoadingFinishEvent(tsconfigIndirect.path), + configFileDiagEvent(tsconfigIndirect.path, tsconfigIndirect.path, []), + projectLoadingStartEvent(tsconfigSrcPath, `User requested reload projects`), + projectLoadingFinishEvent(tsconfigSrcPath), + configFileDiagEvent(tsconfigSrcPath, tsconfigSrcPath, []) ] }); }); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 438e677610ac7..066af48d907ab 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9316,7 +9316,6 @@ declare namespace ts.server { private getFilenameForExceededTotalSizeLimitForNonTsFiles; private createExternalProject; private addFilesToNonInferredProject; - private createConfiguredProject; private updateNonInferredProjectFiles; private updateRootAndOptionsOfNonInferredProject; private sendConfigFileDiagEvent; From f64ffe9697a63af9dd7be9b73078b89fd813f087 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 5 Mar 2020 14:53:20 -0800 Subject: [PATCH 08/11] Ensure to load solution project tree when project is referenced by solution --- src/server/editorServices.ts | 5 +- .../unittests/tsserver/projectReferences.ts | 228 ++++++++++++------ 2 files changed, 154 insertions(+), 79 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d64d03b69991f..d72d6184e61a6 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -3052,7 +3052,10 @@ namespace ts.server { if (forEachPotentialProjectReference( project, potentialRefPath => forProjects!.has(potentialRefPath) - )) { + ) || (project.isSolution() && forEachResolvedProjectReference( + project, + (_ref, resolvedPath) => forProjects!.has(resolvedPath) + ))) { // Load children this.ensureProjectChildren(project, seenProjects); } diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index f1ced5b8b3494..5725b84a8f7e4 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1847,14 +1847,22 @@ bar(); additionalProjects: readonly { projectName: string, files: readonly string[] }[]; expectedOpenEvents: protocol.Event[]; expectedReloadEvents: protocol.Event[]; + expectedReferences: protocol.ReferencesResponseBody; } - const mainPath = `${tscWatch.projectRoot}/src/main.ts`; - const helperPath = `${tscWatch.projectRoot}/src/helpers/functions.ts`; + const main: File = { + path: `${tscWatch.projectRoot}/src/main.ts`, + content: `import { foo } from 'helpers/functions'; +export { foo };` + }; + const helper: File = { + path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, + content: `export const foo = 1;` + }; const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`; const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`; function verifySolutionScenario({ configRefs, additionalFiles, additionalProjects, - expectedOpenEvents, expectedReloadEvents + expectedOpenEvents, expectedReloadEvents, expectedReferences }: VerifySolutionScenario) { const tsconfigSrc: File = { path: tsconfigSrcPath, @@ -1874,15 +1882,6 @@ bar(); files: [] }) }; - const main: File = { - path: mainPath, - content: `import { foo } from 'helpers/functions'; -export { foo };` - }; - const helper: File = { - path: helperPath, - content: `export const foo = 1;` - }; const dummyFile: File = { path: "/dummy/dummy.ts", content: "let a = 10;" @@ -1893,11 +1892,12 @@ export { foo };` service.openClientFile(main.path); verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false); checkEvents(session, expectedOpenEvents); - const info = service.getScriptInfoForPath(mainPath as Path)!; + const info = service.getScriptInfoForPath(main.path as Path)!; const project = service.configuredProjects.get(tsconfigSrc.path)!; assert.equal(info.getDefaultProject(), project); assert.equal(service.findDefaultConfiguredProject(info), project); + // Verify errors verifyGetErrRequest({ session, host, @@ -1906,6 +1906,7 @@ export { foo };` ] }); + // Verify collection of script infos service.openClientFile(dummyFile.path); verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); @@ -1919,11 +1920,23 @@ export { foo };` service.openClientFile(dummyFile.path); verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); + // Verify Reload projects session.clearMessages(); service.reloadProjects(); checkEvents(session, expectedReloadEvents); verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); + // Find all refs + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(main, "foo", { index: 1 }) + }).response as protocol.ReferencesResponseBody; + assert.deepEqual(response, expectedReferences); + + service.closeClientFile(main.path); + service.closeClientFile(dummyFile.path); + service.openClientFile(dummyFile.path); + function verifyProjects(includeConfigured: boolean, includeDummy: boolean) { const inferredProjects = includeDummy ? 1 : 0; const configuredProjects = includeConfigured ? additionalProjects.length + 2 : 0; @@ -1940,96 +1953,155 @@ export { foo };` } } - it("when project is directly referenced by solution", () => { - verifySolutionScenario({ - configRefs: ["./tsconfig-src.json"], - additionalFiles: emptyArray, - additionalProjects: emptyArray, - expectedOpenEvents: [ - projectLoadingStartEvent(tsconfigPath, `Creating possible configured project for ${mainPath} to open`), - projectLoadingFinishEvent(tsconfigPath), - projectInfoTelemetryEvent(), - projectLoadingStartEvent(tsconfigSrcPath, `Creating project referenced in solution ${tsconfigPath} to find possible configured project for ${mainPath} to open`), - projectLoadingFinishEvent(tsconfigSrcPath), - projectInfoTelemetryEvent(), - configFileDiagEvent(mainPath, tsconfigSrcPath, []) + function expectedProjectLoadAndTelemetry(config: string, reason: string) { + return [ + projectLoadingStartEvent(config, reason), + projectLoadingFinishEvent(config), + projectInfoTelemetryEvent(), + ]; + } + + function expectedSolutionLoadAndTelemetry() { + return expectedProjectLoadAndTelemetry(tsconfigPath, `Creating possible configured project for ${main.path} to open`); + } + + function expectedProjectReferenceLoadAndTelemetry(config: string) { + return expectedProjectLoadAndTelemetry(config, `Creating project referenced in solution ${tsconfigPath} to find possible configured project for ${main.path} to open`); + } + + function expectedReloadEvent(config: string) { + return [ + projectLoadingStartEvent(config, `User requested reload projects`), + projectLoadingFinishEvent(config), + configFileDiagEvent(config, config, []) + ]; + } + + function expectedReferencesResponse(): protocol.ReferencesResponseBody { + return { + refs: [ + makeReferenceItem({ + file: main, + text: "foo", + contextText: `import { foo } from 'helpers/functions';`, + isDefinition: true, + isWriteAccess: true, + lineText: `import { foo } from 'helpers/functions';`, + }), + makeReferenceItem({ + file: main, + text: "foo", + options: { index: 1 }, + contextText: `export { foo };`, + isDefinition: true, + isWriteAccess: true, + lineText: `export { foo };`, + }), + makeReferenceItem({ + file: helper, + text: "foo", + contextText: `export const foo = 1;`, + isDefinition: true, + isWriteAccess: true, + lineText: `export const foo = 1;`, + }), ], - expectedReloadEvents: [ - projectLoadingStartEvent(tsconfigPath, `User requested reload projects`), - projectLoadingFinishEvent(tsconfigPath), - configFileDiagEvent(tsconfigPath, tsconfigPath, []), - projectLoadingStartEvent(tsconfigSrcPath, `User requested reload projects`), - projectLoadingFinishEvent(tsconfigSrcPath), - configFileDiagEvent(tsconfigSrcPath, tsconfigSrcPath, []) - ] - }); - }); + symbolName: "foo", + symbolStartOffset: protocolLocationFromSubstring(main.content, "foo").offset, + symbolDisplayString: "(alias) const foo: 1\nexport foo" + }; + } - it("when project is indirectly referenced by solution", () => { + function expectedIndirectRefs(indirect: File) { + return [ + makeReferenceItem({ + file: indirect, + text: "foo", + contextText: `import { foo } from 'main';`, + isDefinition: true, + isWriteAccess: true, + lineText: `import { foo } from 'main';`, + }), + makeReferenceItem({ + file: indirect, + text: "foo", + options: { index: 1 }, + isDefinition: false, + isWriteAccess: false, + lineText: `foo;`, + }), + ]; + } + + function getIndirectProject(postfix = "") { const tsconfigIndirect: File = { - path: `${tscWatch.projectRoot}/tsconfig-indirect.json`, + path: `${tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "./target/", baseUrl: "./src/" }, - files: ["./indirect/main.ts"], + files: [`./indirect${postfix}/main.ts`], references: [{ path: "./tsconfig-src.json" }] }) }; const indirect: File = { - path: `${tscWatch.projectRoot}/indirect/main.ts`, - content: `import { foo } from 'main'; -foo;` - }; - const tsconfigIndirect2: File = { - path: `${tscWatch.projectRoot}/tsconfig-indirect2.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "./target/", - baseUrl: "./src/" - }, - files: ["./indirect2/main2.ts"], - references: [{ path: "./tsconfig-src.json" }] - }) - }; - const indirect2: File = { - path: `${tscWatch.projectRoot}/indirect2/main2.ts`, + path: `${tscWatch.projectRoot}/indirect${postfix}/main.ts`, content: `import { foo } from 'main'; foo;` }; + return { tsconfigIndirect, indirect }; + } + + it("when project is directly referenced by solution", () => { + verifySolutionScenario({ + configRefs: ["./tsconfig-src.json"], + additionalFiles: emptyArray, + additionalProjects: emptyArray, + expectedOpenEvents: [ + ...expectedSolutionLoadAndTelemetry(), + ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath), + configFileDiagEvent(main.path, tsconfigSrcPath, []) + ], + expectedReloadEvents: [ + ...expectedReloadEvent(tsconfigPath), + ...expectedReloadEvent(tsconfigSrcPath), + ], + expectedReferences: expectedReferencesResponse() + }); + }); + + it("when project is indirectly referenced by solution", () => { + const { tsconfigIndirect, indirect } = getIndirectProject(); + const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); + const { refs, ...rest } = expectedReferencesResponse(); verifySolutionScenario({ configRefs: ["./tsconfig-indirect.json", "./tsconfig-indirect2.json"], additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], additionalProjects: [{ projectName: tsconfigIndirect.path, - files: [tsconfigIndirect.path, mainPath, helperPath, indirect.path, libFile.path] + files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] }], expectedOpenEvents: [ - projectLoadingStartEvent(tsconfigPath, `Creating possible configured project for ${mainPath} to open`), - projectLoadingFinishEvent(tsconfigPath), - projectInfoTelemetryEvent(), - projectLoadingStartEvent(tsconfigIndirect.path, `Creating project referenced in solution ${tsconfigPath} to find possible configured project for ${mainPath} to open`), - projectLoadingFinishEvent(tsconfigIndirect.path), - projectInfoTelemetryEvent(), - projectLoadingStartEvent(tsconfigSrcPath, `Creating project referenced in solution ${tsconfigPath} to find possible configured project for ${mainPath} to open`), - projectLoadingFinishEvent(tsconfigSrcPath), - projectInfoTelemetryEvent(), - configFileDiagEvent(mainPath, tsconfigSrcPath, []) + ...expectedSolutionLoadAndTelemetry(), + ...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path), + ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath), + configFileDiagEvent(main.path, tsconfigSrcPath, []) ], expectedReloadEvents: [ - projectLoadingStartEvent(tsconfigPath, `User requested reload projects`), - projectLoadingFinishEvent(tsconfigPath), - configFileDiagEvent(tsconfigPath, tsconfigPath, []), - projectLoadingStartEvent(tsconfigIndirect.path, `User requested reload projects`), - projectLoadingFinishEvent(tsconfigIndirect.path), - configFileDiagEvent(tsconfigIndirect.path, tsconfigIndirect.path, []), - projectLoadingStartEvent(tsconfigSrcPath, `User requested reload projects`), - projectLoadingFinishEvent(tsconfigSrcPath), - configFileDiagEvent(tsconfigSrcPath, tsconfigSrcPath, []) - ] + ...expectedReloadEvent(tsconfigPath), + ...expectedReloadEvent(tsconfigIndirect.path), + ...expectedReloadEvent(tsconfigSrcPath), + ], + expectedReferences: { + refs: [ + ...refs, + ...expectedIndirectRefs(indirect), + ...expectedIndirectRefs(indirect2), + ], + ...rest + } }); }); }); From 914f358c51437c937532c71db466f478bc6b89f2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 5 Mar 2020 15:53:27 -0800 Subject: [PATCH 09/11] Find all refs when the file is referenced via d.ts --- src/server/editorServices.ts | 19 +++- src/server/session.ts | 26 ++++-- .../unittests/tsserver/projectReferences.ts | 86 +++++++++++++++++-- 3 files changed, 112 insertions(+), 19 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d72d6184e61a6..cdf24dce64f0e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2876,10 +2876,25 @@ namespace ts.server { const configFileName = this.getConfigFileNameForFile(originalFileInfo); if (!configFileName) return undefined; - const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || + let configuredProject: ConfiguredProject | undefined = this.findConfiguredProjectByProjectName(configFileName) || this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}`); - if (configuredProject === project) return originalLocation; updateProjectIfDirty(configuredProject); + + if (configuredProject.isSolution()) { + configuredProject = forEachResolvedProjectReferenceProject( + configuredProject, + child => { + updateProjectIfDirty(child); + const info = this.getScriptInfo(fileName); + return info && projectContainsInfoDirectly(child, info) ? child : undefined; + }, + ProjectReferenceProjectLoadKind.FindCreateLoad, + `Creating project referenced in solution ${configuredProject.projectName} to find possible configured project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}` + ); + if (!configuredProject) return undefined; + if (configuredProject === project) return originalLocation; + } + // Keep this configured project as referenced from project addOriginalConfiguredProject(configuredProject); diff --git a/src/server/session.ts b/src/server/session.ts index d38e49adc146d..82f3e00e48a6c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -431,10 +431,16 @@ namespace ts.server { if (initialLocation) { const defaultDefinition = getDefinitionLocation(defaultProject, initialLocation!); if (defaultDefinition) { + const getGeneratedDefinition = memoize(() => defaultProject.isSourceOfProjectReferenceRedirect(defaultDefinition.fileName) ? + defaultDefinition : + defaultProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(defaultDefinition)); + const getSourceDefinition = memoize(() => defaultProject.isSourceOfProjectReferenceRedirect(defaultDefinition.fileName) ? + defaultDefinition : + defaultProject.getLanguageService().getSourceMapper().tryGetSourcePosition(defaultDefinition)); projectService.loadAncestorProjectTree(seenProjects); projectService.forEachEnabledProject(project => { if (!addToSeen(seenProjects, project)) return; - const definition = mapDefinitionInProject(defaultDefinition, defaultProject, project); + const definition = mapDefinitionInProject(defaultDefinition, project, getGeneratedDefinition, getSourceDefinition); if (definition) { toDo = callbackProjectAndLocation({ project, location: definition as TLocation }, projectService, toDo, seenProjects, cb); } @@ -447,17 +453,21 @@ namespace ts.server { } } - function mapDefinitionInProject(definition: DocumentPosition | undefined, definingProject: Project, project: Project): DocumentPosition | undefined { + function mapDefinitionInProject( + definition: DocumentPosition, + project: Project, + getGeneratedDefinition: () => DocumentPosition | undefined, + getSourceDefinition: () => DocumentPosition | undefined + ): DocumentPosition | undefined { // If the definition is actually from the project, definition is correct as is - if (!definition || - project.containsFile(toNormalizedPath(definition.fileName)) && + if (project.containsFile(toNormalizedPath(definition.fileName)) && !isLocationProjectReferenceRedirect(project, definition)) { return definition; } - const mappedDefinition = definingProject.isSourceOfProjectReferenceRedirect(definition.fileName) ? - definition : - definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition); - return mappedDefinition && project.containsFile(toNormalizedPath(mappedDefinition.fileName)) ? mappedDefinition : undefined; + const generatedDefinition = getGeneratedDefinition(); + if (generatedDefinition && project.containsFile(toNormalizedPath(generatedDefinition.fileName))) return generatedDefinition; + const sourceDefinition = getSourceDefinition(); + return sourceDefinition && project.containsFile(toNormalizedPath(sourceDefinition.fileName)) ? sourceDefinition : undefined; } function isLocationProjectReferenceRedirect(project: Project, location: DocumentPosition | undefined) { diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 5725b84a8f7e4..ad7de88dc4cdb 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1848,6 +1848,7 @@ bar(); expectedOpenEvents: protocol.Event[]; expectedReloadEvents: protocol.Event[]; expectedReferences: protocol.ReferencesResponseBody; + expectedReferencesFromDtsProject: protocol.ReferencesResponseBody; } const main: File = { path: `${tscWatch.projectRoot}/src/main.ts`, @@ -1858,11 +1859,44 @@ export { foo };` path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, content: `export const foo = 1;` }; + const mainDts: File = { + path: `${tscWatch.projectRoot}/target/src/main.d.ts`, + content: `import { foo } from 'helpers/functions'; +export { foo }; +//# sourceMappingURL=main.d.ts.map` + }; + const mainDtsMap: File = { + path: `${tscWatch.projectRoot}/target/src/main.d.ts.map`, + content: `{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAC,GAAG,EAAC,CAAC"}` + }; + const helperDts: File = { + path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts`, + content: `export declare const foo = 1; +//# sourceMappingURL=functions.d.ts.map` + }; + const helperDtsMap: File = { + path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts.map`, + content: `{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../src/helpers/functions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,IAAI,CAAC"}` + }; + const tsconfigIndirect3: File = { + path: `${tscWatch.projectRoot}/indirect3/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: "../target/src/" + }, + }) + }; + const fileResolvingToMainDts: File = { + path: `${tscWatch.projectRoot}/indirect3/main.ts`, + content: `import { foo } from 'main'; +foo;` + }; const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`; const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`; function verifySolutionScenario({ configRefs, additionalFiles, additionalProjects, - expectedOpenEvents, expectedReloadEvents, expectedReferences + expectedOpenEvents, expectedReloadEvents, + expectedReferences, expectedReferencesFromDtsProject }: VerifySolutionScenario) { const tsconfigSrc: File = { path: tsconfigSrcPath, @@ -1886,7 +1920,12 @@ export { foo };` path: "/dummy/dummy.ts", content: "let a = 10;" }; - const host = createServerHost([tsconfigSrc, tsconfig, main, helper, libFile, dummyFile, ...additionalFiles]); + const host = createServerHost([ + tsconfigSrc, tsconfig, main, helper, + libFile, dummyFile, + mainDts, mainDtsMap, helperDts, helperDtsMap, + tsconfigIndirect3, fileResolvingToMainDts, + ...additionalFiles]); const session = createSession(host, { canUseEvents: true }); const service = session.getProjectService(); service.openClientFile(main.path); @@ -1935,7 +1974,18 @@ export { foo };` service.closeClientFile(main.path); service.closeClientFile(dummyFile.path); - service.openClientFile(dummyFile.path); + + // Verify when declaration map references the file + service.openClientFile(fileResolvingToMainDts.path); + checkNumberOfProjects(service, { configuredProjects: 1 }); + checkProjectActualFiles(service.configuredProjects.get(tsconfigIndirect3.path)!, [tsconfigIndirect3.path, fileResolvingToMainDts.path, mainDts.path, helperDts.path, libFile.path]); + + // Find all refs from dts include + const response2 = session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(fileResolvingToMainDts, "foo") + }).response as protocol.ReferencesResponseBody; + assert.deepEqual(response2, expectedReferencesFromDtsProject); function verifyProjects(includeConfigured: boolean, includeDummy: boolean) { const inferredProjects = includeDummy ? 1 : 0; @@ -2033,7 +2083,7 @@ export { foo };` ]; } - function getIndirectProject(postfix = "") { + function getIndirectProject(postfix: string) { const tsconfigIndirect: File = { path: `${tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`, content: JSON.stringify({ @@ -2048,13 +2098,13 @@ export { foo };` }; const indirect: File = { path: `${tscWatch.projectRoot}/indirect${postfix}/main.ts`, - content: `import { foo } from 'main'; -foo;` + content: fileResolvingToMainDts.content }; return { tsconfigIndirect, indirect }; } it("when project is directly referenced by solution", () => { + const expectedReferences = expectedReferencesResponse(); verifySolutionScenario({ configRefs: ["./tsconfig-src.json"], additionalFiles: emptyArray, @@ -2068,16 +2118,24 @@ foo;` ...expectedReloadEvent(tsconfigPath), ...expectedReloadEvent(tsconfigSrcPath), ], - expectedReferences: expectedReferencesResponse() + expectedReferences, + expectedReferencesFromDtsProject: { + ...expectedReferences, + refs: [ + ...expectedIndirectRefs(fileResolvingToMainDts), + ...expectedReferences.refs + ], + symbolDisplayString: "(alias) const foo: 1\nimport foo", + } }); }); it("when project is indirectly referenced by solution", () => { - const { tsconfigIndirect, indirect } = getIndirectProject(); + const { tsconfigIndirect, indirect } = getIndirectProject("1"); const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); const { refs, ...rest } = expectedReferencesResponse(); verifySolutionScenario({ - configRefs: ["./tsconfig-indirect.json", "./tsconfig-indirect2.json"], + configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], additionalProjects: [{ projectName: tsconfigIndirect.path, @@ -2101,6 +2159,16 @@ foo;` ...expectedIndirectRefs(indirect2), ], ...rest + }, + expectedReferencesFromDtsProject: { + ...rest, + refs: [ + ...expectedIndirectRefs(fileResolvingToMainDts), + ...refs, + ...expectedIndirectRefs(indirect2), + ...expectedIndirectRefs(indirect), + ], + symbolDisplayString: "(alias) const foo: 1\nimport foo", } }); }); From 728ca69cf37f1fd0e9a8c59a18b979ec93d7d5fe Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 10 Mar 2020 12:06:30 -0700 Subject: [PATCH 10/11] Some comments per feedback --- src/server/editorServices.ts | 7 +++++++ src/server/project.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index cdf24dce64f0e..e58053d2152b3 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -425,9 +425,13 @@ namespace ts.server { } /*@internal*/ + /** Kind of operation to perform to get project reference project */ export enum ProjectReferenceProjectLoadKind { + /** Find existing project for project reference */ Find, + /** Find existing project or create one for the project reference */ FindCreate, + /** Find existing project or create and loas it for the project reference */ FindCreateLoad } @@ -528,6 +532,7 @@ namespace ts.server { } /*@internal*/ + /** true if script info is part of project and is not in project because it is referenced from project reference source */ export function projectContainsInfoDirectly(project: Project, info: ScriptInfo) { return project.containsScriptInfo(info) && !project.isSourceOfProjectReferenceRedirect(info.path); @@ -2776,6 +2781,7 @@ namespace ts.server { else { // reload from the disk this.reloadConfiguredProject(project, reason); + // If this is solution, reload the project till the reloaded project contains the script info directly if (!project.containsScriptInfo(info) && project.isSolution()) { forEachResolvedProjectReferenceProject( project, @@ -2881,6 +2887,7 @@ namespace ts.server { updateProjectIfDirty(configuredProject); if (configuredProject.isSolution()) { + // Find the project that is referenced from this solution that contains the script info directly configuredProject = forEachResolvedProjectReferenceProject( configuredProject, child => { diff --git a/src/server/project.ts b/src/server/project.ts index e7f1bd0675136..918f3b240f84c 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -2171,6 +2171,7 @@ namespace ts.server { } /* @internal */ + /** Find the configured project from the project references in this solution which contains the info directly */ getDefaultChildProjectFromSolution(info: ScriptInfo) { Debug.assert(this.isSolution()); return forEachResolvedProjectReferenceProject( From 62ef1ef2d3efea440ad675eceb70158cd436f7d3 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 11 Mar 2020 16:52:49 -0700 Subject: [PATCH 11/11] Fix typo --- src/server/editorServices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e58053d2152b3..72d2213f6df75 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -431,7 +431,7 @@ namespace ts.server { Find, /** Find existing project or create one for the project reference */ FindCreate, - /** Find existing project or create and loas it for the project reference */ + /** Find existing project or create and load it for the project reference */ FindCreateLoad }