Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
75 changes: 69 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,13 @@ namespace ts {
location = getParseTreeNode(location);
return location ? getSymbolsInScope(location, meaning) : [];
},
getSymbolAtLocation: node => {
getSymbolAtLocation: (node: Node, includeKeywords?: boolean) => {
node = getParseTreeNode(node);
return node ? getSymbolAtLocation(node) : undefined;
if (node) {
return includeKeywords ?
getSymbolAtLocation(node) ?? getSymbolAtKeyword(node) :
getSymbolAtLocation(node);
}
},
getShorthandAssignmentValueSymbol: node => {
node = getParseTreeNode(node);
Expand Down Expand Up @@ -34249,7 +34253,7 @@ namespace ts {
if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) {
return (<ClassDeclaration>constructorDeclaration.parent).symbol;
}
return undefined;
break;

case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
Expand Down Expand Up @@ -34287,10 +34291,69 @@ namespace ts {
return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal) : undefined;

case SyntaxKind.ExportKeyword:
return isExportAssignment(node.parent) ? Debug.assertDefined(node.parent.symbol) : undefined;
if (isExportAssignment(node.parent)) {
return Debug.assertDefined(node.parent.symbol);
}
break;
}
}

default:
return undefined;
/**
Comment thread
rbuckton marked this conversation as resolved.
Outdated
* 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:
Comment thread
rbuckton marked this conversation as resolved.
Outdated
case SyntaxKind.EnumKeyword:
Comment thread
rbuckton marked this conversation as resolved.
Outdated
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) {
Comment thread
rbuckton marked this conversation as resolved.
Outdated
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));
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3405,6 +3405,7 @@ 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
47 changes: 38 additions & 9 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -997,18 +997,34 @@ namespace FourSlash {
definition: string | { text: string, range: ts.TextSpan };
references: ts.ReferenceEntry[];
}
interface RangeMarkerData {
isWriteAccess?: boolean,
isDefinition?: boolean,
isInString?: true,
contextRangeIndex?: number,
contextRangeDelta?: number
}
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 } = (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 (contextRangeIndex !== undefined) {
contextSpan = ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]);
}
return {
fileName: r.fileName,
textSpan: ts.createTextSpanFromRange(r),
isWriteAccess,
isDefinition,
...(contextRangeIndex !== undefined ?
{ contextSpan: ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]) } :
undefined),
...(contextSpan ? { contextSpan } : undefined),
...(isInString ? { isInString: true } : undefined),
};
}),
Expand All @@ -1032,7 +1048,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 +1249,10 @@ namespace FourSlash {
}

public verifyRenameLocations(startRanges: ArrayOrSingle<Range>, options: FourSlashInterface.RenameLocationsOptions) {
interface RangeMarkerData {
contextRangeIndex?: number,
contextRangeDelta?: number
}
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 +1273,22 @@ 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 } = (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 (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
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, { keywords: true }, convertEntryToCallSite), isDefined);
return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : [];
}

Expand Down
15 changes: 12 additions & 3 deletions src/services/findAllReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,15 @@ namespace ts.FindAllReferences {
* Default is false for backwards compatibility.
*/
readonly providePrefixAndSuffixTextForRename?: boolean;
/**
* If the source is a modifier or declaration keyword, find references to its parent declaration.
*/
readonly keywords?: boolean;
}

export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined {
const node = getTouchingPropertyName(sourceFile, position);
const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken);
const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, { keywords: true });
const checker = program.getTypeChecker();
return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined<SymbolAndEntries, ReferencedSymbol>(referencedSymbols, ({ definition, references }) =>
// Only include referenced symbols that have a valid definition.
Expand Down Expand Up @@ -229,7 +233,7 @@ namespace ts.FindAllReferences {
}
else {
// Perform "Find all References" and retrieve only those that are implementations
return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true });
return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, keywords: true });
}
}

Expand Down Expand Up @@ -553,7 +557,7 @@ namespace ts.FindAllReferences {
}

const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(node);
const symbol = checker.getSymbolAtLocation(node, options.keywords);

// Could not find a symbol e.g. unknown identifier
if (!symbol) {
Expand Down Expand Up @@ -723,6 +727,11 @@ namespace ts.FindAllReferences {
/** getReferencedSymbols for special node kinds. */
function getReferencedSymbolsSpecial(node: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined {
if (isTypeKeyword(node.kind)) {
// A void expression (i.e., `void foo()`) is not special, but the `void` type is.
if (node.kind === SyntaxKind.VoidKeyword && isVoidExpression(node.parent)) {
return undefined;
}

// A modifier readonly (like on a property declaration) is not special;
// a readonly type keyword (like `readonly string[]`) is.
if (node.kind === SyntaxKind.ReadonlyKeyword && !isReadonlyTypeOperator(node)) {
Expand Down
13 changes: 8 additions & 5 deletions src/services/rename.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
/* @internal */
namespace ts.Rename {
export function getRenameInfo(program: Program, sourceFile: SourceFile, position: number, options?: RenameInfoOptions): RenameInfo {
const node = getTouchingPropertyName(sourceFile, position);
const renameInfo = node && nodeIsEligibleForRename(node)
? getRenameInfoForNode(node, program.getTypeChecker(), sourceFile, declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile()), options)
: undefined;
return renameInfo || getRenameInfoError(Diagnostics.You_cannot_rename_this_element);
const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position));
if (nodeIsEligibleForRename(node)) {
const renameInfo = getRenameInfoForNode(node, program.getTypeChecker(), sourceFile, declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile()), options);
if (renameInfo) {
return renameInfo;
}
}
return getRenameInfoError(Diagnostics.You_cannot_rename_this_element);
}

function getRenameInfoForNode(node: Node, typeChecker: TypeChecker, sourceFile: SourceFile, isDefinedInLibraryFile: (declaration: Node) => boolean, options?: RenameInfoOptions): RenameInfo | undefined {
Expand Down
4 changes: 2 additions & 2 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1608,7 +1608,7 @@ namespace ts {
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const node = getTouchingPropertyName(sourceFile, position);
const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position));
if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) {
const { openingElement, closingElement } = node.parent.parent;
return [openingElement, closingElement].map((node): RenameLocation => {
Expand All @@ -1628,7 +1628,7 @@ namespace ts {

function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined {
synchronizeHostData();
return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, {}, FindAllReferences.toReferenceEntry);
return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { keywords: true }, FindAllReferences.toReferenceEntry);
}

function getReferencesWorker<T>(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry<T>): T[] | undefined {
Expand Down
Loading