Merge pull request #33636 from microsoft/rootReferenceRedirect

Fix the issue when file is attached to project because its a root file name but program contains instead its d.ts
This commit is contained in:
Sheetal Nandi 2019-09-27 14:36:08 -07:00 committed by GitHub
commit 49a52d02d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 154 additions and 8 deletions

View File

@ -434,7 +434,7 @@ namespace ts.server {
const memGetDefinition = memoize(getDefinition);
projectService.forEachEnabledProject(project => {
if (!addToSeen(seenProjects, project.projectName)) return;
const definition = getDefinitionInProject(memGetDefinition(), defaultProject, project);
const definition = mapDefinitionInProject(memGetDefinition(), defaultProject, project);
if (definition) {
toDo = callbackProjectAndLocation<TLocation>({ project, location: definition as TLocation }, projectService, toDo, seenProjects, cb);
}
@ -446,14 +446,35 @@ namespace ts.server {
}
}
function getDefinitionInProject(definition: DocumentPosition | undefined, definingProject: Project, project: Project): DocumentPosition | undefined {
if (!definition || project.containsFile(toNormalizedPath(definition.fileName))) return definition;
function mapDefinitionInProject(definition: DocumentPosition | undefined, definingProject: Project, project: Project): DocumentPosition | undefined {
// If the definition is actually from the project, definition is correct as is
if (!definition ||
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;
}
function isLocationProjectReferenceRedirect(project: Project, location: DocumentPosition | undefined) {
if (!location) return false;
const program = project.getLanguageService().getProgram();
if (!program) return false;
const sourceFile = program.getSourceFile(location.fileName);
// It is possible that location is attached to project but
// the program actually includes its redirect instead.
// This happens when rootFile in project is one of the file from referenced project
// Thus root is attached but program doesnt have the actual .ts file but .d.ts
// If this is not the file we were actually looking, return rest of the toDo
return !!sourceFile &&
sourceFile.resolvedPath !== sourceFile.path &&
sourceFile.resolvedPath !== project.toPath(location.fileName);
}
function callbackProjectAndLocation<TLocation extends DocumentPosition | undefined>(
projectAndLocation: ProjectAndLocation<TLocation>,
projectService: ProjectService,
@ -461,7 +482,10 @@ namespace ts.server {
seenProjects: Map<true>,
cb: CombineProjectOutputCallback<TLocation>,
): ProjectAndLocation<TLocation>[] | undefined {
if (projectAndLocation.project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled
const { project, location } = projectAndLocation;
if (project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled
// If this is not the file we were actually looking, return rest of the toDo
if (isLocationProjectReferenceRedirect(project, location)) return toDo;
cb(projectAndLocation, (project, location) => {
seenProjects.set(projectAndLocation.project.projectName, true);
const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(project, location);
@ -475,8 +499,8 @@ namespace ts.server {
}
const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalScriptInfo);
if (symlinkedProjectsMap) {
symlinkedProjectsMap.forEach((symlinkedProjects) => {
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
symlinkedProjectsMap.forEach((symlinkedProjects, symlinkedPath) => {
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: { fileName: symlinkedPath, pos: originalLocation.pos } as TLocation }, toDo!, seenProjects);
});
}
return originalLocation === location ? undefined : originalLocation;

View File

@ -6,7 +6,7 @@ namespace ts.projectSystem {
// ts build should succeed
const solutionBuilder = tscWatch.createSolutionBuilder(host, rootNames, {});
solutionBuilder.build();
assert.equal(host.getOutput().length, 0);
assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
return host;
}
@ -1166,7 +1166,7 @@ ${dependencyTs.content}`);
dtsFileCreated: "main",
dtsFileDeleted: ["noDts", noDts => ({
// Map collection after file open
closedInfos: noDts.closedInfos.concat(dtsMapLocation) ,
closedInfos: noDts.closedInfos.concat(dtsMapLocation),
expectsMap: true
})],
dependencyChange: ["change", () => ({
@ -1179,6 +1179,128 @@ ${dependencyTs.content}`);
});
});
describe("when root file is file from referenced project", () => {
function verify(disableSourceOfProjectReferenceRedirect: boolean) {
const projectLocation = `/user/username/projects/project`;
const commonConfig: File = {
path: `${projectLocation}/src/common/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
declarationMap: true,
outDir: "../../out",
baseUrl: "..",
disableSourceOfProjectReferenceRedirect
},
include: ["./**/*"]
})
};
const keyboardTs: File = {
path: `${projectLocation}/src/common/input/keyboard.ts`,
content: `function bar() { return "just a random function so .d.ts location doesnt match"; }
export function evaluateKeyboardEvent() { }`
};
const keyboardTestTs: File = {
path: `${projectLocation}/src/common/input/keyboard.test.ts`,
content: `import { evaluateKeyboardEvent } from 'common/input/keyboard';
function testEvaluateKeyboardEvent() {
return evaluateKeyboardEvent();
}
`
};
const srcConfig: File = {
path: `${projectLocation}/src/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
declarationMap: true,
outDir: "../out",
baseUrl: ".",
paths: {
"common/*": ["./common/*"],
},
tsBuildInfoFile: "../out/src.tsconfig.tsbuildinfo",
disableSourceOfProjectReferenceRedirect
},
include: ["./**/*"],
references: [
{ path: "./common" }
]
})
};
const terminalTs: File = {
path: `${projectLocation}/src/terminal.ts`,
content: `import { evaluateKeyboardEvent } from 'common/input/keyboard';
function foo() {
return evaluateKeyboardEvent();
}
`
};
const host = createHost(
[commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, libFile],
[srcConfig.path]
);
const session = createSession(host);
openFilesForSession([keyboardTs, terminalTs], session);
const searchStr = "evaluateKeyboardEvent";
const importStr = `import { evaluateKeyboardEvent } from 'common/input/keyboard';`;
const result = session.executeCommandSeq<protocol.ReferencesRequest>({
command: protocol.CommandTypes.References,
arguments: protocolFileLocationFromSubstring(keyboardTs, searchStr)
}).response as protocol.ReferencesResponseBody;
assert.deepEqual(result, {
refs: [
makeReferenceItem({
file: keyboardTs,
text: searchStr,
contextText: `export function evaluateKeyboardEvent() { }`,
isDefinition: true,
lineText: `export function evaluateKeyboardEvent() { }`
}),
makeReferenceItem({
file: keyboardTestTs,
text: searchStr,
contextText: importStr,
isDefinition: true,
lineText: importStr
}),
makeReferenceItem({
file: keyboardTestTs,
text: searchStr,
options: { index: 1 },
isDefinition: false,
lineText: ` return evaluateKeyboardEvent();`
}),
makeReferenceItem({
file: terminalTs,
text: searchStr,
contextText: importStr,
isDefinition: true,
lineText: importStr
}),
makeReferenceItem({
file: terminalTs,
text: searchStr,
options: { index: 1 },
isDefinition: false,
lineText: ` return evaluateKeyboardEvent();`
}),
],
symbolName: searchStr,
symbolStartOffset: protocolLocationFromSubstring(keyboardTs.content, searchStr).offset,
symbolDisplayString: "function evaluateKeyboardEvent(): void"
});
}
it(`when using declaration file maps to navigate between projects`, () => {
verify(/*disableSourceOfProjectReferenceRedirect*/ true);
});
it(`when using original source files in the project`, () => {
verify(/*disableSourceOfProjectReferenceRedirect*/ false);
});
});
it("reusing d.ts files from composite and non composite projects", () => {
const projectLocation = "/user/username/projects/myproject";
const configA: File = {