Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3224,20 +3224,39 @@ namespace ts.server {

/*@internal*/
getOriginalLocationEnsuringConfiguredProject(project: Project, location: DocumentPosition): DocumentPosition | undefined {
const originalLocation = project.isSourceOfProjectReferenceRedirect(location.fileName) ?
const isSourceOfProjectReferenceRedirect = project.isSourceOfProjectReferenceRedirect(location.fileName);
const originalLocation = isSourceOfProjectReferenceRedirect ?
location :
project.getSourceMapper().tryGetSourcePosition(location);
if (!originalLocation) return undefined;

const { fileName } = originalLocation;
if (!this.getScriptInfo(fileName) && !this.host.fileExists(fileName)) return undefined;
const scriptInfo = this.getScriptInfo(fileName);
if (!scriptInfo && !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;

let configuredProject: ConfiguredProject | undefined = this.findConfiguredProjectByProjectName(configFileName) ||
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}`);
let configuredProject: ConfiguredProject | undefined = this.findConfiguredProjectByProjectName(configFileName);
if (!configuredProject) {
if (project.getCompilerOptions().disableReferencedProjectLoad) {
// If location was a project reference redirect, then `location` and `originalLocation` are the same.
if (isSourceOfProjectReferenceRedirect) {
return location;
}

// Otherwise, if we found `originalLocation` via a source map instead, then we check whether it's in
// an open project. If it is, we should search the containing project(s), even though the "default"
// configured project isn't open. However, if it's not in an open project, we need to stick with
// `location` (i.e. the .d.ts file) because otherwise we'll miss the references in that file.
return scriptInfo?.containingProjects.length
? originalLocation
: location;
}

configuredProject = this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}`);
}
updateProjectIfDirty(configuredProject);

const projectContainsOriginalInfo = (project: ConfiguredProject) => {
Expand Down
115 changes: 115 additions & 0 deletions src/testRunner/unittests/tsserver/projectReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1353,5 +1353,120 @@ bar;`
});
baselineTsserverLogs("projectReferences", `when files from two projects are open and one project references`, session);
});

describe("find refs to symbol from other project", () => {
const indexA: File = {
path: `${tscWatch.projectRoot}/a/index.ts`,
content: `import { B } from "../b/lib";

const b: B = new B();`
};

const configB: File = {
path: `${tscWatch.projectRoot}/b/tsconfig.json`,
content: `{
"compilerOptions": {
"declarationMap": true,
"outDir": "lib",
"composite": true
}
}`
};

const indexB: File = {
path: `${tscWatch.projectRoot}/b/index.ts`,
content: `export class B {
M() {}
}`
};

const helperB: File = {
path: `${tscWatch.projectRoot}/b/helper.ts`,
content: `import { B } from ".";

const b: B = new B();`
};

const dtsB: File = {
path: `${tscWatch.projectRoot}/b/lib/index.d.ts`,
content: `export declare class B {
M(): void;
}
//# sourceMappingURL=index.d.ts.map`
};

const dtsMapB: File = {
path: `${tscWatch.projectRoot}/b/lib/index.d.ts.map`,
content: `{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;IACV,CAAC;CACJ"}`
};

function baselineDisableReferencedProjectLoad(
projectAlreadyLoaded: boolean,
disableReferencedProjectLoad: boolean,
disableSourceOfProjectReferenceRedirect: boolean,
dtsMapPresent: boolean) {

// Mangled to stay under windows path length limit
const subScenario =
`when proj ${projectAlreadyLoaded ? "is" : "is not"} loaded ` +
` and refd proj loading is ${disableReferencedProjectLoad ? "disabled" : "enabled"}` +
` and proj ref redirects are ${disableSourceOfProjectReferenceRedirect ? "disabled" : "enabled"}` +
` and a decl map is ${dtsMapPresent ? "present" : "missing"}`;
const compilerOptions: CompilerOptions = {
disableReferencedProjectLoad,
disableSourceOfProjectReferenceRedirect,
composite: true
};

it(subScenario, () => {
const configA: File = {
path: `${tscWatch.projectRoot}/a/tsconfig.json`,
content: `{
"compilerOptions": ${JSON.stringify(compilerOptions)},
"references": [{ "path": "../b" }]
}`
};

const host = createServerHost([configA, indexA, configB, indexB, helperB, dtsB, ...(dtsMapPresent ? [dtsMapB] : [])]);
const session = createSession(host, { logger: createLoggerWithInMemoryLogs() });
openFilesForSession([indexA, ...(projectAlreadyLoaded ? [helperB] : [])], session);

session.executeCommandSeq<protocol.ReferencesRequest>({
command: protocol.CommandTypes.References,
arguments: protocolFileLocationFromSubstring(indexA, `B`, { index: 1 })
});
baselineTsserverLogs("projectReferences", `find all references to a symbol declared in another project ${subScenario}`, session);
});
}

/* eslint-disable boolean-trivia */

// Pre-loaded = A file from project B is already open when FAR is invoked
// dRPL = Project A has disableReferencedProjectLoad
// dSOPRR = Project A has disableSourceOfProjectReferenceRedirect
// Map = The declaration map file b/lib/index.d.ts.map exists
// B refs = files under directory b in which references are found (all scenarios find all references in a/index.ts)

// Pre-loaded | dRPL | dSOPRR | Map | B state | Notes | B refs | Notes
// -----------+--------+--------+----------+------------+--------------+---------------------+---------------------------------------------------
baselineDisableReferencedProjectLoad(true, true, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project
baselineDisableReferencedProjectLoad(true, true, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded
baselineDisableReferencedProjectLoad(true, true, false, true); // Pre-loaded | | index.ts, helper.ts |
baselineDisableReferencedProjectLoad(true, true, false, false); // Pre-loaded | | index.ts, helper.ts |
baselineDisableReferencedProjectLoad(true, false, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project
baselineDisableReferencedProjectLoad(true, false, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded
baselineDisableReferencedProjectLoad(true, false, false, true); // Pre-loaded | | index.ts, helper.ts |
baselineDisableReferencedProjectLoad(true, false, false, false); // Pre-loaded | | index.ts, helper.ts |
baselineDisableReferencedProjectLoad(false, true, true, true); // Not loaded | | lib/index.d.ts | Even though map is present
baselineDisableReferencedProjectLoad(false, true, true, false); // Not loaded | | lib/index.d.ts |
baselineDisableReferencedProjectLoad(false, true, false, true); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a
baselineDisableReferencedProjectLoad(false, true, false, false); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a
baselineDisableReferencedProjectLoad(false, false, true, true); // Loaded | Via map | index.ts, helper.ts | Via map and newly loaded project
baselineDisableReferencedProjectLoad(false, false, true, false); // Not loaded | | lib/index.d.ts |
baselineDisableReferencedProjectLoad(false, false, false, true); // Loaded | Via redirect | index.ts, helper.ts |
baselineDisableReferencedProjectLoad(false, false, false, false); // Loaded | Via redirect | index.ts, helper.ts |

/* eslint-enable boolean-trivia */
});
});
}
Loading