namespace ts . FindAllReferences { export interface ImportsResult { /** For every import of the symbol, the location and local symbol for the import. */ importSearches : readonly [ Identifier , Symbol ] [ ] ; /** For rename imports/exports `{ foo as bar }`, `foo` is not a local, so it may be added as a reference immediately without further searching. */ singleReferences : readonly ( Identifier | StringLiteral ) [ ] ; /** List of source files that may (or may not) use the symbol via a namespace. (For UMD modules this is every file.) */ indirectUsers : readonly SourceFile [ ] ; } export type ImportTracker = ( exportSymbol : Symbol , exportInfo : ExportInfo , isForRename : boolean ) => ImportsResult ; /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily.  */ export function createImportTracker ( sourceFiles , sourceFilesSet , checker , cancellationToken ) { const allDirectImports = getDirectImportsMap ( sourceFiles , checker , cancellationToken ) ; return ( exportSymbol , exportInfo , isForRename ) => { const { directImports , indirectUsers } = getImportersForExport ( sourceFiles , sourceFilesSet , allDirectImports , exportInfo , checker , cancellationToken ) ; return { indirectUsers , ... getSearchesFromDirectImports ( directImports , exportSymbol , exportInfo . exportKind , checker , isForRename ) } ; } ; } /** Info about an exported symbol to perform recursive search on. */ export interface ExportInfo { exportingModuleSymbol : Symbol ; exportKind : ExportKind ; } export const enum ExportKind { Named , Default , ExportEquals } export const enum ImportExport { Import , Export } interface AmbientModuleDeclaration extends ModuleDeclaration { body ? : ModuleBlock ; } type SourceFileLike = SourceFile | AmbientModuleDeclaration ; type Importer = AnyImportOrReExport | ValidImportTypeNode | Identifier ; type ImporterOrCallExpression = Importer | CallExpression ; /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ function getImportersForExport ( sourceFiles , sourceFilesSet , allDirectImports , { exportingModuleSymbol , exportKind } , checker , cancellationToken ) { const markSeenDirectImport = nodeSeenTracker < ImporterOrCallExpression > ( ) ; const markSeenIndirectUser = nodeSeenTracker < SourceFileLike > ( ) ; const directImports = [ ] ; const isAvailableThroughGlobal = ! ! exportingModuleSymbol . globalExports ; const indirectUserDeclarations = isAvailableThroughGlobal ? undefined : [ ] ; handleDirectImports ( exportingModuleSymbol ) ; return { directImports , indirectUsers : getIndirectUsers ( ) } ; function getIndirectUsers ( ) { if ( isAvailableThroughGlobal ) { return sourceFiles ; } if ( exportingModuleSymbol . declarations ) { for ( const decl of exportingModuleSymbol . declarations ) { if ( isExternalModuleAugmentation ( decl ) && sourceFilesSet . has ( decl . getSourceFile ( ) . fileName ) ) { addIndirectUser ( decl ) ; } } } return indirectUserDeclarations ! . map < SourceFile > ( getSourceFileOfNode ) ; } function handleDirectImports ( exportingModuleSymbol ) { const theseDirectImports = getDirectImports ( exportingModuleSymbol ) ; if ( theseDirectImports ) { for ( const direct of theseDirectImports ) { if ( ! markSeenDirectImport ( direct ) ) { continue ; } if ( cancellationToken ) cancellationToken . throwIfCancellationRequested ( ) ; switch ( direct . kind ) { case SyntaxKind . CallExpression : if ( isImportCall ( direct ) ) { handleImportCall ( direct ) ; break ; } if ( ! isAvailableThroughGlobal ) { const parent = direct . parent ; if ( exportKind === ExportKind . ExportEquals && parent . kind === SyntaxKind . VariableDeclaration ) { const { name } = parent as VariableDeclaration ; if ( name . kind === SyntaxKind . Identifier ) { directImports . push ( name ) ; break ; } } } break ; case SyntaxKind . Identifier : break ; case SyntaxKind . ImportEqualsDeclaration : handleNamespaceImport ( direct , direct . name , hasSyntacticModifier ( direct , ModifierFlags . Export ) , false ) ; break ; case SyntaxKind . ImportDeclaration : directImports . push ( direct ) ; const namedBindings = direct . importClause && direct . importClause . namedBindings ; if ( namedBindings && namedBindings . kind === SyntaxKind . NamespaceImport ) { handleNamespaceImport ( direct , namedBindings . name , false , true ) ; } else if ( ! isAvailableThroughGlobal && isDefaultImport ( direct ) ) { addIndirectUser ( getSourceFileLikeForImportDeclaration ( direct ) ) ; } break ; case SyntaxKind . ExportDeclaration : if ( ! direct . exportClause ) { handleDirectImports ( getContainingModuleSymbol ( direct , checker ) ) ; } else if ( direct . exportClause . kind === SyntaxKind . NamespaceExport ) { addIndirectUser ( getSourceFileLikeForImportDeclaration ( direct ) , true ) ; } else { directImports . push ( direct ) ; } break ; case SyntaxKind . ImportType : if ( direct . isTypeOf && ! direct . qualifier && isExported ( direct ) ) { addIndirectUser ( direct . getSourceFile ( ) , true ) ; } directImports . push ( direct ) ; break ; default : Debug . failBadSyntaxKind ( direct , "Unexpected import kind." ) ; } } } } function handleImportCall ( importCall ) { const top = findAncestor ( importCall , isAmbientModuleDeclaration ) || importCall . getSourceFile ( ) ; addIndirectUser ( top , ! ! isExported ( importCall , true ) ) ; } function isExported ( node , stopAtAmbientModule = false ) { return findAncestor ( node , node => { if ( stopAtAmbientModule && isAmbientModuleDeclaration ( node ) ) return "quit" ; return some ( node . modifiers , mod => mod . kind === SyntaxKind . ExportKeyword ) ; } ) ; } function handleNamespaceImport ( importDeclaration , name , isReExport , alreadyAddedDirect ) { if ( exportKind === ExportKind . ExportEquals ) { if ( ! alreadyAddedDirect ) directImports . push ( importDeclaration ) ; } else if ( ! isAvailableThroughGlobal ) { const sourceFileLike = getSourceFileLikeForImportDeclaration ( importDeclaration ) ; Debug . assert ( sourceFileLike . kind === SyntaxKind . SourceFile || sourceFileLike . kind === SyntaxKind . ModuleDeclaration ) ; if ( isReExport || findNamespaceReExports ( sourceFileLike , name , checker ) ) { addIndirectUser ( sourceFileLike , true ) ; } else { addIndirectUser ( sourceFileLike ) ; } } } /** Adds a module and all of its transitive dependencies as possible indirect users. */ function addIndirectUser ( sourceFileLike , addTransitiveDependencies = false ) { Debug . assert ( ! isAvailableThroughGlobal ) ; const isNew = markSeenIndirectUser ( sourceFileLike ) ; if ( ! isNew ) return ; indirectUserDeclarations ! . push ( sourceFileLike ) ; if ( ! addTransitiveDependencies ) return ; const moduleSymbol = checker . getMergedSymbol ( sourceFileLike . symbol ) ; if ( ! moduleSymbol ) return ; Debug . assert ( ! ! ( moduleSymbol . flags & SymbolFlags . Module ) ) ; const directImports = getDirectImports ( moduleSymbol ) ; if ( directImports ) { for ( const directImport of directImports ) { if ( ! isImportTypeNode ( directImport ) ) { addIndirectUser ( getSourceFileLikeForImportDeclaration ( directImport ) , true ) ; } } } } function getDirectImports ( moduleSymbol ) { return allDirectImports . get ( getSymbolId ( moduleSymbol ) . toString ( ) ) ; } } /**
     * Given the set of direct imports of a module, we need to find which ones import the particular exported symbol.
     * The returned `importSearches` will result in the entire source file being searched.
     * But re-exports will be placed in 'singleReferences' since they cannot be locally referenced.
     */ function getSearchesFromDirectImports ( directImports , exportSymbol , exportKind , checker , isForRename ) { const importSearches = [ ] ; const singleReferences = [ ] ; function addSearch ( location , symbol ) { importSearches . push ( [ location , symbol ] ) ; } if ( directImports ) { for ( const decl of directImports ) { handleImport ( decl ) ; } } return { importSearches , singleReferences } ; function handleImport ( decl ) { if ( decl . kind === SyntaxKind . ImportEqualsDeclaration ) { if ( isExternalModuleImportEquals ( decl ) ) { handleNamespaceImportLike ( decl . name ) ; } return ; } if ( decl . kind === SyntaxKind . Identifier ) { handleNamespaceImportLike ( decl ) ; return ; } if ( decl . kind === SyntaxKind . ImportType ) { if ( decl . qualifier ) { const firstIdentifier = getFirstIdentifier ( decl . qualifier ) ; if ( firstIdentifier . escapedText === symbolName ( exportSymbol ) ) { singleReferences . push ( firstIdentifier ) ; } } else if ( exportKind === ExportKind . ExportEquals ) { singleReferences . push ( decl . argument . literal ) ; } return ; } if ( decl . moduleSpecifier ! . kind !== SyntaxKind . StringLiteral ) { return ; } if ( decl . kind === SyntaxKind . ExportDeclaration ) { if ( decl . exportClause && isNamedExports ( decl . exportClause ) ) { searchForNamedImport ( decl . exportClause ) ; } return ; } const { name , namedBindings } = decl . importClause || { name : undefined , namedBindings : undefined } ; if ( namedBindings ) { switch ( namedBindings . kind ) { case SyntaxKind . NamespaceImport : handleNamespaceImportLike ( namedBindings . name ) ; break ; case SyntaxKind . NamedImports : if ( exportKind === ExportKind . Named || exportKind === ExportKind . Default ) { searchForNamedImport ( namedBindings ) ; } break ; default : Debug . assertNever ( namedBindings ) ; } } if ( name && ( exportKind === ExportKind . Default || exportKind === ExportKind . ExportEquals ) && ( ! isForRename || name . escapedText === symbolEscapedNameNoDefault ( exportSymbol ) ) ) { const defaultImportAlias = checker . getSymbolAtLocation ( name ) ! ; addSearch ( name , defaultImportAlias ) ; } } /**
         * `import x = require("./x")` or `import * as x from "./x"`.
         * An `export =` may be imported by this syntax, so it may be a direct import.
         * If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here.
         */ function handleNamespaceImportLike ( importName ) { if ( exportKind === ExportKind . ExportEquals && ( ! isForRename || isNameMatch ( importName . escapedText ) ) ) { addSearch ( importName , checker . getSymbolAtLocation ( importName ) ! ) ; } } function searchForNamedImport ( namedBindings ) { if ( ! namedBindings ) { return ; } for ( const element of namedBindings . elements ) { const { name , propertyName } = element ; if ( ! isNameMatch ( ( propertyName || name ) . escapedText ) ) { continue ; } if ( propertyName ) { singleReferences . push ( propertyName ) ; if ( ! isForRename || name . escapedText === exportSymbol . escapedName ) { addSearch ( name , checker . getSymbolAtLocation ( name ) ! ) ; } } else { const localSymbol = element . kind === SyntaxKind . ExportSpecifier && element . propertyName ? checker . getExportSpecifierLocalTargetSymbol ( element ) ! : checker . getSymbolAtLocation ( name ) ! ; addSearch ( name , localSymbol ) ; } } } function isNameMatch ( name ) { return name === exportSymbol . escapedName || exportKind !== ExportKind . Named && name === InternalSymbolName . Default ; } } /** Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally. */ function findNamespaceReExports ( sourceFileLike , name , checker ) { const namespaceImportSymbol = checker . getSymbolAtLocation ( name ) ; return ! ! forEachPossibleImportOrExportStatement ( sourceFileLike , statement => { if ( ! isExportDeclaration ( statement ) ) return ; const { exportClause , moduleSpecifier } = statement ; return ! moduleSpecifier && exportClause && isNamedExports ( exportClause ) && exportClause . elements . some ( element => checker . getExportSpecifierLocalTargetSymbol ( element ) === namespaceImportSymbol ) ; } ) ; } export type ModuleReference = { kind : "import" ; literal : StringLiteralLike ; } | { kind : "reference" ; referencingFile : SourceFile ; ref : FileReference ; } ; export function findModuleReferences ( program , sourceFiles , searchModuleSymbol ) { const refs = [ ] ; const checker = program . getTypeChecker ( ) ; for ( const referencingFile of sourceFiles ) { const searchSourceFile = searchModuleSymbol . valueDeclaration ; if ( searchSourceFile ?. kind === SyntaxKind . SourceFile ) { for ( const ref of referencingFile . referencedFiles ) { if ( program . getSourceFileFromReference ( referencingFile , ref ) === searchSourceFile ) { refs . push ( { kind : "reference" , referencingFile , ref } ) ; } } for ( const ref of referencingFile . typeReferenceDirectives ) { const referenced = program . getResolvedTypeReferenceDirectives ( ) . get ( ref . fileName ) ; if ( referenced !== undefined && referenced . resolvedFileName === ( searchSourceFile as SourceFile ) . fileName ) { refs . push ( { kind : "reference" , referencingFile , ref } ) ; } } } forEachImport ( referencingFile , ( _importDecl , moduleSpecifier ) => { const moduleSymbol = checker . getSymbolAtLocation ( moduleSpecifier ) ; if ( moduleSymbol === searchModuleSymbol ) { refs . push ( { kind : "import" , literal : moduleSpecifier } ) ; } } ) ; } return refs ; } /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ function getDirectImportsMap ( sourceFiles , checker , cancellationToken ) { const map = new Map < string , ImporterOrCallExpression [ ] > ( ) ; for ( const sourceFile of sourceFiles ) { if ( cancellationToken ) cancellationToken . throwIfCancellationRequested ( ) ; forEachImport ( sourceFile , ( importDecl , moduleSpecifier ) => { const moduleSymbol = checker . getSymbolAtLocation ( moduleSpecifier ) ; if ( moduleSymbol ) { const id = getSymbolId ( moduleSymbol ) . toString ( ) ; let imports = map . get ( id ) ; if ( ! imports ) { map . set ( id , imports = [ ] ) ; } imports . push ( importDecl ) ; } } ) ; } return map ; } /** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */ function forEachPossibleImportOrExportStatement < T > ( sourceFileLike , action ) { return forEach ( sourceFileLike . kind === SyntaxKind . SourceFile ? sourceFileLike . statements : sourceFileLike . body ! . statements , statement => action ( statement ) || ( isAmbientModuleDeclaration ( statement ) && forEach ( statement . body && statement . body . statements , action ) ) ) ; } /** Calls `action` for each import, re-export, or require() in a file. */ function forEachImport ( sourceFile , action ) { if ( sourceFile . externalModuleIndicator || sourceFile . imports !== undefined ) { for ( const i of sourceFile . imports ) { action ( importFromModuleSpecifier ( i ) , i ) ; } } else { forEachPossibleImportOrExportStatement ( sourceFile , statement => { switch ( statement . kind ) { case SyntaxKind . ExportDeclaration : case SyntaxKind . ImportDeclaration : { const decl = statement as ImportDeclaration | ExportDeclaration ; if ( decl . moduleSpecifier && isStringLiteral ( decl . moduleSpecifier ) ) { action ( decl , decl . moduleSpecifier ) ; } break ; } case SyntaxKind . ImportEqualsDeclaration : { const decl = statement as ImportEqualsDeclaration ; if ( isExternalModuleImportEquals ( decl ) ) { action ( decl , decl . moduleReference . expression ) ; } break ; } } } ) ; } } export interface ImportedSymbol { kind : ImportExport . Import ; symbol : Symbol ; } export interface ExportedSymbol { kind : ImportExport . Export ; symbol : Symbol ; exportInfo : ExportInfo ; } param comingFromExport export function getImportOrExportSymbol ( node , symbol , checker , comingFromExport ) { return comingFromExport ? getExport ( ) : getExport ( ) || getImport ( ) ; function getExport ( ) { const { parent } = node ; const grandparent = parent . parent ; if ( symbol . exportSymbol ) { if ( parent . kind === SyntaxKind . PropertyAccessExpression ) { return symbol . declarations ?. some ( d => d === parent ) && isBinaryExpression ( grandparent ) ? getSpecialPropertyExport ( grandparent , false ) : undefined ; } else { return exportInfo ( symbol . exportSymbol , getExportKindForDeclaration ( parent ) ) ; } } else { const exportNode = getExportNode ( parent , node ) ; if ( exportNode && hasSyntacticModifier ( exportNode , ModifierFlags . Export ) ) { if ( isImportEqualsDeclaration ( exportNode ) && exportNode . moduleReference === node ) { if ( comingFromExport ) { return undefined ; } const lhsSymbol = checker . getSymbolAtLocation ( exportNode . name ) ! ; return { kind : ImportExport . Import , symbol : lhsSymbol } ; } else { return exportInfo ( symbol , getExportKindForDeclaration ( exportNode ) ) ; } } else if ( isNamespaceExport ( parent ) ) { return exportInfo ( symbol , ExportKind . Named ) ; } else if ( isExportAssignment ( parent ) ) { return getExportAssignmentExport ( parent ) ; } else if ( isExportAssignment ( grandparent ) ) { return getExportAssignmentExport ( grandparent ) ; } else if ( isBinaryExpression ( parent ) ) { return getSpecialPropertyExport ( parent , true ) ; } else if ( isBinaryExpression ( grandparent ) ) { return getSpecialPropertyExport ( grandparent , true ) ; } else if ( isJSDocTypedefTag ( parent ) ) { return exportInfo ( symbol , ExportKind . Named ) ; } } function getExportAssignmentExport ( ex ) { if ( ! ex . symbol . parent ) return undefined ; const exportKind = ex . isExportEquals ? ExportKind . ExportEquals : ExportKind . Default ; return { kind : ImportExport . Export , symbol , exportInfo : { exportingModuleSymbol : ex . symbol . parent , exportKind } } ; } function getSpecialPropertyExport ( node , useLhsSymbol ) { let kind ; switch ( getAssignmentDeclarationKind ( node ) ) { case AssignmentDeclarationKind . ExportsProperty : kind = ExportKind . Named ; break ; case AssignmentDeclarationKind . ModuleExports : kind = ExportKind . ExportEquals ; break ; default : return undefined ; } const sym = useLhsSymbol ? checker . getSymbolAtLocation ( getNameOfAccessExpression ( cast ( node . left , isAccessExpression ) ) ) : symbol ; return sym && exportInfo ( sym , kind ) ; } } function getImport ( ) { const isImport = isNodeImport ( node ) ; if ( ! isImport ) return undefined ; let importedSymbol = checker . getImmediateAliasedSymbol ( symbol ) ; if ( ! importedSymbol ) return undefined ; importedSymbol = skipExportSpecifierSymbol ( importedSymbol , checker ) ; if ( importedSymbol . escapedName === "export=" ) { importedSymbol = getExportEqualsLocalSymbol ( importedSymbol , checker ) ; } const importedName = symbolEscapedNameNoDefault ( importedSymbol ) ; if ( importedName === undefined || importedName === InternalSymbolName . Default || importedName === symbol . escapedName ) { return { kind : ImportExport . Import , symbol : importedSymbol } ; } } function exportInfo ( symbol , kind ) { const exportInfo = getExportInfo ( symbol , kind , checker ) ; return exportInfo && { kind : ImportExport . Export , symbol , exportInfo } ; } function getExportKindForDeclaration ( node ) { return hasSyntacticModifier ( node , ModifierFlags . Default ) ? ExportKind . Default : ExportKind . Named ; } } function getExportEqualsLocalSymbol ( importedSymbol , checker ) { if ( importedSymbol . flags & SymbolFlags . Alias ) { return Debug . checkDefined ( checker . getImmediateAliasedSymbol ( importedSymbol ) ) ; } const decl = Debug . checkDefined ( importedSymbol . valueDeclaration ) ; if ( isExportAssignment ( decl ) ) { return Debug . checkDefined ( decl . expression . symbol ) ; } else if ( isBinaryExpression ( decl ) ) { return Debug . checkDefined ( decl . right . symbol ) ; } else if ( isSourceFile ( decl ) ) { return Debug . checkDefined ( decl . symbol ) ; } return Debug . fail ( ) ; } function getExportNode ( parent , node ) { const declaration = isVariableDeclaration ( parent ) ? parent : isBindingElement ( parent ) ? walkUpBindingElementsAndPatterns ( parent ) : undefined ; if ( declaration ) { return ( parent as VariableDeclaration | BindingElement ) . name !== node ? undefined : isCatchClause ( declaration . parent ) ? undefined : isVariableStatement ( declaration . parent . parent ) ? declaration . parent . parent : undefined ; } else { return parent ; } } function isNodeImport ( node ) { const { parent } = node ; switch ( parent . kind ) { case SyntaxKind . ImportEqualsDeclaration : return ( parent as ImportEqualsDeclaration ) . name === node && isExternalModuleImportEquals ( parent as ImportEqualsDeclaration ) ; case SyntaxKind . ImportSpecifier : return ! ( parent as ImportSpecifier ) . propertyName ; case SyntaxKind . ImportClause : case SyntaxKind . NamespaceImport : Debug . assert ( ( parent as ImportClause | NamespaceImport ) . name === node ) ; return true ; case SyntaxKind . BindingElement : return isInJSFile ( node ) && isRequireVariableDeclaration ( parent ) ; default : return false ; } } export function getExportInfo ( exportSymbol , exportKind , checker ) { const moduleSymbol = exportSymbol . parent ; if ( ! moduleSymbol ) return undefined ; const exportingModuleSymbol = checker . getMergedSymbol ( moduleSymbol ) ; return isExternalModuleSymbol ( exportingModuleSymbol ) ? { exportingModuleSymbol , exportKind } : undefined ; } /** If at an export specifier, go to the symbol it refers to. */ function skipExportSpecifierSymbol ( symbol , checker ) { if ( symbol . declarations ) { for ( const declaration of symbol . declarations ) { if ( isExportSpecifier ( declaration ) && ! declaration . propertyName && ! declaration . parent . parent . moduleSpecifier ) { return checker . getExportSpecifierLocalTargetSymbol ( declaration ) ! ; } else if ( isPropertyAccessExpression ( declaration ) && isModuleExportsAccessExpression ( declaration . expression ) && ! isPrivateIdentifier ( declaration . name ) ) { return checker . getSymbolAtLocation ( declaration ) ! ; } else if ( isShorthandPropertyAssignment ( declaration ) && isBinaryExpression ( declaration . parent . parent ) && getAssignmentDeclarationKind ( declaration . parent . parent ) === AssignmentDeclarationKind . ModuleExports ) { return checker . getExportSpecifierLocalTargetSymbol ( declaration . name ) ! ; } } } return symbol ; } function getContainingModuleSymbol ( importer , checker ) { return checker . getMergedSymbol ( getSourceFileLikeForImportDeclaration ( importer ) . symbol ) ; } function getSourceFileLikeForImportDeclaration ( node ) { if ( node . kind === SyntaxKind . CallExpression ) { return node . getSourceFile ( ) ; } const { parent } = node ; if ( parent . kind === SyntaxKind . SourceFile ) { return parent as SourceFile ; } Debug . assert ( parent . kind === SyntaxKind . ModuleBlock ) ; return cast ( parent . parent , isAmbientModuleDeclaration ) ; } function isAmbientModuleDeclaration ( node ) { return node . kind === SyntaxKind . ModuleDeclaration && ( node as ModuleDeclaration ) . name . kind === SyntaxKind . StringLiteral ; } function isExternalModuleImportEquals ( eq ) { return eq . moduleReference . kind === SyntaxKind . ExternalModuleReference && eq . moduleReference . expression . kind === SyntaxKind . StringLiteral ; } }