mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-24 20:44:53 -05:00
2718 lines
133 KiB
TypeScript
2718 lines
133 KiB
TypeScript
import {
|
|
__String,
|
|
addToSeen,
|
|
append,
|
|
AssignmentDeclarationKind,
|
|
BinaryExpression,
|
|
BindingElement,
|
|
Block,
|
|
CallExpression,
|
|
CancellationToken,
|
|
canHaveSymbol,
|
|
cast,
|
|
CheckFlags,
|
|
ClassLikeDeclaration,
|
|
climbPastPropertyAccess,
|
|
compareValues,
|
|
ConstructorDeclaration,
|
|
contains,
|
|
createQueue,
|
|
createTextSpan,
|
|
createTextSpanFromBounds,
|
|
createTextSpanFromRange,
|
|
Debug,
|
|
Declaration,
|
|
displayPart,
|
|
DocumentSpan,
|
|
emptyArray,
|
|
emptyOptions,
|
|
escapeLeadingUnderscores,
|
|
ExportSpecifier,
|
|
Expression,
|
|
externalHelpersModuleNameText,
|
|
FileIncludeReason,
|
|
FileReference,
|
|
filter,
|
|
find,
|
|
findAncestor,
|
|
findChildOfKind,
|
|
findIndex,
|
|
first,
|
|
firstDefined,
|
|
firstOrUndefined,
|
|
flatMap,
|
|
forEachChild,
|
|
forEachChildRecursively,
|
|
forEachReturnStatement,
|
|
ForInOrOfStatement,
|
|
FunctionDeclaration,
|
|
FunctionExpression,
|
|
FunctionLikeDeclaration,
|
|
GetAccessorDeclaration,
|
|
getAdjustedReferenceLocation,
|
|
getAdjustedRenameLocation,
|
|
getAllSuperTypeNodes,
|
|
getAncestor,
|
|
getAssignmentDeclarationKind,
|
|
getCheckFlags,
|
|
getContainerNode,
|
|
getContainingObjectLiteralElement,
|
|
getContextualTypeFromParentOrAncestorTypeNode,
|
|
getDeclarationFromName,
|
|
getDeclarationOfKind,
|
|
getEffectiveModifierFlags,
|
|
getLocalSymbolForExportDefault,
|
|
getMeaningFromDeclaration,
|
|
getMeaningFromLocation,
|
|
getModeForUsageLocation,
|
|
getNameOfDeclaration,
|
|
getNameTable,
|
|
getNextJSDocCommentLocation,
|
|
getNodeId,
|
|
getNodeKind,
|
|
getPropertySymbolFromBindingElement,
|
|
getPropertySymbolsFromContextualType,
|
|
getQuoteFromPreference,
|
|
getReferencedFileLocation,
|
|
getSuperContainer,
|
|
getSymbolId,
|
|
getSyntacticModifierFlags,
|
|
getTargetLabel,
|
|
getTextOfNode,
|
|
getThisContainer,
|
|
getTouchingPropertyName,
|
|
GoToDefinition,
|
|
hasEffectiveModifier,
|
|
hasInitializer,
|
|
hasSyntacticModifier,
|
|
hasType,
|
|
HighlightSpan,
|
|
HighlightSpanKind,
|
|
Identifier,
|
|
ImplementationLocation,
|
|
InterfaceDeclaration,
|
|
InternalSymbolName,
|
|
isAccessExpression,
|
|
isArrayLiteralOrObjectLiteralDestructuringPattern,
|
|
isAssertionExpression,
|
|
isBinaryExpression,
|
|
isBindableObjectDefinePropertyCall,
|
|
isBindingElement,
|
|
isBreakOrContinueStatement,
|
|
isCallExpression,
|
|
isCallExpressionTarget,
|
|
isCatchClause,
|
|
isClassLike,
|
|
isClassStaticBlockDeclaration,
|
|
isComputedPropertyName,
|
|
isConstructorDeclaration,
|
|
isDeclaration,
|
|
isDeclarationName,
|
|
isExportAssignment,
|
|
isExportSpecifier,
|
|
isExpressionOfExternalModuleImportEqualsDeclaration,
|
|
isExpressionStatement,
|
|
isExpressionWithTypeArguments,
|
|
isExternalModule,
|
|
isExternalModuleSymbol,
|
|
isExternalOrCommonJsModule,
|
|
isForInOrOfStatement,
|
|
isFunctionExpression,
|
|
isFunctionLike,
|
|
isFunctionLikeDeclaration,
|
|
isIdentifier,
|
|
isIdentifierPart,
|
|
isImportMeta,
|
|
isImportOrExportSpecifier,
|
|
isImportSpecifier,
|
|
isImportTypeNode,
|
|
isInJSFile,
|
|
isInNonReferenceComment,
|
|
isInString,
|
|
isInterfaceDeclaration,
|
|
isJSDocMemberName,
|
|
isJSDocTag,
|
|
isJsxClosingElement,
|
|
isJsxElement,
|
|
isJsxFragment,
|
|
isJsxOpeningElement,
|
|
isJsxSelfClosingElement,
|
|
isJumpStatementTarget,
|
|
isLabeledStatement,
|
|
isLabelOfLabeledStatement,
|
|
isLiteralComputedPropertyDeclarationName,
|
|
isLiteralNameOfPropertyDeclarationOrIndexAccess,
|
|
isLiteralTypeNode,
|
|
isMethodOrAccessor,
|
|
isModuleDeclaration,
|
|
isModuleExportsAccessExpression,
|
|
isModuleOrEnumDeclaration,
|
|
isModuleSpecifierLike,
|
|
isNameOfModuleDeclaration,
|
|
isNamespaceExportDeclaration,
|
|
isNewExpressionTarget,
|
|
isNoSubstitutionTemplateLiteral,
|
|
isNumericLiteral,
|
|
isObjectBindingElementWithoutPropertyName,
|
|
isObjectLiteralExpression,
|
|
isObjectLiteralMethod,
|
|
isParameter,
|
|
isParameterPropertyDeclaration,
|
|
isPrivateIdentifierClassElementDeclaration,
|
|
isPropertyAccessExpression,
|
|
isQualifiedName,
|
|
isReferencedFile,
|
|
isReferenceFileLocation,
|
|
isRightSideOfPropertyAccess,
|
|
isShorthandPropertyAssignment,
|
|
isSourceFile,
|
|
isStatement,
|
|
isStatic,
|
|
isStaticModifier,
|
|
isStringLiteralLike,
|
|
isSuperProperty,
|
|
isThis,
|
|
isTypeAliasDeclaration,
|
|
isTypeElement,
|
|
isTypeKeyword,
|
|
isTypeLiteralNode,
|
|
isTypeNode,
|
|
isTypeOperatorNode,
|
|
isUnionTypeNode,
|
|
isVariableDeclarationInitializedToBareOrAccessedRequire,
|
|
isVariableDeclarationList,
|
|
isVariableLike,
|
|
isVariableStatement,
|
|
isVoidExpression,
|
|
isWriteAccess,
|
|
JSDocTag,
|
|
map,
|
|
mapDefined,
|
|
MethodDeclaration,
|
|
ModifierFlags,
|
|
ModuleDeclaration,
|
|
MultiMap,
|
|
NamedDeclaration,
|
|
Node,
|
|
NodeFlags,
|
|
nodeSeenTracker,
|
|
NumericLiteral,
|
|
ObjectLiteralExpression,
|
|
or,
|
|
ParameterDeclaration,
|
|
ParenthesizedExpression,
|
|
Path,
|
|
PrivateIdentifier,
|
|
Program,
|
|
PropertyAccessExpression,
|
|
PropertyAssignment,
|
|
PropertyDeclaration,
|
|
punctuationPart,
|
|
QuotePreference,
|
|
rangeIsOnSingleLine,
|
|
ReferencedSymbol,
|
|
ReferencedSymbolDefinitionInfo,
|
|
ReferencedSymbolEntry,
|
|
ReferenceEntry,
|
|
RenameLocation,
|
|
ScriptElementKind,
|
|
ScriptTarget,
|
|
SemanticMeaning,
|
|
SetAccessorDeclaration,
|
|
SignatureDeclaration,
|
|
skipAlias,
|
|
some,
|
|
SourceFile,
|
|
Statement,
|
|
StringLiteral,
|
|
StringLiteralLike,
|
|
stripQuotes,
|
|
SuperContainer,
|
|
Symbol,
|
|
SymbolDisplay,
|
|
SymbolDisplayPart,
|
|
SymbolDisplayPartKind,
|
|
SymbolFlags,
|
|
SymbolId,
|
|
symbolName,
|
|
SyntaxKind,
|
|
textPart,
|
|
TextSpan,
|
|
tokenToString,
|
|
TransformFlags,
|
|
tryAddToSet,
|
|
tryCast,
|
|
tryGetClassExtendingExpressionWithTypeArguments,
|
|
tryGetImportFromModuleSpecifier,
|
|
TypeChecker,
|
|
TypeLiteralNode,
|
|
VariableDeclaration,
|
|
} from "./_namespaces/ts";
|
|
import {
|
|
createImportTracker,
|
|
ExportInfo,
|
|
ExportKind,
|
|
findModuleReferences,
|
|
getExportInfo,
|
|
getImportOrExportSymbol,
|
|
ImportExport,
|
|
ImportsResult,
|
|
ImportTracker,
|
|
ModuleReference,
|
|
} from "./_namespaces/ts.FindAllReferences";
|
|
|
|
/** @internal */
|
|
export interface SymbolAndEntries {
|
|
readonly definition: Definition | undefined;
|
|
readonly references: readonly Entry[];
|
|
}
|
|
|
|
/** @internal */
|
|
export const enum DefinitionKind { Symbol, Label, Keyword, This, String, TripleSlashReference }
|
|
/** @internal */
|
|
export type Definition =
|
|
| { readonly type: DefinitionKind.Symbol; readonly symbol: Symbol }
|
|
| { readonly type: DefinitionKind.Label; readonly node: Identifier }
|
|
| { readonly type: DefinitionKind.Keyword; readonly node: Node }
|
|
| { readonly type: DefinitionKind.This; readonly node: Node }
|
|
| { readonly type: DefinitionKind.String; readonly node: StringLiteralLike }
|
|
| { readonly type: DefinitionKind.TripleSlashReference; readonly reference: FileReference, readonly file: SourceFile };
|
|
|
|
/** @internal */
|
|
export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal }
|
|
/** @internal */
|
|
export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal;
|
|
/** @internal */
|
|
export type Entry = NodeEntry | SpanEntry;
|
|
/** @internal */
|
|
export interface ContextWithStartAndEndNode {
|
|
start: Node;
|
|
end: Node;
|
|
}
|
|
/** @internal */
|
|
export type ContextNode = Node | ContextWithStartAndEndNode;
|
|
/** @internal */
|
|
export interface NodeEntry {
|
|
readonly kind: NodeEntryKind;
|
|
readonly node: Node;
|
|
readonly context?: ContextNode;
|
|
}
|
|
/** @internal */
|
|
export interface SpanEntry {
|
|
readonly kind: EntryKind.Span;
|
|
readonly fileName: string;
|
|
readonly textSpan: TextSpan;
|
|
}
|
|
/** @internal */
|
|
export function nodeEntry(node: Node, kind: NodeEntryKind = EntryKind.Node): NodeEntry {
|
|
return {
|
|
kind,
|
|
node: (node as NamedDeclaration).name || node,
|
|
context: getContextNodeForNodeEntry(node)
|
|
};
|
|
}
|
|
|
|
/** @internal */
|
|
export function isContextWithStartAndEndNode(node: ContextNode): node is ContextWithStartAndEndNode {
|
|
return node && (node as Node).kind === undefined;
|
|
}
|
|
|
|
function getContextNodeForNodeEntry(node: Node): ContextNode | undefined {
|
|
if (isDeclaration(node)) {
|
|
return getContextNode(node);
|
|
}
|
|
|
|
if (!node.parent) return undefined;
|
|
|
|
if (!isDeclaration(node.parent) && !isExportAssignment(node.parent)) {
|
|
// Special property assignment in javascript
|
|
if (isInJSFile(node)) {
|
|
const binaryExpression = isBinaryExpression(node.parent) ?
|
|
node.parent :
|
|
isAccessExpression(node.parent) &&
|
|
isBinaryExpression(node.parent.parent) &&
|
|
node.parent.parent.left === node.parent ?
|
|
node.parent.parent :
|
|
undefined;
|
|
if (binaryExpression && getAssignmentDeclarationKind(binaryExpression) !== AssignmentDeclarationKind.None) {
|
|
return getContextNode(binaryExpression);
|
|
}
|
|
}
|
|
|
|
// Jsx Tags
|
|
if (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) {
|
|
return node.parent.parent;
|
|
}
|
|
else if (isJsxSelfClosingElement(node.parent) ||
|
|
isLabeledStatement(node.parent) ||
|
|
isBreakOrContinueStatement(node.parent)) {
|
|
return node.parent;
|
|
}
|
|
else if (isStringLiteralLike(node)) {
|
|
const validImport = tryGetImportFromModuleSpecifier(node);
|
|
if (validImport) {
|
|
const declOrStatement = findAncestor(validImport, node =>
|
|
isDeclaration(node) ||
|
|
isStatement(node) ||
|
|
isJSDocTag(node)
|
|
)! as NamedDeclaration | Statement | JSDocTag;
|
|
return isDeclaration(declOrStatement) ?
|
|
getContextNode(declOrStatement) :
|
|
declOrStatement;
|
|
}
|
|
}
|
|
|
|
// Handle computed property name
|
|
const propertyName = findAncestor(node, isComputedPropertyName);
|
|
return propertyName ?
|
|
getContextNode(propertyName.parent) :
|
|
undefined;
|
|
}
|
|
|
|
if (node.parent.name === node || // node is name of declaration, use parent
|
|
isConstructorDeclaration(node.parent) ||
|
|
isExportAssignment(node.parent) ||
|
|
// Property name of the import export specifier or binding pattern, use parent
|
|
((isImportOrExportSpecifier(node.parent) || isBindingElement(node.parent))
|
|
&& node.parent.propertyName === node) ||
|
|
// Is default export
|
|
(node.kind === SyntaxKind.DefaultKeyword && hasSyntacticModifier(node.parent, ModifierFlags.ExportDefault))) {
|
|
return getContextNode(node.parent);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/** @internal */
|
|
export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | undefined): ContextNode | undefined {
|
|
if (!node) return undefined;
|
|
switch (node.kind) {
|
|
case SyntaxKind.VariableDeclaration:
|
|
return !isVariableDeclarationList(node.parent) || node.parent.declarations.length !== 1 ?
|
|
node :
|
|
isVariableStatement(node.parent.parent) ?
|
|
node.parent.parent :
|
|
isForInOrOfStatement(node.parent.parent) ?
|
|
getContextNode(node.parent.parent) :
|
|
node.parent;
|
|
|
|
case SyntaxKind.BindingElement:
|
|
return getContextNode(node.parent.parent as NamedDeclaration);
|
|
|
|
case SyntaxKind.ImportSpecifier:
|
|
return node.parent.parent.parent;
|
|
|
|
case SyntaxKind.ExportSpecifier:
|
|
case SyntaxKind.NamespaceImport:
|
|
return node.parent.parent;
|
|
|
|
case SyntaxKind.ImportClause:
|
|
case SyntaxKind.NamespaceExport:
|
|
return node.parent;
|
|
|
|
case SyntaxKind.BinaryExpression:
|
|
return isExpressionStatement(node.parent) ?
|
|
node.parent :
|
|
node;
|
|
|
|
case SyntaxKind.ForOfStatement:
|
|
case SyntaxKind.ForInStatement:
|
|
return {
|
|
start: (node as ForInOrOfStatement).initializer,
|
|
end: (node as ForInOrOfStatement).expression
|
|
};
|
|
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
return isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent) ?
|
|
getContextNode(
|
|
findAncestor(node.parent, node =>
|
|
isBinaryExpression(node) || isForInOrOfStatement(node)
|
|
) as BinaryExpression | ForInOrOfStatement
|
|
) :
|
|
node;
|
|
|
|
default:
|
|
return node;
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context?: ContextNode): { contextSpan: TextSpan } | undefined {
|
|
if (!context) return undefined;
|
|
const contextSpan = isContextWithStartAndEndNode(context) ?
|
|
getTextSpan(context.start, sourceFile, context.end) :
|
|
getTextSpan(context, sourceFile);
|
|
return contextSpan.start !== textSpan.start || contextSpan.length !== textSpan.length ?
|
|
{ contextSpan } :
|
|
undefined;
|
|
}
|
|
|
|
/** @internal */
|
|
export const enum FindReferencesUse {
|
|
/**
|
|
* When searching for references to a symbol, the location will not be adjusted (this is the default behavior when not specified).
|
|
*/
|
|
Other,
|
|
/**
|
|
* When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword.
|
|
*/
|
|
References,
|
|
/**
|
|
* When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword.
|
|
* Unlike `References`, the location will only be adjusted keyword belonged to a declaration with a valid name.
|
|
* If set, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name.
|
|
*/
|
|
Rename,
|
|
}
|
|
|
|
/** @internal */
|
|
export interface Options {
|
|
readonly findInStrings?: boolean;
|
|
readonly findInComments?: boolean;
|
|
readonly use?: FindReferencesUse;
|
|
/** True if we are searching for implementations. We will have a different method of adding references if so. */
|
|
readonly implementations?: boolean;
|
|
/**
|
|
* True to opt in for enhanced renaming of shorthand properties and import/export specifiers.
|
|
* The options controls the behavior for the whole rename operation; it cannot be changed on a per-file basis.
|
|
* Default is false for backwards compatibility.
|
|
*/
|
|
readonly providePrefixAndSuffixTextForRename?: boolean;
|
|
}
|
|
|
|
/** @internal */
|
|
export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined {
|
|
const node = getTouchingPropertyName(sourceFile, position);
|
|
const options = { use: FindReferencesUse.References };
|
|
const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options);
|
|
const checker = program.getTypeChecker();
|
|
// Unless the starting node is a declaration (vs e.g. JSDoc), don't attempt to compute isDefinition
|
|
const adjustedNode = Core.getAdjustedNode(node, options);
|
|
const symbol = isDefinitionForReference(adjustedNode) ? checker.getSymbolAtLocation(adjustedNode) : undefined;
|
|
return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined<SymbolAndEntries, ReferencedSymbol>(referencedSymbols, ({ definition, references }) =>
|
|
// Only include referenced symbols that have a valid definition.
|
|
definition && {
|
|
definition: checker.runWithCancellationToken(cancellationToken, checker => definitionToReferencedSymbolDefinitionInfo(definition, checker, node)),
|
|
references: references.map(r => toReferencedSymbolEntry(r, symbol))
|
|
});
|
|
}
|
|
|
|
function isDefinitionForReference(node: Node): boolean {
|
|
return node.kind === SyntaxKind.DefaultKeyword
|
|
|| !!getDeclarationFromName(node)
|
|
|| isLiteralComputedPropertyDeclarationName(node)
|
|
|| (node.kind === SyntaxKind.ConstructorKeyword && isConstructorDeclaration(node.parent));
|
|
}
|
|
|
|
/** @internal */
|
|
export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined {
|
|
const node = getTouchingPropertyName(sourceFile, position);
|
|
let referenceEntries: Entry[] | undefined;
|
|
const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position);
|
|
|
|
if (
|
|
node.parent.kind === SyntaxKind.PropertyAccessExpression
|
|
|| node.parent.kind === SyntaxKind.BindingElement
|
|
|| node.parent.kind === SyntaxKind.ElementAccessExpression
|
|
|| node.kind === SyntaxKind.SuperKeyword
|
|
) {
|
|
referenceEntries = entries && [...entries];
|
|
}
|
|
else if (entries) {
|
|
const queue = createQueue(entries);
|
|
const seenNodes = new Map<number, true>();
|
|
while (!queue.isEmpty()) {
|
|
const entry = queue.dequeue() as NodeEntry;
|
|
if (!addToSeen(seenNodes, getNodeId(entry.node))) {
|
|
continue;
|
|
}
|
|
referenceEntries = append(referenceEntries, entry);
|
|
const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos);
|
|
if (entries) {
|
|
queue.enqueue(...entries);
|
|
}
|
|
}
|
|
}
|
|
const checker = program.getTypeChecker();
|
|
return map(referenceEntries, entry => toImplementationLocation(entry, checker));
|
|
}
|
|
|
|
function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined {
|
|
if (node.kind === SyntaxKind.SourceFile) {
|
|
return undefined;
|
|
}
|
|
|
|
const checker = program.getTypeChecker();
|
|
// If invoked directly on a shorthand property assignment, then return
|
|
// the declaration of the symbol being assigned (not the symbol being assigned to).
|
|
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
|
const result: NodeEntry[] = [];
|
|
Core.getReferenceEntriesForShorthandPropertyAssignment(node, checker, node => result.push(nodeEntry(node)));
|
|
return result;
|
|
}
|
|
else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) {
|
|
// References to and accesses on the super keyword only have one possible implementation, so no
|
|
// need to "Find all References"
|
|
const symbol = checker.getSymbolAtLocation(node)!;
|
|
return symbol.valueDeclaration && [nodeEntry(symbol.valueDeclaration)];
|
|
}
|
|
else {
|
|
// Perform "Find all References" and retrieve only those that are implementations
|
|
return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, use: FindReferencesUse.References });
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
export function findReferenceOrRenameEntries<T>(
|
|
program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, options: Options | undefined,
|
|
convertEntry: ToReferenceOrRenameEntry<T>,
|
|
): T[] | undefined {
|
|
return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node, program.getTypeChecker()));
|
|
}
|
|
|
|
/** @internal */
|
|
export type ToReferenceOrRenameEntry<T> = (entry: Entry, originalNode: Node, checker: TypeChecker) => T;
|
|
|
|
/** @internal */
|
|
export function getReferenceEntriesForNode(
|
|
position: number,
|
|
node: Node,
|
|
program: Program,
|
|
sourceFiles: readonly SourceFile[],
|
|
cancellationToken: CancellationToken,
|
|
options: Options = {},
|
|
sourceFilesSet: ReadonlySet<string> = new Set(sourceFiles.map(f => f.fileName)),
|
|
): readonly Entry[] | undefined {
|
|
return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet));
|
|
}
|
|
|
|
function flattenEntries(referenceSymbols: readonly SymbolAndEntries[] | undefined): readonly Entry[] | undefined {
|
|
return referenceSymbols && flatMap(referenceSymbols, r => r.references);
|
|
}
|
|
|
|
function definitionToReferencedSymbolDefinitionInfo(def: Definition, checker: TypeChecker, originalNode: Node): ReferencedSymbolDefinitionInfo {
|
|
const info = ((): { sourceFile: SourceFile, textSpan: TextSpan, name: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], context?: Node | ContextWithStartAndEndNode } => {
|
|
switch (def.type) {
|
|
case DefinitionKind.Symbol: {
|
|
const { symbol } = def;
|
|
const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, checker, originalNode);
|
|
const name = displayParts.map(p => p.text).join("");
|
|
const declaration = symbol.declarations && firstOrUndefined(symbol.declarations);
|
|
const node = declaration ? (getNameOfDeclaration(declaration) || declaration) : originalNode;
|
|
return {
|
|
...getFileAndTextSpanFromNode(node),
|
|
name,
|
|
kind,
|
|
displayParts,
|
|
context: getContextNode(declaration)
|
|
};
|
|
}
|
|
case DefinitionKind.Label: {
|
|
const { node } = def;
|
|
return { ...getFileAndTextSpanFromNode(node), name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] };
|
|
}
|
|
case DefinitionKind.Keyword: {
|
|
const { node } = def;
|
|
const name = tokenToString(node.kind)!;
|
|
return { ...getFileAndTextSpanFromNode(node), name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] };
|
|
}
|
|
case DefinitionKind.This: {
|
|
const { node } = def;
|
|
const symbol = checker.getSymbolAtLocation(node);
|
|
const displayParts = symbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(
|
|
checker, symbol, node.getSourceFile(), getContainerNode(node), node).displayParts || [textPart("this")];
|
|
return { ...getFileAndTextSpanFromNode(node), name: "this", kind: ScriptElementKind.variableElement, displayParts };
|
|
}
|
|
case DefinitionKind.String: {
|
|
const { node } = def;
|
|
return {
|
|
...getFileAndTextSpanFromNode(node),
|
|
name: node.text,
|
|
kind: ScriptElementKind.variableElement,
|
|
displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)]
|
|
};
|
|
}
|
|
case DefinitionKind.TripleSlashReference: {
|
|
return {
|
|
textSpan: createTextSpanFromRange(def.reference),
|
|
sourceFile: def.file,
|
|
name: def.reference.fileName,
|
|
kind: ScriptElementKind.string,
|
|
displayParts: [displayPart(`"${def.reference.fileName}"`, SymbolDisplayPartKind.stringLiteral)]
|
|
};
|
|
}
|
|
default:
|
|
return Debug.assertNever(def);
|
|
}
|
|
})();
|
|
|
|
const { sourceFile, textSpan, name, kind, displayParts, context } = info;
|
|
return {
|
|
containerKind: ScriptElementKind.unknown,
|
|
containerName: "",
|
|
fileName: sourceFile.fileName,
|
|
kind,
|
|
name,
|
|
textSpan,
|
|
displayParts,
|
|
...toContextSpan(textSpan, sourceFile, context)
|
|
};
|
|
}
|
|
|
|
function getFileAndTextSpanFromNode(node: Node) {
|
|
const sourceFile = node.getSourceFile();
|
|
return {
|
|
sourceFile,
|
|
textSpan: getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile)
|
|
};
|
|
}
|
|
|
|
function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } {
|
|
const meaning = Core.getIntersectingMeaningFromDeclarations(node, symbol);
|
|
const enclosingDeclaration = symbol.declarations && firstOrUndefined(symbol.declarations) || node;
|
|
const { displayParts, symbolKind } =
|
|
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, enclosingDeclaration.getSourceFile(), enclosingDeclaration, enclosingDeclaration, meaning);
|
|
return { displayParts, kind: symbolKind };
|
|
}
|
|
|
|
/** @internal */
|
|
export function toRenameLocation(entry: Entry, originalNode: Node, checker: TypeChecker, providePrefixAndSuffixText: boolean, quotePreference: QuotePreference): RenameLocation {
|
|
return { ...entryToDocumentSpan(entry), ...(providePrefixAndSuffixText && getPrefixAndSuffixText(entry, originalNode, checker, quotePreference)) };
|
|
}
|
|
|
|
function toReferencedSymbolEntry(entry: Entry, symbol: Symbol | undefined): ReferencedSymbolEntry {
|
|
const referenceEntry = toReferenceEntry(entry);
|
|
if (!symbol) return referenceEntry;
|
|
return {
|
|
...referenceEntry,
|
|
isDefinition: entry.kind !== EntryKind.Span && isDeclarationOfSymbol(entry.node, symbol)
|
|
};
|
|
}
|
|
|
|
/** @internal */
|
|
export function toReferenceEntry(entry: Entry): ReferenceEntry {
|
|
const documentSpan = entryToDocumentSpan(entry);
|
|
if (entry.kind === EntryKind.Span) {
|
|
return { ...documentSpan, isWriteAccess: false };
|
|
}
|
|
const { kind, node } = entry;
|
|
return {
|
|
...documentSpan,
|
|
isWriteAccess: isWriteAccessForReference(node),
|
|
isInString: kind === EntryKind.StringLiteral ? true : undefined,
|
|
};
|
|
}
|
|
|
|
function entryToDocumentSpan(entry: Entry): DocumentSpan {
|
|
if (entry.kind === EntryKind.Span) {
|
|
return { textSpan: entry.textSpan, fileName: entry.fileName };
|
|
}
|
|
else {
|
|
const sourceFile = entry.node.getSourceFile();
|
|
const textSpan = getTextSpan(entry.node, sourceFile);
|
|
return {
|
|
textSpan,
|
|
fileName: sourceFile.fileName,
|
|
...toContextSpan(textSpan, sourceFile, entry.context)
|
|
};
|
|
}
|
|
}
|
|
|
|
interface PrefixAndSuffix { readonly prefixText?: string; readonly suffixText?: string; }
|
|
function getPrefixAndSuffixText(entry: Entry, originalNode: Node, checker: TypeChecker, quotePreference: QuotePreference): PrefixAndSuffix {
|
|
if (entry.kind !== EntryKind.Span && isIdentifier(originalNode)) {
|
|
const { node, kind } = entry;
|
|
const parent = node.parent;
|
|
const name = originalNode.text;
|
|
const isShorthandAssignment = isShorthandPropertyAssignment(parent);
|
|
if (isShorthandAssignment || (isObjectBindingElementWithoutPropertyName(parent) && parent.name === node && parent.dotDotDotToken === undefined)) {
|
|
const prefixColon: PrefixAndSuffix = { prefixText: name + ": " };
|
|
const suffixColon: PrefixAndSuffix = { suffixText: ": " + name };
|
|
if (kind === EntryKind.SearchedLocalFoundProperty) {
|
|
return prefixColon;
|
|
}
|
|
if (kind === EntryKind.SearchedPropertyFoundLocal) {
|
|
return suffixColon;
|
|
}
|
|
|
|
// In `const o = { x }; o.x`, symbolAtLocation at `x` in `{ x }` is the property symbol.
|
|
// For a binding element `const { x } = o;`, symbolAtLocation at `x` is the property symbol.
|
|
if (isShorthandAssignment) {
|
|
const grandParent = parent.parent;
|
|
if (isObjectLiteralExpression(grandParent) &&
|
|
isBinaryExpression(grandParent.parent) &&
|
|
isModuleExportsAccessExpression(grandParent.parent.left)) {
|
|
return prefixColon;
|
|
}
|
|
return suffixColon;
|
|
}
|
|
else {
|
|
return prefixColon;
|
|
}
|
|
}
|
|
else if (isImportSpecifier(parent) && !parent.propertyName) {
|
|
// If the original symbol was using this alias, just rename the alias.
|
|
const originalSymbol = isExportSpecifier(originalNode.parent) ? checker.getExportSpecifierLocalTargetSymbol(originalNode.parent) : checker.getSymbolAtLocation(originalNode);
|
|
return contains(originalSymbol!.declarations, parent) ? { prefixText: name + " as " } : emptyOptions;
|
|
}
|
|
else if (isExportSpecifier(parent) && !parent.propertyName) {
|
|
// If the symbol for the node is same as declared node symbol use prefix text
|
|
return originalNode === entry.node || checker.getSymbolAtLocation(originalNode) === checker.getSymbolAtLocation(entry.node) ?
|
|
{ prefixText: name + " as " } :
|
|
{ suffixText: " as " + name };
|
|
}
|
|
}
|
|
|
|
// If the node is a numerical indexing literal, then add quotes around the property access.
|
|
if (entry.kind !== EntryKind.Span && isNumericLiteral(entry.node) && isAccessExpression(entry.node.parent)) {
|
|
const quote = getQuoteFromPreference(quotePreference);
|
|
return { prefixText: quote, suffixText: quote };
|
|
}
|
|
|
|
return emptyOptions;
|
|
}
|
|
|
|
function toImplementationLocation(entry: Entry, checker: TypeChecker): ImplementationLocation {
|
|
const documentSpan = entryToDocumentSpan(entry);
|
|
if (entry.kind !== EntryKind.Span) {
|
|
const { node } = entry;
|
|
return {
|
|
...documentSpan,
|
|
...implementationKindDisplayParts(node, checker)
|
|
};
|
|
}
|
|
else {
|
|
return { ...documentSpan, kind: ScriptElementKind.unknown, displayParts: [] };
|
|
}
|
|
}
|
|
|
|
function implementationKindDisplayParts(node: Node, checker: TypeChecker): { kind: ScriptElementKind, displayParts: SymbolDisplayPart[] } {
|
|
const symbol = checker.getSymbolAtLocation(isDeclaration(node) && node.name ? node.name : node);
|
|
if (symbol) {
|
|
return getDefinitionKindAndDisplayParts(symbol, checker, node);
|
|
}
|
|
else if (node.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
return {
|
|
kind: ScriptElementKind.interfaceElement,
|
|
displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("object literal"), punctuationPart(SyntaxKind.CloseParenToken)]
|
|
};
|
|
}
|
|
else if (node.kind === SyntaxKind.ClassExpression) {
|
|
return {
|
|
kind: ScriptElementKind.localClassElement,
|
|
displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("anonymous local class"), punctuationPart(SyntaxKind.CloseParenToken)]
|
|
};
|
|
}
|
|
else {
|
|
return { kind: getNodeKind(node), displayParts: [] };
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
export function toHighlightSpan(entry: Entry): { fileName: string, span: HighlightSpan } {
|
|
const documentSpan = entryToDocumentSpan(entry);
|
|
if (entry.kind === EntryKind.Span) {
|
|
return {
|
|
fileName: documentSpan.fileName,
|
|
span: {
|
|
textSpan: documentSpan.textSpan,
|
|
kind: HighlightSpanKind.reference
|
|
}
|
|
};
|
|
}
|
|
|
|
const writeAccess = isWriteAccessForReference(entry.node);
|
|
const span: HighlightSpan = {
|
|
textSpan: documentSpan.textSpan,
|
|
kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference,
|
|
isInString: entry.kind === EntryKind.StringLiteral ? true : undefined,
|
|
...documentSpan.contextSpan && { contextSpan: documentSpan.contextSpan }
|
|
};
|
|
return { fileName: documentSpan.fileName, span };
|
|
}
|
|
|
|
function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan {
|
|
let start = node.getStart(sourceFile);
|
|
let end = (endNode || node).getEnd();
|
|
if (isStringLiteralLike(node) && (end - start) > 2) {
|
|
Debug.assert(endNode === undefined);
|
|
start += 1;
|
|
end -= 1;
|
|
}
|
|
return createTextSpanFromBounds(start, end);
|
|
}
|
|
|
|
/** @internal */
|
|
export function getTextSpanOfEntry(entry: Entry) {
|
|
return entry.kind === EntryKind.Span ? entry.textSpan :
|
|
getTextSpan(entry.node, entry.node.getSourceFile());
|
|
}
|
|
|
|
/**
|
|
* A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment.
|
|
*
|
|
* @internal
|
|
*/
|
|
export function isWriteAccessForReference(node: Node): boolean {
|
|
const decl = getDeclarationFromName(node);
|
|
return !!decl && declarationIsWriteAccess(decl) || node.kind === SyntaxKind.DefaultKeyword || isWriteAccess(node);
|
|
}
|
|
|
|
/**
|
|
* Whether a reference, `node`, is a definition of the `target` symbol
|
|
*
|
|
* @internal
|
|
*/
|
|
export function isDeclarationOfSymbol(node: Node, target: Symbol | undefined): boolean {
|
|
if (!target) return false;
|
|
const source = getDeclarationFromName(node) ||
|
|
(node.kind === SyntaxKind.DefaultKeyword ? node.parent
|
|
: isLiteralComputedPropertyDeclarationName(node) ? node.parent.parent
|
|
: node.kind === SyntaxKind.ConstructorKeyword && isConstructorDeclaration(node.parent) ? node.parent.parent
|
|
: undefined);
|
|
const commonjsSource = source && isBinaryExpression(source) ? source.left as unknown as Declaration : undefined;
|
|
return !!(source && target.declarations?.some(d => d === source || d === commonjsSource));
|
|
}
|
|
|
|
/**
|
|
* True if 'decl' provides a value, as in `function f() {}`;
|
|
* false if 'decl' is just a location for a future write, as in 'let x;'
|
|
*/
|
|
function declarationIsWriteAccess(decl: Declaration): boolean {
|
|
// Consider anything in an ambient declaration to be a write access since it may be coming from JS.
|
|
if (!!(decl.flags & NodeFlags.Ambient)) return true;
|
|
|
|
switch (decl.kind) {
|
|
case SyntaxKind.BinaryExpression:
|
|
case SyntaxKind.BindingElement:
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.DefaultKeyword:
|
|
case SyntaxKind.EnumDeclaration:
|
|
case SyntaxKind.EnumMember:
|
|
case SyntaxKind.ExportSpecifier:
|
|
case SyntaxKind.ImportClause: // default import
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
case SyntaxKind.ImportSpecifier:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.JSDocCallbackTag:
|
|
case SyntaxKind.JSDocTypedefTag:
|
|
case SyntaxKind.JsxAttribute:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.NamespaceExportDeclaration:
|
|
case SyntaxKind.NamespaceImport:
|
|
case SyntaxKind.NamespaceExport:
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
case SyntaxKind.TypeParameter:
|
|
return true;
|
|
|
|
case SyntaxKind.PropertyAssignment:
|
|
// In `({ x: y } = 0);`, `x` is not a write access. (Won't call this function for `y`.)
|
|
return !isArrayLiteralOrObjectLiteralDestructuringPattern((decl as PropertyAssignment).parent);
|
|
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
return !!(decl as FunctionDeclaration | FunctionExpression | ConstructorDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration).body;
|
|
|
|
case SyntaxKind.VariableDeclaration:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
return !!(decl as VariableDeclaration | PropertyDeclaration).initializer || isCatchClause(decl.parent);
|
|
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.JSDocPropertyTag:
|
|
case SyntaxKind.JSDocParameterTag:
|
|
return false;
|
|
|
|
default:
|
|
return Debug.failBadSyntaxKind(decl);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encapsulates the core find-all-references algorithm.
|
|
*
|
|
* @internal
|
|
*/
|
|
export namespace Core {
|
|
/** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */
|
|
export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlySet<string> = new Set(sourceFiles.map(f => f.fileName))): readonly SymbolAndEntries[] | undefined {
|
|
node = getAdjustedNode(node, options);
|
|
if (isSourceFile(node)) {
|
|
const resolvedRef = GoToDefinition.getReferenceAtPosition(node, position, program);
|
|
if (!resolvedRef?.file) {
|
|
return undefined;
|
|
}
|
|
const moduleSymbol = program.getTypeChecker().getMergedSymbol(resolvedRef.file.symbol);
|
|
if (moduleSymbol) {
|
|
return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet);
|
|
}
|
|
const fileIncludeReasons = program.getFileIncludeReasons();
|
|
if (!fileIncludeReasons) {
|
|
return undefined;
|
|
}
|
|
return [{
|
|
definition: { type: DefinitionKind.TripleSlashReference, reference: resolvedRef.reference, file: node },
|
|
references: getReferencesForNonModule(resolvedRef.file, fileIncludeReasons, program) || emptyArray
|
|
}];
|
|
}
|
|
|
|
if (!options.implementations) {
|
|
const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken);
|
|
if (special) {
|
|
return special;
|
|
}
|
|
}
|
|
|
|
const checker = program.getTypeChecker();
|
|
// constructors should use the class symbol, detected by name, if present
|
|
const symbol = checker.getSymbolAtLocation(isConstructorDeclaration(node) && node.parent.name || node);
|
|
|
|
// Could not find a symbol e.g. unknown identifier
|
|
if (!symbol) {
|
|
// String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial.
|
|
if (!options.implementations && isStringLiteralLike(node)) {
|
|
if (isModuleSpecifierLike(node)) {
|
|
const fileIncludeReasons = program.getFileIncludeReasons();
|
|
const referencedFileName = node.getSourceFile().resolvedModules?.get(node.text, getModeForUsageLocation(node.getSourceFile(), node))?.resolvedModule?.resolvedFileName;
|
|
const referencedFile = referencedFileName ? program.getSourceFile(referencedFileName) : undefined;
|
|
if (referencedFile) {
|
|
return [{ definition: { type: DefinitionKind.String, node }, references: getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray }];
|
|
}
|
|
// Fall through to string literal references. This is not very likely to return
|
|
// anything useful, but I guess it's better than nothing, and there's an existing
|
|
// test that expects this to happen (fourslash/cases/untypedModuleImport.ts).
|
|
}
|
|
return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
if (symbol.escapedName === InternalSymbolName.ExportEquals) {
|
|
return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet);
|
|
}
|
|
|
|
const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet);
|
|
if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) {
|
|
return moduleReferences;
|
|
}
|
|
|
|
const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker);
|
|
const moduleReferencesOfExportTarget = aliasedSymbol &&
|
|
getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet);
|
|
|
|
const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options);
|
|
return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget);
|
|
}
|
|
|
|
export function getAdjustedNode(node: Node, options: Options) {
|
|
if (options.use === FindReferencesUse.References) {
|
|
node = getAdjustedReferenceLocation(node);
|
|
}
|
|
else if (options.use === FindReferencesUse.Rename) {
|
|
node = getAdjustedRenameLocation(node);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
export function getReferencesForFileName(fileName: string, program: Program, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string> = new Set(sourceFiles.map(f => f.fileName))): readonly Entry[] {
|
|
const moduleSymbol = program.getSourceFile(fileName)?.symbol;
|
|
if (moduleSymbol) {
|
|
return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet)[0]?.references || emptyArray;
|
|
}
|
|
const fileIncludeReasons = program.getFileIncludeReasons();
|
|
const referencedFile = program.getSourceFile(fileName);
|
|
return referencedFile && fileIncludeReasons && getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray;
|
|
}
|
|
|
|
function getReferencesForNonModule(referencedFile: SourceFile, refFileMap: MultiMap<Path, FileIncludeReason>, program: Program): readonly SpanEntry[] | undefined {
|
|
let entries: SpanEntry[] | undefined;
|
|
const references = refFileMap.get(referencedFile.path) || emptyArray;
|
|
for (const ref of references) {
|
|
if (isReferencedFile(ref)) {
|
|
const referencingFile = program.getSourceFileByPath(ref.file)!;
|
|
const location = getReferencedFileLocation(program.getSourceFileByPath, ref);
|
|
if (isReferenceFileLocation(location)) {
|
|
entries = append(entries, {
|
|
kind: EntryKind.Span,
|
|
fileName: referencingFile.fileName,
|
|
textSpan: createTextSpanFromRange(location)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) {
|
|
if (node.parent && isNamespaceExportDeclaration(node.parent)) {
|
|
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
|
const targetSymbol = checker.getMergedSymbol(aliasedSymbol);
|
|
if (aliasedSymbol !== targetSymbol) {
|
|
return targetSymbol;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet<string>) {
|
|
const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile);
|
|
if (!moduleSourceFile) return undefined;
|
|
const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals);
|
|
// If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them.
|
|
const moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet);
|
|
if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences;
|
|
// Continue to get references to 'export ='.
|
|
const checker = program.getTypeChecker();
|
|
symbol = skipAlias(exportEquals, checker);
|
|
return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options));
|
|
}
|
|
|
|
/**
|
|
* Merges the references by sorting them (by file index in sourceFiles and their location in it) that point to same definition symbol
|
|
*/
|
|
function mergeReferences(program: Program, ...referencesToMerge: (SymbolAndEntries[] | undefined)[]): SymbolAndEntries[] | undefined {
|
|
let result: SymbolAndEntries[] | undefined;
|
|
for (const references of referencesToMerge) {
|
|
if (!references || !references.length) continue;
|
|
if (!result) {
|
|
result = references;
|
|
continue;
|
|
}
|
|
for (const entry of references) {
|
|
if (!entry.definition || entry.definition.type !== DefinitionKind.Symbol) {
|
|
result.push(entry);
|
|
continue;
|
|
}
|
|
const symbol = entry.definition.symbol;
|
|
const refIndex = findIndex(result, ref => !!ref.definition &&
|
|
ref.definition.type === DefinitionKind.Symbol &&
|
|
ref.definition.symbol === symbol);
|
|
if (refIndex === -1) {
|
|
result.push(entry);
|
|
continue;
|
|
}
|
|
|
|
const reference = result[refIndex];
|
|
result[refIndex] = {
|
|
definition: reference.definition,
|
|
references: reference.references.concat(entry.references).sort((entry1, entry2) => {
|
|
const entry1File = getSourceFileIndexOfEntry(program, entry1);
|
|
const entry2File = getSourceFileIndexOfEntry(program, entry2);
|
|
if (entry1File !== entry2File) {
|
|
return compareValues(entry1File, entry2File);
|
|
}
|
|
|
|
const entry1Span = getTextSpanOfEntry(entry1);
|
|
const entry2Span = getTextSpanOfEntry(entry2);
|
|
return entry1Span.start !== entry2Span.start ?
|
|
compareValues(entry1Span.start, entry2Span.start) :
|
|
compareValues(entry1Span.length, entry2Span.length);
|
|
})
|
|
};
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getSourceFileIndexOfEntry(program: Program, entry: Entry) {
|
|
const sourceFile = entry.kind === EntryKind.Span ?
|
|
program.getSourceFile(entry.fileName)! :
|
|
entry.node.getSourceFile();
|
|
return program.getSourceFiles().indexOf(sourceFile);
|
|
}
|
|
|
|
function getReferencedSymbolsForModule(program: Program, symbol: Symbol, excludeImportTypeOfExportEquals: boolean, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string>): SymbolAndEntries[] {
|
|
Debug.assert(!!symbol.valueDeclaration);
|
|
|
|
const references = mapDefined<ModuleReference, Entry>(findModuleReferences(program, sourceFiles, symbol), reference => {
|
|
if (reference.kind === "import") {
|
|
const parent = reference.literal.parent;
|
|
if (isLiteralTypeNode(parent)) {
|
|
const importType = cast(parent.parent, isImportTypeNode);
|
|
if (excludeImportTypeOfExportEquals && !importType.qualifier) {
|
|
return undefined;
|
|
}
|
|
}
|
|
// import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway.
|
|
return nodeEntry(reference.literal);
|
|
}
|
|
else if (reference.kind === "implicit") {
|
|
// Return either: The first JSX node in the (if not a tslib import), the first statement of the file, or the whole file if neither of those exist
|
|
const range = reference.literal.text !== externalHelpersModuleNameText && forEachChildRecursively(
|
|
reference.referencingFile,
|
|
n => !(n.transformFlags & TransformFlags.ContainsJsx) ? "skip" : isJsxElement(n) || isJsxSelfClosingElement(n) || isJsxFragment(n) ? n : undefined
|
|
) || reference.referencingFile.statements[0] || reference.referencingFile;
|
|
return nodeEntry(range);
|
|
}
|
|
else {
|
|
return {
|
|
kind: EntryKind.Span,
|
|
fileName: reference.referencingFile.fileName,
|
|
textSpan: createTextSpanFromRange(reference.ref),
|
|
};
|
|
}
|
|
});
|
|
|
|
if (symbol.declarations) {
|
|
for (const decl of symbol.declarations) {
|
|
switch (decl.kind) {
|
|
case SyntaxKind.SourceFile:
|
|
// Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.)
|
|
break;
|
|
case SyntaxKind.ModuleDeclaration:
|
|
if (sourceFilesSet.has(decl.getSourceFile().fileName)) {
|
|
references.push(nodeEntry((decl as ModuleDeclaration).name));
|
|
}
|
|
break;
|
|
default:
|
|
// This may be merged with something.
|
|
Debug.assert(!!(symbol.flags & SymbolFlags.Transient), "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration.");
|
|
}
|
|
}
|
|
}
|
|
|
|
const exported = symbol.exports!.get(InternalSymbolName.ExportEquals);
|
|
if (exported?.declarations) {
|
|
for (const decl of exported.declarations) {
|
|
const sourceFile = decl.getSourceFile();
|
|
if (sourceFilesSet.has(sourceFile.fileName)) {
|
|
// At `module.exports = ...`, reference node is `module`
|
|
const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left) ? decl.left.expression :
|
|
isExportAssignment(decl) ? Debug.checkDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile)) :
|
|
getNameOfDeclaration(decl) || decl;
|
|
references.push(nodeEntry(node));
|
|
}
|
|
}
|
|
}
|
|
|
|
return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray;
|
|
}
|
|
|
|
/** As in a `readonly prop: any` or `constructor(readonly prop: any)`, not a `readonly any[]`. */
|
|
function isReadonlyTypeOperator(node: Node): boolean {
|
|
return node.kind === SyntaxKind.ReadonlyKeyword
|
|
&& isTypeOperatorNode(node.parent)
|
|
&& node.parent.operator === SyntaxKind.ReadonlyKeyword;
|
|
}
|
|
|
|
/** 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)) {
|
|
return undefined;
|
|
}
|
|
// Likewise, when we *are* looking for a special keyword, make sure we
|
|
// *don't* include readonly member modifiers.
|
|
return getAllReferencesForKeyword(
|
|
sourceFiles,
|
|
node.kind,
|
|
cancellationToken,
|
|
node.kind === SyntaxKind.ReadonlyKeyword ? isReadonlyTypeOperator : undefined);
|
|
}
|
|
|
|
if (isImportMeta(node.parent) && node.parent.name === node) {
|
|
return getAllReferencesForImportMeta(sourceFiles, cancellationToken);
|
|
}
|
|
|
|
if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) {
|
|
return [{ definition: { type: DefinitionKind.Keyword, node }, references: [nodeEntry(node)] }];
|
|
}
|
|
|
|
// Labels
|
|
if (isJumpStatementTarget(node)) {
|
|
const labelDefinition = getTargetLabel(node.parent, node.text);
|
|
// if we have a label definition, look within its statement for references, if not, then
|
|
// the label is undefined and we have no results..
|
|
return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition);
|
|
}
|
|
else if (isLabelOfLabeledStatement(node)) {
|
|
// it is a label definition and not a target, search within the parent labeledStatement
|
|
return getLabelReferencesInNode(node.parent, node);
|
|
}
|
|
|
|
if (isThis(node)) {
|
|
return getReferencesForThisKeyword(node, sourceFiles, cancellationToken);
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.SuperKeyword) {
|
|
return getReferencesForSuperKeyword(node);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/** Core find-all-references algorithm for a normal symbol. */
|
|
function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string>, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] {
|
|
const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol;
|
|
|
|
// Compute the meaning from the location and the symbol it references
|
|
const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All;
|
|
const result: SymbolAndEntries[] = [];
|
|
const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result);
|
|
|
|
const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) || !symbol.declarations ? undefined : find(symbol.declarations, isExportSpecifier);
|
|
if (exportSpecifier) {
|
|
// When renaming at an export specifier, rename the export and not the thing being exported.
|
|
getReferencesAtExportSpecifier(exportSpecifier.name, symbol, exportSpecifier, state.createSearch(node, originalSymbol, /*comingFrom*/ undefined), state, /*addReferencesHere*/ true, /*alwaysGetReferences*/ true);
|
|
}
|
|
else if (node && node.kind === SyntaxKind.DefaultKeyword && symbol.escapedName === InternalSymbolName.Default && symbol.parent) {
|
|
addReference(node, symbol, state);
|
|
searchForImportsOfExport(node, symbol, { exportingModuleSymbol: symbol.parent, exportKind: ExportKind.Default }, state);
|
|
}
|
|
else {
|
|
const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, options.use === FindReferencesUse.Rename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] });
|
|
getReferencesInContainerOrFiles(symbol, state, search);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void {
|
|
// Try to get the smallest valid scope that we can limit our search to;
|
|
// otherwise we'll need to search globally (i.e. include each file).
|
|
const scope = getSymbolScope(symbol);
|
|
if (scope) {
|
|
getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope)));
|
|
}
|
|
else {
|
|
// Global search
|
|
for (const sourceFile of state.sourceFiles) {
|
|
state.cancellationToken.throwIfCancellationRequested();
|
|
searchForName(sourceFile, search, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getSpecialSearchKind(node: Node): SpecialSearchKind {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.ConstructorKeyword:
|
|
return SpecialSearchKind.Constructor;
|
|
case SyntaxKind.Identifier:
|
|
if (isClassLike(node.parent)) {
|
|
Debug.assert(node.parent.name === node);
|
|
return SpecialSearchKind.Class;
|
|
}
|
|
// falls through
|
|
default:
|
|
return SpecialSearchKind.None;
|
|
}
|
|
}
|
|
|
|
/** Handle a few special cases relating to export/import specifiers. */
|
|
function skipPastExportOrImportSpecifierOrUnion(symbol: Symbol, node: Node, checker: TypeChecker, useLocalSymbolForExportSpecifier: boolean): Symbol | undefined {
|
|
const { parent } = node;
|
|
if (isExportSpecifier(parent) && useLocalSymbolForExportSpecifier) {
|
|
return getLocalSymbolForExportSpecifier(node as Identifier, symbol, parent, checker);
|
|
}
|
|
// If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references.
|
|
return firstDefined(symbol.declarations, decl => {
|
|
if (!decl.parent) {
|
|
// Ignore UMD module and global merge
|
|
if (symbol.flags & SymbolFlags.Transient) return undefined;
|
|
// Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here.
|
|
Debug.fail(`Unexpected symbol at ${Debug.formatSyntaxKind(node.kind)}: ${Debug.formatSymbol(symbol)}`);
|
|
}
|
|
return isTypeLiteralNode(decl.parent) && isUnionTypeNode(decl.parent.parent)
|
|
? checker.getPropertyOfType(checker.getTypeFromTypeNode(decl.parent.parent), symbol.name)
|
|
: undefined;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Symbol that is currently being searched for.
|
|
* This will be replaced if we find an alias for the symbol.
|
|
*/
|
|
interface Search {
|
|
/** If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). */
|
|
readonly comingFrom?: ImportExport;
|
|
|
|
readonly symbol: Symbol;
|
|
readonly text: string;
|
|
readonly escapedText: __String;
|
|
/** Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. */
|
|
readonly parents: readonly Symbol[] | undefined;
|
|
readonly allSearchSymbols: readonly Symbol[];
|
|
|
|
/**
|
|
* Whether a symbol is in the search set.
|
|
* Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`.
|
|
*/
|
|
includes(symbol: Symbol): boolean;
|
|
}
|
|
|
|
const enum SpecialSearchKind {
|
|
None,
|
|
Constructor,
|
|
Class,
|
|
}
|
|
|
|
function getNonModuleSymbolOfMergedModuleSymbol(symbol: Symbol) {
|
|
if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Transient))) return undefined;
|
|
const decl = symbol.declarations && find(symbol.declarations, d => !isSourceFile(d) && !isModuleDeclaration(d));
|
|
return decl && decl.symbol;
|
|
}
|
|
|
|
/**
|
|
* Holds all state needed for the finding references.
|
|
* Unlike `Search`, there is only one `State`.
|
|
*/
|
|
class State {
|
|
/** Cache for `explicitlyinheritsFrom`. */
|
|
readonly inheritsFromCache = new Map<string, boolean>();
|
|
|
|
/**
|
|
* Type nodes can contain multiple references to the same type. For example:
|
|
* let x: Foo & (Foo & Bar) = ...
|
|
* Because we are returning the implementation locations and not the identifier locations,
|
|
* duplicate entries would be returned here as each of the type references is part of
|
|
* the same implementation. For that reason, check before we add a new entry.
|
|
*/
|
|
readonly markSeenContainingTypeReference = nodeSeenTracker();
|
|
|
|
/**
|
|
* It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once.
|
|
* For example:
|
|
* // b.ts
|
|
* export { foo as bar } from "./a";
|
|
* import { bar } from "./b";
|
|
*
|
|
* Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local).
|
|
* But another reference to it may appear in the same source file.
|
|
* See `tests/cases/fourslash/transitiveExportImports3.ts`.
|
|
*/
|
|
readonly markSeenReExportRHS = nodeSeenTracker();
|
|
|
|
constructor(
|
|
readonly sourceFiles: readonly SourceFile[],
|
|
readonly sourceFilesSet: ReadonlySet<string>,
|
|
readonly specialSearchKind: SpecialSearchKind,
|
|
readonly checker: TypeChecker,
|
|
readonly cancellationToken: CancellationToken,
|
|
readonly searchMeaning: SemanticMeaning,
|
|
readonly options: Options,
|
|
private readonly result: SymbolAndEntries[]) {
|
|
}
|
|
|
|
includesSourceFile(sourceFile: SourceFile): boolean {
|
|
return this.sourceFilesSet.has(sourceFile.fileName);
|
|
}
|
|
|
|
private importTracker: ImportTracker | undefined;
|
|
/** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */
|
|
getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult {
|
|
if (!this.importTracker) this.importTracker = createImportTracker(this.sourceFiles, this.sourceFilesSet, this.checker, this.cancellationToken);
|
|
return this.importTracker(exportSymbol, exportInfo, this.options.use === FindReferencesUse.Rename);
|
|
}
|
|
|
|
/** @param allSearchSymbols set of additional symbols for use by `includes`. */
|
|
createSearch(location: Node | undefined, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search {
|
|
// Note: if this is an external module symbol, the name doesn't include quotes.
|
|
// Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`.
|
|
// The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form
|
|
// here appears to be intentional).
|
|
const {
|
|
text = stripQuotes(symbolName(getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol)),
|
|
allSearchSymbols = [symbol],
|
|
} = searchOptions;
|
|
const escapedText = escapeLeadingUnderscores(text);
|
|
const parents = this.options.implementations && location ? getParentSymbolsOfPropertyAccess(location, symbol, this.checker) : undefined;
|
|
return { symbol, comingFrom, text, escapedText, parents, allSearchSymbols, includes: sym => contains(allSearchSymbols, sym) };
|
|
}
|
|
|
|
private readonly symbolIdToReferences: Entry[][] = [];
|
|
/**
|
|
* Callback to add references for a particular searched symbol.
|
|
* This initializes a reference group, so only call this if you will add at least one reference.
|
|
*/
|
|
referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void {
|
|
const symbolId = getSymbolId(searchSymbol);
|
|
let references = this.symbolIdToReferences[symbolId];
|
|
if (!references) {
|
|
references = this.symbolIdToReferences[symbolId] = [];
|
|
this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references });
|
|
}
|
|
return (node, kind) => references.push(nodeEntry(node, kind));
|
|
}
|
|
|
|
/** Add a reference with no associated definition. */
|
|
addStringOrCommentReference(fileName: string, textSpan: TextSpan): void {
|
|
this.result.push({
|
|
definition: undefined,
|
|
references: [{ kind: EntryKind.Span, fileName, textSpan }]
|
|
});
|
|
}
|
|
|
|
// Source file ID -> symbol ID -> Whether the symbol has been searched for in the source file.
|
|
private readonly sourceFileToSeenSymbols: Set<number>[] = [];
|
|
/** Returns `true` the first time we search for a symbol in a file and `false` afterwards. */
|
|
markSearchedSymbols(sourceFile: SourceFile, symbols: readonly Symbol[]): boolean {
|
|
const sourceId = getNodeId(sourceFile);
|
|
const seenSymbols = this.sourceFileToSeenSymbols[sourceId] || (this.sourceFileToSeenSymbols[sourceId] = new Set<number>());
|
|
|
|
let anyNewSymbols = false;
|
|
for (const sym of symbols) {
|
|
anyNewSymbols = tryAddToSet(seenSymbols, getSymbolId(sym)) || anyNewSymbols;
|
|
}
|
|
return anyNewSymbols;
|
|
}
|
|
}
|
|
|
|
/** Search for all imports of a given exported symbol using `State.getImportSearches`. */
|
|
function searchForImportsOfExport(exportLocation: Node, exportSymbol: Symbol, exportInfo: ExportInfo, state: State): void {
|
|
const { importSearches, singleReferences, indirectUsers } = state.getImportSearches(exportSymbol, exportInfo);
|
|
|
|
// For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file.
|
|
if (singleReferences.length) {
|
|
const addRef = state.referenceAdder(exportSymbol);
|
|
for (const singleRef of singleReferences) {
|
|
if (shouldAddSingleReference(singleRef, state)) addRef(singleRef);
|
|
}
|
|
}
|
|
|
|
// For each import, find all references to that import in its source file.
|
|
for (const [importLocation, importSymbol] of importSearches) {
|
|
getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state);
|
|
}
|
|
|
|
if (indirectUsers.length) {
|
|
let indirectSearch: Search | undefined;
|
|
switch (exportInfo.exportKind) {
|
|
case ExportKind.Named:
|
|
indirectSearch = state.createSearch(exportLocation, exportSymbol, ImportExport.Export);
|
|
break;
|
|
case ExportKind.Default:
|
|
// Search for a property access to '.default'. This can't be renamed.
|
|
indirectSearch = state.options.use === FindReferencesUse.Rename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" });
|
|
break;
|
|
case ExportKind.ExportEquals:
|
|
break;
|
|
}
|
|
if (indirectSearch) {
|
|
for (const indirectUser of indirectUsers) {
|
|
searchForName(indirectUser, indirectSearch, state);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function eachExportReference(
|
|
sourceFiles: readonly SourceFile[],
|
|
checker: TypeChecker,
|
|
cancellationToken: CancellationToken | undefined,
|
|
exportSymbol: Symbol,
|
|
exportingModuleSymbol: Symbol,
|
|
exportName: string,
|
|
isDefaultExport: boolean,
|
|
cb: (ref: Identifier) => void,
|
|
): void {
|
|
const importTracker = createImportTracker(sourceFiles, new Set(sourceFiles.map(f => f.fileName)), checker, cancellationToken);
|
|
const { importSearches, indirectUsers, singleReferences } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false);
|
|
for (const [importLocation] of importSearches) {
|
|
cb(importLocation);
|
|
}
|
|
for (const singleReference of singleReferences) {
|
|
if (isIdentifier(singleReference) && isImportTypeNode(singleReference.parent)) {
|
|
cb(singleReference);
|
|
}
|
|
}
|
|
for (const indirectUser of indirectUsers) {
|
|
for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) {
|
|
// Import specifiers should be handled by importSearches
|
|
const symbol = checker.getSymbolAtLocation(node);
|
|
const hasExportAssignmentDeclaration = some(symbol?.declarations, d => tryCast(d, isExportAssignment) ? true : false);
|
|
if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && (symbol === exportSymbol || hasExportAssignmentDeclaration)) {
|
|
cb(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean {
|
|
if (!hasMatchingMeaning(singleRef, state)) return false;
|
|
if (state.options.use !== FindReferencesUse.Rename) return true;
|
|
// Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;`
|
|
if (!isIdentifier(singleRef)) return false;
|
|
// At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename.
|
|
return !(isImportOrExportSpecifier(singleRef.parent) && singleRef.escapedText === InternalSymbolName.Default);
|
|
}
|
|
|
|
// Go to the symbol we imported from and find references for it.
|
|
function searchForImportedSymbol(symbol: Symbol, state: State): void {
|
|
if (!symbol.declarations) return;
|
|
|
|
for (const declaration of symbol.declarations) {
|
|
const exportingFile = declaration.getSourceFile();
|
|
// Need to search in the file even if it's not in the search-file set, because it might export the symbol.
|
|
getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state, state.includesSourceFile(exportingFile));
|
|
}
|
|
}
|
|
|
|
/** Search for all occurrences of an identifier in a source file (and filter out the ones that match). */
|
|
function searchForName(sourceFile: SourceFile, search: Search, state: State): void {
|
|
if (getNameTable(sourceFile).get(search.escapedText) !== undefined) {
|
|
getReferencesInSourceFile(sourceFile, search, state);
|
|
}
|
|
}
|
|
|
|
function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined {
|
|
return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent)
|
|
? checker.getPropertySymbolOfDestructuringAssignment(location as Identifier)
|
|
: undefined;
|
|
}
|
|
|
|
/**
|
|
* Determines the smallest scope in which a symbol may have named references.
|
|
* Note that not every construct has been accounted for. This function can
|
|
* probably be improved.
|
|
*
|
|
* @returns undefined if the scope cannot be determined, implying that
|
|
* a reference to a symbol can occur anywhere.
|
|
*/
|
|
function getSymbolScope(symbol: Symbol): Node | undefined {
|
|
// If this is the symbol of a named function expression or named class expression,
|
|
// then named references are limited to its own scope.
|
|
const { declarations, flags, parent, valueDeclaration } = symbol;
|
|
if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) {
|
|
return valueDeclaration;
|
|
}
|
|
|
|
if (!declarations) {
|
|
return undefined;
|
|
}
|
|
|
|
// If this is private property or method, the scope is the containing class
|
|
if (flags & (SymbolFlags.Property | SymbolFlags.Method)) {
|
|
const privateDeclaration = find(declarations, d => hasEffectiveModifier(d, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(d));
|
|
if (privateDeclaration) {
|
|
return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration);
|
|
}
|
|
// Else this is a public property and could be accessed from anywhere.
|
|
return undefined;
|
|
}
|
|
|
|
// If symbol is of object binding pattern element without property name we would want to
|
|
// look for property too and that could be anywhere
|
|
if (declarations.some(isObjectBindingElementWithoutPropertyName)) {
|
|
return undefined;
|
|
}
|
|
|
|
/*
|
|
If the symbol has a parent, it's globally visible unless:
|
|
- It's a private property (handled above).
|
|
- It's a type parameter.
|
|
- The parent is an external module: then we should only search in the module (and recurse on the export later).
|
|
- But if the parent has `export as namespace`, the symbol is globally visible through that namespace.
|
|
*/
|
|
const exposedByParent = parent && !(symbol.flags & SymbolFlags.TypeParameter);
|
|
if (exposedByParent && !(isExternalModuleSymbol(parent) && !parent.globalExports)) {
|
|
return undefined;
|
|
}
|
|
|
|
let scope: Node | undefined;
|
|
for (const declaration of declarations) {
|
|
const container = getContainerNode(declaration);
|
|
if (scope && scope !== container) {
|
|
// Different declarations have different containers, bail out
|
|
return undefined;
|
|
}
|
|
|
|
if (!container || container.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(container as SourceFile)) {
|
|
// This is a global variable and not an external module, any declaration defined
|
|
// within this scope is visible outside the file
|
|
return undefined;
|
|
}
|
|
|
|
scope = container;
|
|
if (isFunctionExpression(scope)) {
|
|
let next: Node | undefined;
|
|
while (next = getNextJSDocCommentLocation(scope)) {
|
|
scope = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.)
|
|
// For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.:
|
|
// declare module "a" { export type T = number; }
|
|
// declare module "b" { import { T } from "a"; export const x: T; }
|
|
// So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.)
|
|
return exposedByParent ? scope!.getSourceFile() : scope; // TODO: GH#18217
|
|
}
|
|
|
|
/** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */
|
|
export function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, searchContainer: Node = sourceFile): boolean {
|
|
return eachSymbolReferenceInFile(definition, checker, sourceFile, () => true, searchContainer) || false;
|
|
}
|
|
|
|
export function eachSymbolReferenceInFile<T>(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T, searchContainer: Node = sourceFile): T | undefined {
|
|
const symbol = isParameterPropertyDeclaration(definition.parent, definition.parent.parent)
|
|
? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text))
|
|
: checker.getSymbolAtLocation(definition);
|
|
if (!symbol) return undefined;
|
|
for (const token of getPossibleSymbolReferenceNodes(sourceFile, symbol.name, searchContainer)) {
|
|
if (!isIdentifier(token) || token === definition || token.escapedText !== definition.escapedText) continue;
|
|
const referenceSymbol = checker.getSymbolAtLocation(token)!;
|
|
if (referenceSymbol === symbol
|
|
|| checker.getShorthandAssignmentValueSymbol(token.parent) === symbol
|
|
|| isExportSpecifier(token.parent) && getLocalSymbolForExportSpecifier(token, referenceSymbol, token.parent, checker) === symbol) {
|
|
const res = cb(token);
|
|
if (res) return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getTopMostDeclarationNamesInFile(declarationName: string, sourceFile: SourceFile): readonly Node[] {
|
|
const candidates = filter(getPossibleSymbolReferenceNodes(sourceFile, declarationName), name => !!getDeclarationFromName(name));
|
|
return candidates.reduce((topMost, decl) => {
|
|
const depth = getDepth(decl);
|
|
if (!some(topMost.declarationNames) || depth === topMost.depth) {
|
|
topMost.declarationNames.push(decl);
|
|
topMost.depth = depth;
|
|
}
|
|
else if (depth < topMost.depth) {
|
|
topMost.declarationNames = [decl];
|
|
topMost.depth = depth;
|
|
}
|
|
return topMost;
|
|
}, { depth: Infinity, declarationNames: [] as Node[] }).declarationNames;
|
|
|
|
function getDepth(declaration: Node | undefined) {
|
|
let depth = 0;
|
|
while (declaration) {
|
|
declaration = getContainerNode(declaration);
|
|
depth++;
|
|
}
|
|
return depth;
|
|
}
|
|
}
|
|
|
|
export function someSignatureUsage(
|
|
signature: SignatureDeclaration,
|
|
sourceFiles: readonly SourceFile[],
|
|
checker: TypeChecker,
|
|
cb: (name: Identifier, call?: CallExpression) => boolean
|
|
): boolean {
|
|
if (!signature.name || !isIdentifier(signature.name)) return false;
|
|
|
|
const symbol = Debug.checkDefined(checker.getSymbolAtLocation(signature.name));
|
|
|
|
for (const sourceFile of sourceFiles) {
|
|
for (const name of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) {
|
|
if (!isIdentifier(name) || name === signature.name || name.escapedText !== signature.name.escapedText) continue;
|
|
const called = climbPastPropertyAccess(name);
|
|
const call = isCallExpression(called.parent) && called.parent.expression === called ? called.parent : undefined;
|
|
const referenceSymbol = checker.getSymbolAtLocation(name);
|
|
if (referenceSymbol && checker.getRootSymbols(referenceSymbol).some(s => s === symbol)) {
|
|
if (cb(name, call)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getPossibleSymbolReferenceNodes(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly Node[] {
|
|
return mapDefined(getPossibleSymbolReferencePositions(sourceFile, symbolName, container), pos => {
|
|
const referenceLocation = getTouchingPropertyName(sourceFile, pos);
|
|
return referenceLocation === sourceFile ? undefined : referenceLocation;
|
|
});
|
|
}
|
|
|
|
function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly number[] {
|
|
const positions: number[] = [];
|
|
|
|
/// TODO: Cache symbol existence for files to save text search
|
|
// Also, need to make this work for unicode escapes.
|
|
|
|
// Be resilient in the face of a symbol with no name or zero length name
|
|
if (!symbolName || !symbolName.length) {
|
|
return positions;
|
|
}
|
|
|
|
const text = sourceFile.text;
|
|
const sourceLength = text.length;
|
|
const symbolNameLength = symbolName.length;
|
|
|
|
let position = text.indexOf(symbolName, container.pos);
|
|
while (position >= 0) {
|
|
// If we are past the end, stop looking
|
|
if (position > container.end) break;
|
|
|
|
// We found a match. Make sure it's not part of a larger word (i.e. the char
|
|
// before and after it have to be a non-identifier char).
|
|
const endPosition = position + symbolNameLength;
|
|
|
|
if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) &&
|
|
(endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) {
|
|
// Found a real match. Keep searching.
|
|
positions.push(position);
|
|
}
|
|
position = text.indexOf(symbolName, position + symbolNameLength + 1);
|
|
}
|
|
|
|
return positions;
|
|
}
|
|
|
|
function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] {
|
|
const sourceFile = container.getSourceFile();
|
|
const labelName = targetLabel.text;
|
|
const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), node =>
|
|
// Only pick labels that are either the target label, or have a target that is the target label
|
|
node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel) ? nodeEntry(node) : undefined);
|
|
return [{ definition: { type: DefinitionKind.Label, node: targetLabel }, references }];
|
|
}
|
|
|
|
function isValidReferencePosition(node: Node, searchSymbolName: string): boolean {
|
|
// Compare the length so we filter out strict superstrings of the symbol we are looking for
|
|
switch (node.kind) {
|
|
case SyntaxKind.PrivateIdentifier:
|
|
if (isJSDocMemberName(node.parent)) {
|
|
return true;
|
|
}
|
|
// falls through I guess
|
|
case SyntaxKind.Identifier:
|
|
return (node as PrivateIdentifier | Identifier).text.length === searchSymbolName.length;
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
case SyntaxKind.StringLiteral: {
|
|
const str = node as StringLiteralLike;
|
|
return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) &&
|
|
str.text.length === searchSymbolName.length;
|
|
}
|
|
|
|
case SyntaxKind.NumericLiteral:
|
|
return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral) && (node as NumericLiteral).text.length === searchSymbolName.length;
|
|
|
|
case SyntaxKind.DefaultKeyword:
|
|
return "default".length === searchSymbolName.length;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getAllReferencesForImportMeta(sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined {
|
|
const references = flatMap(sourceFiles, sourceFile => {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "meta", sourceFile), node => {
|
|
const parent = node.parent;
|
|
if (isImportMeta(parent)) {
|
|
return nodeEntry(parent);
|
|
}
|
|
});
|
|
});
|
|
return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined;
|
|
}
|
|
|
|
function getAllReferencesForKeyword(sourceFiles: readonly SourceFile[], keywordKind: SyntaxKind, cancellationToken: CancellationToken, filter?: (node: Node) => boolean): SymbolAndEntries[] | undefined {
|
|
const references = flatMap(sourceFiles, sourceFile => {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, tokenToString(keywordKind)!, sourceFile), referenceLocation => {
|
|
if (referenceLocation.kind === keywordKind && (!filter || filter(referenceLocation))) {
|
|
return nodeEntry(referenceLocation);
|
|
}
|
|
});
|
|
});
|
|
return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined;
|
|
}
|
|
|
|
function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State, addReferencesHere = true): void {
|
|
state.cancellationToken.throwIfCancellationRequested();
|
|
return getReferencesInContainer(sourceFile, sourceFile, search, state, addReferencesHere);
|
|
}
|
|
|
|
/**
|
|
* Search within node "container" for references for a search value, where the search value is defined as a
|
|
* tuple of(searchSymbol, searchText, searchLocation, and searchMeaning).
|
|
* searchLocation: a node where the search value
|
|
*/
|
|
function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State, addReferencesHere: boolean): void {
|
|
if (!state.markSearchedSymbols(sourceFile, search.allSearchSymbols)) {
|
|
return;
|
|
}
|
|
|
|
for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container)) {
|
|
getReferencesAtLocation(sourceFile, position, search, state, addReferencesHere);
|
|
}
|
|
}
|
|
|
|
function hasMatchingMeaning(referenceLocation: Node, state: State): boolean {
|
|
return !!(getMeaningFromLocation(referenceLocation) & state.searchMeaning);
|
|
}
|
|
|
|
function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void {
|
|
const referenceLocation = getTouchingPropertyName(sourceFile, position);
|
|
|
|
if (!isValidReferencePosition(referenceLocation, search.text)) {
|
|
// This wasn't the start of a token. Check to see if it might be a
|
|
// match in a comment or string if that's what the caller is asking
|
|
// for.
|
|
if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) {
|
|
// In the case where we're looking inside comments/strings, we don't have
|
|
// an actual definition. So just use 'undefined' here. Features like
|
|
// 'Rename' won't care (as they ignore the definitions), and features like
|
|
// 'FindReferences' will just filter out these results.
|
|
state.addStringOrCommentReference(sourceFile.fileName, createTextSpan(position, search.text.length));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!hasMatchingMeaning(referenceLocation, state)) return;
|
|
|
|
let referenceSymbol = state.checker.getSymbolAtLocation(referenceLocation);
|
|
if (!referenceSymbol) {
|
|
return;
|
|
}
|
|
|
|
const parent = referenceLocation.parent;
|
|
if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) {
|
|
// This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again.
|
|
return;
|
|
}
|
|
|
|
if (isExportSpecifier(parent)) {
|
|
Debug.assert(referenceLocation.kind === SyntaxKind.Identifier);
|
|
getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state, addReferencesHere);
|
|
return;
|
|
}
|
|
|
|
const relatedSymbol = getRelatedSymbol(search, referenceSymbol, referenceLocation, state);
|
|
if (!relatedSymbol) {
|
|
getReferenceForShorthandProperty(referenceSymbol, search, state);
|
|
return;
|
|
}
|
|
|
|
switch (state.specialSearchKind) {
|
|
case SpecialSearchKind.None:
|
|
if (addReferencesHere) addReference(referenceLocation, relatedSymbol, state);
|
|
break;
|
|
case SpecialSearchKind.Constructor:
|
|
addConstructorReferences(referenceLocation, sourceFile, search, state);
|
|
break;
|
|
case SpecialSearchKind.Class:
|
|
addClassStaticThisReferences(referenceLocation, search, state);
|
|
break;
|
|
default:
|
|
Debug.assertNever(state.specialSearchKind);
|
|
}
|
|
|
|
// Use the parent symbol if the location is commonjs require syntax on javascript files only.
|
|
if (isInJSFile(referenceLocation)
|
|
&& isBindingElement(referenceLocation.parent)
|
|
&& isVariableDeclarationInitializedToBareOrAccessedRequire(referenceLocation.parent.parent.parent)) {
|
|
referenceSymbol = referenceLocation.parent.symbol;
|
|
// The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In
|
|
// this case, just skip it, since the bound identifiers are not an alias of the import.
|
|
if (!referenceSymbol) return;
|
|
}
|
|
|
|
getImportOrExportReferences(referenceLocation, referenceSymbol, search, state);
|
|
}
|
|
|
|
function getReferencesAtExportSpecifier(
|
|
referenceLocation: Identifier,
|
|
referenceSymbol: Symbol,
|
|
exportSpecifier: ExportSpecifier,
|
|
search: Search,
|
|
state: State,
|
|
addReferencesHere: boolean,
|
|
alwaysGetReferences?: boolean,
|
|
): void {
|
|
Debug.assert(!alwaysGetReferences || !!state.options.providePrefixAndSuffixTextForRename, "If alwaysGetReferences is true, then prefix/suffix text must be enabled");
|
|
|
|
const { parent, propertyName, name } = exportSpecifier;
|
|
const exportDeclaration = parent.parent;
|
|
const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker);
|
|
if (!alwaysGetReferences && !search.includes(localSymbol)) {
|
|
return;
|
|
}
|
|
|
|
if (!propertyName) {
|
|
// Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export)
|
|
if (!(state.options.use === FindReferencesUse.Rename && (name.escapedText === InternalSymbolName.Default))) {
|
|
addRef();
|
|
}
|
|
}
|
|
else if (referenceLocation === propertyName) {
|
|
// For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export.
|
|
// For `export { foo as bar };`, where `foo` is a local, so add it now.
|
|
if (!exportDeclaration.moduleSpecifier) {
|
|
addRef();
|
|
}
|
|
|
|
if (addReferencesHere && state.options.use !== FindReferencesUse.Rename && state.markSeenReExportRHS(name)) {
|
|
addReference(name, Debug.checkDefined(exportSpecifier.symbol), state);
|
|
}
|
|
}
|
|
else {
|
|
if (state.markSeenReExportRHS(referenceLocation)) {
|
|
addRef();
|
|
}
|
|
}
|
|
|
|
// For `export { foo as bar }`, rename `foo`, but not `bar`.
|
|
if (!isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences) {
|
|
const isDefaultExport = referenceLocation.escapedText === "default"
|
|
|| exportSpecifier.name.escapedText === "default";
|
|
const exportKind = isDefaultExport ? ExportKind.Default : ExportKind.Named;
|
|
const exportSymbol = Debug.checkDefined(exportSpecifier.symbol);
|
|
const exportInfo = getExportInfo(exportSymbol, exportKind, state.checker);
|
|
if (exportInfo) {
|
|
searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo, state);
|
|
}
|
|
}
|
|
|
|
// At `export { x } from "foo"`, also search for the imported symbol `"foo".x`.
|
|
if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName && !isForRenameWithPrefixAndSuffixText(state.options)) {
|
|
const imported = state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier);
|
|
if (imported) searchForImportedSymbol(imported, state);
|
|
}
|
|
|
|
function addRef() {
|
|
if (addReferencesHere) addReference(referenceLocation, localSymbol, state);
|
|
}
|
|
}
|
|
|
|
function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol {
|
|
return isExportSpecifierAlias(referenceLocation, exportSpecifier) && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier) || referenceSymbol;
|
|
}
|
|
|
|
function isExportSpecifierAlias(referenceLocation: Identifier, exportSpecifier: ExportSpecifier): boolean {
|
|
const { parent, propertyName, name } = exportSpecifier;
|
|
Debug.assert(propertyName === referenceLocation || name === referenceLocation);
|
|
if (propertyName) {
|
|
// Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol.
|
|
return propertyName === referenceLocation;
|
|
}
|
|
else {
|
|
// `export { foo } from "foo"` is a re-export.
|
|
// `export { foo };` is not a re-export, it creates an alias for the local variable `foo`.
|
|
return !parent.parent.moduleSpecifier;
|
|
}
|
|
}
|
|
|
|
function getImportOrExportReferences(referenceLocation: Node, referenceSymbol: Symbol, search: Search, state: State): void {
|
|
const importOrExport = getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom === ImportExport.Export);
|
|
if (!importOrExport) return;
|
|
|
|
const { symbol } = importOrExport;
|
|
|
|
if (importOrExport.kind === ImportExport.Import) {
|
|
if (!(isForRenameWithPrefixAndSuffixText(state.options))) {
|
|
searchForImportedSymbol(symbol, state);
|
|
}
|
|
}
|
|
else {
|
|
searchForImportsOfExport(referenceLocation, symbol, importOrExport.exportInfo, state);
|
|
}
|
|
}
|
|
|
|
function getReferenceForShorthandProperty({ flags, valueDeclaration }: Symbol, search: Search, state: State): void {
|
|
const shorthandValueSymbol = state.checker.getShorthandAssignmentValueSymbol(valueDeclaration)!;
|
|
const name = valueDeclaration && getNameOfDeclaration(valueDeclaration);
|
|
/*
|
|
* Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment
|
|
* has two meanings: property name and property value. Therefore when we do findAllReference at the position where
|
|
* an identifier is declared, the language service should return the position of the variable declaration as well as
|
|
* the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the
|
|
* position of property accessing, the referenceEntry of such position will be handled in the first case.
|
|
*/
|
|
if (!(flags & SymbolFlags.Transient) && name && search.includes(shorthandValueSymbol)) {
|
|
addReference(name, shorthandValueSymbol, state);
|
|
}
|
|
}
|
|
|
|
function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void {
|
|
const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line local/no-in-operator
|
|
|
|
// if rename symbol from default export anonymous function, for example `export default function() {}`, we do not need to add reference
|
|
if (state.options.use === FindReferencesUse.Rename && referenceLocation.kind === SyntaxKind.DefaultKeyword) {
|
|
return;
|
|
}
|
|
|
|
const addRef = state.referenceAdder(symbol);
|
|
if (state.options.implementations) {
|
|
addImplementationReferences(referenceLocation, addRef, state);
|
|
}
|
|
else {
|
|
addRef(referenceLocation, kind);
|
|
}
|
|
}
|
|
|
|
/** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */
|
|
function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void {
|
|
if (isNewExpressionTarget(referenceLocation)) {
|
|
addReference(referenceLocation, search.symbol, state);
|
|
}
|
|
|
|
const pusher = () => state.referenceAdder(search.symbol);
|
|
|
|
if (isClassLike(referenceLocation.parent)) {
|
|
Debug.assert(referenceLocation.kind === SyntaxKind.DefaultKeyword || referenceLocation.parent.name === referenceLocation);
|
|
// This is the class declaration containing the constructor.
|
|
findOwnConstructorReferences(search.symbol, sourceFile, pusher());
|
|
}
|
|
else {
|
|
// If this class appears in `extends C`, then the extending class' "super" calls are references.
|
|
const classExtending = tryGetClassByExtendingIdentifier(referenceLocation);
|
|
if (classExtending) {
|
|
findSuperConstructorAccesses(classExtending, pusher());
|
|
findInheritedConstructorReferences(classExtending, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void {
|
|
addReference(referenceLocation, search.symbol, state);
|
|
const classLike = referenceLocation.parent;
|
|
if (state.options.use === FindReferencesUse.Rename || !isClassLike(classLike)) return;
|
|
Debug.assert(classLike.name === referenceLocation);
|
|
const addRef = state.referenceAdder(search.symbol);
|
|
for (const member of classLike.members) {
|
|
if (!(isMethodOrAccessor(member) && isStatic(member))) {
|
|
continue;
|
|
}
|
|
if (member.body) {
|
|
member.body.forEachChild(function cb(node) {
|
|
if (node.kind === SyntaxKind.ThisKeyword) {
|
|
addRef(node);
|
|
}
|
|
else if (!isFunctionLike(node) && !isClassLike(node)) {
|
|
node.forEachChild(cb);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `classSymbol` is the class where the constructor was defined.
|
|
* Reference the constructor and all calls to `new this()`.
|
|
*/
|
|
function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void {
|
|
const constructorSymbol = getClassConstructorSymbol(classSymbol);
|
|
if (constructorSymbol && constructorSymbol.declarations) {
|
|
for (const decl of constructorSymbol.declarations) {
|
|
const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!;
|
|
Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword);
|
|
addNode(ctrKeyword);
|
|
}
|
|
}
|
|
|
|
if (classSymbol.exports) {
|
|
classSymbol.exports.forEach(member => {
|
|
const decl = member.valueDeclaration;
|
|
if (decl && decl.kind === SyntaxKind.MethodDeclaration) {
|
|
const body = (decl as MethodDeclaration).body;
|
|
if (body) {
|
|
forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => {
|
|
if (isNewExpressionTarget(thisKeyword)) {
|
|
addNode(thisKeyword);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined {
|
|
return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor);
|
|
}
|
|
|
|
/** Find references to `super` in the constructor of an extending class. */
|
|
function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void {
|
|
const constructor = getClassConstructorSymbol(classDeclaration.symbol);
|
|
if (!(constructor && constructor.declarations)) {
|
|
return;
|
|
}
|
|
|
|
for (const decl of constructor.declarations) {
|
|
Debug.assert(decl.kind === SyntaxKind.Constructor);
|
|
const body = (decl as ConstructorDeclaration).body;
|
|
if (body) {
|
|
forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => {
|
|
if (isCallExpressionTarget(node)) {
|
|
addNode(node);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean {
|
|
return !!getClassConstructorSymbol(classDeclaration.symbol);
|
|
}
|
|
|
|
function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void {
|
|
if (hasOwnConstructor(classDeclaration)) return;
|
|
const classSymbol = classDeclaration.symbol;
|
|
const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined);
|
|
getReferencesInContainerOrFiles(classSymbol, state, search);
|
|
}
|
|
|
|
function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void {
|
|
// Check if we found a function/propertyAssignment/method with an implementation or initializer
|
|
if (isDeclarationName(refNode) && isImplementation(refNode.parent)) {
|
|
addReference(refNode);
|
|
return;
|
|
}
|
|
|
|
if (refNode.kind !== SyntaxKind.Identifier) {
|
|
return;
|
|
}
|
|
|
|
if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
|
// Go ahead and dereference the shorthand assignment by going to its definition
|
|
getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addReference);
|
|
}
|
|
|
|
// Check if the node is within an extends or implements clause
|
|
const containingNode = getContainingNodeIfInHeritageClause(refNode);
|
|
if (containingNode) {
|
|
addReference(containingNode);
|
|
return;
|
|
}
|
|
|
|
// If we got a type reference, try and see if the reference applies to any expressions that can implement an interface
|
|
// Find the first node whose parent isn't a type node -- i.e., the highest type node.
|
|
const typeNode = findAncestor(refNode, a => !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent))!;
|
|
const typeHavingNode = typeNode.parent;
|
|
if (hasType(typeHavingNode) && typeHavingNode.type === typeNode && state.markSeenContainingTypeReference(typeHavingNode)) {
|
|
if (hasInitializer(typeHavingNode)) {
|
|
addIfImplementation(typeHavingNode.initializer!);
|
|
}
|
|
else if (isFunctionLike(typeHavingNode) && (typeHavingNode as FunctionLikeDeclaration).body) {
|
|
const body = (typeHavingNode as FunctionLikeDeclaration).body!;
|
|
if (body.kind === SyntaxKind.Block) {
|
|
forEachReturnStatement(body as Block, returnStatement => {
|
|
if (returnStatement.expression) addIfImplementation(returnStatement.expression);
|
|
});
|
|
}
|
|
else {
|
|
addIfImplementation(body);
|
|
}
|
|
}
|
|
else if (isAssertionExpression(typeHavingNode)) {
|
|
addIfImplementation(typeHavingNode.expression);
|
|
}
|
|
}
|
|
|
|
function addIfImplementation(e: Expression): void {
|
|
if (isImplementationExpression(e)) addReference(e);
|
|
}
|
|
}
|
|
|
|
function getContainingNodeIfInHeritageClause(node: Node): ClassLikeDeclaration | InterfaceDeclaration | undefined {
|
|
return isIdentifier(node) || isPropertyAccessExpression(node) ? getContainingNodeIfInHeritageClause(node.parent)
|
|
: isExpressionWithTypeArguments(node) ? tryCast(node.parent.parent, or(isClassLike, isInterfaceDeclaration)) : undefined;
|
|
}
|
|
|
|
/**
|
|
* Returns true if this is an expression that can be considered an implementation
|
|
*/
|
|
function isImplementationExpression(node: Expression): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return isImplementationExpression((node as ParenthesizedExpression).expression);
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol
|
|
* is an interface, determines if some ancestor of the child symbol extends or inherits from it.
|
|
* Also takes in a cache of previous results which makes this slightly more efficient and is
|
|
* necessary to avoid potential loops like so:
|
|
* class A extends B { }
|
|
* class B extends A { }
|
|
*
|
|
* We traverse the AST rather than using the type checker because users are typically only interested
|
|
* in explicit implementations of an interface/class when calling "Go to Implementation". Sibling
|
|
* implementations of types that share a common ancestor with the type whose implementation we are
|
|
* searching for need to be filtered out of the results. The type checker doesn't let us make the
|
|
* distinction between structurally compatible implementations and explicit implementations, so we
|
|
* must use the AST.
|
|
*
|
|
* @param symbol A class or interface Symbol
|
|
* @param parent Another class or interface Symbol
|
|
* @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results
|
|
*/
|
|
function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: Map<string, boolean>, checker: TypeChecker): boolean {
|
|
if (symbol === parent) {
|
|
return true;
|
|
}
|
|
|
|
const key = getSymbolId(symbol) + "," + getSymbolId(parent);
|
|
const cached = cachedResults.get(key);
|
|
if (cached !== undefined) {
|
|
return cached;
|
|
}
|
|
|
|
// Set the key so that we don't infinitely recurse
|
|
cachedResults.set(key, false);
|
|
|
|
const inherits = !!symbol.declarations && symbol.declarations.some(declaration =>
|
|
getAllSuperTypeNodes(declaration).some(typeReference => {
|
|
const type = checker.getTypeAtLocation(typeReference);
|
|
return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker);
|
|
}));
|
|
cachedResults.set(key, inherits);
|
|
return inherits;
|
|
}
|
|
|
|
function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] | undefined {
|
|
let searchSpaceNode: SuperContainer | ClassLikeDeclaration | TypeLiteralNode | InterfaceDeclaration | ObjectLiteralExpression | undefined = getSuperContainer(superKeyword, /*stopOnFunctions*/ false);
|
|
if (!searchSpaceNode) {
|
|
return undefined;
|
|
}
|
|
// Whether 'super' occurs in a static context within a class.
|
|
let staticFlag = ModifierFlags.Static;
|
|
|
|
switch (searchSpaceNode.kind) {
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
staticFlag &= getSyntacticModifierFlags(searchSpaceNode);
|
|
searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class
|
|
break;
|
|
default:
|
|
return undefined;
|
|
}
|
|
|
|
const sourceFile = searchSpaceNode.getSourceFile();
|
|
const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), node => {
|
|
if (node.kind !== SyntaxKind.SuperKeyword) {
|
|
return;
|
|
}
|
|
|
|
const container = getSuperContainer(node, /*stopOnFunctions*/ false);
|
|
|
|
// If we have a 'super' container, we must have an enclosing class.
|
|
// Now make sure the owning class is the same as the search-space
|
|
// and has the same static qualifier as the original 'super's owner.
|
|
return container && isStatic(container) === !!staticFlag && container.parent.symbol === searchSpaceNode!.symbol ? nodeEntry(node) : undefined;
|
|
});
|
|
|
|
return [{ definition: { type: DefinitionKind.Symbol, symbol: searchSpaceNode.symbol }, references }];
|
|
}
|
|
|
|
function isParameterName(node: Node) {
|
|
return node.kind === SyntaxKind.Identifier && node.parent.kind === SyntaxKind.Parameter && (node.parent as ParameterDeclaration).name === node;
|
|
}
|
|
|
|
function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined {
|
|
let searchSpaceNode: Node = getThisContainer(thisOrSuperKeyword, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
|
|
|
|
// Whether 'this' occurs in a static context within a class.
|
|
let staticFlag = ModifierFlags.Static;
|
|
|
|
switch (searchSpaceNode.kind) {
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
if (isObjectLiteralMethod(searchSpaceNode)) {
|
|
staticFlag &= getSyntacticModifierFlags(searchSpaceNode);
|
|
searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning object literals
|
|
break;
|
|
}
|
|
// falls through
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
staticFlag &= getSyntacticModifierFlags(searchSpaceNode);
|
|
searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class
|
|
break;
|
|
case SyntaxKind.SourceFile:
|
|
if (isExternalModule(searchSpaceNode as SourceFile) || isParameterName(thisOrSuperKeyword)) {
|
|
return undefined;
|
|
}
|
|
// falls through
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
break;
|
|
// Computed properties in classes are not handled here because references to this are illegal,
|
|
// so there is no point finding references to them.
|
|
default:
|
|
return undefined;
|
|
}
|
|
|
|
const references = flatMap(searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFiles : [searchSpaceNode.getSourceFile()], sourceFile => {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
return getPossibleSymbolReferenceNodes(sourceFile, "this", isSourceFile(searchSpaceNode) ? sourceFile : searchSpaceNode).filter(node => {
|
|
if (!isThis(node)) {
|
|
return false;
|
|
}
|
|
const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
|
|
if (!canHaveSymbol(container)) return false;
|
|
switch (searchSpaceNode.kind) {
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
return (searchSpaceNode as FunctionExpression | FunctionDeclaration).symbol === container.symbol;
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
return isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol;
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
// Make sure the container belongs to the same class/object literals
|
|
// and has the appropriate static modifier from the original container.
|
|
return container.parent && canHaveSymbol(container.parent) && (searchSpaceNode as ClassLikeDeclaration | ObjectLiteralExpression).symbol === container.parent.symbol && isStatic(container) === !!staticFlag;
|
|
case SyntaxKind.SourceFile:
|
|
return container.kind === SyntaxKind.SourceFile && !isExternalModule(container) && !isParameterName(node);
|
|
}
|
|
});
|
|
}).map(n => nodeEntry(n));
|
|
|
|
const thisParameter = firstDefined(references, r => isParameter(r.node.parent) ? r.node : undefined);
|
|
return [{
|
|
definition: { type: DefinitionKind.This, node: thisParameter || thisOrSuperKeyword },
|
|
references
|
|
}];
|
|
}
|
|
|
|
function getReferencesForStringLiteral(node: StringLiteralLike, sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] {
|
|
const type = getContextualTypeFromParentOrAncestorTypeNode(node, checker);
|
|
const references = flatMap(sourceFiles, sourceFile => {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => {
|
|
if (isStringLiteralLike(ref) && ref.text === node.text) {
|
|
if (type) {
|
|
const refType = getContextualTypeFromParentOrAncestorTypeNode(ref, checker);
|
|
if (type !== checker.getStringType() && type === refType) {
|
|
return nodeEntry(ref, EntryKind.StringLiteral);
|
|
}
|
|
}
|
|
else {
|
|
return isNoSubstitutionTemplateLiteral(ref) && !rangeIsOnSingleLine(ref, sourceFile) ? undefined :
|
|
nodeEntry(ref, EntryKind.StringLiteral);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
return [{
|
|
definition: { type: DefinitionKind.String, node },
|
|
references
|
|
}];
|
|
}
|
|
|
|
// For certain symbol kinds, we need to include other symbols in the search set.
|
|
// This is not needed when searching for re-exports.
|
|
function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, providePrefixAndSuffixText: boolean, implementations: boolean): Symbol[] {
|
|
const result: Symbol[] = [];
|
|
forEachRelatedSymbol<void>(symbol, location, checker, isForRename, !(isForRename && providePrefixAndSuffixText),
|
|
(sym, root, base) => {
|
|
// static method/property and instance method/property might have the same name. Only include static or only include instance.
|
|
if (base) {
|
|
if (isStaticSymbol(symbol) !== isStaticSymbol(base)) {
|
|
base = undefined;
|
|
}
|
|
}
|
|
result.push(base || root || sym);
|
|
},
|
|
// when try to find implementation, implementations is true, and not allowed to find base class
|
|
/*allowBaseTypes*/() => !implementations);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @param allowBaseTypes return true means it would try to find in base class or interface.
|
|
*/
|
|
function forEachRelatedSymbol<T>(
|
|
symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean, onlyIncludeBindingElementAtReferenceLocation: boolean,
|
|
/**
|
|
* @param baseSymbol This symbol means one property/mehtod from base class or interface when it is not null or undefined,
|
|
*/
|
|
cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined,
|
|
allowBaseTypes: (rootSymbol: Symbol) => boolean,
|
|
): T | undefined {
|
|
const containingObjectLiteralElement = getContainingObjectLiteralElement(location);
|
|
if (containingObjectLiteralElement) {
|
|
/* Because in short-hand property assignment, location has two meaning : property name and as value of the property
|
|
* When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of
|
|
* property name and variable declaration of the identifier.
|
|
* Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service
|
|
* should show both 'name' in 'obj' and 'name' in variable declaration
|
|
* const name = "Foo";
|
|
* const obj = { name };
|
|
* In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment
|
|
* so that when matching with potential reference symbol, both symbols from property declaration and variable declaration
|
|
* will be included correctly.
|
|
*/
|
|
const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol
|
|
if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) {
|
|
// When renaming 'x' in `const o = { x }`, just rename the local variable, not the property.
|
|
return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty);
|
|
}
|
|
|
|
// If the location is in a context sensitive location (i.e. in an object literal) try
|
|
// to get a contextual type for it, and add the property symbol from the contextual
|
|
// type to the search set
|
|
const contextualType = checker.getContextualType(containingObjectLiteralElement.parent);
|
|
const res = contextualType && firstDefined(
|
|
getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true),
|
|
sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal));
|
|
if (res) return res;
|
|
|
|
// If the location is name of property symbol from object literal destructuring pattern
|
|
// Search the property symbol
|
|
// for ( { property: p2 } of elems) { }
|
|
const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker);
|
|
const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal);
|
|
if (res1) return res1;
|
|
|
|
const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty);
|
|
if (res2) return res2;
|
|
}
|
|
|
|
const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, checker);
|
|
if (aliasedSymbol) {
|
|
// In case of UMD module and global merging, search for global as well
|
|
const res = cbSymbol(aliasedSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node);
|
|
if (res) return res;
|
|
}
|
|
|
|
const res = fromRoot(symbol);
|
|
if (res) return res;
|
|
|
|
if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration, symbol.valueDeclaration.parent)) {
|
|
// For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property).
|
|
const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(cast(symbol.valueDeclaration, isParameter), symbol.name);
|
|
Debug.assert(paramProps.length === 2 && !!(paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && !!(paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property]
|
|
return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]);
|
|
}
|
|
|
|
const exportSpecifier = getDeclarationOfKind<ExportSpecifier>(symbol, SyntaxKind.ExportSpecifier);
|
|
if (!isForRenamePopulateSearchSymbolSet || exportSpecifier && !exportSpecifier.propertyName) {
|
|
const localSymbol = exportSpecifier && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier);
|
|
if (localSymbol) {
|
|
const res = cbSymbol(localSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node);
|
|
if (res) return res;
|
|
}
|
|
}
|
|
|
|
// symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property.
|
|
// Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local.
|
|
if (!isForRenamePopulateSearchSymbolSet) {
|
|
let bindingElementPropertySymbol: Symbol | undefined;
|
|
if (onlyIncludeBindingElementAtReferenceLocation) {
|
|
bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined;
|
|
}
|
|
else {
|
|
bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker);
|
|
}
|
|
return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal);
|
|
}
|
|
|
|
Debug.assert(isForRenamePopulateSearchSymbolSet);
|
|
// due to the above assert and the arguments at the uses of this function,
|
|
// (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds
|
|
const includeOriginalSymbolOfBindingElement = onlyIncludeBindingElementAtReferenceLocation;
|
|
|
|
if (includeOriginalSymbolOfBindingElement) {
|
|
const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker);
|
|
return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal);
|
|
}
|
|
|
|
function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined {
|
|
// If this is a union property:
|
|
// - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types.
|
|
// - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search.
|
|
// If the symbol is an instantiation from a another symbol (e.g. widened symbol):
|
|
// - In populateSearchSymbolsSet, add the root the list
|
|
// - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.)
|
|
return firstDefined(checker.getRootSymbols(sym), rootSymbol =>
|
|
cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind)
|
|
// Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions
|
|
|| (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol)
|
|
? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind))
|
|
: undefined));
|
|
}
|
|
|
|
function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined {
|
|
const bindingElement = getDeclarationOfKind<BindingElement>(symbol, SyntaxKind.BindingElement);
|
|
if (bindingElement && isObjectBindingElementWithoutPropertyName(bindingElement)) {
|
|
return getPropertySymbolFromBindingElement(checker, bindingElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find symbol of the given property-name and add the symbol to the given result array
|
|
* @param symbol a symbol to start searching for the given propertyName
|
|
* @param propertyName a name of property to search for
|
|
* @param result an array of symbol of found property symbols
|
|
* @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol.
|
|
* The value of previousIterationSymbol is undefined when the function is first called.
|
|
*/
|
|
function getPropertySymbolsFromBaseTypes<T>(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined {
|
|
const seen = new Map<SymbolId, true>();
|
|
return recur(symbol);
|
|
|
|
function recur(symbol: Symbol): T | undefined {
|
|
// Use `addToSeen` to ensure we don't infinitely recurse in this situation:
|
|
// interface C extends C {
|
|
// /*findRef*/propName: string;
|
|
// }
|
|
if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, getSymbolId(symbol))) return;
|
|
|
|
return firstDefined(symbol.declarations, declaration => firstDefined(getAllSuperTypeNodes(declaration), typeReference => {
|
|
const type = checker.getTypeAtLocation(typeReference);
|
|
const propertySymbol = type && type.symbol && checker.getPropertyOfType(type, propertyName);
|
|
// Visit the typeReference as well to see if it directly or indirectly uses that property
|
|
return type && propertySymbol && (firstDefined(checker.getRootSymbols(propertySymbol), cb) || recur(type.symbol));
|
|
}));
|
|
}
|
|
}
|
|
|
|
interface RelatedSymbol {
|
|
readonly symbol: Symbol;
|
|
readonly kind: NodeEntryKind | undefined;
|
|
}
|
|
|
|
function isStaticSymbol(symbol: Symbol): boolean {
|
|
if (!symbol.valueDeclaration) return false;
|
|
const modifierFlags = getEffectiveModifierFlags(symbol.valueDeclaration);
|
|
return !!(modifierFlags & ModifierFlags.Static);
|
|
}
|
|
|
|
function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined {
|
|
const { checker } = state;
|
|
return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false,
|
|
/*onlyIncludeBindingElementAtReferenceLocation*/ state.options.use !== FindReferencesUse.Rename || !!state.options.providePrefixAndSuffixTextForRename,
|
|
(sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => {
|
|
// check whether the symbol used to search itself is just the searched one.
|
|
if (baseSymbol) {
|
|
// static method/property and instance method/property might have the same name. Only check static or only check instance.
|
|
if (isStaticSymbol(referenceSymbol) !== isStaticSymbol(baseSymbol)) {
|
|
baseSymbol = undefined;
|
|
}
|
|
}
|
|
return search.includes(baseSymbol || rootSymbol || sym)
|
|
// For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol.
|
|
? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind }
|
|
: undefined;
|
|
},
|
|
/*allowBaseTypes*/ rootSymbol =>
|
|
!(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker)))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations
|
|
* of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class
|
|
* then we need to widen the search to include type positions as well.
|
|
* On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated
|
|
* module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module)
|
|
* do not intersect in any of the three spaces.
|
|
*/
|
|
export function getIntersectingMeaningFromDeclarations(node: Node, symbol: Symbol): SemanticMeaning {
|
|
let meaning = getMeaningFromLocation(node);
|
|
const { declarations } = symbol;
|
|
if (declarations) {
|
|
let lastIterationMeaning: SemanticMeaning;
|
|
do {
|
|
// The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module]
|
|
// we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module
|
|
// intersects with the class in the value space.
|
|
// To achieve that we will keep iterating until the result stabilizes.
|
|
|
|
// Remember the last meaning
|
|
lastIterationMeaning = meaning;
|
|
|
|
for (const declaration of declarations) {
|
|
const declarationMeaning = getMeaningFromDeclaration(declaration);
|
|
|
|
if (declarationMeaning & meaning) {
|
|
meaning |= declarationMeaning;
|
|
}
|
|
}
|
|
}
|
|
while (meaning !== lastIterationMeaning);
|
|
}
|
|
return meaning;
|
|
}
|
|
|
|
function isImplementation(node: Node): boolean {
|
|
return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) :
|
|
(isVariableLike(node) ? hasInitializer(node) :
|
|
isFunctionLikeDeclaration(node) ? !!node.body :
|
|
isClassLike(node) || isModuleOrEnumDeclaration(node));
|
|
}
|
|
|
|
export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void {
|
|
const refSymbol = checker.getSymbolAtLocation(node)!;
|
|
const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration);
|
|
|
|
if (shorthandSymbol) {
|
|
for (const declaration of shorthandSymbol.getDeclarations()!) {
|
|
if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) {
|
|
addReference(declaration);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void {
|
|
forEachChild(node, child => {
|
|
if (child.kind === kind) {
|
|
action(child);
|
|
}
|
|
forEachDescendantOfKind(child, kind, action);
|
|
});
|
|
}
|
|
|
|
/** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */
|
|
function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined {
|
|
return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent);
|
|
}
|
|
|
|
/**
|
|
* If we are just looking for implementations and this is a property access expression, we need to get the
|
|
* symbol of the local type of the symbol the property is being accessed on. This is because our search
|
|
* symbol may have a different parent symbol if the local type's symbol does not declare the property
|
|
* being accessed (i.e. it is declared in some parent class or interface)
|
|
*/
|
|
function getParentSymbolsOfPropertyAccess(location: Node, symbol: Symbol, checker: TypeChecker): readonly Symbol[] | undefined {
|
|
const propertyAccessExpression = isRightSideOfPropertyAccess(location) ? location.parent as PropertyAccessExpression : undefined;
|
|
const lhsType = propertyAccessExpression && checker.getTypeAtLocation(propertyAccessExpression.expression);
|
|
const res = mapDefined(lhsType && (lhsType.isUnionOrIntersection() ? lhsType.types : lhsType.symbol === symbol.parent ? undefined : [lhsType]), t =>
|
|
t.symbol && t.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? t.symbol : undefined);
|
|
return res.length === 0 ? undefined : res;
|
|
}
|
|
|
|
function isForRenameWithPrefixAndSuffixText(options: Options) {
|
|
return options.use === FindReferencesUse.Rename && options.providePrefixAndSuffixTextForRename;
|
|
}
|
|
}
|