Skip to content

Commit 01af3aa

Browse files
authored
Support 'find references' on most declaration-related keywords (#36490)
* Allow 'find references' to work on most declaration keywords * Add support for rename * Add more keywords, move logic out of checker and into services * Add additional type and expression keywords
1 parent afddaf0 commit 01af3aa

17 files changed

+1079
-56
lines changed

src/harness/fourslashImpl.ts

+91-17
Original file line numberDiff line numberDiff line change
@@ -1003,18 +1003,43 @@ namespace FourSlash {
10031003
definition: string | { text: string, range: ts.TextSpan };
10041004
references: ts.ReferenceEntry[];
10051005
}
1006+
interface RangeMarkerData {
1007+
id?: string;
1008+
isWriteAccess?: boolean,
1009+
isDefinition?: boolean,
1010+
isInString?: true,
1011+
contextRangeIndex?: number,
1012+
contextRangeDelta?: number,
1013+
contextRangeId?: string
1014+
}
10061015
const fullExpected = ts.map<FourSlashInterface.ReferenceGroup, ReferenceGroupJson>(parts, ({ definition, ranges }) => ({
10071016
definition: typeof definition === "string" ? definition : { ...definition, range: ts.createTextSpanFromRange(definition.range) },
10081017
references: ranges.map<ts.ReferenceEntry>(r => {
1009-
const { isWriteAccess = false, isDefinition = false, isInString, contextRangeIndex } = (r.marker && r.marker.data || {}) as { isWriteAccess?: boolean, isDefinition?: boolean, isInString?: true, contextRangeIndex?: number };
1018+
const { isWriteAccess = false, isDefinition = false, isInString, contextRangeIndex, contextRangeDelta, contextRangeId } = (r.marker && r.marker.data || {}) as RangeMarkerData;
1019+
let contextSpan: ts.TextSpan | undefined;
1020+
if (contextRangeDelta !== undefined) {
1021+
const allRanges = this.getRanges();
1022+
const index = allRanges.indexOf(r);
1023+
if (index !== -1) {
1024+
contextSpan = ts.createTextSpanFromRange(allRanges[index + contextRangeDelta]);
1025+
}
1026+
}
1027+
else if (contextRangeId !== undefined) {
1028+
const allRanges = this.getRanges();
1029+
const contextRange = ts.find(allRanges, range => (range.marker?.data as RangeMarkerData)?.id === contextRangeId);
1030+
if (contextRange) {
1031+
contextSpan = ts.createTextSpanFromRange(contextRange);
1032+
}
1033+
}
1034+
else if (contextRangeIndex !== undefined) {
1035+
contextSpan = ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]);
1036+
}
10101037
return {
1011-
fileName: r.fileName,
10121038
textSpan: ts.createTextSpanFromRange(r),
1039+
fileName: r.fileName,
1040+
...(contextSpan ? { contextSpan } : undefined),
10131041
isWriteAccess,
10141042
isDefinition,
1015-
...(contextRangeIndex !== undefined ?
1016-
{ contextSpan: ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]) } :
1017-
undefined),
10181043
...(isInString ? { isInString: true } : undefined),
10191044
};
10201045
}),
@@ -1038,7 +1063,7 @@ namespace FourSlash {
10381063
}
10391064

