• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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