Skip to content

Support 'find references' on most declaration-related keywords #36490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 91 additions & 17 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -997,18 +997,43 @@ namespace FourSlash {
definition: string | { text: string, range: ts.TextSpan };
references: ts.ReferenceEntry[];
}
interface RangeMarkerData {
id?: string;
isWriteAccess?: boolean,
isDefinition?: boolean,
isInString?: true,
contextRangeIndex?: number,
contextRangeDelta?: number,
contextRangeId?: string
}
const fullExpected = ts.map<FourSlashInterface.ReferenceGroup, ReferenceGroupJson>(parts, ({ definition, ranges }) => ({
definition: typeof definition === "string" ? definition : { ...definition, range: ts.createTextSpanFromRange(definition.range) },
references: ranges.map<ts.ReferenceEntry>(r => {
const { isWriteAccess = false, isDefinition = false, isInString, contextRangeIndex } = (r.marker && r.marker.data || {}) as { isWriteAccess?: boolean, isDefinition?: boolean, isInString?: true, contextRangeIndex?: number };
const { isWriteAccess = false, isDefinition = false, isInString, contextRangeIndex, contextRangeDelta, contextRangeId } = (r.marker && r.marker.data || {}) as RangeMarkerData;
let contextSpan: ts.TextSpan | undefined;
if (contextRangeDelta !== undefined) {
const allRanges = this.getRanges();
const index = allRanges.indexOf(r);
if (index !== -1) {
contextSpan = ts.createTextSpanFromRange(allRanges[index + contextRangeDelta]);
}
}
else if (contextRangeId !== undefined) {
const allRanges = this.getRanges();
const contextRange = ts.find(allRanges, range => (range.marker?.data as RangeMarkerData)?.id === contextRangeId);
if (contextRange) {
contextSpan = ts.createTextSpanFromRange(contextRange);
}
}
else if (contextRangeIndex !== undefined) {
contextSpan = ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]);
}
return {
fileName: r.fileName,
textSpan: ts.createTextSpanFromRange(r),
fileName: r.fileName,
...(contextSpan ? { contextSpan } : undefined),
isWriteAccess,
isDefinition,
...(contextRangeIndex !== undefined ?
{ contextSpan: ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]) } :
undefined),
...(isInString ? { isInString: true } : undefined),
};
}),
Expand All @@ -1032,7 +1057,7 @@ namespace FourSlash {
}

public verifyNoReferences(markerNameOrRange?: string | Range) {
if (markerNameOrRange) this.goToMarkerOrRange(markerNameOrRange);
if (markerNameOrRange !== undefined) this.goToMarkerOrRange(markerNameOrRange);
const refs = this.getReferencesAtCaret();
if (refs && refs.length) {
this.raiseError(`Expected getReferences to fail, but saw references: ${stringify(refs)}`);
Expand Down Expand Up @@ -1233,6 +1258,12 @@ namespace FourSlash {
}

public verifyRenameLocations(startRanges: ArrayOrSingle<Range>, options: FourSlashInterface.RenameLocationsOptions) {
interface RangeMarkerData {
id?: string;
contextRangeIndex?: number,
contextRangeDelta?: number
contextRangeId?: string;
}
const { findInStrings = false, findInComments = false, ranges = this.getRanges(), providePrefixAndSuffixTextForRename = true } = ts.isArray(options) ? { findInStrings: false, findInComments: false, ranges: options, providePrefixAndSuffixTextForRename: true } : options;

const _startRanges = toArray(startRanges);
Expand All @@ -1253,13 +1284,29 @@ namespace FourSlash {
locations && ts.sort(locations, (r1, r2) => ts.compareStringsCaseSensitive(r1.fileName, r2.fileName) || r1.textSpan.start - r2.textSpan.start);
assert.deepEqual(sort(references), sort(ranges.map((rangeOrOptions): ts.RenameLocation => {
const { range, ...prefixSuffixText } = "range" in rangeOrOptions ? rangeOrOptions : { range: rangeOrOptions }; // eslint-disable-line no-in-operator
const { contextRangeIndex } = (range.marker && range.marker.data || {}) as { contextRangeIndex?: number; };
const { contextRangeIndex, contextRangeDelta, contextRangeId } = (range.marker && range.marker.data || {}) as RangeMarkerData;
let contextSpan: ts.TextSpan | undefined;
if (contextRangeDelta !== undefined) {
const allRanges = this.getRanges();
const index = allRanges.indexOf(range);
if (index !== -1) {
contextSpan = ts.createTextSpanFromRange(allRanges[index + contextRangeDelta]);
}
}
else if (contextRangeId !== undefined) {
const allRanges = this.getRanges();
const contextRange = ts.find(allRanges, range => (range.marker?.data as RangeMarkerData)?.id === contextRangeId);
if (contextRange) {
contextSpan = ts.createTextSpanFromRange(contextRange);
}
}
else if (contextRangeIndex !== undefined) {
contextSpan = ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]);
}
return {
fileName: range.fileName,
textSpan: ts.createTextSpanFromRange(range),
...(contextRangeIndex !== undefined ?
{ contextSpan: ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]) } :
undefined),
...(contextSpan ? { contextSpan } : undefined),
...prefixSuffixText
};
})));
Expand Down Expand Up @@ -3589,19 +3636,41 @@ namespace FourSlash {
// Parse out the files and their metadata
const testData = parseTestData(absoluteBasePath, content, absoluteFileName);
const state = new TestState(absoluteFileName, absoluteBasePath, testType, testData);
const output = ts.transpileModule(content, { reportDiagnostics: true, compilerOptions: { target: ts.ScriptTarget.ES2015 } });
const actualFileName = Harness.IO.resolvePath(fileName) || absoluteFileName;
const output = ts.transpileModule(content, { reportDiagnostics: true, fileName: actualFileName, compilerOptions: { target: ts.ScriptTarget.ES2015, inlineSourceMap: true } });
if (output.diagnostics!.length > 0) {
throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics![0].messageText}`);
}
runCode(output.outputText, state);
runCode(output.outputText, state, actualFileName);
}

function runCode(code: string, state: TestState): void {
function runCode(code: string, state: TestState, fileName: string): void {
// Compile and execute the test
const wrappedCode =
`(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {
${code}
})`;
const generatedFile = ts.changeExtension(fileName, ".js");
const wrappedCode = `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {${code}\n//# sourceURL=${generatedFile}\n})`;

type SourceMapSupportModule = typeof import("source-map-support") & {
// TODO(rbuckton): This is missing from the DT definitions and needs to be added.
resetRetrieveHandlers(): void
};

// Provide the content of the current test to 'source-map-support' so that it can give us the correct source positions
// for test failures.
let sourceMapSupportModule: SourceMapSupportModule | undefined;
try {
sourceMapSupportModule = require("source-map-support");
}
catch {
// do nothing
}

sourceMapSupportModule?.install({
retrieveFile: path => {
return path === generatedFile ? wrappedCode :
undefined!;
}
});

try {
const test = new FourSlashInterface.Test(state);
const goTo = new FourSlashInterface.GoTo(state);
Expand All @@ -3616,8 +3685,13 @@ ${code}
f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, verifyOperationIsCancelled);
}
catch (err) {
// ensure 'source-map-support' is triggered while we still have the handler attached by accessing `error.stack`.
err.stack?.toString();
throw err;
}
finally {
sourceMapSupportModule?.resetRetrieveHandlers();
}
}

function chompLeadingSpace(content: string) {
Expand Down
2 changes: 1 addition & 1 deletion src/services/callHierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ namespace ts.CallHierarchy {
return [];
}
const location = getCallHierarchyDeclarationReferenceNode(declaration);
const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, /*options*/ undefined, convertEntryToCallSite), isDefined);
const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, { use: FindAllReferences.FindReferencesUse.References }, convertEntryToCallSite), isDefined);
return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : [];
}

Expand Down
Loading