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