mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #26032 from Microsoft/projectReferences
Keep the configured projects referenced during original location and project get, alive till the referencing project is alive
This commit is contained in:
@@ -326,6 +326,13 @@ namespace ts.server {
|
||||
syntaxOnly?: boolean;
|
||||
}
|
||||
|
||||
interface OriginalFileInfo { fileName: NormalizedPath; path: Path; }
|
||||
type OpenScriptInfoOrClosedFileInfo = ScriptInfo | OriginalFileInfo;
|
||||
|
||||
function isOpenScriptInfo(infoOrFileName: OpenScriptInfoOrClosedFileInfo): infoOrFileName is ScriptInfo {
|
||||
return !!(infoOrFileName as ScriptInfo).containingProjects;
|
||||
}
|
||||
|
||||
function getDetailWatchInfo(watchType: WatchType, project: Project | undefined) {
|
||||
return `Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`;
|
||||
}
|
||||
@@ -700,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);
|
||||
}
|
||||
@@ -1044,12 +1051,12 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
|
||||
private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: OpenScriptInfoOrClosedFileInfo) {
|
||||
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
|
||||
if (configFileExistenceInfo) {
|
||||
// By default the info would get impacted by presence of config file since its in the detection path
|
||||
// Only adding the info as a root to inferred project will need the existence to be watched by file watcher
|
||||
if (!configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
|
||||
if (isOpenScriptInfo(info) && !configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
|
||||
configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, false);
|
||||
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd);
|
||||
}
|
||||
@@ -1066,9 +1073,11 @@ namespace ts.server {
|
||||
// Or the whole chain of config files for the roots of the inferred projects
|
||||
|
||||
// Cache the host value of file exists and add the info to map of open files impacted by this config file
|
||||
const openFilesImpactedByConfigFile = createMap<boolean>();
|
||||
openFilesImpactedByConfigFile.set(info.path, false);
|
||||
const exists = this.host.fileExists(configFileName);
|
||||
const openFilesImpactedByConfigFile = createMap<boolean>();
|
||||
if (isOpenScriptInfo(info)) {
|
||||
openFilesImpactedByConfigFile.set(info.path, false);
|
||||
}
|
||||
configFileExistenceInfo = { exists, openFilesImpactedByConfigFile };
|
||||
this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo);
|
||||
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd);
|
||||
@@ -1180,7 +1189,7 @@ namespace ts.server {
|
||||
*/
|
||||
private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) {
|
||||
Debug.assert(!info.isScriptOpen());
|
||||
this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => {
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
|
||||
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
|
||||
if (configFileExistenceInfo) {
|
||||
const infoIsRootOfInferredProject = configFileExistenceInfo.openFilesImpactedByConfigFile.get(info.path);
|
||||
@@ -1214,7 +1223,7 @@ namespace ts.server {
|
||||
/* @internal */
|
||||
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
|
||||
Debug.assert(info.isScriptOpen());
|
||||
this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => {
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
|
||||
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
|
||||
if (!configFileExistenceInfo) {
|
||||
// Create the cache
|
||||
@@ -1242,7 +1251,7 @@ namespace ts.server {
|
||||
*/
|
||||
/* @internal */
|
||||
stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
|
||||
this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => {
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
|
||||
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
|
||||
if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
|
||||
Debug.assert(info.isScriptOpen());
|
||||
@@ -1265,12 +1274,12 @@ namespace ts.server {
|
||||
* The server must start searching from the directory containing
|
||||
* the newly opened file.
|
||||
*/
|
||||
private forEachConfigFileLocation(info: ScriptInfo, infoShouldBeOpen: boolean, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) {
|
||||
private forEachConfigFileLocation(info: OpenScriptInfoOrClosedFileInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) {
|
||||
if (this.syntaxOnly) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Debug.assert(!infoShouldBeOpen || this.openFiles.has(info.path));
|
||||
Debug.assert(!isOpenScriptInfo(info) || this.openFiles.has(info.path));
|
||||
const projectRootPath = this.openFiles.get(info.path);
|
||||
|
||||
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
|
||||
@@ -1309,11 +1318,13 @@ namespace ts.server {
|
||||
* current directory (the directory in which tsc was invoked).
|
||||
* The server must start searching from the directory containing
|
||||
* the newly opened file.
|
||||
* If script info is passed in, it is asserted to be open script info
|
||||
* otherwise just file name
|
||||
*/
|
||||
private getConfigFileNameForFile(info: ScriptInfo, infoShouldBeOpen: boolean) {
|
||||
if (infoShouldBeOpen) Debug.assert(info.isScriptOpen());
|
||||
private getConfigFileNameForFile(info: OpenScriptInfoOrClosedFileInfo) {
|
||||
if (isOpenScriptInfo(info)) Debug.assert(info.isScriptOpen());
|
||||
this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`);
|
||||
const configFileName = this.forEachConfigFileLocation(info, infoShouldBeOpen, (configFileName, canonicalConfigFilePath) =>
|
||||
const configFileName = this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) =>
|
||||
this.configFileExists(configFileName, canonicalConfigFilePath, info));
|
||||
if (configFileName) {
|
||||
this.logger.info(`For info: ${info.fileName} :: Config file name: ${configFileName}`);
|
||||
@@ -2005,7 +2016,7 @@ namespace ts.server {
|
||||
// we first detect if there is already a configured project created for it: if so,
|
||||
// we re- read the tsconfig file content and update the project only if we havent already done so
|
||||
// otherwise we create a new one.
|
||||
const configFileName = this.getConfigFileNameForFile(info, /*infoShouldBeOpen*/ true);
|
||||
const configFileName = this.getConfigFileNameForFile(info);
|
||||
if (configFileName) {
|
||||
const project = this.findConfiguredProjectByProjectName(configFileName);
|
||||
if (!project) {
|
||||
@@ -2093,17 +2104,40 @@ namespace ts.server {
|
||||
return this.openClientFileWithNormalizedPath(toNormalizedPath(fileName), fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath ? toNormalizedPath(projectRootPath) : undefined);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
getProjectForFileWithoutOpening(fileName: NormalizedPath): { readonly scriptInfo: ScriptInfo, readonly projects: ReadonlyArray<Project> } | undefined {
|
||||
const scriptInfo = this.filenameToScriptInfo.get(fileName) ||
|
||||
this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName, this.currentDirectory, /*fileContent*/ undefined, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined);
|
||||
if (!scriptInfo) return undefined;
|
||||
if (scriptInfo.containingProjects.length) {
|
||||
return { scriptInfo, projects: scriptInfo.containingProjects };
|
||||
/*@internal*/
|
||||
getOriginalLocationEnsuringConfiguredProject(project: Project, location: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined {
|
||||
const originalLocation = project.getSourceMapper().tryGetOriginalLocation(location);
|
||||
if (!originalLocation) return undefined;
|
||||
|
||||
const { fileName } = originalLocation;
|
||||
if (!this.getScriptInfo(fileName) && !this.host.fileExists(fileName)) return undefined;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
const configFileName = this.getConfigFileNameForFile(scriptInfo, /*infoShouldBeOpen*/ false);
|
||||
const project = configFileName === undefined ? undefined : this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
|
||||
return project && project.containsScriptInfo(scriptInfo) ? { scriptInfo, projects: [project] } : undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -2128,7 +2162,7 @@ namespace ts.server {
|
||||
this.openFiles.set(info.path, projectRootPath);
|
||||
let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info);
|
||||
if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization
|
||||
configFileName = this.getConfigFileNameForFile(info, /*infoShouldBeOpen*/ true);
|
||||
configFileName = this.getConfigFileNameForFile(info);
|
||||
if (configFileName) {
|
||||
project = this.findConfiguredProjectByProjectName(configFileName);
|
||||
if (!project) {
|
||||
@@ -2166,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
|
||||
@@ -2201,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() {
|
||||
@@ -711,7 +714,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
containsFile(filename: NormalizedPath, requireOpen?: boolean): boolean {
|
||||
const info = this.projectService.getScriptInfoForPath(this.toPath(filename));
|
||||
const info = this.projectService.getScriptInfoForNormalizedPath(filename);
|
||||
if (info && (info.isScriptOpen() || !requireOpen)) {
|
||||
return this.containsScriptInfo(info);
|
||||
}
|
||||
|
||||
+11
-14
@@ -427,23 +427,20 @@ namespace ts.server {
|
||||
if (projectAndLocation.project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled
|
||||
cb(projectAndLocation, (project, location) => {
|
||||
seenProjects.set(projectAndLocation.project.projectName, true);
|
||||
const originalLocation = project.getSourceMapper().tryGetOriginalLocation(location);
|
||||
const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(project, location);
|
||||
if (!originalLocation) return false;
|
||||
const originalProjectAndScriptInfo = projectService.getProjectForFileWithoutOpening(toNormalizedPath(originalLocation.fileName));
|
||||
if (!originalProjectAndScriptInfo) return false;
|
||||
|
||||
if (originalProjectAndScriptInfo) {
|
||||
toDo = toDo || [];
|
||||
const originalScriptInfo = projectService.getScriptInfo(originalLocation.fileName)!;
|
||||
toDo = toDo || [];
|
||||
|
||||
for (const project of originalProjectAndScriptInfo.projects) {
|
||||
addToTodo({ project, location: originalLocation as TLocation }, toDo, seenProjects);
|
||||
}
|
||||
const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalProjectAndScriptInfo.scriptInfo);
|
||||
if (symlinkedProjectsMap) {
|
||||
symlinkedProjectsMap.forEach((symlinkedProjects) => {
|
||||
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
|
||||
});
|
||||
}
|
||||
for (const project of originalScriptInfo.containingProjects) {
|
||||
addToTodo({ project, location: originalLocation as TLocation }, toDo, seenProjects);
|
||||
}
|
||||
const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalScriptInfo);
|
||||
if (symlinkedProjectsMap) {
|
||||
symlinkedProjectsMap.forEach((symlinkedProjects) => {
|
||||
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -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,10 +9524,11 @@ export function Test2() {
|
||||
],
|
||||
},
|
||||
]);
|
||||
verifySingleInferredProject(session);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Untitled files", () => {
|
||||
describe("tsserverProjectSystem Untitled files", () => {
|
||||
it("Can convert positions to locations", () => {
|
||||
const aTs: File = { path: "/proj/a.ts", content: "" };
|
||||
const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" };
|
||||
|
||||
Reference in New Issue
Block a user