10401065
public verifyNoReferences(markerNameOrRange?: string | Range) {
1041-
if (markerNameOrRange) this.goToMarkerOrRange(markerNameOrRange);
1066+
if (markerNameOrRange !== undefined) this.goToMarkerOrRange(markerNameOrRange);
10421067
const refs = this.getReferencesAtCaret();
10431068
if (refs && refs.length) {
10441069
this.raiseError(`Expected getReferences to fail, but saw references: ${stringify(refs)}`);
@@ -1239,6 +1264,12 @@ namespace FourSlash {
12391264
}
12401265

12411266
public verifyRenameLocations(startRanges: ArrayOrSingle<Range>, options: FourSlashInterface.RenameLocationsOptions) {
1267+
interface RangeMarkerData {
1268+
id?: string;
1269+
contextRangeIndex?: number,
1270+
contextRangeDelta?: number
1271+
contextRangeId?: string;
1272+
}
12421273
const { findInStrings = false, findInComments = false, ranges = this.getRanges(), providePrefixAndSuffixTextForRename = true } = ts.isArray(options) ? { findInStrings: false, findInComments: false, ranges: options, providePrefixAndSuffixTextForRename: true } : options;
12431274

12441275
const _startRanges = toArray(startRanges);
@@ -1259,13 +1290,29 @@ namespace FourSlash {
12591290
locations && ts.sort(locations, (r1, r2) => ts.compareStringsCaseSensitive(r1.fileName, r2.fileName) || r1.textSpan.start - r2.textSpan.start);
12601291
assert.deepEqual(sort(references), sort(ranges.map((rangeOrOptions): ts.RenameLocation => {
12611292
const { range, ...prefixSuffixText } = "range" in rangeOrOptions ? rangeOrOptions : { range: rangeOrOptions }; // eslint-disable-line no-in-operator
1262-
const { contextRangeIndex } = (range.marker && range.marker.data || {}) as { contextRangeIndex?: number; };
1293+
const { contextRangeIndex, contextRangeDelta, contextRangeId } = (range.marker && range.marker.data || {}) as RangeMarkerData;
1294+
let contextSpan: ts.TextSpan | undefined;
1295+
if (contextRangeDelta !== undefined) {
1296+
const allRanges = this.getRanges();
1297+
const index = allRanges.indexOf(range);
1298+
if (index !== -1) {
1299+
contextSpan = ts.createTextSpanFromRange(allRanges[index + contextRangeDelta]);
1300+
}
1301+
}
1302+
else if (contextRangeId !== undefined) {
1303+
const allRanges = this.getRanges();
1304+
const contextRange = ts.find(allRanges, range => (range.marker?.data as RangeMarkerData)?.id === contextRangeId);
1305+
if (contextRange) {
1306+
contextSpan = ts.createTextSpanFromRange(contextRange);
1307+
}
1308+
}
1309+
else if (contextRangeIndex !== undefined) {
1310+
contextSpan = ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]);
1311+
}
12631312
return {
12641313
fileName: range.fileName,
12651314
textSpan: ts.createTextSpanFromRange(range),
1266-
...(contextRangeIndex !== undefined ?
1267-
{ contextSpan: ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]) } :
1268-
undefined),
1315+
...(contextSpan ? { contextSpan } : undefined),
12691316
...prefixSuffixText
12701317
};
12711318
})));
@@ -3595,19 +3642,41 @@ namespace FourSlash {
35953642
// Parse out the files and their metadata
35963643
const testData = parseTestData(absoluteBasePath, content, absoluteFileName);
35973644
const state = new TestState(absoluteFileName, absoluteBasePath, testType, testData);
3598-
const output = ts.transpileModule(content, { reportDiagnostics: true, compilerOptions: { target: ts.ScriptTarget.ES2015 } });
3645+
const actualFileName = Harness.IO.resolvePath(fileName) || absoluteFileName;
3646+
const output = ts.transpileModule(content, { reportDiagnostics: true, fileName: actualFileName, compilerOptions: { target: ts.ScriptTarget.ES2015, inlineSourceMap: true } });
35993647
if (output.diagnostics!.length > 0) {
36003648
throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics![0].messageText}`);
36013649
}
3602-
runCode(output.outputText, state);
3650+
runCode(output.outputText, state, actualFileName);
36033651
}
36043652

3605-
function runCode(code: string, state: TestState): void {
3653+
function runCode(code: string, state: TestState, fileName: string): void {
36063654
// Compile and execute the test
3607-
const wrappedCode =
3608-
`(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {
3609-
${code}
3610-
})`;
3655+
const generatedFile = ts.changeExtension(fileName, ".js");
3656+
const wrappedCode = `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {${code}\n//# sourceURL=${generatedFile}\n})`;
3657+
3658+
type SourceMapSupportModule = typeof import("source-map-support") & {
3659+
// TODO(rbuckton): This is missing from the DT definitions and needs to be added.
3660+
resetRetrieveHandlers(): void
3661+
};
3662+
3663+
// Provide the content of the current test to 'source-map-support' so that it can give us the correct source positions
3664+
// for test failures.
3665+
let sourceMapSupportModule: SourceMapSupportModule | undefined;
3666+
try {
3667+
sourceMapSupportModule = require("source-map-support");
3668+
}
3669+
catch {
3670+
// do nothing
3671+
}
3672+
3673+
sourceMapSupportModule?.install({
3674+
retrieveFile: path => {
3675+
return path === generatedFile ? wrappedCode :
3676+
undefined!;
3677+
}
3678+
});
3679+
36113680
try {
36123681
const test = new FourSlashInterface.Test(state);
36133682
const goTo = new FourSlashInterface.GoTo(state);
@@ -3622,8 +3691,13 @@ ${code}
36223691
f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, verifyOperationIsCancelled);
36233692
}
36243693
catch (err) {
3694+
// ensure 'source-map-support' is triggered while we still have the handler attached by accessing `error.stack`.
3695+
err.stack?.toString();
36253696
throw err;
36263697
}
3698+
finally {
3699+
sourceMapSupportModule?.resetRetrieveHandlers();
3700+
}
36273701
}
36283702

36293703
function chompLeadingSpace(content: string) {

src/services/callHierarchy.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ namespace ts.CallHierarchy {
293293
return [];
294294
}
295295
const location = getCallHierarchyDeclarationReferenceNode(declaration);
296-
const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, /*options*/ undefined, convertEntryToCallSite), isDefined);
296+
const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, { use: FindAllReferences.FindReferencesUse.References }, convertEntryToCallSite), isDefined);
297297
return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : [];
298298
}
299299

0 commit comments

Comments
 (0)