1/* Code for finding imports of an exported symbol. Used only by FindAllReferences. */ 2/* @internal */ 3namespace ts.FindAllReferences { 4 export interface ImportsResult { 5 /** For every import of the symbol, the location and local symbol for the import. */ 6 importSearches: readonly [Identifier, Symbol][]; 7 /** For rename imports/exports `{ foo as bar }`, `foo` is not a local, so it may be added as a reference immediately without further searching. */ 8 singleReferences: readonly (Identifier | StringLiteral)[]; 9 /** List of source files that may (or may not) use the symbol via a namespace. (For UMD modules this is every file.) */ 10 indirectUsers: readonly SourceFile[]; 11 } 12 export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; 13 14 /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ 15 export function createImportTracker(sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string>, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker { 16 const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken); 17 return (exportSymbol, exportInfo, isForRename) => { 18 const { directImports, indirectUsers } = getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker, cancellationToken); 19 return { indirectUsers, ...getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename) }; 20 }; 21 } 22 23 /** Info about an exported symbol to perform recursive search on. */ 24 export interface ExportInfo { 25 exportingModuleSymbol: Symbol; 26 exportKind: ExportKind; 27 } 28 29 export const enum ExportKind { Named, Default, ExportEquals } 30 31 export const enum ImportExport { Import, Export } 32 33 interface AmbientModuleDeclaration extends ModuleDeclaration { body?: ModuleBlock; } 34 type SourceFileLike = SourceFile | AmbientModuleDeclaration; 35 // Identifier for the case of `const x = require("y")`. 36 type Importer = AnyImportOrReExport | ValidImportTypeNode | Identifier; 37 type ImporterOrCallExpression = Importer | CallExpression; 38 39 /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ 40 function getImportersForExport( 41 sourceFiles: readonly SourceFile[], 42 sourceFilesSet: ReadonlySet<string>, 43 allDirectImports: ESMap<string, ImporterOrCallExpression[]>, 44 { exportingModuleSymbol, exportKind }: ExportInfo, 45 checker: TypeChecker, 46 cancellationToken: CancellationToken | undefined, 47 ): { directImports: Importer[], indirectUsers: readonly SourceFile[] } { 48 const markSeenDirectImport = nodeSeenTracker<ImporterOrCallExpression>(); 49 const markSeenIndirectUser = nodeSeenTracker<SourceFileLike>(); 50 const directImports: Importer[] = []; 51 const isAvailableThroughGlobal = !!exportingModuleSymbol.globalExports; 52 const indirectUserDeclarations: SourceFileLike[] | undefined = isAvailableThroughGlobal ? undefined : []; 53 54 handleDirectImports(exportingModuleSymbol); 55 56 return { directImports, indirectUsers: getIndirectUsers() }; 57 58 function getIndirectUsers(): readonly SourceFile[] { 59 if (isAvailableThroughGlobal) { 60 // It has `export as namespace`, so anything could potentially use it. 61 return sourceFiles; 62 } 63 64 // Module augmentations may use this module's exports without importing it. 65 if (exportingModuleSymbol.declarations) { 66 for (const decl of exportingModuleSymbol.declarations) { 67 if (isExternalModuleAugmentation(decl) && sourceFilesSet.has(decl.getSourceFile().fileName)) { 68 addIndirectUser(decl); 69 } 70 } 71 } 72 73 // This may return duplicates (if there are multiple module declarations in a single source file, all importing the same thing as a namespace), but `State.markSearchedSymbol` will handle that. 74 return indirectUserDeclarations!.map<SourceFile>(getSourceFileOfNode); 75 } 76 77 function handleDirectImports(exportingModuleSymbol: Symbol): void { 78 const theseDirectImports = getDirectImports(exportingModuleSymbol); 79 if (theseDirectImports) { 80 for (const direct of theseDirectImports) { 81 if (!markSeenDirectImport(direct)) { 82 continue; 83 } 84 85 if (cancellationToken) cancellationToken.throwIfCancellationRequested(); 86 87 switch (direct.kind) { 88 case SyntaxKind.CallExpression: 89 if (isImportCall(direct)) { 90 handleImportCall(direct); 91 break; 92 } 93 if (!isAvailableThroughGlobal) { 94 const parent = direct.parent; 95 if (exportKind === ExportKind.ExportEquals && parent.kind === SyntaxKind.VariableDeclaration) { 96 const { name } = parent as VariableDeclaration; 97 if (name.kind === SyntaxKind.Identifier) { 98 directImports.push(name); 99 break; 100 } 101 } 102 } 103 break; 104 105 case SyntaxKind.Identifier: // for 'const x = require("y"); 106 break; // TODO: GH#23879 107 108 case SyntaxKind.ImportEqualsDeclaration: 109 handleNamespaceImport(direct, direct.name, hasSyntacticModifier(direct, ModifierFlags.Export), /*alreadyAddedDirect*/ false); 110 break; 111 112 case SyntaxKind.ImportDeclaration: 113 directImports.push(direct); 114 const namedBindings = direct.importClause && direct.importClause.namedBindings; 115 if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { 116 handleNamespaceImport(direct, namedBindings.name, /*isReExport*/ false, /*alreadyAddedDirect*/ true); 117 } 118 else if (!isAvailableThroughGlobal && isDefaultImport(direct)) { 119 addIndirectUser(getSourceFileLikeForImportDeclaration(direct)); // Add a check for indirect uses to handle synthetic default imports 120 } 121 break; 122 123 case SyntaxKind.ExportDeclaration: 124 if (!direct.exportClause) { 125 // This is `export * from "foo"`, so imports of this module may import the export too. 126 handleDirectImports(getContainingModuleSymbol(direct, checker)); 127 } 128 else if (direct.exportClause.kind === SyntaxKind.NamespaceExport) { 129 // `export * as foo from "foo"` add to indirect uses 130 addIndirectUser(getSourceFileLikeForImportDeclaration(direct), /** addTransitiveDependencies */ true); 131 } 132 else { 133 // This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports. 134 directImports.push(direct); 135 } 136 break; 137 138 case SyntaxKind.ImportType: 139 // Only check for typeof import('xyz') 140 if (!isAvailableThroughGlobal && direct.isTypeOf && !direct.qualifier && isExported(direct)) { 141 addIndirectUser(direct.getSourceFile(), /** addTransitiveDependencies */ true); 142 } 143 directImports.push(direct); 144 break; 145 146 default: 147 Debug.failBadSyntaxKind(direct, "Unexpected import kind."); 148 } 149 } 150 } 151 } 152 153 function handleImportCall(importCall: ImportCall) { 154 const top = findAncestor(importCall, isAmbientModuleDeclaration) || importCall.getSourceFile(); 155 addIndirectUser(top, /** addTransitiveDependencies */ !!isExported(importCall, /** stopAtAmbientModule */ true)); 156 } 157 158 function isExported(node: Node, stopAtAmbientModule = false) { 159 return findAncestor(node, node => { 160 if (stopAtAmbientModule && isAmbientModuleDeclaration(node)) return "quit"; 161 return canHaveModifiers(node) && some(node.modifiers, isExportModifier); 162 }); 163 } 164 165 function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport: boolean, alreadyAddedDirect: boolean): void { 166 if (exportKind === ExportKind.ExportEquals) { 167 // This is a direct import, not import-as-namespace. 168 if (!alreadyAddedDirect) directImports.push(importDeclaration); 169 } 170 else if (!isAvailableThroughGlobal) { 171 const sourceFileLike = getSourceFileLikeForImportDeclaration(importDeclaration); 172 Debug.assert(sourceFileLike.kind === SyntaxKind.SourceFile || sourceFileLike.kind === SyntaxKind.ModuleDeclaration); 173 if (isReExport || findNamespaceReExports(sourceFileLike, name, checker)) { 174 addIndirectUser(sourceFileLike, /** addTransitiveDependencies */ true); 175 } 176 else { 177 addIndirectUser(sourceFileLike); 178 } 179 } 180 } 181 182 /** Adds a module and all of its transitive dependencies as possible indirect users. */ 183 function addIndirectUser(sourceFileLike: SourceFileLike, addTransitiveDependencies = false): void { 184 Debug.assert(!isAvailableThroughGlobal); 185 const isNew = markSeenIndirectUser(sourceFileLike); 186 if (!isNew) return; 187 indirectUserDeclarations!.push(sourceFileLike); // TODO: GH#18217 188 189 if (!addTransitiveDependencies) return; 190 const moduleSymbol = checker.getMergedSymbol(sourceFileLike.symbol); 191 if (!moduleSymbol) return; 192 Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); 193 const directImports = getDirectImports(moduleSymbol); 194 if (directImports) { 195 for (const directImport of directImports) { 196 if (!isImportTypeNode(directImport)) { 197 addIndirectUser(getSourceFileLikeForImportDeclaration(directImport), /** addTransitiveDependencies */ true); 198 } 199 } 200 } 201 } 202 203 function getDirectImports(moduleSymbol: Symbol): ImporterOrCallExpression[] | undefined { 204 return allDirectImports.get(getSymbolId(moduleSymbol).toString()); 205 } 206 } 207 208 /** 209 * Given the set of direct imports of a module, we need to find which ones import the particular exported symbol. 210 * The returned `importSearches` will result in the entire source file being searched. 211 * But re-exports will be placed in 'singleReferences' since they cannot be locally referenced. 212 */ 213 function getSearchesFromDirectImports(directImports: Importer[], exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker, isForRename: boolean): Pick<ImportsResult, "importSearches" | "singleReferences"> { 214 const importSearches: [Identifier, Symbol][] = []; 215 const singleReferences: (Identifier | StringLiteral)[] = []; 216 function addSearch(location: Identifier, symbol: Symbol): void { 217 importSearches.push([location, symbol]); 218 } 219 220 if (directImports) { 221 for (const decl of directImports) { 222 handleImport(decl); 223 } 224 } 225 226 return { importSearches, singleReferences }; 227 228 function handleImport(decl: Importer): void { 229 if (decl.kind === SyntaxKind.ImportEqualsDeclaration) { 230 if (isExternalModuleImportEquals(decl)) { 231 handleNamespaceImportLike(decl.name); 232 } 233 return; 234 } 235 236 if (decl.kind === SyntaxKind.Identifier) { 237 handleNamespaceImportLike(decl); 238 return; 239 } 240 241 if (decl.kind === SyntaxKind.ImportType) { 242 if (decl.qualifier) { 243 const firstIdentifier = getFirstIdentifier(decl.qualifier); 244 if (firstIdentifier.escapedText === symbolName(exportSymbol)) { 245 singleReferences.push(firstIdentifier); 246 } 247 } 248 else if (exportKind === ExportKind.ExportEquals) { 249 singleReferences.push(decl.argument.literal); 250 } 251 return; 252 } 253 254 // Ignore if there's a grammar error 255 if (decl.moduleSpecifier!.kind !== SyntaxKind.StringLiteral) { 256 return; 257 } 258 259 if (decl.kind === SyntaxKind.ExportDeclaration) { 260 if (decl.exportClause && isNamedExports(decl.exportClause)) { 261 searchForNamedImport(decl.exportClause); 262 } 263 return; 264 } 265 266 const { name, namedBindings } = decl.importClause || { name: undefined, namedBindings: undefined }; 267 268 if (namedBindings) { 269 switch (namedBindings.kind) { 270 case SyntaxKind.NamespaceImport: 271 handleNamespaceImportLike(namedBindings.name); 272 break; 273 case SyntaxKind.NamedImports: 274 // 'default' might be accessed as a named import `{ default as foo }`. 275 if (exportKind === ExportKind.Named || exportKind === ExportKind.Default) { 276 searchForNamedImport(namedBindings); 277 } 278 break; 279 default: 280 Debug.assertNever(namedBindings); 281 } 282 } 283 284 // `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals. 285 // If a default import has the same name as the default export, allow to rename it. 286 // Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that. 287 if (name && (exportKind === ExportKind.Default || exportKind === ExportKind.ExportEquals) && (!isForRename || name.escapedText === symbolEscapedNameNoDefault(exportSymbol))) { 288 const defaultImportAlias = checker.getSymbolAtLocation(name)!; 289 addSearch(name, defaultImportAlias); 290 } 291 } 292 293 /** 294 * `import x = require("./x")` or `import * as x from "./x"`. 295 * An `export =` may be imported by this syntax, so it may be a direct import. 296 * If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here. 297 */ 298 function handleNamespaceImportLike(importName: Identifier): void { 299 // Don't rename an import that already has a different name than the export. 300 if (exportKind === ExportKind.ExportEquals && (!isForRename || isNameMatch(importName.escapedText))) { 301 addSearch(importName, checker.getSymbolAtLocation(importName)!); 302 } 303 } 304 305 function searchForNamedImport(namedBindings: NamedImportsOrExports | undefined): void { 306 if (!namedBindings) { 307 return; 308 } 309 310 for (const element of namedBindings.elements) { 311 const { name, propertyName } = element; 312 if (!isNameMatch((propertyName || name).escapedText)) { 313 continue; 314 } 315 316 if (propertyName) { 317 // This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference. 318 singleReferences.push(propertyName); 319 // If renaming `{ foo as bar }`, don't touch `bar`, just `foo`. 320 // But do rename `foo` in ` { default as foo }` if that's the original export name. 321 if (!isForRename || name.escapedText === exportSymbol.escapedName) { 322 // Search locally for `bar`. 323 addSearch(name, checker.getSymbolAtLocation(name)!); 324 } 325 } 326 else { 327 const localSymbol = element.kind === SyntaxKind.ExportSpecifier && element.propertyName 328 ? checker.getExportSpecifierLocalTargetSymbol(element)! // For re-exporting under a different name, we want to get the re-exported symbol. 329 : checker.getSymbolAtLocation(name)!; 330 addSearch(name, localSymbol); 331 } 332 } 333 } 334 335 function isNameMatch(name: __String): boolean { 336 // Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports 337 return name === exportSymbol.escapedName || exportKind !== ExportKind.Named && name === InternalSymbolName.Default; 338 } 339 } 340 341 /** Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally. */ 342 function findNamespaceReExports(sourceFileLike: SourceFileLike, name: Identifier, checker: TypeChecker): boolean { 343 const namespaceImportSymbol = checker.getSymbolAtLocation(name); 344 345 return !!forEachPossibleImportOrExportStatement(sourceFileLike, statement => { 346 if (!isExportDeclaration(statement)) return; 347 const { exportClause, moduleSpecifier } = statement; 348 return !moduleSpecifier && exportClause && isNamedExports(exportClause) && 349 exportClause.elements.some(element => checker.getExportSpecifierLocalTargetSymbol(element) === namespaceImportSymbol); 350 }); 351 } 352 353 export type ModuleReference = 354 /** "import" also includes require() calls. */ 355 | { kind: "import", literal: StringLiteralLike } 356 /** <reference path> or <reference types> */ 357 | { kind: "reference", referencingFile: SourceFile, ref: FileReference }; 358 export function findModuleReferences(program: Program, sourceFiles: readonly SourceFile[], searchModuleSymbol: Symbol): ModuleReference[] { 359 const refs: ModuleReference[] = []; 360 const checker = program.getTypeChecker(); 361 for (const referencingFile of sourceFiles) { 362 const searchSourceFile = searchModuleSymbol.valueDeclaration; 363 if (searchSourceFile?.kind === SyntaxKind.SourceFile) { 364 for (const ref of referencingFile.referencedFiles) { 365 if (program.getSourceFileFromReference(referencingFile, ref) === searchSourceFile) { 366 refs.push({ kind: "reference", referencingFile, ref }); 367 } 368 } 369 for (const ref of referencingFile.typeReferenceDirectives) { 370 const referenced = program.getResolvedTypeReferenceDirectives().get(ref.fileName, ref.resolutionMode || referencingFile.impliedNodeFormat); 371 if (referenced !== undefined && referenced.resolvedFileName === (searchSourceFile as SourceFile).fileName) { 372 refs.push({ kind: "reference", referencingFile, ref }); 373 } 374 } 375 } 376 377 forEachImport(referencingFile, (_importDecl, moduleSpecifier) => { 378 const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); 379 if (moduleSymbol === searchModuleSymbol) { 380 refs.push({ kind: "import", literal: moduleSpecifier }); 381 } 382 }); 383 } 384 return refs; 385 } 386 387 /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ 388 function getDirectImportsMap(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken | undefined): ESMap<string, ImporterOrCallExpression[]> { 389 const map = new Map<string, ImporterOrCallExpression[]>(); 390 391 for (const sourceFile of sourceFiles) { 392 if (cancellationToken) cancellationToken.throwIfCancellationRequested(); 393 forEachImport(sourceFile, (importDecl, moduleSpecifier) => { 394 const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); 395 if (moduleSymbol) { 396 const id = getSymbolId(moduleSymbol).toString(); 397 let imports = map.get(id); 398 if (!imports) { 399 map.set(id, imports = []); 400 } 401 imports.push(importDecl); 402 } 403 }); 404 } 405 406 return map; 407 } 408 409 /** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */ 410 function forEachPossibleImportOrExportStatement<T>(sourceFileLike: SourceFileLike, action: (statement: Statement) => T) { 411 return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body!.statements, statement => // TODO: GH#18217 412 action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action))); 413 } 414 415 /** Calls `action` for each import, re-export, or require() in a file. */ 416 function forEachImport(sourceFile: SourceFile, action: (importStatement: ImporterOrCallExpression, imported: StringLiteralLike) => void): void { 417 if (sourceFile.externalModuleIndicator || sourceFile.imports !== undefined) { 418 for (const i of sourceFile.imports) { 419 action(importFromModuleSpecifier(i), i); 420 } 421 } 422 else { 423 forEachPossibleImportOrExportStatement(sourceFile, statement => { 424 switch (statement.kind) { 425 case SyntaxKind.ExportDeclaration: 426 case SyntaxKind.ImportDeclaration: { 427 const decl = statement as ImportDeclaration | ExportDeclaration; 428 if (decl.moduleSpecifier && isStringLiteral(decl.moduleSpecifier)) { 429 action(decl, decl.moduleSpecifier); 430 } 431 break; 432 } 433 434 case SyntaxKind.ImportEqualsDeclaration: { 435 const decl = statement as ImportEqualsDeclaration; 436 if (isExternalModuleImportEquals(decl)) { 437 action(decl, decl.moduleReference.expression); 438 } 439 break; 440 } 441 } 442 }); 443 } 444 } 445 446 export interface ImportedSymbol { 447 kind: ImportExport.Import; 448 symbol: Symbol; 449 } 450 export interface ExportedSymbol { 451 kind: ImportExport.Export; 452 symbol: Symbol; 453 exportInfo: ExportInfo; 454 } 455 456 /** 457 * Given a local reference, we might notice that it's an import/export and recursively search for references of that. 458 * If at an import, look locally for the symbol it imports. 459 * If at an export, look for all imports of it. 460 * This doesn't handle export specifiers; that is done in `getReferencesAtExportSpecifier`. 461 * @param comingFromExport If we are doing a search for all exports, don't bother looking backwards for the imported symbol, since that's the reason we're here. 462 */ 463 export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined { 464 return comingFromExport ? getExport() : getExport() || getImport(); 465 466 function getExport(): ExportedSymbol | ImportedSymbol | undefined { 467 const { parent } = node; 468 const grandparent = parent.parent; 469 if (symbol.exportSymbol) { 470 if (parent.kind === SyntaxKind.PropertyAccessExpression) { 471 // When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use. 472 // So check that we are at the declaration. 473 return symbol.declarations?.some(d => d === parent) && isBinaryExpression(grandparent) 474 ? getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ false) 475 : undefined; 476 } 477 else { 478 return exportInfo(symbol.exportSymbol, getExportKindForDeclaration(parent)); 479 } 480 } 481 else { 482 const exportNode = getExportNode(parent, node); 483 if (exportNode && hasSyntacticModifier(exportNode, ModifierFlags.Export)) { 484 if (isImportEqualsDeclaration(exportNode) && exportNode.moduleReference === node) { 485 // We're at `Y` in `export import X = Y`. This is not the exported symbol, the left-hand-side is. So treat this as an import statement. 486 if (comingFromExport) { 487 return undefined; 488 } 489 490 const lhsSymbol = checker.getSymbolAtLocation(exportNode.name)!; 491 return { kind: ImportExport.Import, symbol: lhsSymbol }; 492 } 493 else { 494 return exportInfo(symbol, getExportKindForDeclaration(exportNode)); 495 } 496 } 497 else if (isNamespaceExport(parent)) { 498 return exportInfo(symbol, ExportKind.Named); 499 } 500 // If we are in `export = a;` or `export default a;`, `parent` is the export assignment. 501 else if (isExportAssignment(parent)) { 502 return getExportAssignmentExport(parent); 503 } 504 // If we are in `export = class A {};` (or `export = class A {};`) at `A`, `parent.parent` is the export assignment. 505 else if (isExportAssignment(grandparent)) { 506 return getExportAssignmentExport(grandparent); 507 } 508 // Similar for `module.exports =` and `exports.A =`. 509 else if (isBinaryExpression(parent)) { 510 return getSpecialPropertyExport(parent, /*useLhsSymbol*/ true); 511 } 512 else if (isBinaryExpression(grandparent)) { 513 return getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ true); 514 } 515 else if (isJSDocTypedefTag(parent)) { 516 return exportInfo(symbol, ExportKind.Named); 517 } 518 } 519 520 function getExportAssignmentExport(ex: ExportAssignment): ExportedSymbol | undefined { 521 // Get the symbol for the `export =` node; its parent is the module it's the export of. 522 if (!ex.symbol.parent) return undefined; 523 const exportKind = ex.isExportEquals ? ExportKind.ExportEquals : ExportKind.Default; 524 return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol: ex.symbol.parent, exportKind } }; 525 } 526 527 function getSpecialPropertyExport(node: BinaryExpression, useLhsSymbol: boolean): ExportedSymbol | undefined { 528 let kind: ExportKind; 529 switch (getAssignmentDeclarationKind(node)) { 530 case AssignmentDeclarationKind.ExportsProperty: 531 kind = ExportKind.Named; 532 break; 533 case AssignmentDeclarationKind.ModuleExports: 534 kind = ExportKind.ExportEquals; 535 break; 536 default: 537 return undefined; 538 } 539 540 const sym = useLhsSymbol ? checker.getSymbolAtLocation(getNameOfAccessExpression(cast(node.left, isAccessExpression))) : symbol; 541 return sym && exportInfo(sym, kind); 542 } 543 } 544 545 function getImport(): ImportedSymbol | undefined { 546 const isImport = isNodeImport(node); 547 if (!isImport) return undefined; 548 549 // A symbol being imported is always an alias. So get what that aliases to find the local symbol. 550 let importedSymbol = checker.getImmediateAliasedSymbol(symbol); 551 if (!importedSymbol) return undefined; 552 553 // Search on the local symbol in the exporting module, not the exported symbol. 554 importedSymbol = skipExportSpecifierSymbol(importedSymbol, checker); 555 // Similarly, skip past the symbol for 'export =' 556 if (importedSymbol.escapedName === "export=") { 557 importedSymbol = getExportEqualsLocalSymbol(importedSymbol, checker); 558 if (importedSymbol === undefined) return undefined; 559 } 560 561 // If the import has a different name than the export, do not continue searching. 562 // If `importedName` is undefined, do continue searching as the export is anonymous. 563 // (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.) 564 const importedName = symbolEscapedNameNoDefault(importedSymbol); 565 if (importedName === undefined || importedName === InternalSymbolName.Default || importedName === symbol.escapedName) { 566 return { kind: ImportExport.Import, symbol: importedSymbol }; 567 } 568 } 569 570 function exportInfo(symbol: Symbol, kind: ExportKind): ExportedSymbol | undefined { 571 const exportInfo = getExportInfo(symbol, kind, checker); 572 return exportInfo && { kind: ImportExport.Export, symbol, exportInfo }; 573 } 574 575 // Not meant for use with export specifiers or export assignment. 576 function getExportKindForDeclaration(node: Node): ExportKind { 577 return hasSyntacticModifier(node, ModifierFlags.Default) ? ExportKind.Default : ExportKind.Named; 578 } 579 } 580 581 function getExportEqualsLocalSymbol(importedSymbol: Symbol, checker: TypeChecker): Symbol | undefined { 582 if (importedSymbol.flags & SymbolFlags.Alias) { 583 return checker.getImmediateAliasedSymbol(importedSymbol); 584 } 585 586 const decl = Debug.checkDefined(importedSymbol.valueDeclaration); 587 if (isExportAssignment(decl)) { // `export = class {}` 588 return decl.expression.symbol; 589 } 590 else if (isBinaryExpression(decl)) { // `module.exports = class {}` 591 return decl.right.symbol; 592 } 593 else if (isSourceFile(decl)) { // json module 594 return decl.symbol; 595 } 596 return undefined; 597 } 598 599 // If a reference is a class expression, the exported node would be its parent. 600 // If a reference is a variable declaration, the exported node would be the variable statement. 601 function getExportNode(parent: Node, node: Node): Node | undefined { 602 const declaration = isVariableDeclaration(parent) ? parent : isBindingElement(parent) ? walkUpBindingElementsAndPatterns(parent) : undefined; 603 if (declaration) { 604 return (parent as VariableDeclaration | BindingElement).name !== node ? undefined : 605 isCatchClause(declaration.parent) ? undefined : isVariableStatement(declaration.parent.parent) ? declaration.parent.parent : undefined; 606 } 607 else { 608 return parent; 609 } 610 } 611 612 function isNodeImport(node: Node): boolean { 613 const { parent } = node; 614 switch (parent.kind) { 615 case SyntaxKind.ImportEqualsDeclaration: 616 return (parent as ImportEqualsDeclaration).name === node && isExternalModuleImportEquals(parent as ImportEqualsDeclaration); 617 case SyntaxKind.ImportSpecifier: 618 // For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`. 619 return !(parent as ImportSpecifier).propertyName; 620 case SyntaxKind.ImportClause: 621 case SyntaxKind.NamespaceImport: 622 Debug.assert((parent as ImportClause | NamespaceImport).name === node); 623 return true; 624 case SyntaxKind.BindingElement: 625 return isInJSFile(node) && isVariableDeclarationInitializedToBareOrAccessedRequire(parent.parent.parent); 626 default: 627 return false; 628 } 629 } 630 631 export function getExportInfo(exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker): ExportInfo | undefined { 632 const moduleSymbol = exportSymbol.parent; 633 if (!moduleSymbol) return undefined; // This can happen if an `export` is not at the top-level (which is a compile error). 634 const exportingModuleSymbol = checker.getMergedSymbol(moduleSymbol); // Need to get merged symbol in case there's an augmentation. 635 // `export` may appear in a namespace. In that case, just rely on global search. 636 return isExternalModuleSymbol(exportingModuleSymbol) ? { exportingModuleSymbol, exportKind } : undefined; 637 } 638 639 /** If at an export specifier, go to the symbol it refers to. */ 640 function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol { 641 // For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does. 642 if (symbol.declarations) { 643 for (const declaration of symbol.declarations) { 644 if (isExportSpecifier(declaration) && !declaration.propertyName && !declaration.parent.parent.moduleSpecifier) { 645 return checker.getExportSpecifierLocalTargetSymbol(declaration)!; 646 } 647 else if (isPropertyAccessExpression(declaration) && isModuleExportsAccessExpression(declaration.expression) && !isPrivateIdentifier(declaration.name)) { 648 // Export of form 'module.exports.propName = expr'; 649 return checker.getSymbolAtLocation(declaration)!; 650 } 651 else if (isShorthandPropertyAssignment(declaration) 652 && isBinaryExpression(declaration.parent.parent) 653 && getAssignmentDeclarationKind(declaration.parent.parent) === AssignmentDeclarationKind.ModuleExports) { 654 return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!; 655 } 656 } 657 } 658 return symbol; 659 } 660 661 function getContainingModuleSymbol(importer: Importer, checker: TypeChecker): Symbol { 662 return checker.getMergedSymbol(getSourceFileLikeForImportDeclaration(importer).symbol); 663 } 664 665 function getSourceFileLikeForImportDeclaration(node: ImporterOrCallExpression): SourceFileLike { 666 if (node.kind === SyntaxKind.CallExpression) { 667 return node.getSourceFile(); 668 } 669 670 const { parent } = node; 671 if (parent.kind === SyntaxKind.SourceFile) { 672 return parent as SourceFile; 673 } 674 Debug.assert(parent.kind === SyntaxKind.ModuleBlock); 675 return cast(parent.parent, isAmbientModuleDeclaration); 676 } 677 678 function isAmbientModuleDeclaration(node: Node): node is AmbientModuleDeclaration { 679 return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).name.kind === SyntaxKind.StringLiteral; 680 } 681 682 function isExternalModuleImportEquals(eq: ImportEqualsDeclaration): eq is ImportEqualsDeclaration & { moduleReference: { expression: StringLiteral } } { 683 return eq.moduleReference.kind === SyntaxKind.ExternalModuleReference && eq.moduleReference.expression.kind === SyntaxKind.StringLiteral; 684 } 685} 686