Fix bug: Get mapped location of definition for findAllReferencesFull (#27113)

This commit is contained in:
Andy 2018-09-17 14:14:55 -07:00 committed by GitHub
parent c9f190283e
commit bfc00935df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 91 additions and 17 deletions

View File

@ -336,16 +336,23 @@ namespace ts.server {
function combineProjectOutputForReferences(projects: Projects, defaultProject: Project, initialLocation: sourcemaps.SourceMappableLocation, projectService: ProjectService): ReadonlyArray<ReferencedSymbol> {
const outputs: ReferencedSymbol[] = [];
combineProjectOutputWorker<sourcemaps.SourceMappableLocation>(projects, defaultProject, initialLocation, projectService, ({ project, location }, tryAddToTodo) => {
combineProjectOutputWorker<sourcemaps.SourceMappableLocation>(projects, defaultProject, initialLocation, projectService, ({ project, location }, getMappedLocation) => {
for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.position) || emptyArray) {
let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, outputReferencedSymbol.definition));
const mappedDefinitionFile = getMappedLocation(project, documentSpanLocation(outputReferencedSymbol.definition));
const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ? outputReferencedSymbol.definition : {
...outputReferencedSymbol.definition,
textSpan: createTextSpan(mappedDefinitionFile.position, outputReferencedSymbol.definition.textSpan.length),
fileName: mappedDefinitionFile.fileName,
};
let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, definition));
if (!symbolToAddTo) {
symbolToAddTo = { definition: outputReferencedSymbol.definition, references: [] };
symbolToAddTo = { definition, references: [] };
outputs.push(symbolToAddTo);
}
for (const ref of outputReferencedSymbol.references) {
if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !tryAddToTodo(project, documentSpanLocation(ref))) {
// If it's in a mapped file, that is added to the todo list by `getMappedLocation`.
if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !getMappedLocation(project, documentSpanLocation(ref))) {
symbolToAddTo.references.push(ref);
}
}
@ -373,12 +380,17 @@ namespace ts.server {
}
}
type CombineProjectOutputCallback<TLocation extends sourcemaps.SourceMappableLocation | undefined> = (
where: ProjectAndLocation<TLocation>,
getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => sourcemaps.SourceMappableLocation | undefined,
) => void;
function combineProjectOutputWorker<TLocation extends sourcemaps.SourceMappableLocation | undefined>(
projects: Projects,
defaultProject: Project,
initialLocation: TLocation,
projectService: ProjectService,
cb: (where: ProjectAndLocation<TLocation>, getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => boolean) => void,
cb: CombineProjectOutputCallback<TLocation>,
getDefinition: (() => sourcemaps.SourceMappableLocation | undefined) | undefined,
): void {
let toDo: ProjectAndLocation<TLocation>[] | undefined;
@ -417,13 +429,13 @@ namespace ts.server {
projectService: ProjectService,
toDo: ProjectAndLocation<TLocation>[] | undefined,
seenProjects: Map<true>,
cb: (where: ProjectAndLocation<TLocation>, getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => boolean) => void,
cb: CombineProjectOutputCallback<TLocation>,
): ProjectAndLocation<TLocation>[] | undefined {
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 = projectService.getOriginalLocationEnsuringConfiguredProject(project, location);
if (!originalLocation) return false;
if (!originalLocation) return undefined;
const originalScriptInfo = projectService.getScriptInfo(originalLocation.fileName)!;
toDo = toDo || [];
@ -437,7 +449,7 @@ namespace ts.server {
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
});
}
return true;
return originalLocation;
});
return toDo;
}

View File

