Find references of a module by filename (#41805)

* Naive implementation enough to build and write a test

* Add simple test

* Add project references test

* Add deduplication test, accept baselines

* Add test for referencing a script (doesn’t do anything)

* Update API baselines

* Use refFileMap for non-module references

* Fix find-all-refs on module specifier

* Remove unused util

* Don’t store text range on ts.RefFile

* Ensure string literal could itself be a file reference

* Remove unused utilities

* Improve baseline format

* Preserve old behavior of falling back to string literal references

* Update baselines from master

* Fix old RefFileMap code after merge

* Add test for additional response info

* Undo test change
This commit is contained in:
Andrew Branch
2020-12-11 12:37:02 -08:00
committed by GitHub
parent 1c1cd9b08d
commit 9dfbf07d8a
36 changed files with 810 additions and 45 deletions

View File

@@ -37,6 +37,9 @@ namespace ts.server.protocol {
/* @internal */
EmitOutput = "emit-output",
Exit = "exit",
FileReferences = "fileReferences",
/* @internal */
FileReferencesFull = "fileReferences-full",
Format = "format",
Formatonkey = "formatonkey",
/* @internal */
@@ -1152,6 +1155,25 @@ namespace ts.server.protocol {
body?: ReferencesResponseBody;
}
export interface FileReferencesRequest extends FileRequest {
command: CommandTypes.FileReferences;
}
export interface FileReferencesResponseBody {
/**
* The file locations referencing the symbol.
*/
refs: readonly ReferencesResponseItem[];
/**
* The name of the symbol.
*/
symbolName: string;
}
export interface FileReferencesResponse extends Response {
body?: FileReferencesResponseBody;
}
/**
* Argument for RenameRequest request.
*/

View File

@@ -409,6 +409,29 @@ namespace ts.server {
return outputs.filter(o => o.references.length !== 0);
}
function combineProjectOutputForFileReferences(
projects: Projects,
defaultProject: Project,
fileName: string
): readonly ReferenceEntry[] {
const outputs: ReferenceEntry[] = [];
combineProjectOutputWorker(
projects,
defaultProject,
/*initialLocation*/ undefined,
project => {
for (const referenceEntry of project.getLanguageService().getFileReferences(fileName) || emptyArray) {
if (!contains(outputs, referenceEntry, documentSpansEqual)) {
outputs.push(referenceEntry);
}
}
},
);
return outputs;
}
interface ProjectAndLocation<TLocation extends DocumentPosition | undefined> {
readonly project: Project;
readonly location: TLocation;
@@ -1509,7 +1532,7 @@ namespace ts.server {
return arrayFrom(map.values());
}
private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | undefined | readonly ReferencedSymbol[] {
private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | readonly ReferencedSymbol[] {
const file = toNormalizedPath(args.file);
const projects = this.getProjects(args);
const position = this.getPositionInFile(args, file);
@@ -1528,22 +1551,28 @@ namespace ts.server {
const nameSpan = nameInfo && nameInfo.textSpan;
const symbolStartOffset = nameSpan ? scriptInfo.positionToLineOffset(nameSpan.start).offset : 0;
const symbolName = nameSpan ? scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)) : "";
const refs: readonly protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol =>
referencedSymbol.references.map(({ fileName, textSpan, contextSpan, isWriteAccess, isDefinition }): protocol.ReferencesResponseItem => {
const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(fileName));
const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo);
const lineSpan = scriptInfo.lineToTextSpan(span.start.line - 1);
const lineText = scriptInfo.getSnapshot().getText(lineSpan.start, textSpanEnd(lineSpan)).replace(/\r|\n/g, "");
return {
file: fileName,
...span,
lineText,
isWriteAccess,
isDefinition
};
}));
const refs: readonly protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol => {
return referencedSymbol.references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry));
});
return { refs, symbolName, symbolStartOffset, symbolDisplayString };
}
private getFileReferences(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.FileReferencesResponseBody | readonly ReferenceEntry[] {
const projects = this.getProjects(args);
const references = combineProjectOutputForFileReferences(
projects,
this.getDefaultProject(args),
args.file,
);
if (!simplifiedResult) return references;
const refs = references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry));
return {
refs,
symbolName: `"${args.file}"`
};
}
/**
* @param fileName is the name of the file to be opened
* @param fileContent is a version of the file content that is known to be more up to date than the one on disk
@@ -2639,6 +2668,12 @@ namespace ts.server {
[CommandNames.GetSpanOfEnclosingComment]: (request: protocol.SpanOfEnclosingCommentRequest) => {
return this.requiredResponse(this.getSpanOfEnclosingComment(request.arguments));
},
[CommandNames.FileReferences]: (request: protocol.FileReferencesRequest) => {
return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.FileReferencesFull]: (request: protocol.FileReferencesRequest) => {
return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.Format]: (request: protocol.FormatRequest) => {
return this.requiredResponse(this.getFormattingEditsForRange(request.arguments));
},
@@ -3068,4 +3103,18 @@ namespace ts.server {
return text;
}
function referenceEntryToReferencesResponseItem(projectService: ProjectService, { fileName, textSpan, contextSpan, isWriteAccess, isDefinition }: ReferenceEntry): protocol.ReferencesResponseItem {
const scriptInfo = Debug.checkDefined(projectService.getScriptInfo(fileName));
const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo);
const lineSpan = scriptInfo.lineToTextSpan(span.start.line - 1);
const lineText = scriptInfo.getSnapshot().getText(lineSpan.start, textSpanEnd(lineSpan)).replace(/\r|\n/g, "");
return {
file: fileName,
...span,
lineText,
isWriteAccess,
isDefinition
};
}
}