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

@@ -362,6 +362,18 @@ namespace ts.server {
}));
}
getFileReferences(fileName: string): ReferenceEntry[] {
const request = this.processRequest<protocol.FileReferencesRequest>(CommandNames.FileReferences, { file: fileName });
const response = this.processResponse<protocol.FileReferencesResponse>(request);
return response.body!.refs.map(entry => ({ // TODO: GH#18217
fileName: entry.file,
textSpan: this.decodeSpan(entry),
isWriteAccess: entry.isWriteAccess,
isDefinition: entry.isDefinition,
}));
}
getEmitOutput(file: string): EmitOutput {
const request = this.processRequest<protocol.EmitOutputRequest>(protocol.CommandTypes.EmitOutput, { file });
const response = this.processResponse<protocol.EmitOutputResponse>(request);

View File

@@ -1114,38 +1114,77 @@ namespace FourSlash {
}
}
public verifyBaselineFindAllReferences(markerName: string) {
const marker = this.getMarkerByName(markerName);
const references = this.languageService.findReferences(marker.fileName, marker.position);
public verifyBaselineFindAllReferences(...markerNames: string[]) {
const baseline = markerNames.map(markerName => {
const marker = this.getMarkerByName(markerName);
const references = this.languageService.findReferences(marker.fileName, marker.position);
const refsByFile = references
? ts.group(ts.sort(ts.flatMap(references, r => r.references), (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName)
: ts.emptyArray;
// Write input files
const baselineContent = this.getBaselineContentForGroupedReferences(refsByFile, markerName);
// Write response JSON
return baselineContent + JSON.stringify(references, undefined, 2);
}).join("\n\n");
Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baseline);
}
public verifyBaselineGetFileReferences(fileName: string) {
const references = this.languageService.getFileReferences(fileName);
const refsByFile = references
? ts.group(ts.sort(ts.flatMap(references, r => r.references), (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName)
? ts.group(ts.sort(references, (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName)
: ts.emptyArray;
// Write input files
let baselineContent = this.getBaselineContentForGroupedReferences(refsByFile);
// Write response JSON
baselineContent += JSON.stringify(references, undefined, 2);
Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baselineContent);
}
private getBaselineContentForGroupedReferences(refsByFile: readonly (readonly ts.ReferenceEntry[])[], markerName?: string) {
const marker = markerName !== undefined ? this.getMarkerByName(markerName) : undefined;
let baselineContent = "";
for (const group of refsByFile) {
baselineContent += getBaselineContentForFile(group[0].fileName, this.getFileContent(group[0].fileName));
baselineContent += "\n\n";
}
// Write response JSON
baselineContent += JSON.stringify(references, undefined, 2);
Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baselineContent);
return baselineContent;
function getBaselineContentForFile(fileName: string, content: string) {
let newContent = `=== ${fileName} ===\n`;
let pos = 0;
for (const { textSpan } of refsByFile.find(refs => refs[0].fileName === fileName) ?? ts.emptyArray) {
if (fileName === marker.fileName && ts.textSpanContainsPosition(textSpan, marker.position)) {
newContent += "/*FIND ALL REFS*/";
}
const end = textSpan.start + textSpan.length;
newContent += content.slice(pos, textSpan.start);
newContent += "[|";
newContent += content.slice(textSpan.start, end);
pos = textSpan.start;
// It's easier to read if the /*FIND ALL REFS*/ comment is outside the range markers, which makes
// this code a bit more verbose than it would be if I were less picky about the baseline format.
if (fileName === marker?.fileName && marker.position === textSpan.start) {
newContent += "/*FIND ALL REFS*/";
newContent += "[|";
}
else if (fileName === marker?.fileName && ts.textSpanContainsPosition(textSpan, marker.position)) {
newContent += "[|";
newContent += content.slice(pos, marker.position);
newContent += "/*FIND ALL REFS*/";
pos = marker.position;
}
else {
newContent += "[|";
}
newContent += content.slice(pos, end);
newContent += "|]";
pos = end;
}
if (marker?.fileName === fileName && marker.position >= pos) {
newContent += content.slice(pos, marker.position);
newContent += "/*FIND ALL REFS*/";
pos = marker.position;
}
newContent += content.slice(pos);
return newContent.split(/\r?\n/).map(l => "// " + l).join("\n");
}

View File

@@ -332,8 +332,12 @@ namespace FourSlashInterface {
this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected);
}
public baselineFindAllReferences(markerName: string) {
this.state.verifyBaselineFindAllReferences(markerName);
public baselineFindAllReferences(...markerNames: string[]) {
this.state.verifyBaselineFindAllReferences(...markerNames);
}
public baselineGetFileReferences(fileName: string) {
this.state.verifyBaselineGetFileReferences(fileName);
}
public referenceGroups(starts: ArrayOrSingle<string> | ArrayOrSingle<FourSlash.Range>, parts: ReferenceGroup[]) {

View File

@@ -519,6 +519,9 @@ namespace Harness.LanguageService {
findReferences(fileName: string, position: number): ts.ReferencedSymbol[] {
return unwrapJSONCallResult(this.shim.findReferences(fileName, position));
}
getFileReferences(fileName: string): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getFileReferences(fileName));
}
getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position));
}