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