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