Keep the original configured project alive created during findAllReferences
This commit is contained in:
parent
f3b0a2de06
commit
d65cb21fcd
|
@ -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;
|
||||
|
|
|
@ -158,6 +158,9 @@ namespace ts.server {
|
|||
/*@internal*/
|
||||
typingFiles: SortedReadonlyArray<string> = emptyArray;
|
||||
|
||||
/*@internal*/
|
||||
originalConfiguredProjects: Map<true> | undefined;
|
||||
|
||||
private readonly cancellationToken: ThrottledCancellationToken;
|
||||
|
||||
public isNonTsProject() {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue