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 1 commit
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
Prev Previous commit
Next Next commit
Add more keywords, move logic out of checker and into services
  • Loading branch information
rbuckton committed Jan 31, 2020
commit 6fdba9f42d14deb80322db8716eca03251b0383f
67 changes: 2 additions & 65 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,13 +413,9 @@ namespace ts {
location = getParseTreeNode(location);
return location ? getSymbolsInScope(location, meaning) : [];
},
getSymbolAtLocation: (node: Node, includeKeywords?: boolean) => {
getSymbolAtLocation: (node: Node) => {
node = getParseTreeNode(node);
if (node) {
return includeKeywords ?
getSymbolAtLocation(node) ?? getSymbolAtKeyword(node) :
getSymbolAtLocation(node);
}
return node && getSymbolAtLocation(node);
},
getShorthandAssignmentValueSymbol: node => {
node = getParseTreeNode(node);
Expand Down Expand Up @@ -34298,65 +34294,6 @@ namespace ts {
}
}

/**
* Gets the symbol related to the provided location, if it that location is a keyword.
* These additional keywords are normally only used to resolve references but would
* not be used for document highlights, quickinfo, etc.
*/
function getSymbolAtKeyword(node: Node): Symbol | undefined {
if (node.flags & NodeFlags.InWithStatement) {
// We cannot answer semantic questions within a with block, do not proceed any further
return undefined;
}

const { parent } = node;

// If the node is a modifier of its parent, get the symbol for the parent.
if (isModifier(node) && contains(parent.modifiers, node)) {
return getSymbolOfNode(parent);
}

switch (node.kind) {
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.EnumKeyword:
case SyntaxKind.NamespaceKeyword:
case SyntaxKind.ModuleKeyword:
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
return getSymbolOfNode(parent);

case SyntaxKind.TypeKeyword:
if (isTypeAliasDeclaration(parent)) {
return getSymbolOfNode(parent);
}
if (isImportClause(parent)) {
return getSymbolAtLocation(parent.parent.moduleSpecifier);
}
if (isLiteralImportTypeNode(parent)) {
return getSymbolAtLocation(parent.argument.literal);
}
break;

case SyntaxKind.VarKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.LetKeyword:
if (isVariableDeclarationList(parent) && parent.declarations.length === 1) {
return getSymbolOfNode(parent.declarations[0]);
}
break;
}
if (node.kind === SyntaxKind.NewKeyword && isNewExpression(parent) ||
node.kind === SyntaxKind.VoidKeyword && isVoidExpression(parent) ||
node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) ||
node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) ||
node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) ||
node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent)) {
if (parent.expression) {
return getSymbolAtLocation(skipOuterExpressions(parent.expression));
}
}
}

function getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined {
if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) {
return resolveEntityName((<ShorthandPropertyAssignment>location).name, SymbolFlags.Value | SymbolFlags.Alias);
Expand Down
1 change: 0 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3405,7 +3405,6 @@ namespace ts {

getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[];
getSymbolAtLocation(node: Node): Symbol | undefined;
/* @internal*/ getSymbolAtLocation(node: Node, includeKeywords?: boolean): Symbol | undefined; // eslint-disable-line @typescript-eslint/unified-signatures
getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[];
/**
* The function returns the value (local variable) symbol of an identifier in the short-hand property assignment.
Expand Down
75 changes: 61 additions & 14 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ namespace FourSlash {
}
}

constructor(private originalInputFileName: string, private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) {
constructor(public originalInputFileName: string, private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) {
// Create a new Services Adapter
this.cancellationToken = new TestCancellationToken();
let compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions);
Expand Down Expand Up @@ -998,16 +998,18 @@ namespace FourSlash {
references: ts.ReferenceEntry[];
}
interface RangeMarkerData {
id?: string;
isWriteAccess?: boolean,
isDefinition?: boolean,
isInString?: true,
contextRangeIndex?: number,
contextRangeDelta?: 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, contextRangeDelta } = (r.marker && r.marker.data || {}) as RangeMarkerData;
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();
Expand All @@ -1016,15 +1018,22 @@ namespace FourSlash {
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,
...(contextSpan ? { contextSpan } : undefined),
...(isInString ? { isInString: true } : undefined),
};
}),
Expand Down Expand Up @@ -1250,8 +1259,10 @@ 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;

Expand All @@ -1273,7 +1284,7 @@ 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, contextRangeDelta } = (range.marker && range.marker.data || {}) as RangeMarkerData;
const { contextRangeIndex, contextRangeDelta, contextRangeId } = (range.marker && range.marker.data || {}) as RangeMarkerData;
let contextSpan: ts.TextSpan | undefined;
if (contextRangeDelta !== undefined) {
const allRanges = this.getRanges();
Expand All @@ -1282,6 +1293,13 @@ namespace FourSlash {
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]);
}
Expand Down Expand Up @@ -3618,19 +3636,43 @@ 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, sourceMap: true } });
if (output.diagnostics!.length > 0) {
throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics![0].messageText}`);
}
runCode(output.outputText, state);
runCode(output, state, actualFileName);
}

function runCode(code: string, state: TestState): void {
function runCode(output: ts.TranspileOutput, 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 mapFile = generatedFile + ".map";
const wrappedCode = `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {${output.outputText}\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 :
path === mapFile ? output.sourceMapText! :
undefined!;
}
});

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

function chompLeadingSpace(content: string) {
Expand Down Expand Up @@ -3815,7 +3862,7 @@ ${code}
markerValue = JSON.parse("{ " + text + " }");
}
catch (e) {
reportError(fileName, location.sourceLine, location.sourceColumn, "Unable to parse marker text " + e.message);
reportError(fileName, location.sourceLine, location.sourceColumn, "Unable to parse marker text " + e.message + "\nSource:\n {| " + text + " |}");
}

if (markerValue === undefined) {
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, { keywords: true }, 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
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy