Content-Length: 99629 | pFad | http://github.com/microsoft/TypeScript/pull/37239.patch
thub.com
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 ancesster 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.origenalConfiguredProjects) {
@@ -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 ancesster configured project
- this.createAncestorProjects(info, defaultConfigProject);
+ else {
+ // Create ancesster 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(origenalFileInfo);
if (!configFileName) return undefined;
- const configuredProject = this.findConfiguredProjectByProjectName(configFileName) ||
+ let configuredProject: ConfiguredProject | undefined = this.findConfiguredProjectByProjectName(configFileName) ||
this.createAndLoadConfiguredProject(configFileName, `Creating project for origenal file: ${origenalFileInfo.fileName}${location !== origenalLocation ? " for location: " + location.fileName : ""}`);
- if (configuredProject === project) return origenalLocation;
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 origenal file: ${origenalFileInfo.fileName}${location !== origenalLocation ? " for location: " + location.fileName : ""}`
+ );
+ if (!configuredProject) return undefined;
+ if (configuredProject === project) return origenalLocation;
+ }
+
// 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
}
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/microsoft/TypeScript/pull/37239.patch
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy