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