1/* @internal */ 2namespace ts.FindAllReferences { 3 export interface SymbolAndEntries { 4 readonly definition: Definition | undefined; 5 readonly references: readonly Entry[]; 6 } 7 8 export const enum DefinitionKind { Symbol, Label, Keyword, This, String, TripleSlashReference } 9 export type Definition = 10 | { readonly type: DefinitionKind.Symbol; readonly symbol: Symbol } 11 | { readonly type: DefinitionKind.Label; readonly node: Identifier } 12 | { readonly type: DefinitionKind.Keyword; readonly node: Node } 13 | { readonly type: DefinitionKind.This; readonly node: Node } 14 | { readonly type: DefinitionKind.String; readonly node: StringLiteralLike } 15 | { readonly type: DefinitionKind.TripleSlashReference; readonly reference: FileReference, readonly file: SourceFile }; 16 17 export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal } 18 export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal; 19 export type Entry = NodeEntry | SpanEntry; 20 export interface ContextWithStartAndEndNode { 21 start: Node; 22 end: Node; 23 } 24 export type ContextNode = Node | ContextWithStartAndEndNode; 25 export interface NodeEntry { 26 readonly kind: NodeEntryKind; 27 readonly node: Node; 28 readonly context?: ContextNode; 29 } 30 export interface SpanEntry { 31 readonly kind: EntryKind.Span; 32 readonly fileName: string; 33 readonly textSpan: TextSpan; 34 } 35 export function nodeEntry(node: Node, kind: NodeEntryKind = EntryKind.Node): NodeEntry { 36 return { 37 kind, 38 node: (node as NamedDeclaration).name || node, 39 context: getContextNodeForNodeEntry(node) 40 }; 41 } 42 43 export function isContextWithStartAndEndNode(node: ContextNode): node is ContextWithStartAndEndNode { 44 return node && (node as Node).kind === undefined; 45 } 46 47 function getContextNodeForNodeEntry(node: Node): ContextNode | undefined { 48 if (isDeclaration(node)) { 49 return getContextNode(node); 50 } 51 52 if (!node.parent) return undefined; 53 54 if (!isDeclaration(node.parent) && !isExportAssignment(node.parent)) { 55 // Special property assignment in javascript 56 if (isInJSFile(node)) { 57 const binaryExpression = isBinaryExpression(node.parent) ? 58 node.parent : 59 isAccessExpression(node.parent) && 60 isBinaryExpression(node.parent.parent) && 61 node.parent.parent.left === node.parent ? 62 node.parent.parent : 63 undefined; 64 if (binaryExpression && getAssignmentDeclarationKind(binaryExpression) !== AssignmentDeclarationKind.None) { 65 return getContextNode(binaryExpression); 66 } 67 } 68 69 // Jsx Tags 70 if (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) { 71 return node.parent.parent; 72 } 73 else if (isJsxSelfClosingElement(node.parent) || 74 isLabeledStatement(node.parent) || 75 isBreakOrContinueStatement(node.parent)) { 76 return node.parent; 77 } 78 else if (isStringLiteralLike(node)) { 79 const validImport = tryGetImportFromModuleSpecifier(node); 80 if (validImport) { 81 const declOrStatement = findAncestor(validImport, node => 82 isDeclaration(node) || 83 isStatement(node) || 84 isJSDocTag(node) 85 )! as NamedDeclaration | Statement | JSDocTag; 86 return isDeclaration(declOrStatement) ? 87 getContextNode(declOrStatement) : 88 declOrStatement; 89 } 90 } 91 92 // Handle computed property name 93 const propertyName = findAncestor(node, isComputedPropertyName); 94 return propertyName ? 95 getContextNode(propertyName.parent) : 96 undefined; 97 } 98 99 if (node.parent.name === node || // node is name of declaration, use parent 100 isConstructorDeclaration(node.parent) || 101 isExportAssignment(node.parent) || 102 // Property name of the import export specifier or binding pattern, use parent 103 ((isImportOrExportSpecifier(node.parent) || isBindingElement(node.parent)) 104 && node.parent.propertyName === node) || 105 // Is default export 106 (node.kind === SyntaxKind.DefaultKeyword && hasSyntacticModifier(node.parent, ModifierFlags.ExportDefault))) { 107 return getContextNode(node.parent); 108 } 109 110 return undefined; 111 } 112 113 export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | undefined): ContextNode | undefined { 114 if (!node) return undefined; 115 switch (node.kind) { 116 case SyntaxKind.VariableDeclaration: 117 return !isVariableDeclarationList(node.parent) || node.parent.declarations.length !== 1 ? 118 node : 119 isVariableStatement(node.parent.parent) ? 120 node.parent.parent : 121 isForInOrOfStatement(node.parent.parent) ? 122 getContextNode(node.parent.parent) : 123 node.parent; 124 125 case SyntaxKind.BindingElement: 126 return getContextNode(node.parent.parent as NamedDeclaration); 127 128 case SyntaxKind.ImportSpecifier: 129 return node.parent.parent.parent; 130 131 case SyntaxKind.ExportSpecifier: 132 case SyntaxKind.NamespaceImport: 133 return node.parent.parent; 134 135 case SyntaxKind.ImportClause: 136 case SyntaxKind.NamespaceExport: 137 return node.parent; 138 139 case SyntaxKind.BinaryExpression: 140 return isExpressionStatement(node.parent) ? 141 node.parent : 142 node; 143 144 case SyntaxKind.ForOfStatement: 145 case SyntaxKind.ForInStatement: 146 return { 147 start: (node as ForInOrOfStatement).initializer, 148 end: (node as ForInOrOfStatement).expression 149 }; 150 151 case SyntaxKind.PropertyAssignment: 152 case SyntaxKind.ShorthandPropertyAssignment: 153 return isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent) ? 154 getContextNode( 155 findAncestor(node.parent, node => 156 isBinaryExpression(node) || isForInOrOfStatement(node) 157 ) as BinaryExpression | ForInOrOfStatement 158 ) : 159 node; 160 161 default: 162 return node; 163 } 164 } 165 166 export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context?: ContextNode): { contextSpan: TextSpan } | undefined { 167 if (!context) return undefined; 168 const contextSpan = isContextWithStartAndEndNode(context) ? 169 getTextSpan(context.start, sourceFile, context.end) : 170 getTextSpan(context, sourceFile); 171 return contextSpan.start !== textSpan.start || contextSpan.length !== textSpan.length ? 172 { contextSpan } : 173 undefined; 174 } 175 176 export const enum FindReferencesUse { 177 /** 178 * When searching for references to a symbol, the location will not be adjusted (this is the default behavior when not specified). 179 */ 180 Other, 181 /** 182 * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. 183 */ 184 References, 185 /** 186 * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. 187 * Unlike `References`, the location will only be adjusted keyword belonged to a declaration with a valid name. 188 * If set, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name. 189 */ 190 Rename, 191 } 192 193 export interface Options { 194 readonly findInStrings?: boolean; 195 readonly findInComments?: boolean; 196 readonly use?: FindReferencesUse; 197 /** True if we are searching for implementations. We will have a different method of adding references if so. */ 198 readonly implementations?: boolean; 199 /** 200 * True to opt in for enhanced renaming of shorthand properties and import/export specifiers. 201 * The options controls the behavior for the whole rename operation; it cannot be changed on a per-file basis. 202 * Default is false for backwards compatibility. 203 */ 204 readonly providePrefixAndSuffixTextForRename?: boolean; 205 } 206 207 export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { 208 const node = getTouchingPropertyName(sourceFile, position); 209 const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, { use: FindReferencesUse.References }); 210 const checker = program.getTypeChecker(); 211 return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined<SymbolAndEntries, ReferencedSymbol>(referencedSymbols, ({ definition, references }) => 212 // Only include referenced symbols that have a valid definition. 213 definition && { 214 definition: checker.runWithCancellationToken(cancellationToken, checker => definitionToReferencedSymbolDefinitionInfo(definition, checker, node)), 215 references: references.map(toReferenceEntry) 216 }); 217 } 218 219 export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined { 220 const node = getTouchingPropertyName(sourceFile, position); 221 let referenceEntries: Entry[] | undefined; 222 const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position); 223 224 if ( 225 node.parent.kind === SyntaxKind.PropertyAccessExpression 226 || node.parent.kind === SyntaxKind.BindingElement 227 || node.parent.kind === SyntaxKind.ElementAccessExpression 228 || node.kind === SyntaxKind.SuperKeyword 229 ) { 230 referenceEntries = entries && [...entries]; 231 } 232 else { 233 const queue = entries && [...entries]; 234 const seenNodes = new Map<string, true>(); 235 while (queue && queue.length) { 236 const entry = queue.shift() as NodeEntry; 237 if (!addToSeen(seenNodes, getNodeId(entry.node))) { 238 continue; 239 } 240 referenceEntries = append(referenceEntries, entry); 241 const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos); 242 if (entries) { 243 queue.push(...entries); 244 } 245 } 246 } 247 const checker = program.getTypeChecker(); 248 return map(referenceEntries, entry => toImplementationLocation(entry, checker)); 249 } 250 251 function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined { 252 if (node.kind === SyntaxKind.SourceFile) { 253 return undefined; 254 } 255 256 const checker = program.getTypeChecker(); 257 // If invoked directly on a shorthand property assignment, then return 258 // the declaration of the symbol being assigned (not the symbol being assigned to). 259 if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { 260 const result: NodeEntry[] = []; 261 Core.getReferenceEntriesForShorthandPropertyAssignment(node, checker, node => result.push(nodeEntry(node))); 262 return result; 263 } 264 else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) { 265 // References to and accesses on the super keyword only have one possible implementation, so no 266 // need to "Find all References" 267 const symbol = checker.getSymbolAtLocation(node)!; 268 return symbol.valueDeclaration && [nodeEntry(symbol.valueDeclaration)]; 269 } 270 else { 271 // Perform "Find all References" and retrieve only those that are implementations 272 return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, use: FindReferencesUse.References }); 273 } 274 } 275 276 export function findReferenceOrRenameEntries<T>( 277 program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, options: Options | undefined, 278 convertEntry: ToReferenceOrRenameEntry<T>, 279 ): T[] | undefined { 280 return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node, program.getTypeChecker())); 281 } 282 283 export type ToReferenceOrRenameEntry<T> = (entry: Entry, originalNode: Node, checker: TypeChecker) => T; 284 285 export function getReferenceEntriesForNode( 286 position: number, 287 node: Node, 288 program: Program, 289 sourceFiles: readonly SourceFile[], 290 cancellationToken: CancellationToken, 291 options: Options = {}, 292 sourceFilesSet: ReadonlySet<string> = new Set(sourceFiles.map(f => f.fileName)), 293 ): readonly Entry[] | undefined { 294 return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet)); 295 } 296 297 function flattenEntries(referenceSymbols: readonly SymbolAndEntries[] | undefined): readonly Entry[] | undefined { 298 return referenceSymbols && flatMap(referenceSymbols, r => r.references); 299 } 300 301 function definitionToReferencedSymbolDefinitionInfo(def: Definition, checker: TypeChecker, originalNode: Node): ReferencedSymbolDefinitionInfo { 302 const info = ((): { sourceFile: SourceFile, textSpan: TextSpan, name: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], context?: Node | ContextWithStartAndEndNode } => { 303 switch (def.type) { 304 case DefinitionKind.Symbol: { 305 const { symbol } = def; 306 const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, checker, originalNode); 307 const name = displayParts.map(p => p.text).join(""); 308 const declaration = symbol.declarations && firstOrUndefined(symbol.declarations); 309 const node = declaration ? (getNameOfDeclaration(declaration) || declaration) : originalNode; 310 return { 311 ...getFileAndTextSpanFromNode(node), 312 name, 313 kind, 314 displayParts, 315 context: getContextNode(declaration) 316 }; 317 } 318 case DefinitionKind.Label: { 319 const { node } = def; 320 return { ...getFileAndTextSpanFromNode(node), name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] }; 321 } 322 case DefinitionKind.Keyword: { 323 const { node } = def; 324 const name = tokenToString(node.kind)!; 325 return { ...getFileAndTextSpanFromNode(node), name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] }; 326 } 327 case DefinitionKind.This: { 328 const { node } = def; 329 const symbol = checker.getSymbolAtLocation(node); 330 const displayParts = symbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind( 331 checker, symbol, node.getSourceFile(), getContainerNode(node), node).displayParts || [textPart("this")]; 332 return { ...getFileAndTextSpanFromNode(node), name: "this", kind: ScriptElementKind.variableElement, displayParts }; 333 } 334 case DefinitionKind.String: { 335 const { node } = def; 336 return { 337 ...getFileAndTextSpanFromNode(node), 338 name: node.text, 339 kind: ScriptElementKind.variableElement, 340 displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] 341 }; 342 } 343 case DefinitionKind.TripleSlashReference: { 344 return { 345 textSpan: createTextSpanFromRange(def.reference), 346 sourceFile: def.file, 347 name: def.reference.fileName, 348 kind: ScriptElementKind.string, 349 displayParts: [displayPart(`"${def.reference.fileName}"`, SymbolDisplayPartKind.stringLiteral)] 350 }; 351 } 352 default: 353 return Debug.assertNever(def); 354 } 355 })(); 356 357 const { sourceFile, textSpan, name, kind, displayParts, context } = info; 358 return { 359 containerKind: ScriptElementKind.unknown, 360 containerName: "", 361 fileName: sourceFile.fileName, 362 kind, 363 name, 364 textSpan, 365 displayParts, 366 ...toContextSpan(textSpan, sourceFile, context) 367 }; 368 } 369 370 function getFileAndTextSpanFromNode(node: Node) { 371 const sourceFile = node.getSourceFile(); 372 return { 373 sourceFile, 374 textSpan: getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile) 375 }; 376 } 377 378 function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } { 379 const meaning = Core.getIntersectingMeaningFromDeclarations(node, symbol); 380 const enclosingDeclaration = symbol.declarations && firstOrUndefined(symbol.declarations) || node; 381 const { displayParts, symbolKind } = 382 SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, enclosingDeclaration.getSourceFile(), enclosingDeclaration, enclosingDeclaration, meaning); 383 return { displayParts, kind: symbolKind }; 384 } 385 386 export function toRenameLocation(entry: Entry, originalNode: Node, checker: TypeChecker, providePrefixAndSuffixText: boolean): RenameLocation { 387 return { ...entryToDocumentSpan(entry), ...(providePrefixAndSuffixText && getPrefixAndSuffixText(entry, originalNode, checker)) }; 388 } 389 390 export function toReferenceEntry(entry: Entry): ReferenceEntry { 391 const documentSpan = entryToDocumentSpan(entry); 392 if (entry.kind === EntryKind.Span) { 393 return { ...documentSpan, isWriteAccess: false, isDefinition: false }; 394 } 395 const { kind, node } = entry; 396 return { 397 ...documentSpan, 398 isWriteAccess: isWriteAccessForReference(node), 399 isDefinition: isDefinitionForReference(node), 400 isInString: kind === EntryKind.StringLiteral ? true : undefined, 401 }; 402 } 403 404 function entryToDocumentSpan(entry: Entry): DocumentSpan { 405 if (entry.kind === EntryKind.Span) { 406 return { textSpan: entry.textSpan, fileName: entry.fileName }; 407 } 408 else { 409 const sourceFile = entry.node.getSourceFile(); 410 const textSpan = getTextSpan(entry.node, sourceFile); 411 return { 412 textSpan, 413 fileName: sourceFile.fileName, 414 ...toContextSpan(textSpan, sourceFile, entry.context) 415 }; 416 } 417 } 418 419 interface PrefixAndSuffix { readonly prefixText?: string; readonly suffixText?: string; } 420 function getPrefixAndSuffixText(entry: Entry, originalNode: Node, checker: TypeChecker): PrefixAndSuffix { 421 if (entry.kind !== EntryKind.Span && isIdentifier(originalNode)) { 422 const { node, kind } = entry; 423 const parent = node.parent; 424 const name = originalNode.text; 425 const isShorthandAssignment = isShorthandPropertyAssignment(parent); 426 if (isShorthandAssignment || (isObjectBindingElementWithoutPropertyName(parent) && parent.name === node && parent.dotDotDotToken === undefined)) { 427 const prefixColon: PrefixAndSuffix = { prefixText: name + ": " }; 428 const suffixColon: PrefixAndSuffix = { suffixText: ": " + name }; 429 if (kind === EntryKind.SearchedLocalFoundProperty) { 430 return prefixColon; 431 } 432 if (kind === EntryKind.SearchedPropertyFoundLocal) { 433 return suffixColon; 434 } 435 436 // In `const o = { x }; o.x`, symbolAtLocation at `x` in `{ x }` is the property symbol. 437 // For a binding element `const { x } = o;`, symbolAtLocation at `x` is the property symbol. 438 if (isShorthandAssignment) { 439 const grandParent = parent.parent; 440 if (isObjectLiteralExpression(grandParent) && 441 isBinaryExpression(grandParent.parent) && 442 isModuleExportsAccessExpression(grandParent.parent.left)) { 443 return prefixColon; 444 } 445 return suffixColon; 446 } 447 else { 448 return prefixColon; 449 } 450 } 451 else if (isImportSpecifier(parent) && !parent.propertyName) { 452 // If the original symbol was using this alias, just rename the alias. 453 const originalSymbol = isExportSpecifier(originalNode.parent) ? checker.getExportSpecifierLocalTargetSymbol(originalNode.parent) : checker.getSymbolAtLocation(originalNode); 454 return contains(originalSymbol!.declarations, parent) ? { prefixText: name + " as " } : emptyOptions; 455 } 456 else if (isExportSpecifier(parent) && !parent.propertyName) { 457 // If the symbol for the node is same as declared node symbol use prefix text 458 return originalNode === entry.node || checker.getSymbolAtLocation(originalNode) === checker.getSymbolAtLocation(entry.node) ? 459 { prefixText: name + " as " } : 460 { suffixText: " as " + name }; 461 } 462 } 463 464 return emptyOptions; 465 } 466 467 function toImplementationLocation(entry: Entry, checker: TypeChecker): ImplementationLocation { 468 const documentSpan = entryToDocumentSpan(entry); 469 if (entry.kind !== EntryKind.Span) { 470 const { node } = entry; 471 return { 472 ...documentSpan, 473 ...implementationKindDisplayParts(node, checker) 474 }; 475 } 476 else { 477 return { ...documentSpan, kind: ScriptElementKind.unknown, displayParts: [] }; 478 } 479 } 480 481 function implementationKindDisplayParts(node: Node, checker: TypeChecker): { kind: ScriptElementKind, displayParts: SymbolDisplayPart[] } { 482 const symbol = checker.getSymbolAtLocation(isDeclaration(node) && node.name ? node.name : node); 483 if (symbol) { 484 return getDefinitionKindAndDisplayParts(symbol, checker, node); 485 } 486 else if (node.kind === SyntaxKind.ObjectLiteralExpression) { 487 return { 488 kind: ScriptElementKind.interfaceElement, 489 displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("object literal"), punctuationPart(SyntaxKind.CloseParenToken)] 490 }; 491 } 492 else if (node.kind === SyntaxKind.ClassExpression) { 493 return { 494 kind: ScriptElementKind.localClassElement, 495 displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("anonymous local class"), punctuationPart(SyntaxKind.CloseParenToken)] 496 }; 497 } 498 else { 499 return { kind: getNodeKind(node), displayParts: [] }; 500 } 501 } 502 503 export function toHighlightSpan(entry: Entry): { fileName: string, span: HighlightSpan } { 504 const documentSpan = entryToDocumentSpan(entry); 505 if (entry.kind === EntryKind.Span) { 506 return { 507 fileName: documentSpan.fileName, 508 span: { 509 textSpan: documentSpan.textSpan, 510 kind: HighlightSpanKind.reference 511 } 512 }; 513 } 514 515 const writeAccess = isWriteAccessForReference(entry.node); 516 const span: HighlightSpan = { 517 textSpan: documentSpan.textSpan, 518 kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference, 519 isInString: entry.kind === EntryKind.StringLiteral ? true : undefined, 520 ...documentSpan.contextSpan && { contextSpan: documentSpan.contextSpan } 521 }; 522 return { fileName: documentSpan.fileName, span }; 523 } 524 525 function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan { 526 let start = node.getStart(sourceFile); 527 let end = (endNode || node).getEnd(); 528 if (isStringLiteralLike(node)) { 529 Debug.assert(endNode === undefined); 530 start += 1; 531 end -= 1; 532 } 533 return createTextSpanFromBounds(start, end); 534 } 535 536 export function getTextSpanOfEntry(entry: Entry) { 537 return entry.kind === EntryKind.Span ? entry.textSpan : 538 getTextSpan(entry.node, entry.node.getSourceFile()); 539 } 540 541 /** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */ 542 function isWriteAccessForReference(node: Node): boolean { 543 const decl = getDeclarationFromName(node); 544 return !!decl && declarationIsWriteAccess(decl) || node.kind === SyntaxKind.DefaultKeyword || isWriteAccess(node); 545 } 546 547 function isDefinitionForReference(node: Node): boolean { 548 return node.kind === SyntaxKind.DefaultKeyword 549 || !!getDeclarationFromName(node) 550 || isLiteralComputedPropertyDeclarationName(node) 551 || (node.kind === SyntaxKind.ConstructorKeyword && isConstructorDeclaration(node.parent)); 552 } 553 554 /** 555 * True if 'decl' provides a value, as in `function f() {}`; 556 * false if 'decl' is just a location for a future write, as in 'let x;' 557 */ 558 function declarationIsWriteAccess(decl: Declaration): boolean { 559 // Consider anything in an ambient declaration to be a write access since it may be coming from JS. 560 if (!!(decl.flags & NodeFlags.Ambient)) return true; 561 562 switch (decl.kind) { 563 case SyntaxKind.BinaryExpression: 564 case SyntaxKind.BindingElement: 565 case SyntaxKind.ClassDeclaration: 566 case SyntaxKind.ClassExpression: 567 case SyntaxKind.StructDeclaration: 568 case SyntaxKind.DefaultKeyword: 569 case SyntaxKind.EnumDeclaration: 570 case SyntaxKind.EnumMember: 571 case SyntaxKind.ExportSpecifier: 572 case SyntaxKind.ImportClause: // default import 573 case SyntaxKind.ImportEqualsDeclaration: 574 case SyntaxKind.ImportSpecifier: 575 case SyntaxKind.InterfaceDeclaration: 576 case SyntaxKind.JSDocCallbackTag: 577 case SyntaxKind.JSDocTypedefTag: 578 case SyntaxKind.JsxAttribute: 579 case SyntaxKind.ModuleDeclaration: 580 case SyntaxKind.NamespaceExportDeclaration: 581 case SyntaxKind.NamespaceImport: 582 case SyntaxKind.NamespaceExport: 583 case SyntaxKind.Parameter: 584 case SyntaxKind.ShorthandPropertyAssignment: 585 case SyntaxKind.TypeAliasDeclaration: 586 case SyntaxKind.TypeParameter: 587 return true; 588 589 case SyntaxKind.PropertyAssignment: 590 // In `({ x: y } = 0);`, `x` is not a write access. (Won't call this function for `y`.) 591 return !isArrayLiteralOrObjectLiteralDestructuringPattern((decl as PropertyAssignment).parent); 592 593 case SyntaxKind.FunctionDeclaration: 594 case SyntaxKind.FunctionExpression: 595 case SyntaxKind.Constructor: 596 case SyntaxKind.MethodDeclaration: 597 case SyntaxKind.GetAccessor: 598 case SyntaxKind.SetAccessor: 599 return !!(decl as FunctionDeclaration | FunctionExpression | ConstructorDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration).body; 600 601 case SyntaxKind.VariableDeclaration: 602 case SyntaxKind.PropertyDeclaration: 603 return !!(decl as VariableDeclaration | PropertyDeclaration).initializer || isCatchClause(decl.parent); 604 605 case SyntaxKind.MethodSignature: 606 case SyntaxKind.PropertySignature: 607 case SyntaxKind.JSDocPropertyTag: 608 case SyntaxKind.JSDocParameterTag: 609 return false; 610 611 default: 612 return Debug.failBadSyntaxKind(decl); 613 } 614 } 615 616 /** Encapsulates the core find-all-references algorithm. */ 617 export namespace Core { 618 /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ 619 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 { 620 if (options.use === FindReferencesUse.References) { 621 node = getAdjustedReferenceLocation(node); 622 } 623 else if (options.use === FindReferencesUse.Rename) { 624 node = getAdjustedRenameLocation(node); 625 } 626 if (isSourceFile(node)) { 627 const resolvedRef = GoToDefinition.getReferenceAtPosition(node, position, program); 628 if (!resolvedRef) { 629 return undefined; 630 } 631 const moduleSymbol = program.getTypeChecker().getMergedSymbol(resolvedRef.file.symbol); 632 if (moduleSymbol) { 633 return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); 634 } 635 const fileIncludeReasons = program.getFileIncludeReasons(); 636 if (!fileIncludeReasons) { 637 return undefined; 638 } 639 return [{ 640 definition: { type: DefinitionKind.TripleSlashReference, reference: resolvedRef.reference, file: node }, 641 references: getReferencesForNonModule(resolvedRef.file, fileIncludeReasons, program) || emptyArray 642 }]; 643 } 644 645 if (!options.implementations) { 646 const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken); 647 if (special) { 648 return special; 649 } 650 } 651 652 const checker = program.getTypeChecker(); 653 // constructors should use the class symbol, detected by name, if present 654 const symbol = checker.getSymbolAtLocation(isConstructorDeclaration(node) && node.parent.name || node); 655 656 // Could not find a symbol e.g. unknown identifier 657 if (!symbol) { 658 // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. 659 if (!options.implementations && isStringLiteralLike(node)) { 660 if (isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ true) || isExternalModuleReference(node.parent) || isImportDeclaration(node.parent) || isImportCall(node.parent)) { 661 const fileIncludeReasons = program.getFileIncludeReasons(); 662 const referencedFileName = node.getSourceFile().resolvedModules?.get(node.text)?.resolvedFileName; 663 const referencedFile = referencedFileName ? program.getSourceFile(referencedFileName) : undefined; 664 if (referencedFile) { 665 return [{ definition: { type: DefinitionKind.String, node }, references: getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray }]; 666 } 667 // Fall through to string literal references. This is not very likely to return 668 // anything useful, but I guess it's better than nothing, and there's an existing 669 // test that expects this to happen (fourslash/cases/untypedModuleImport.ts). 670 } 671 return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken); 672 } 673 return undefined; 674 } 675 676 if (symbol.escapedName === InternalSymbolName.ExportEquals) { 677 return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); 678 } 679 680 const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); 681 if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) { 682 return moduleReferences; 683 } 684 685 const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker); 686 const moduleReferencesOfExportTarget = aliasedSymbol && 687 getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); 688 689 const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); 690 return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); 691 } 692 693 export function getReferencesForFileName(fileName: string, program: Program, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string> = new Set(sourceFiles.map(f => f.fileName))): readonly Entry[] { 694 const moduleSymbol = program.getSourceFile(fileName)?.symbol; 695 if (moduleSymbol) { 696 return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet)[0]?.references || emptyArray; 697 } 698 const fileIncludeReasons = program.getFileIncludeReasons(); 699 const referencedFile = program.getSourceFile(fileName); 700 return referencedFile && fileIncludeReasons && getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray; 701 } 702 703 function getReferencesForNonModule(referencedFile: SourceFile, refFileMap: MultiMap<Path, FileIncludeReason>, program: Program): readonly SpanEntry[] | undefined { 704 let entries: SpanEntry[] | undefined; 705 const references = refFileMap.get(referencedFile.path) || emptyArray; 706 for (const ref of references) { 707 if (isReferencedFile(ref)) { 708 const referencingFile = program.getSourceFileByPath(ref.file)!; 709 const location = getReferencedFileLocation(program.getSourceFileByPath, ref); 710 if (isReferenceFileLocation(location)) { 711 entries = append(entries, { 712 kind: EntryKind.Span, 713 fileName: referencingFile.fileName, 714 textSpan: createTextSpanFromRange(location) 715 }); 716 } 717 } 718 } 719 return entries; 720 } 721 722 function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) { 723 if (node.parent && isNamespaceExportDeclaration(node.parent)) { 724 const aliasedSymbol = checker.getAliasedSymbol(symbol); 725 const targetSymbol = checker.getMergedSymbol(aliasedSymbol); 726 if (aliasedSymbol !== targetSymbol) { 727 return targetSymbol; 728 } 729 } 730 return undefined; 731 } 732 733 function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet<string>) { 734 const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); 735 if (!moduleSourceFile) return undefined; 736 const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); 737 // If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them. 738 const moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet); 739 if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences; 740 // Continue to get references to 'export ='. 741 const checker = program.getTypeChecker(); 742 symbol = skipAlias(exportEquals, checker); 743 return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options)); 744 } 745 746 /** 747 * Merges the references by sorting them (by file index in sourceFiles and their location in it) that point to same definition symbol 748 */ 749 function mergeReferences(program: Program, ...referencesToMerge: (SymbolAndEntries[] | undefined)[]): SymbolAndEntries[] | undefined { 750 let result: SymbolAndEntries[] | undefined; 751 for (const references of referencesToMerge) { 752 if (!references || !references.length) continue; 753 if (!result) { 754 result = references; 755 continue; 756 } 757 for (const entry of references) { 758 if (!entry.definition || entry.definition.type !== DefinitionKind.Symbol) { 759 result.push(entry); 760 continue; 761 } 762 const symbol = entry.definition.symbol; 763 const refIndex = findIndex(result, ref => !!ref.definition && 764 ref.definition.type === DefinitionKind.Symbol && 765 ref.definition.symbol === symbol); 766 if (refIndex === -1) { 767 result.push(entry); 768 continue; 769 } 770 771 const reference = result[refIndex]; 772 result[refIndex] = { 773 definition: reference.definition, 774 references: reference.references.concat(entry.references).sort((entry1, entry2) => { 775 const entry1File = getSourceFileIndexOfEntry(program, entry1); 776 const entry2File = getSourceFileIndexOfEntry(program, entry2); 777 if (entry1File !== entry2File) { 778 return compareValues(entry1File, entry2File); 779 } 780 781 const entry1Span = getTextSpanOfEntry(entry1); 782 const entry2Span = getTextSpanOfEntry(entry2); 783 return entry1Span.start !== entry2Span.start ? 784 compareValues(entry1Span.start, entry2Span.start) : 785 compareValues(entry1Span.length, entry2Span.length); 786 }) 787 }; 788 } 789 } 790 return result; 791 } 792 793 function getSourceFileIndexOfEntry(program: Program, entry: Entry) { 794 const sourceFile = entry.kind === EntryKind.Span ? 795 program.getSourceFile(entry.fileName)! : 796 entry.node.getSourceFile(); 797 return program.getSourceFiles().indexOf(sourceFile); 798 } 799 800 function getReferencedSymbolsForModule(program: Program, symbol: Symbol, excludeImportTypeOfExportEquals: boolean, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string>): SymbolAndEntries[] { 801 Debug.assert(!!symbol.valueDeclaration); 802 803 const references = mapDefined<ModuleReference, Entry>(findModuleReferences(program, sourceFiles, symbol), reference => { 804 if (reference.kind === "import") { 805 const parent = reference.literal.parent; 806 if (isLiteralTypeNode(parent)) { 807 const importType = cast(parent.parent, isImportTypeNode); 808 if (excludeImportTypeOfExportEquals && !importType.qualifier) { 809 return undefined; 810 } 811 } 812 // import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway. 813 return nodeEntry(reference.literal); 814 } 815 else { 816 return { 817 kind: EntryKind.Span, 818 fileName: reference.referencingFile.fileName, 819 textSpan: createTextSpanFromRange(reference.ref), 820 }; 821 } 822 }); 823 824 if (symbol.declarations) { 825 for (const decl of symbol.declarations) { 826 switch (decl.kind) { 827 case SyntaxKind.SourceFile: 828 // Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.) 829 break; 830 case SyntaxKind.ModuleDeclaration: 831 if (sourceFilesSet.has(decl.getSourceFile().fileName)) { 832 references.push(nodeEntry((decl as ModuleDeclaration).name)); 833 } 834 break; 835 default: 836 // This may be merged with something. 837 Debug.assert(!!(symbol.flags & SymbolFlags.Transient), "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration."); 838 } 839 } 840 } 841 842 const exported = symbol.exports!.get(InternalSymbolName.ExportEquals); 843 if (exported) { 844 for (const decl of exported.declarations) { 845 const sourceFile = decl.getSourceFile(); 846 if (sourceFilesSet.has(sourceFile.fileName)) { 847 // At `module.exports = ...`, reference node is `module` 848 const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left) ? decl.left.expression : 849 isExportAssignment(decl) ? Debug.checkDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile)) : 850 getNameOfDeclaration(decl) || decl; 851 references.push(nodeEntry(node)); 852 } 853 } 854 } 855 856 return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray; 857 } 858 859 /** As in a `readonly prop: any` or `constructor(readonly prop: any)`, not a `readonly any[]`. */ 860 function isReadonlyTypeOperator(node: Node): boolean { 861 return node.kind === SyntaxKind.ReadonlyKeyword 862 && isTypeOperatorNode(node.parent) 863 && node.parent.operator === SyntaxKind.ReadonlyKeyword; 864 } 865 866 /** getReferencedSymbols for special node kinds. */ 867 function getReferencedSymbolsSpecial(node: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { 868 if (isTypeKeyword(node.kind)) { 869 // A void expression (i.e., `void foo()`) is not special, but the `void` type is. 870 if (node.kind === SyntaxKind.VoidKeyword && isVoidExpression(node.parent)) { 871 return undefined; 872 } 873 874 // A modifier readonly (like on a property declaration) is not special; 875 // a readonly type keyword (like `readonly string[]`) is. 876 if (node.kind === SyntaxKind.ReadonlyKeyword && !isReadonlyTypeOperator(node)) { 877 return undefined; 878 } 879 // Likewise, when we *are* looking for a special keyword, make sure we 880 // *don’t* include readonly member modifiers. 881 return getAllReferencesForKeyword( 882 sourceFiles, 883 node.kind, 884 cancellationToken, 885 node.kind === SyntaxKind.ReadonlyKeyword ? isReadonlyTypeOperator : undefined); 886 } 887 888 // Labels 889 if (isJumpStatementTarget(node)) { 890 const labelDefinition = getTargetLabel(node.parent, node.text); 891 // if we have a label definition, look within its statement for references, if not, then 892 // the label is undefined and we have no results.. 893 return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition); 894 } 895 else if (isLabelOfLabeledStatement(node)) { 896 // it is a label definition and not a target, search within the parent labeledStatement 897 return getLabelReferencesInNode(node.parent, node); 898 } 899 900 if (isThis(node)) { 901 return getReferencesForThisKeyword(node, sourceFiles, cancellationToken); 902 } 903 904 if (node.kind === SyntaxKind.SuperKeyword) { 905 return getReferencesForSuperKeyword(node); 906 } 907 908 return undefined; 909 } 910 911 /** Core find-all-references algorithm for a normal symbol. */ 912 function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string>, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { 913 const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol; 914 915 // Compute the meaning from the location and the symbol it references 916 const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All; 917 const result: SymbolAndEntries[] = []; 918 const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); 919 920 const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) ? undefined : find(symbol.declarations, isExportSpecifier); 921 if (exportSpecifier) { 922 // When renaming at an export specifier, rename the export and not the thing being exported. 923 getReferencesAtExportSpecifier(exportSpecifier.name, symbol, exportSpecifier, state.createSearch(node, originalSymbol, /*comingFrom*/ undefined), state, /*addReferencesHere*/ true, /*alwaysGetReferences*/ true); 924 } 925 else if (node && node.kind === SyntaxKind.DefaultKeyword) { 926 addReference(node, symbol, state); 927 searchForImportsOfExport(node, symbol, { exportingModuleSymbol: Debug.checkDefined(symbol.parent, "Expected export symbol to have a parent"), exportKind: ExportKind.Default }, state); 928 } 929 else { 930 const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, options.use === FindReferencesUse.Rename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] }); 931 getReferencesInContainerOrFiles(symbol, state, search); 932 } 933 934 return result; 935 } 936 937 function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void { 938 // Try to get the smallest valid scope that we can limit our search to; 939 // otherwise we'll need to search globally (i.e. include each file). 940 const scope = getSymbolScope(symbol); 941 if (scope) { 942 getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope))); 943 } 944 else { 945 // Global search 946 for (const sourceFile of state.sourceFiles) { 947 state.cancellationToken.throwIfCancellationRequested(); 948 searchForName(sourceFile, search, state); 949 } 950 } 951 } 952 953 function getSpecialSearchKind(node: Node): SpecialSearchKind { 954 switch (node.kind) { 955 case SyntaxKind.Constructor: 956 case SyntaxKind.ConstructorKeyword: 957 return SpecialSearchKind.Constructor; 958 case SyntaxKind.Identifier: 959 if (isClassLike(node.parent)) { 960 Debug.assert(node.parent.name === node); 961 return SpecialSearchKind.Class; 962 } 963 // falls through 964 default: 965 return SpecialSearchKind.None; 966 } 967 } 968 969 /** Handle a few special cases relating to export/import specifiers. */ 970 function skipPastExportOrImportSpecifierOrUnion(symbol: Symbol, node: Node, checker: TypeChecker, useLocalSymbolForExportSpecifier: boolean): Symbol | undefined { 971 const { parent } = node; 972 if (isExportSpecifier(parent) && useLocalSymbolForExportSpecifier) { 973 return getLocalSymbolForExportSpecifier(node as Identifier, symbol, parent, checker); 974 } 975 // 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. 976 return firstDefined(symbol.declarations, decl => { 977 if (!decl.parent) { 978 // Ignore UMD module and global merge 979 if (symbol.flags & SymbolFlags.Transient) return undefined; 980 // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. 981 Debug.fail(`Unexpected symbol at ${Debug.formatSyntaxKind(node.kind)}: ${Debug.formatSymbol(symbol)}`); 982 } 983 return isTypeLiteralNode(decl.parent) && isUnionTypeNode(decl.parent.parent) 984 ? checker.getPropertyOfType(checker.getTypeFromTypeNode(decl.parent.parent), symbol.name) 985 : undefined; 986 }); 987 } 988 989 /** 990 * Symbol that is currently being searched for. 991 * This will be replaced if we find an alias for the symbol. 992 */ 993 interface Search { 994 /** If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). */ 995 readonly comingFrom?: ImportExport; 996 997 readonly symbol: Symbol; 998 readonly text: string; 999 readonly escapedText: __String; 1000 /** Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. */ 1001 readonly parents: readonly Symbol[] | undefined; 1002 readonly allSearchSymbols: readonly Symbol[]; 1003 1004 /** 1005 * Whether a symbol is in the search set. 1006 * Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. 1007 */ 1008 includes(symbol: Symbol): boolean; 1009 } 1010 1011 const enum SpecialSearchKind { 1012 None, 1013 Constructor, 1014 Class, 1015 } 1016 1017 function getNonModuleSymbolOfMergedModuleSymbol(symbol: Symbol) { 1018 if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Transient))) return undefined; 1019 const decl = symbol.declarations && find(symbol.declarations, d => !isSourceFile(d) && !isModuleDeclaration(d)); 1020 return decl && decl.symbol; 1021 } 1022 1023 /** 1024 * Holds all state needed for the finding references. 1025 * Unlike `Search`, there is only one `State`. 1026 */ 1027 class State { 1028 /** Cache for `explicitlyinheritsFrom`. */ 1029 readonly inheritsFromCache = new Map<string, boolean>(); 1030 1031 /** 1032 * Type nodes can contain multiple references to the same type. For example: 1033 * let x: Foo & (Foo & Bar) = ... 1034 * Because we are returning the implementation locations and not the identifier locations, 1035 * duplicate entries would be returned here as each of the type references is part of 1036 * the same implementation. For that reason, check before we add a new entry. 1037 */ 1038 readonly markSeenContainingTypeReference = nodeSeenTracker(); 1039 1040 /** 1041 * It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once. 1042 * For example: 1043 * // b.ts 1044 * export { foo as bar } from "./a"; 1045 * import { bar } from "./b"; 1046 * 1047 * Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local). 1048 * But another reference to it may appear in the same source file. 1049 * See `tests/cases/fourslash/transitiveExportImports3.ts`. 1050 */ 1051 readonly markSeenReExportRHS = nodeSeenTracker(); 1052 1053 constructor( 1054 readonly sourceFiles: readonly SourceFile[], 1055 readonly sourceFilesSet: ReadonlySet<string>, 1056 readonly specialSearchKind: SpecialSearchKind, 1057 readonly checker: TypeChecker, 1058 readonly cancellationToken: CancellationToken, 1059 readonly searchMeaning: SemanticMeaning, 1060 readonly options: Options, 1061 private readonly result: Push<SymbolAndEntries>) { 1062 } 1063 1064 includesSourceFile(sourceFile: SourceFile): boolean { 1065 return this.sourceFilesSet.has(sourceFile.fileName); 1066 } 1067 1068 private importTracker: ImportTracker | undefined; 1069 /** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */ 1070 getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { 1071 if (!this.importTracker) this.importTracker = createImportTracker(this.sourceFiles, this.sourceFilesSet, this.checker, this.cancellationToken); 1072 return this.importTracker(exportSymbol, exportInfo, this.options.use === FindReferencesUse.Rename); 1073 } 1074 1075 /** @param allSearchSymbols set of additional symbols for use by `includes`. */ 1076 createSearch(location: Node | undefined, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search { 1077 // Note: if this is an external module symbol, the name doesn't include quotes. 1078 // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. 1079 // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form 1080 // here appears to be intentional). 1081 const { 1082 text = stripQuotes(symbolName(getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol)), 1083 allSearchSymbols = [symbol], 1084 } = searchOptions; 1085 const escapedText = escapeLeadingUnderscores(text); 1086 const parents = this.options.implementations && location ? getParentSymbolsOfPropertyAccess(location, symbol, this.checker) : undefined; 1087 return { symbol, comingFrom, text, escapedText, parents, allSearchSymbols, includes: sym => contains(allSearchSymbols, sym) }; 1088 } 1089 1090 private readonly symbolIdToReferences: Entry[][] = []; 1091 /** 1092 * Callback to add references for a particular searched symbol. 1093 * This initializes a reference group, so only call this if you will add at least one reference. 1094 */ 1095 referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void { 1096 const symbolId = getSymbolId(searchSymbol); 1097 let references = this.symbolIdToReferences[symbolId]; 1098 if (!references) { 1099 references = this.symbolIdToReferences[symbolId] = []; 1100 this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references }); 1101 } 1102 return (node, kind) => references.push(nodeEntry(node, kind)); 1103 } 1104 1105 /** Add a reference with no associated definition. */ 1106 addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { 1107 this.result.push({ 1108 definition: undefined, 1109 references: [{ kind: EntryKind.Span, fileName, textSpan }] 1110 }); 1111 } 1112 1113 // Source file ID → symbol ID → Whether the symbol has been searched for in the source file. 1114 private readonly sourceFileToSeenSymbols: Set<number>[] = []; 1115 /** Returns `true` the first time we search for a symbol in a file and `false` afterwards. */ 1116 markSearchedSymbols(sourceFile: SourceFile, symbols: readonly Symbol[]): boolean { 1117 const sourceId = getNodeId(sourceFile); 1118 const seenSymbols = this.sourceFileToSeenSymbols[sourceId] || (this.sourceFileToSeenSymbols[sourceId] = new Set<number>()); 1119 1120 let anyNewSymbols = false; 1121 for (const sym of symbols) { 1122 anyNewSymbols = tryAddToSet(seenSymbols, getSymbolId(sym)) || anyNewSymbols; 1123 } 1124 return anyNewSymbols; 1125 } 1126 } 1127 1128 /** Search for all imports of a given exported symbol using `State.getImportSearches`. */ 1129 function searchForImportsOfExport(exportLocation: Node, exportSymbol: Symbol, exportInfo: ExportInfo, state: State): void { 1130 const { importSearches, singleReferences, indirectUsers } = state.getImportSearches(exportSymbol, exportInfo); 1131 1132 // For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file. 1133 if (singleReferences.length) { 1134 const addRef = state.referenceAdder(exportSymbol); 1135 for (const singleRef of singleReferences) { 1136 if (shouldAddSingleReference(singleRef, state)) addRef(singleRef); 1137 } 1138 } 1139 1140 // For each import, find all references to that import in its source file. 1141 for (const [importLocation, importSymbol] of importSearches) { 1142 getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); 1143 } 1144 1145 if (indirectUsers.length) { 1146 let indirectSearch: Search | undefined; 1147 switch (exportInfo.exportKind) { 1148 case ExportKind.Named: 1149 indirectSearch = state.createSearch(exportLocation, exportSymbol, ImportExport.Export); 1150 break; 1151 case ExportKind.Default: 1152 // Search for a property access to '.default'. This can't be renamed. 1153 indirectSearch = state.options.use === FindReferencesUse.Rename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); 1154 break; 1155 case ExportKind.ExportEquals: 1156 break; 1157 } 1158 if (indirectSearch) { 1159 for (const indirectUser of indirectUsers) { 1160 searchForName(indirectUser, indirectSearch, state); 1161 } 1162 } 1163 } 1164 } 1165 1166 export function eachExportReference( 1167 sourceFiles: readonly SourceFile[], 1168 checker: TypeChecker, 1169 cancellationToken: CancellationToken | undefined, 1170 exportSymbol: Symbol, 1171 exportingModuleSymbol: Symbol, 1172 exportName: string, 1173 isDefaultExport: boolean, 1174 cb: (ref: Identifier) => void, 1175 ): void { 1176 const importTracker = createImportTracker(sourceFiles, new Set(sourceFiles.map(f => f.fileName)), checker, cancellationToken); 1177 const { importSearches, indirectUsers } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false); 1178 for (const [importLocation] of importSearches) { 1179 cb(importLocation); 1180 } 1181 for (const indirectUser of indirectUsers) { 1182 for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) { 1183 // Import specifiers should be handled by importSearches 1184 if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && checker.getSymbolAtLocation(node) === exportSymbol) { 1185 cb(node); 1186 } 1187 } 1188 } 1189 } 1190 1191 function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean { 1192 if (!hasMatchingMeaning(singleRef, state)) return false; 1193 if (state.options.use !== FindReferencesUse.Rename) return true; 1194 // Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;` 1195 if (!isIdentifier(singleRef)) return false; 1196 // At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename. 1197 return !(isImportOrExportSpecifier(singleRef.parent) && singleRef.escapedText === InternalSymbolName.Default); 1198 } 1199 1200 // Go to the symbol we imported from and find references for it. 1201 function searchForImportedSymbol(symbol: Symbol, state: State): void { 1202 if (!symbol.declarations) return; 1203 1204 for (const declaration of symbol.declarations) { 1205 const exportingFile = declaration.getSourceFile(); 1206 // Need to search in the file even if it's not in the search-file set, because it might export the symbol. 1207 getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state, state.includesSourceFile(exportingFile)); 1208 } 1209 } 1210 1211 /** Search for all occurences of an identifier in a source file (and filter out the ones that match). */ 1212 function searchForName(sourceFile: SourceFile, search: Search, state: State): void { 1213 if (getNameTable(sourceFile).get(search.escapedText) !== undefined) { 1214 getReferencesInSourceFile(sourceFile, search, state); 1215 } 1216 } 1217 1218 function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { 1219 return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) 1220 ? checker.getPropertySymbolOfDestructuringAssignment(<Identifier>location) 1221 : undefined; 1222 } 1223 1224 /** 1225 * Determines the smallest scope in which a symbol may have named references. 1226 * Note that not every construct has been accounted for. This function can 1227 * probably be improved. 1228 * 1229 * @returns undefined if the scope cannot be determined, implying that 1230 * a reference to a symbol can occur anywhere. 1231 */ 1232 function getSymbolScope(symbol: Symbol): Node | undefined { 1233 // If this is the symbol of a named function expression or named class expression, 1234 // then named references are limited to its own scope. 1235 const { declarations, flags, parent, valueDeclaration } = symbol; 1236 if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) { 1237 return valueDeclaration; 1238 } 1239 1240 if (!declarations) { 1241 return undefined; 1242 } 1243 1244 // If this is private property or method, the scope is the containing class 1245 if (flags & (SymbolFlags.Property | SymbolFlags.Method)) { 1246 const privateDeclaration = find(declarations, d => hasEffectiveModifier(d, ModifierFlags.Private) || isPrivateIdentifierPropertyDeclaration(d)); 1247 if (privateDeclaration) { 1248 return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration); 1249 } 1250 // Else this is a public property and could be accessed from anywhere. 1251 return undefined; 1252 } 1253 1254 // If symbol is of object binding pattern element without property name we would want to 1255 // look for property too and that could be anywhere 1256 if (declarations.some(isObjectBindingElementWithoutPropertyName)) { 1257 return undefined; 1258 } 1259 1260 /* 1261 If the symbol has a parent, it's globally visible unless: 1262 - It's a private property (handled above). 1263 - It's a type parameter. 1264 - The parent is an external module: then we should only search in the module (and recurse on the export later). 1265 - But if the parent has `export as namespace`, the symbol is globally visible through that namespace. 1266 */ 1267 const exposedByParent = parent && !(symbol.flags & SymbolFlags.TypeParameter); 1268 if (exposedByParent && !(isExternalModuleSymbol(parent!) && !parent!.globalExports)) { 1269 return undefined; 1270 } 1271 1272 let scope: Node | undefined; 1273 for (const declaration of declarations) { 1274 const container = getContainerNode(declaration); 1275 if (scope && scope !== container) { 1276 // Different declarations have different containers, bail out 1277 return undefined; 1278 } 1279 1280 if (!container || container.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(<SourceFile>container)) { 1281 // This is a global variable and not an external module, any declaration defined 1282 // within this scope is visible outside the file 1283 return undefined; 1284 } 1285 1286 scope = container; 1287 if (isFunctionExpression(scope)) { 1288 let next: Node | undefined; 1289 while (next = getNextJSDocCommentLocation(scope)) { 1290 scope = next; 1291 } 1292 } 1293 } 1294 1295 // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) 1296 // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: 1297 // declare module "a" { export type T = number; } 1298 // declare module "b" { import { T } from "a"; export const x: T; } 1299 // 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.) 1300 return exposedByParent ? scope!.getSourceFile() : scope; // TODO: GH#18217 1301 } 1302 1303 /** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */ 1304 export function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, searchContainer: Node = sourceFile): boolean { 1305 return eachSymbolReferenceInFile(definition, checker, sourceFile, () => true, searchContainer) || false; 1306 } 1307 1308 export function eachSymbolReferenceInFile<T>(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T, searchContainer: Node = sourceFile): T | undefined { 1309 const symbol = isParameterPropertyDeclaration(definition.parent, definition.parent.parent) 1310 ? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text)) 1311 : checker.getSymbolAtLocation(definition); 1312 if (!symbol) return undefined; 1313 for (const token of getPossibleSymbolReferenceNodes(sourceFile, symbol.name, searchContainer)) { 1314 if (!isIdentifier(token) || token === definition || token.escapedText !== definition.escapedText) continue; 1315 const referenceSymbol: Symbol = checker.getSymbolAtLocation(token)!; // See GH#19955 for why the type annotation is necessary 1316 if (referenceSymbol === symbol 1317 || checker.getShorthandAssignmentValueSymbol(token.parent) === symbol 1318 || isExportSpecifier(token.parent) && getLocalSymbolForExportSpecifier(token, referenceSymbol, token.parent, checker) === symbol) { 1319 const res = cb(token); 1320 if (res) return res; 1321 } 1322 } 1323 } 1324 1325 export function someSignatureUsage( 1326 signature: SignatureDeclaration, 1327 sourceFiles: readonly SourceFile[], 1328 checker: TypeChecker, 1329 cb: (name: Identifier, call?: CallExpression) => boolean 1330 ): boolean { 1331 if (!signature.name || !isIdentifier(signature.name)) return false; 1332 1333 const symbol = Debug.checkDefined(checker.getSymbolAtLocation(signature.name)); 1334 1335 for (const sourceFile of sourceFiles) { 1336 for (const name of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) { 1337 if (!isIdentifier(name) || name === signature.name || name.escapedText !== signature.name.escapedText) continue; 1338 const called = climbPastPropertyAccess(name); 1339 const call = isCallExpression(called.parent) && called.parent.expression === called ? called.parent : undefined; 1340 const referenceSymbol = checker.getSymbolAtLocation(name); 1341 if (referenceSymbol && checker.getRootSymbols(referenceSymbol).some(s => s === symbol)) { 1342 if (cb(name, call)) { 1343 return true; 1344 } 1345 } 1346 } 1347 } 1348 return false; 1349 } 1350 1351 function getPossibleSymbolReferenceNodes(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly Node[] { 1352 return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos)); 1353 } 1354 1355 function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly number[] { 1356 const positions: number[] = []; 1357 1358 /// TODO: Cache symbol existence for files to save text search 1359 // Also, need to make this work for unicode escapes. 1360 1361 // Be resilient in the face of a symbol with no name or zero length name 1362 if (!symbolName || !symbolName.length) { 1363 return positions; 1364 } 1365 1366 const text = sourceFile.text; 1367 const sourceLength = text.length; 1368 const symbolNameLength = symbolName.length; 1369 1370 let position = text.indexOf(symbolName, container.pos); 1371 while (position >= 0) { 1372 // If we are past the end, stop looking 1373 if (position > container.end) break; 1374 1375 // We found a match. Make sure it's not part of a larger word (i.e. the char 1376 // before and after it have to be a non-identifier char). 1377 const endPosition = position + symbolNameLength; 1378 1379 if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && 1380 (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { 1381 // Found a real match. Keep searching. 1382 positions.push(position); 1383 } 1384 position = text.indexOf(symbolName, position + symbolNameLength + 1); 1385 } 1386 1387 return positions; 1388 } 1389 1390 function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] { 1391 const sourceFile = container.getSourceFile(); 1392 const labelName = targetLabel.text; 1393 const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), node => 1394 // Only pick labels that are either the target label, or have a target that is the target label 1395 node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel) ? nodeEntry(node) : undefined); 1396 return [{ definition: { type: DefinitionKind.Label, node: targetLabel }, references }]; 1397 } 1398 1399 function isValidReferencePosition(node: Node, searchSymbolName: string): boolean { 1400 // Compare the length so we filter out strict superstrings of the symbol we are looking for 1401 switch (node.kind) { 1402 case SyntaxKind.PrivateIdentifier: 1403 case SyntaxKind.Identifier: 1404 return (node as PrivateIdentifier | Identifier).text.length === searchSymbolName.length; 1405 case SyntaxKind.NoSubstitutionTemplateLiteral: 1406 case SyntaxKind.StringLiteral: { 1407 const str = node as StringLiteralLike; 1408 return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) && 1409 str.text.length === searchSymbolName.length; 1410 } 1411 1412 case SyntaxKind.NumericLiteral: 1413 return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral) && (node as NumericLiteral).text.length === searchSymbolName.length; 1414 1415 case SyntaxKind.DefaultKeyword: 1416 return "default".length === searchSymbolName.length; 1417 1418 default: 1419 return false; 1420 } 1421 } 1422 1423 function getAllReferencesForKeyword(sourceFiles: readonly SourceFile[], keywordKind: SyntaxKind, cancellationToken: CancellationToken, filter?: (node: Node) => boolean): SymbolAndEntries[] | undefined { 1424 const references = flatMap(sourceFiles, sourceFile => { 1425 cancellationToken.throwIfCancellationRequested(); 1426 return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, tokenToString(keywordKind)!, sourceFile), referenceLocation => { 1427 if (referenceLocation.kind === keywordKind && (!filter || filter(referenceLocation))) { 1428 return nodeEntry(referenceLocation); 1429 } 1430 }); 1431 }); 1432 return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined; 1433 } 1434 1435 function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State, addReferencesHere = true): void { 1436 state.cancellationToken.throwIfCancellationRequested(); 1437 return getReferencesInContainer(sourceFile, sourceFile, search, state, addReferencesHere); 1438 } 1439 1440 /** 1441 * Search within node "container" for references for a search value, where the search value is defined as a 1442 * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). 1443 * searchLocation: a node where the search value 1444 */ 1445 function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State, addReferencesHere: boolean): void { 1446 if (!state.markSearchedSymbols(sourceFile, search.allSearchSymbols)) { 1447 return; 1448 } 1449 1450 for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container)) { 1451 getReferencesAtLocation(sourceFile, position, search, state, addReferencesHere); 1452 } 1453 } 1454 1455 function hasMatchingMeaning(referenceLocation: Node, state: State): boolean { 1456 return !!(getMeaningFromLocation(referenceLocation) & state.searchMeaning); 1457 } 1458 1459 function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void { 1460 const referenceLocation = getTouchingPropertyName(sourceFile, position); 1461 1462 if (!isValidReferencePosition(referenceLocation, search.text)) { 1463 // This wasn't the start of a token. Check to see if it might be a 1464 // match in a comment or string if that's what the caller is asking 1465 // for. 1466 if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) { 1467 // In the case where we're looking inside comments/strings, we don't have 1468 // an actual definition. So just use 'undefined' here. Features like 1469 // 'Rename' won't care (as they ignore the definitions), and features like 1470 // 'FindReferences' will just filter out these results. 1471 state.addStringOrCommentReference(sourceFile.fileName, createTextSpan(position, search.text.length)); 1472 } 1473 1474 return; 1475 } 1476 1477 if (!hasMatchingMeaning(referenceLocation, state)) return; 1478 1479 const referenceSymbol = state.checker.getSymbolAtLocation(referenceLocation); 1480 if (!referenceSymbol) { 1481 return; 1482 } 1483 1484 const parent = referenceLocation.parent; 1485 if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) { 1486 // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. 1487 return; 1488 } 1489 1490 if (isExportSpecifier(parent)) { 1491 Debug.assert(referenceLocation.kind === SyntaxKind.Identifier); 1492 getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state, addReferencesHere); 1493 return; 1494 } 1495 1496 const relatedSymbol = getRelatedSymbol(search, referenceSymbol, referenceLocation, state); 1497 if (!relatedSymbol) { 1498 getReferenceForShorthandProperty(referenceSymbol, search, state); 1499 return; 1500 } 1501 1502 switch (state.specialSearchKind) { 1503 case SpecialSearchKind.None: 1504 if (addReferencesHere) addReference(referenceLocation, relatedSymbol, state); 1505 break; 1506 case SpecialSearchKind.Constructor: 1507 addConstructorReferences(referenceLocation, sourceFile, search, state); 1508 break; 1509 case SpecialSearchKind.Class: 1510 addClassStaticThisReferences(referenceLocation, search, state); 1511 break; 1512 default: 1513 Debug.assertNever(state.specialSearchKind); 1514 } 1515 1516 getImportOrExportReferences(referenceLocation, referenceSymbol, search, state); 1517 } 1518 1519 function getReferencesAtExportSpecifier( 1520 referenceLocation: Identifier, 1521 referenceSymbol: Symbol, 1522 exportSpecifier: ExportSpecifier, 1523 search: Search, 1524 state: State, 1525 addReferencesHere: boolean, 1526 alwaysGetReferences?: boolean, 1527 ): void { 1528 Debug.assert(!alwaysGetReferences || !!state.options.providePrefixAndSuffixTextForRename, "If alwaysGetReferences is true, then prefix/suffix text must be enabled"); 1529 1530 const { parent, propertyName, name } = exportSpecifier; 1531 const exportDeclaration = parent.parent; 1532 const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker); 1533 if (!alwaysGetReferences && !search.includes(localSymbol)) { 1534 return; 1535 } 1536 1537 if (!propertyName) { 1538 // Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export) 1539 if (!(state.options.use === FindReferencesUse.Rename && (name.escapedText === InternalSymbolName.Default))) { 1540 addRef(); 1541 } 1542 } 1543 else if (referenceLocation === propertyName) { 1544 // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export. 1545 // For `export { foo as bar };`, where `foo` is a local, so add it now. 1546 if (!exportDeclaration.moduleSpecifier) { 1547 addRef(); 1548 } 1549 1550 if (addReferencesHere && state.options.use !== FindReferencesUse.Rename && state.markSeenReExportRHS(name)) { 1551 addReference(name, Debug.checkDefined(exportSpecifier.symbol), state); 1552 } 1553 } 1554 else { 1555 if (state.markSeenReExportRHS(referenceLocation)) { 1556 addRef(); 1557 } 1558 } 1559 1560 // For `export { foo as bar }`, rename `foo`, but not `bar`. 1561 if (!isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences) { 1562 const isDefaultExport = referenceLocation.originalKeywordKind === SyntaxKind.DefaultKeyword 1563 || exportSpecifier.name.originalKeywordKind === SyntaxKind.DefaultKeyword; 1564 const exportKind = isDefaultExport ? ExportKind.Default : ExportKind.Named; 1565 const exportSymbol = Debug.checkDefined(exportSpecifier.symbol); 1566 const exportInfo = getExportInfo(exportSymbol, exportKind, state.checker); 1567 if (exportInfo) { 1568 searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo, state); 1569 } 1570 } 1571 1572 // At `export { x } from "foo"`, also search for the imported symbol `"foo".x`. 1573 if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName && !isForRenameWithPrefixAndSuffixText(state.options)) { 1574 const imported = state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); 1575 if (imported) searchForImportedSymbol(imported, state); 1576 } 1577 1578 function addRef() { 1579 if (addReferencesHere) addReference(referenceLocation, localSymbol, state); 1580 } 1581 } 1582 1583 function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol { 1584 return isExportSpecifierAlias(referenceLocation, exportSpecifier) && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier) || referenceSymbol; 1585 } 1586 1587 function isExportSpecifierAlias(referenceLocation: Identifier, exportSpecifier: ExportSpecifier): boolean { 1588 const { parent, propertyName, name } = exportSpecifier; 1589 Debug.assert(propertyName === referenceLocation || name === referenceLocation); 1590 if (propertyName) { 1591 // Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol. 1592 return propertyName === referenceLocation; 1593 } 1594 else { 1595 // `export { foo } from "foo"` is a re-export. 1596 // `export { foo };` is not a re-export, it creates an alias for the local variable `foo`. 1597 return !parent.parent.moduleSpecifier; 1598 } 1599 } 1600 1601 function getImportOrExportReferences(referenceLocation: Node, referenceSymbol: Symbol, search: Search, state: State): void { 1602 const importOrExport = getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom === ImportExport.Export); 1603 if (!importOrExport) return; 1604 1605 const { symbol } = importOrExport; 1606 1607 if (importOrExport.kind === ImportExport.Import) { 1608 if (!(isForRenameWithPrefixAndSuffixText(state.options))) { 1609 searchForImportedSymbol(symbol, state); 1610 } 1611 } 1612 else { 1613 searchForImportsOfExport(referenceLocation, symbol, importOrExport.exportInfo, state); 1614 } 1615 } 1616 1617 function getReferenceForShorthandProperty({ flags, valueDeclaration }: Symbol, search: Search, state: State): void { 1618 const shorthandValueSymbol = state.checker.getShorthandAssignmentValueSymbol(valueDeclaration)!; 1619 const name = valueDeclaration && getNameOfDeclaration(valueDeclaration); 1620 /* 1621 * Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment 1622 * has two meanings: property name and property value. Therefore when we do findAllReference at the position where 1623 * an identifier is declared, the language service should return the position of the variable declaration as well as 1624 * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the 1625 * position of property accessing, the referenceEntry of such position will be handled in the first case. 1626 */ 1627 if (!(flags & SymbolFlags.Transient) && name && search.includes(shorthandValueSymbol)) { 1628 addReference(name, shorthandValueSymbol, state); 1629 } 1630 } 1631 1632 function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void { 1633 const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line no-in-operator 1634 const addRef = state.referenceAdder(symbol); 1635 if (state.options.implementations) { 1636 addImplementationReferences(referenceLocation, addRef, state); 1637 } 1638 else { 1639 addRef(referenceLocation, kind); 1640 } 1641 } 1642 1643 /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ 1644 function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { 1645 if (isNewExpressionTarget(referenceLocation)) { 1646 addReference(referenceLocation, search.symbol, state); 1647 } 1648 1649 const pusher = () => state.referenceAdder(search.symbol); 1650 1651 if (isClassLike(referenceLocation.parent)) { 1652 Debug.assert(referenceLocation.kind === SyntaxKind.DefaultKeyword || referenceLocation.parent.name === referenceLocation); 1653 // This is the class declaration containing the constructor. 1654 findOwnConstructorReferences(search.symbol, sourceFile, pusher()); 1655 } 1656 else { 1657 // If this class appears in `extends C`, then the extending class' "super" calls are references. 1658 const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); 1659 if (classExtending) { 1660 findSuperConstructorAccesses(classExtending, pusher()); 1661 findInheritedConstructorReferences(classExtending, state); 1662 } 1663 } 1664 } 1665 1666 function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void { 1667 addReference(referenceLocation, search.symbol, state); 1668 const classLike = referenceLocation.parent; 1669 if (state.options.use === FindReferencesUse.Rename || !isClassLike(classLike)) return; 1670 Debug.assert(classLike.name === referenceLocation); 1671 const addRef = state.referenceAdder(search.symbol); 1672 for (const member of classLike.members) { 1673 if (!(isMethodOrAccessor(member) && hasSyntacticModifier(member, ModifierFlags.Static))) { 1674 continue; 1675 } 1676 if (member.body) { 1677 member.body.forEachChild(function cb(node) { 1678 if (node.kind === SyntaxKind.ThisKeyword) { 1679 addRef(node); 1680 } 1681 else if (!isFunctionLike(node) && !isClassLike(node)) { 1682 node.forEachChild(cb); 1683 } 1684 }); 1685 } 1686 } 1687 } 1688 1689 /** 1690 * `classSymbol` is the class where the constructor was defined. 1691 * Reference the constructor and all calls to `new this()`. 1692 */ 1693 function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void { 1694 const constructorSymbol = getClassConstructorSymbol(classSymbol); 1695 if (constructorSymbol && constructorSymbol.declarations) { 1696 for (const decl of constructorSymbol.declarations) { 1697 const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!; 1698 Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); 1699 addNode(ctrKeyword); 1700 } 1701 } 1702 1703 if (classSymbol.exports) { 1704 classSymbol.exports.forEach(member => { 1705 const decl = member.valueDeclaration; 1706 if (decl && decl.kind === SyntaxKind.MethodDeclaration) { 1707 const body = (<MethodDeclaration>decl).body; 1708 if (body) { 1709 forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { 1710 if (isNewExpressionTarget(thisKeyword)) { 1711 addNode(thisKeyword); 1712 } 1713 }); 1714 } 1715 } 1716 }); 1717 } 1718 } 1719 1720 function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined { 1721 return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor); 1722 } 1723 1724 /** Find references to `super` in the constructor of an extending class. */ 1725 function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void { 1726 const constructor = getClassConstructorSymbol(classDeclaration.symbol); 1727 if (!(constructor && constructor.declarations)) { 1728 return; 1729 } 1730 1731 for (const decl of constructor.declarations) { 1732 Debug.assert(decl.kind === SyntaxKind.Constructor); 1733 const body = (<ConstructorDeclaration>decl).body; 1734 if (body) { 1735 forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { 1736 if (isCallExpressionTarget(node)) { 1737 addNode(node); 1738 } 1739 }); 1740 } 1741 } 1742 } 1743 1744 function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean { 1745 return !!getClassConstructorSymbol(classDeclaration.symbol); 1746 } 1747 1748 function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void { 1749 if (hasOwnConstructor(classDeclaration)) return; 1750 const classSymbol = classDeclaration.symbol; 1751 const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined); 1752 getReferencesInContainerOrFiles(classSymbol, state, search); 1753 } 1754 1755 function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { 1756 // Check if we found a function/propertyAssignment/method with an implementation or initializer 1757 if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { 1758 addReference(refNode); 1759 return; 1760 } 1761 1762 if (refNode.kind !== SyntaxKind.Identifier) { 1763 return; 1764 } 1765 1766 if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { 1767 // Go ahead and dereference the shorthand assignment by going to its definition 1768 getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addReference); 1769 } 1770 1771 // Check if the node is within an extends or implements clause 1772 const containingClass = getContainingClassIfInHeritageClause(refNode); 1773 if (containingClass) { 1774 addReference(containingClass); 1775 return; 1776 } 1777 1778 // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface 1779 // Find the first node whose parent isn't a type node -- i.e., the highest type node. 1780 const typeNode = findAncestor(refNode, a => !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent))!; 1781 const typeHavingNode = typeNode.parent; 1782 if (hasType(typeHavingNode) && typeHavingNode.type === typeNode && state.markSeenContainingTypeReference(typeHavingNode)) { 1783 if (hasInitializer(typeHavingNode)) { 1784 addIfImplementation(typeHavingNode.initializer!); 1785 } 1786 else if (isFunctionLike(typeHavingNode) && (typeHavingNode as FunctionLikeDeclaration).body) { 1787 const body = (typeHavingNode as FunctionLikeDeclaration).body!; 1788 if (body.kind === SyntaxKind.Block) { 1789 forEachReturnStatement(<Block>body, returnStatement => { 1790 if (returnStatement.expression) addIfImplementation(returnStatement.expression); 1791 }); 1792 } 1793 else { 1794 addIfImplementation(body); 1795 } 1796 } 1797 else if (isAssertionExpression(typeHavingNode)) { 1798 addIfImplementation(typeHavingNode.expression); 1799 } 1800 } 1801 1802 function addIfImplementation(e: Expression): void { 1803 if (isImplementationExpression(e)) addReference(e); 1804 } 1805 } 1806 1807 function getContainingClassIfInHeritageClause(node: Node): ClassLikeDeclaration | InterfaceDeclaration | undefined { 1808 return isIdentifier(node) || isPropertyAccessExpression(node) ? getContainingClassIfInHeritageClause(node.parent) 1809 : isExpressionWithTypeArguments(node) ? tryCast(node.parent.parent, isClassLike) : undefined; 1810 } 1811 1812 /** 1813 * Returns true if this is an expression that can be considered an implementation 1814 */ 1815 function isImplementationExpression(node: Expression): boolean { 1816 switch (node.kind) { 1817 case SyntaxKind.ParenthesizedExpression: 1818 return isImplementationExpression((<ParenthesizedExpression>node).expression); 1819 case SyntaxKind.ArrowFunction: 1820 case SyntaxKind.FunctionExpression: 1821 case SyntaxKind.ObjectLiteralExpression: 1822 case SyntaxKind.ClassExpression: 1823 case SyntaxKind.ArrayLiteralExpression: 1824 return true; 1825 default: 1826 return false; 1827 } 1828 } 1829 1830 /** 1831 * Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol 1832 * is an interface, determines if some ancestor of the child symbol extends or inherits from it. 1833 * Also takes in a cache of previous results which makes this slightly more efficient and is 1834 * necessary to avoid potential loops like so: 1835 * class A extends B { } 1836 * class B extends A { } 1837 * 1838 * We traverse the AST rather than using the type checker because users are typically only interested 1839 * in explicit implementations of an interface/class when calling "Go to Implementation". Sibling 1840 * implementations of types that share a common ancestor with the type whose implementation we are 1841 * searching for need to be filtered out of the results. The type checker doesn't let us make the 1842 * distinction between structurally compatible implementations and explicit implementations, so we 1843 * must use the AST. 1844 * 1845 * @param symbol A class or interface Symbol 1846 * @param parent Another class or interface Symbol 1847 * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results 1848 */ 1849 function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: ESMap<string, boolean>, checker: TypeChecker): boolean { 1850 if (symbol === parent) { 1851 return true; 1852 } 1853 1854 const key = getSymbolId(symbol) + "," + getSymbolId(parent); 1855 const cached = cachedResults.get(key); 1856 if (cached !== undefined) { 1857 return cached; 1858 } 1859 1860 // Set the key so that we don't infinitely recurse 1861 cachedResults.set(key, false); 1862 1863 const inherits = !!symbol.declarations && symbol.declarations.some(declaration => 1864 getAllSuperTypeNodes(declaration).some(typeReference => { 1865 const type = checker.getTypeAtLocation(typeReference); 1866 return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker); 1867 })); 1868 cachedResults.set(key, inherits); 1869 return inherits; 1870 } 1871 1872 function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] | undefined { 1873 let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); 1874 if (!searchSpaceNode) { 1875 return undefined; 1876 } 1877 // Whether 'super' occurs in a static context within a class. 1878 let staticFlag = ModifierFlags.Static; 1879 1880 switch (searchSpaceNode.kind) { 1881 case SyntaxKind.PropertyDeclaration: 1882 case SyntaxKind.PropertySignature: 1883 case SyntaxKind.MethodDeclaration: 1884 case SyntaxKind.MethodSignature: 1885 case SyntaxKind.Constructor: 1886 case SyntaxKind.GetAccessor: 1887 case SyntaxKind.SetAccessor: 1888 staticFlag &= getSyntacticModifierFlags(searchSpaceNode); 1889 searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class 1890 break; 1891 default: 1892 return undefined; 1893 } 1894 1895 const sourceFile = searchSpaceNode.getSourceFile(); 1896 const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), node => { 1897 if (node.kind !== SyntaxKind.SuperKeyword) { 1898 return; 1899 } 1900 1901 const container = getSuperContainer(node, /*stopOnFunctions*/ false); 1902 1903 // If we have a 'super' container, we must have an enclosing class. 1904 // Now make sure the owning class is the same as the search-space 1905 // and has the same static qualifier as the original 'super's owner. 1906 return container && (ModifierFlags.Static & getSyntacticModifierFlags(container)) === staticFlag && container.parent.symbol === searchSpaceNode.symbol ? nodeEntry(node) : undefined; 1907 }); 1908 1909 return [{ definition: { type: DefinitionKind.Symbol, symbol: searchSpaceNode.symbol }, references }]; 1910 } 1911 1912 function isParameterName(node: Node) { 1913 return node.kind === SyntaxKind.Identifier && node.parent.kind === SyntaxKind.Parameter && (<ParameterDeclaration>node.parent).name === node; 1914 } 1915 1916 function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { 1917 let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); 1918 1919 // Whether 'this' occurs in a static context within a class. 1920 let staticFlag = ModifierFlags.Static; 1921 1922 switch (searchSpaceNode.kind) { 1923 case SyntaxKind.MethodDeclaration: 1924 case SyntaxKind.MethodSignature: 1925 if (isObjectLiteralMethod(searchSpaceNode)) { 1926 break; 1927 } 1928 // falls through 1929 case SyntaxKind.PropertyDeclaration: 1930 case SyntaxKind.PropertySignature: 1931 case SyntaxKind.Constructor: 1932 case SyntaxKind.GetAccessor: 1933 case SyntaxKind.SetAccessor: 1934 staticFlag &= getSyntacticModifierFlags(searchSpaceNode); 1935 searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class 1936 break; 1937 case SyntaxKind.SourceFile: 1938 if (isExternalModule(<SourceFile>searchSpaceNode) || isParameterName(thisOrSuperKeyword)) { 1939 return undefined; 1940 } 1941 // falls through 1942 case SyntaxKind.FunctionDeclaration: 1943 case SyntaxKind.FunctionExpression: 1944 break; 1945 // Computed properties in classes are not handled here because references to this are illegal, 1946 // so there is no point finding references to them. 1947 default: 1948 return undefined; 1949 } 1950 1951 const references = flatMap(searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFiles : [searchSpaceNode.getSourceFile()], sourceFile => { 1952 cancellationToken.throwIfCancellationRequested(); 1953 return getPossibleSymbolReferenceNodes(sourceFile, "this", isSourceFile(searchSpaceNode) ? sourceFile : searchSpaceNode).filter(node => { 1954 if (!isThis(node)) { 1955 return false; 1956 } 1957 const container = getThisContainer(node, /* includeArrowFunctions */ false); 1958 switch (searchSpaceNode.kind) { 1959 case SyntaxKind.FunctionExpression: 1960 case SyntaxKind.FunctionDeclaration: 1961 return searchSpaceNode.symbol === container.symbol; 1962 case SyntaxKind.MethodDeclaration: 1963 case SyntaxKind.MethodSignature: 1964 return isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol; 1965 case SyntaxKind.ClassExpression: 1966 case SyntaxKind.ClassDeclaration: 1967 case SyntaxKind.StructDeclaration: 1968 // Make sure the container belongs to the same class 1969 // and has the appropriate static modifier from the original container. 1970 return container.parent && searchSpaceNode.symbol === container.parent.symbol && (getSyntacticModifierFlags(container) & ModifierFlags.Static) === staticFlag; 1971 case SyntaxKind.SourceFile: 1972 return container.kind === SyntaxKind.SourceFile && !isExternalModule(<SourceFile>container) && !isParameterName(node); 1973 } 1974 }); 1975 }).map(n => nodeEntry(n)); 1976 1977 const thisParameter = firstDefined(references, r => isParameter(r.node.parent) ? r.node : undefined); 1978 return [{ 1979 definition: { type: DefinitionKind.This, node: thisParameter || thisOrSuperKeyword }, 1980 references 1981 }]; 1982 } 1983 1984 function getReferencesForStringLiteral(node: StringLiteralLike, sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] { 1985 const type = getContextualTypeOrAncestorTypeNodeType(node, checker); 1986 const references = flatMap(sourceFiles, sourceFile => { 1987 cancellationToken.throwIfCancellationRequested(); 1988 return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => { 1989 if (isStringLiteralLike(ref) && ref.text === node.text) { 1990 if (type) { 1991 const refType = getContextualTypeOrAncestorTypeNodeType(ref, checker); 1992 if (type !== checker.getStringType() && type === refType) { 1993 return nodeEntry(ref, EntryKind.StringLiteral); 1994 } 1995 } 1996 else { 1997 return nodeEntry(ref, EntryKind.StringLiteral); 1998 } 1999 } 2000 }); 2001 }); 2002 2003 return [{ 2004 definition: { type: DefinitionKind.String, node }, 2005 references 2006 }]; 2007 } 2008 2009 // For certain symbol kinds, we need to include other symbols in the search set. 2010 // This is not needed when searching for re-exports. 2011 function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, providePrefixAndSuffixText: boolean, implementations: boolean): Symbol[] { 2012 const result: Symbol[] = []; 2013 forEachRelatedSymbol<void>(symbol, location, checker, isForRename, !(isForRename && providePrefixAndSuffixText), 2014 (sym, root, base) => { 2015 // static method/property and instance method/property might have the same name. Only include static or only include instance. 2016 if (base) { 2017 if (isStatic(symbol) !== isStatic(base)) { 2018 base = undefined; 2019 } 2020 } 2021 result.push(base || root || sym); 2022 }, 2023 // when try to find implementation, implementations is true, and not allowed to find base class 2024 /*allowBaseTypes*/() => !implementations); 2025 return result; 2026 } 2027 2028 /** 2029 * @param allowBaseTypes return true means it would try to find in base class or interface. 2030 */ 2031 function forEachRelatedSymbol<T>( 2032 symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean, onlyIncludeBindingElementAtReferenceLocation: boolean, 2033 /** 2034 * @param baseSymbol This symbol means one property/mehtod from base class or interface when it is not null or undefined, 2035 */ 2036 cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined, 2037 allowBaseTypes: (rootSymbol: Symbol) => boolean, 2038 ): T | undefined { 2039 const containingObjectLiteralElement = getContainingObjectLiteralElement(location); 2040 if (containingObjectLiteralElement) { 2041 /* Because in short-hand property assignment, location has two meaning : property name and as value of the property 2042 * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of 2043 * property name and variable declaration of the identifier. 2044 * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service 2045 * should show both 'name' in 'obj' and 'name' in variable declaration 2046 * const name = "Foo"; 2047 * const obj = { name }; 2048 * 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 2049 * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration 2050 * will be included correctly. 2051 */ 2052 const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol 2053 if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) { 2054 // When renaming 'x' in `const o = { x }`, just rename the local variable, not the property. 2055 return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); 2056 } 2057 2058 // If the location is in a context sensitive location (i.e. in an object literal) try 2059 // to get a contextual type for it, and add the property symbol from the contextual 2060 // type to the search set 2061 const contextualType = checker.getContextualType(containingObjectLiteralElement.parent); 2062 const res = contextualType && firstDefined( 2063 getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), 2064 sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal)); 2065 if (res) return res; 2066 2067 // If the location is name of property symbol from object literal destructuring pattern 2068 // Search the property symbol 2069 // for ( { property: p2 } of elems) { } 2070 const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker); 2071 const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal); 2072 if (res1) return res1; 2073 2074 const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); 2075 if (res2) return res2; 2076 } 2077 2078 const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, checker); 2079 if (aliasedSymbol) { 2080 // In case of UMD module and global merging, search for global as well 2081 const res = cbSymbol(aliasedSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); 2082 if (res) return res; 2083 } 2084 2085 const res = fromRoot(symbol); 2086 if (res) return res; 2087 2088 if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration, symbol.valueDeclaration.parent)) { 2089 // For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property). 2090 const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(cast(symbol.valueDeclaration, isParameter), symbol.name); 2091 Debug.assert(paramProps.length === 2 && !!(paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && !!(paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property] 2092 return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]); 2093 } 2094 2095 const exportSpecifier = getDeclarationOfKind<ExportSpecifier>(symbol, SyntaxKind.ExportSpecifier); 2096 if (!isForRenamePopulateSearchSymbolSet || exportSpecifier && !exportSpecifier.propertyName) { 2097 const localSymbol = exportSpecifier && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); 2098 if (localSymbol) { 2099 const res = cbSymbol(localSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); 2100 if (res) return res; 2101 } 2102 } 2103 2104 // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. 2105 // Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local. 2106 if (!isForRenamePopulateSearchSymbolSet) { 2107 let bindingElementPropertySymbol: Symbol | undefined; 2108 if (onlyIncludeBindingElementAtReferenceLocation) { 2109 bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined; 2110 } 2111 else { 2112 bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); 2113 } 2114 return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); 2115 } 2116 2117 Debug.assert(isForRenamePopulateSearchSymbolSet); 2118 // due to the above assert and the arguments at the uses of this function, 2119 // (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds 2120 const includeOriginalSymbolOfBindingElement = onlyIncludeBindingElementAtReferenceLocation; 2121 2122 if (includeOriginalSymbolOfBindingElement) { 2123 const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); 2124 return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); 2125 } 2126 2127 function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined { 2128 // If this is a union property: 2129 // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. 2130 // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. 2131 // If the symbol is an instantiation from a another symbol (e.g. widened symbol): 2132 // - In populateSearchSymbolsSet, add the root the list 2133 // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) 2134 return firstDefined(checker.getRootSymbols(sym), rootSymbol => 2135 cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind) 2136 // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions 2137 || (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol) 2138 ? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind)) 2139 : undefined)); 2140 } 2141 2142 function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined { 2143 const bindingElement = getDeclarationOfKind<BindingElement>(symbol, SyntaxKind.BindingElement); 2144 if (bindingElement && isObjectBindingElementWithoutPropertyName(bindingElement)) { 2145 return getPropertySymbolFromBindingElement(checker, bindingElement); 2146 } 2147 } 2148 } 2149 2150 /** 2151 * Find symbol of the given property-name and add the symbol to the given result array 2152 * @param symbol a symbol to start searching for the given propertyName 2153 * @param propertyName a name of property to search for 2154 * @param result an array of symbol of found property symbols 2155 * @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol. 2156 * The value of previousIterationSymbol is undefined when the function is first called. 2157 */ 2158 function getPropertySymbolsFromBaseTypes<T>(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined { 2159 const seen = new Map<string, true>(); 2160 return recur(symbol); 2161 2162 function recur(symbol: Symbol): T | undefined { 2163 // Use `addToSeen` to ensure we don't infinitely recurse in this situation: 2164 // interface C extends C { 2165 // /*findRef*/propName: string; 2166 // } 2167 if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, getSymbolId(symbol))) return; 2168 2169 return firstDefined(symbol.declarations, declaration => firstDefined(getAllSuperTypeNodes(declaration), typeReference => { 2170 const type = checker.getTypeAtLocation(typeReference); 2171 const propertySymbol = type && type.symbol && checker.getPropertyOfType(type, propertyName); 2172 // Visit the typeReference as well to see if it directly or indirectly uses that property 2173 return type && propertySymbol && (firstDefined(checker.getRootSymbols(propertySymbol), cb) || recur(type.symbol)); 2174 })); 2175 } 2176 } 2177 2178 interface RelatedSymbol { 2179 readonly symbol: Symbol; 2180 readonly kind: NodeEntryKind | undefined; 2181 } 2182 2183 function isStatic(symbol: Symbol): boolean { 2184 if (!symbol.valueDeclaration) { return false; } 2185 const modifierFlags = getEffectiveModifierFlags(symbol.valueDeclaration); 2186 return !!(modifierFlags & ModifierFlags.Static); 2187 } 2188 2189 function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined { 2190 const { checker } = state; 2191 return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false, 2192 /*onlyIncludeBindingElementAtReferenceLocation*/ state.options.use !== FindReferencesUse.Rename || !!state.options.providePrefixAndSuffixTextForRename, 2193 (sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => { 2194 // check whether the symbol used to search itself is just the searched one. 2195 if (baseSymbol) { 2196 // static method/property and instance method/property might have the same name. Only check static or only check instance. 2197 if (isStatic(referenceSymbol) !== isStatic(baseSymbol)) { 2198 baseSymbol = undefined; 2199 } 2200 } 2201 return search.includes(baseSymbol || rootSymbol || sym) 2202 // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. 2203 ? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind } 2204 : undefined; 2205 }, 2206 /*allowBaseTypes*/ rootSymbol => 2207 !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker))) 2208 ); 2209 } 2210 2211 /** 2212 * Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations 2213 * of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class 2214 * then we need to widen the search to include type positions as well. 2215 * On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated 2216 * module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module) 2217 * do not intersect in any of the three spaces. 2218 */ 2219 export function getIntersectingMeaningFromDeclarations(node: Node, symbol: Symbol): SemanticMeaning { 2220 let meaning = getMeaningFromLocation(node); 2221 const { declarations } = symbol; 2222 if (declarations) { 2223 let lastIterationMeaning: SemanticMeaning; 2224 do { 2225 // The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module] 2226 // we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module 2227 // intersects with the class in the value space. 2228 // To achieve that we will keep iterating until the result stabilizes. 2229 2230 // Remember the last meaning 2231 lastIterationMeaning = meaning; 2232 2233 for (const declaration of declarations) { 2234 const declarationMeaning = getMeaningFromDeclaration(declaration); 2235 2236 if (declarationMeaning & meaning) { 2237 meaning |= declarationMeaning; 2238 } 2239 } 2240 } 2241 while (meaning !== lastIterationMeaning); 2242 } 2243 return meaning; 2244 } 2245 2246 function isImplementation(node: Node): boolean { 2247 return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) : 2248 (isVariableLike(node) ? hasInitializer(node) : 2249 isFunctionLikeDeclaration(node) ? !!node.body : 2250 isClassLike(node) || isModuleOrEnumDeclaration(node)); 2251 } 2252 2253 export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { 2254 const refSymbol = checker.getSymbolAtLocation(node)!; 2255 const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); 2256 2257 if (shorthandSymbol) { 2258 for (const declaration of shorthandSymbol.getDeclarations()!) { 2259 if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { 2260 addReference(declaration); 2261 } 2262 } 2263 } 2264 } 2265 2266 function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void { 2267 forEachChild(node, child => { 2268 if (child.kind === kind) { 2269 action(child); 2270 } 2271 forEachDescendantOfKind(child, kind, action); 2272 }); 2273 } 2274 2275 /** 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. */ 2276 function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { 2277 return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); 2278 } 2279 2280 /** 2281 * If we are just looking for implementations and this is a property access expression, we need to get the 2282 * symbol of the local type of the symbol the property is being accessed on. This is because our search 2283 * symbol may have a different parent symbol if the local type's symbol does not declare the property 2284 * being accessed (i.e. it is declared in some parent class or interface) 2285 */ 2286 function getParentSymbolsOfPropertyAccess(location: Node, symbol: Symbol, checker: TypeChecker): readonly Symbol[] | undefined { 2287 const propertyAccessExpression = isRightSideOfPropertyAccess(location) ? <PropertyAccessExpression>location.parent : undefined; 2288 const lhsType = propertyAccessExpression && checker.getTypeAtLocation(propertyAccessExpression.expression); 2289 const res = mapDefined(lhsType && (lhsType.isUnionOrIntersection() ? lhsType.types : lhsType.symbol === symbol.parent ? undefined : [lhsType]), t => 2290 t.symbol && t.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? t.symbol : undefined); 2291 return res.length === 0 ? undefined : res; 2292 } 2293 2294 function isForRenameWithPrefixAndSuffixText(options: Options) { 2295 return options.use === FindReferencesUse.Rename && options.providePrefixAndSuffixTextForRename; 2296 } 2297 } 2298} 2299