Keep the original configured project alive created during findAllReferences

This commit is contained in:
Sheetal Nandi 2018-07-27 15:35:13 -07:00
parent f3b0a2de06
commit d65cb21fcd
3 changed files with 148 additions and 41 deletions

View file

@ -707,7 +707,7 @@ namespace ts.server {
/* @internal */
private forEachProject(cb: (project: Project) => void) {
for (const p of this.inferredProjects) cb(p);
this.inferredProjects.forEach(cb);
this.configuredProjects.forEach(cb);
this.externalProjects.forEach(cb);
}
@ -2110,19 +2110,34 @@ namespace ts.server {
if (!originalLocation) return undefined;
const { fileName } = originalLocation;
const originalScriptInfo = this.getScriptInfo(fileName);
if (originalScriptInfo && originalScriptInfo.containingProjects.length) {
return originalLocation;
}
if (!this.getScriptInfo(fileName) && !this.host.fileExists(fileName)) return undefined;
const info: OriginalFileInfo = { fileName: toNormalizedPath(fileName), path: this.toPath(fileName) };
const configFileName = this.getConfigFileNameForFile(info);
const originalFileInfo: OriginalFileInfo = { fileName: toNormalizedPath(fileName), path: this.toPath(fileName) };
const configFileName = this.getConfigFileNameForFile(originalFileInfo);
if (!configFileName) return undefined;
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
updateProjectIfDirty(configuredProject);
// Keep this configured project as referenced from project
addOriginalConfiguredProject(configuredProject);
return configuredProject.containsFile(info.fileName) ? originalLocation : undefined;
const originalScriptInfo = this.getScriptInfo(fileName);
if (!originalScriptInfo || !originalScriptInfo.containingProjects.length) return undefined;
// Add configured projects as referenced
originalScriptInfo.containingProjects.forEach(project => {
if (project.projectKind === ProjectKind.Configured) {
addOriginalConfiguredProject(project as ConfiguredProject);
}
});
return originalLocation;
function addOriginalConfiguredProject(originalProject: ConfiguredProject) {
if (!project.originalConfiguredProjects) {
project.originalConfiguredProjects = createMap<true>();
}
project.originalConfiguredProjects.set(originalProject.canonicalConfigFilePath, true);
}
}
/** @internal */
@ -2185,14 +2200,9 @@ namespace ts.server {
}
Debug.assert(!info.isOrphan());
// Remove the configured projects that have zero references from open files.
// This was postponed from closeOpenFile to after opening next file,
// so that we can reuse the project if we need to right away
this.configuredProjects.forEach(project => {
if (!project.hasOpenRef()) {
this.removeProject(project);
}
});
this.removeOrphanConfiguredProjects();
// Remove orphan inferred projects now that we have reused projects
// We need to create a duplicate because we cant guarantee order after removal
@ -2220,6 +2230,30 @@ namespace ts.server {
return { configFileName, configFileErrors };
}
private removeOrphanConfiguredProjects() {
const toRemoveConfiguredProjects = cloneMap(this.configuredProjects);
// Do not remove configured projects that are used as original projects of other
this.inferredProjects.forEach(markOriginalProjectsAsUsed);
this.externalProjects.forEach(markOriginalProjectsAsUsed);
this.configuredProjects.forEach(project => {
// If project has open ref (there are more than zero references from external project/open file), keep it alive as well as any project it references
if (project.hasOpenRef()) {
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
markOriginalProjectsAsUsed(project);
}
});
// Remove all the non marked projects
toRemoveConfiguredProjects.forEach(project => this.removeProject(project));
function markOriginalProjectsAsUsed(project: Project) {
if (!project.isOrphan() && project.originalConfiguredProjects) {
project.originalConfiguredProjects.forEach((_value, configuredProjectPath) => toRemoveConfiguredProjects.delete(configuredProjectPath));
}
}
}
private telemetryOnOpenFile(scriptInfo: ScriptInfo): void {
if (this.syntaxOnly || !this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) {
return;

View file

@ -158,6 +158,9 @@ namespace ts.server {
/*@internal*/
typingFiles: SortedReadonlyArray<string> = emptyArray;
/*@internal*/
originalConfiguredProjects: Map<true> | undefined;
private readonly cancellationToken: ThrottledCancellationToken;
public isNonTsProject() {

View file

@ -9110,7 +9110,7 @@ export function Test2() {
});
});
function makeSampleProjects() {
describe("tsserverProjectSystem project references", () => {
const aTs: File = {
path: "/a/a.ts",
content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};",
@ -9166,62 +9166,121 @@ export function Test2() {
content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`,
};
const dummyFile: File = {
path: "/dummy/dummy.ts",
content: "let a = 10;"
};
const userTs: File = {
path: "/user/user.ts",
content: 'import { fnA, instanceA } from "../a/bin/a";\nimport { fnB } from "../b/bin/b";\nexport function fnUser() { fnA(); fnB(); instanceA; }',
};
const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, userTs]);
const session = createSession(host);
function makeSampleProjects() {
const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, userTs, dummyFile]);
const session = createSession(host);
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
checkDeclarationFiles(bTs, session, [bDtsMap, bDts]);
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
checkDeclarationFiles(bTs, session, [bDtsMap, bDts]);
// Testing what happens if we delete the original sources.
host.removeFile(bTs.path);
// Testing what happens if we delete the original sources.
host.removeFile(bTs.path);
openFilesForSession([userTs], session);
openFilesForSession([userTs], session);
const service = session.getProjectService();
checkNumberOfProjects(service, { inferredProjects: 1 });
return session;
}
return { session, aTs, bDts, userTs };
}
function verifyInferredProjectUnchanged(session: TestSession) {
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]);
}
function verifyDummyProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]);
}
function verifyOnlyOrphanInferredProject(session: TestSession) {
openFilesForSession([dummyFile], session);
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
verifyDummyProject(session);
}
function verifySingleInferredProject(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
verifyInferredProjectUnchanged(session);
// Close user file should close all the projects after opening dummy file
closeFilesForSession([userTs], session);
verifyOnlyOrphanInferredProject(session);
}
function verifyATsConfigProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]);
}
function verifyATsConfigOriginalProject(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyInferredProjectUnchanged(session);
verifyATsConfigProject(session);
// Close user file should close all the projects
closeFilesForSession([userTs], session);
verifyOnlyOrphanInferredProject(session);
}
function verifyATsConfigWhenOpened(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyInferredProjectUnchanged(session);
verifyATsConfigProject(session);
closeFilesForSession([userTs], session);
openFilesForSession([dummyFile], session);
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyDummyProject(session);
verifyATsConfigProject(session); // ATsConfig should still be alive
}
describe("tsserverProjectSystem project references", () => {
it("goToDefinition", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]);
verifySingleInferredProject(session);
});
it("getDefinitionAndBoundSpan", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, {
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA", { index: 1 }),
definitions: [protocolFileSpanFromSubstring(aTs, "fnA")],
});
verifySingleInferredProject(session);
});
it("goToType", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.TypeDefinitionRequest, protocol.TypeDefinitionResponse>(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA"));
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "IfaceA")]);
verifySingleInferredProject(session);
});
it("goToImplementation", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.ImplementationRequest, protocol.ImplementationResponse>(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]);
verifySingleInferredProject(session);
});
it("goToDefinition -- target does not exist", () => {
const { session, bDts, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()"));
// bTs does not exist, so stick with bDts
assert.deepEqual(response, [protocolFileSpanFromSubstring(bDts, "fnB")]);
verifySingleInferredProject(session);
});
it("navigateTo", () => {
const { session, aTs, bDts, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.NavtoRequest, protocol.NavtoResponse>(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" });
assert.deepEqual<ReadonlyArray<protocol.NavtoItem> | undefined>(response, [
{
@ -9249,6 +9308,8 @@ export function Test2() {
kindModifiers: "export",
},
]);
verifyATsConfigOriginalProject(session);
});
const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem(aTs, /*isDefinition*/ true, "fnA", "export function fnA() {}");
@ -9258,7 +9319,7 @@ export function Test2() {
];
it("findAllReferences", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
@ -9267,10 +9328,12 @@ export function Test2() {
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset,
symbolDisplayString: "(alias) fnA(): void\nimport fnA",
});
verifyATsConfigOriginalProject(session);
});
it("findAllReferences -- starting at definition", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
@ -9279,10 +9342,11 @@ export function Test2() {
symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset,
symbolDisplayString: "function fnA(): void",
});
verifyATsConfigWhenOpened(session);
});
it("findAllReferencesFull", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
interface ReferencesFullRequest extends protocol.FileLocationRequest { command: protocol.CommandTypes.ReferencesFull; }
interface ReferencesFullResponse extends protocol.Response { body: ReadonlyArray<ReferencedSymbol>; }
@ -9339,10 +9403,11 @@ export function Test2() {
],
}
]);
verifyATsConfigOriginalProject(session);
});
it("findAllReferences -- target does not exist", () => {
const { session, bDts, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
@ -9355,6 +9420,7 @@ export function Test2() {
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset,
symbolDisplayString: "(alias) fnB(): void\nimport fnB",
});
verifySingleInferredProject(session);
});
const renameATs = (aTs: File): protocol.SpanGroup => ({
@ -9370,7 +9436,7 @@ export function Test2() {
});
it("renameLocations", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
info: {
@ -9383,10 +9449,11 @@ export function Test2() {
},
locs: [renameUserTs(userTs), renameATs(aTs)],
});
verifyATsConfigOriginalProject(session);
});
it("renameLocations -- starting at definition", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
@ -9400,20 +9467,22 @@ export function Test2() {
},
locs: [renameATs(aTs), renameUserTs(userTs)],
});
verifyATsConfigWhenOpened(session);
});
it("renameLocationsFull", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.RenameFullRequest, protocol.RenameFullResponse>(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()"));
assert.deepEqual<ReadonlyArray<RenameLocation>>(response, [
documentSpanFromSubstring(userTs, "fnA"),
documentSpanFromSubstring(userTs, "fnA", { index: 1 }),
documentSpanFromSubstring(aTs, "fnA"),
]);
verifyATsConfigOriginalProject(session);
});
it("renameLocations -- target does not exist", () => {
const { session, bDts, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()"));
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
info: {
@ -9438,11 +9507,11 @@ export function Test2() {
}
],
});
verifySingleInferredProject(session);
});
it("getEditsForFileRename", () => {
const { session, aTs, userTs } = makeSampleProjects();
const session = makeSampleProjects();
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, protocol.CommandTypes.GetEditsForFileRename, {
oldFilePath: aTs.path,
newFilePath: "/a/aNew.ts",
@ -9455,6 +9524,7 @@ export function Test2() {
],
},
]);
verifySingleInferredProject(session);
});
});