@ -3491,7 +3491,7 @@ namespace ts.projectSystem {
host.checkTimeoutQueueLength(2); // Update configured project and projects for open file
checkProjectActualFiles(services.configuredProjects.get(config.path)!, filesWithFileA.map(f => f.path));
// host.fileExists = originalFileExists;
// host.fileExists = originalFileExists;
openFile(fileSubA);
// This should create inferred project since fileSubA not on the disk
checkProjectActualFiles(services.configuredProjects.get(config.path)!, mapDefined(filesWithFileA, f => f === fileA ? undefined : f.path));
@ -3672,7 +3672,7 @@ namespace ts.projectSystem {
path: `${projectRoot}/app1/tsconfig.json`,
content: JSON.stringify({
files: ["app.ts", "../core/core.ts"],
compilerOptions: { outFile : "build/output.js" },
compilerOptions: { outFile: "build/output.js" },
compileOnSave: true
})
};
@ -6778,9 +6778,9 @@ namespace ts.projectSystem {
fileName: "/a.1.ts",
textChanges: [
{
start: { line: 0, offset: 0 },
end: { line: 0, offset: 0 },
newText: "export const a = 0;",
start: { line: 0, offset: 0 },
end: { line: 0, offset: 0 },
newText: "export const a = 0;",
},
],
}
@ -8672,7 +8672,7 @@ new C();`
}));
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, arrayFrom(expectedRecursiveDirectories.keys()), /*recursive*/ true);
}
}
describe("from files in same folder", () => {
function getFiles(fileContent: string) {
@ -9750,7 +9750,7 @@ declare class TestLib {
function verifyATsConfigOriginalProject(session: TestSession) {
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
verifyInferredProjectUnchanged(session);
verifyATsConfigProject(session);
verifyATsConfigProject(session);
// Close user file should close all the projects
closeFilesForSession([userTs], session);
verifyOnlyOrphanInferredProject(session);
@ -9900,11 +9900,12 @@ declare class TestLib {
verifyATsConfigWhenOpened(session);
});
interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; }
interface ReferencesFullResponse extends protocol.Response { readonly body: ReadonlyArray<ReferencedSymbol>; }
it("findAllReferencesFull", () => {
const session = makeSampleProjects();
interface ReferencesFullRequest extends protocol.FileLocationRequest { command: protocol.CommandTypes.ReferencesFull; }
interface ReferencesFullResponse extends protocol.Response { body: ReadonlyArray<ReferencedSymbol>; }
const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()"));
function fnAVoid(kind: SymbolDisplayPartKind): SymbolDisplayPart[] {
@ -9961,6 +9962,67 @@ declare class TestLib {
verifyATsConfigOriginalProject(session);
});
it("findAllReferencesFull definition is in mapped file", () => {
const aTs: File = { path: "/a/a.ts", content: `function f() {}` };
const aTsconfig: File = {
path: "/a/tsconfig.json",
content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }),
};
const bTs: File = { path: "/b/b.ts", content: `f();` };
const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) };
const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` };
const aDtsMap: File = {
path: "/bin/a.d.ts.map",
content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }),
};
const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap]));
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
openFilesForSession([bTs], session);
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()"));
assert.deepEqual<ReadonlyArray<ReferencedSymbol>>(responseFull, [
{
definition: {
containerKind: ScriptElementKind.unknown,
containerName: "",
displayParts: [
keywordPart(SyntaxKind.FunctionKeyword),
spacePart(),
displayPart("f", SymbolDisplayPartKind.functionName),
punctuationPart(SyntaxKind.OpenParenToken),
punctuationPart(SyntaxKind.CloseParenToken),
punctuationPart(SyntaxKind.ColonToken),
spacePart(),
keywordPart(SyntaxKind.VoidKeyword),
],
fileName: aTs.path,
kind: ScriptElementKind.functionElement,
name: "function f(): void",
textSpan: { start: 9, length: 1 },
},
references: [
{
fileName: bTs.path,
isDefinition: false,
isInString: undefined,
isWriteAccess: false,
textSpan: { start: 0, length: 1 },
},
{
fileName: aTs.path,
isDefinition: true,
isInString: undefined,
isWriteAccess: true,
textSpan: { start: 9, length: 1 },
},
],
}
]);
});
it("findAllReferences -- target does not exist", () => {
const session = makeSampleProjects();