"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExportAnalyzer = void 0;
const ts = __importStar(require("typescript"));
const node_core_library_1 = require("@rushstack/node-core-library");
const TypeScriptHelpers_1 = require("./TypeScriptHelpers");
const AstSymbol_1 = require("./AstSymbol");
const AstImport_1 = require("./AstImport");
const AstModule_1 = require("./AstModule");
const TypeScriptInternals_1 = require("./TypeScriptInternals");
const SourceFileLocationFormatter_1 = require("./SourceFileLocationFormatter");
const AstNamespaceImport_1 = require("./AstNamespaceImport");
const SyntaxHelpers_1 = require("./SyntaxHelpers");
/**
 * The ExportAnalyzer is an internal part of AstSymbolTable that has been moved out into its own source file
 * because it is a complex and mostly self-contained algorithm.
 *
 * Its job is to build up AstModule objects by crawling import statements to discover where declarations come from.
 * This is conceptually the same as the compiler's own TypeChecker.getExportsOfModule(), except that when
 * ExportAnalyzer encounters a declaration that was imported from an external package, it remembers how it was imported
 * (i.e. the AstImport object).  Today the compiler API does not expose this information, which is crucial for
 * generating .d.ts rollups.
 */
class ExportAnalyzer {
    constructor(program, typeChecker, bundledPackageNames, astSymbolTable) {
        this._astModulesByModuleSymbol = new Map();
        // Used with isImportableAmbientSourceFile()
        this._importableAmbientSourceFiles = new Set();
        this._astImportsByKey = new Map();
        this._astNamespaceImportByModule = new Map();
        this._program = program;
        this._typeChecker = typeChecker;
        this._bundledPackageNames = bundledPackageNames;
        this._astSymbolTable = astSymbolTable;
    }
    /**
     * For a given source file, this analyzes all of its exports and produces an AstModule object.
     *
     * @param moduleReference - contextual information about the import statement that took us to this source file.
     * or `undefined` if this source file is the initial entry point
     * @param isExternal - whether the given `moduleReference` is external.
     */
    fetchAstModuleFromSourceFile(sourceFile, moduleReference, isExternal) {
        const moduleSymbol = this._getModuleSymbolFromSourceFile(sourceFile, moduleReference);
        // Don't traverse into a module that we already processed before:
        // The compiler allows m1 to have "export * from 'm2'" and "export * from 'm3'",
        // even if m2 and m3 both have "export * from 'm4'".
        let astModule = this._astModulesByModuleSymbol.get(moduleSymbol);
        if (!astModule) {
            // (If moduleReference === undefined, then this is the entry point of the local project being analyzed.)
            const externalModulePath = moduleReference !== undefined && isExternal ? moduleReference.moduleSpecifier : undefined;
            astModule = new AstModule_1.AstModule({ sourceFile, moduleSymbol, externalModulePath });
            this._astModulesByModuleSymbol.set(moduleSymbol, astModule);
            if (astModule.isExternal) {
                // It's an external package, so do the special simplified analysis that doesn't crawl into referenced modules
                for (const exportedSymbol of this._typeChecker.getExportsOfModule(moduleSymbol)) {
                    if (externalModulePath === undefined) {
                        throw new node_core_library_1.InternalError('Failed assertion: externalModulePath=undefined but astModule.isExternal=true');
                    }
                    const followedSymbol = TypeScriptHelpers_1.TypeScriptHelpers.followAliases(exportedSymbol, this._typeChecker);
                    // Ignore virtual symbols that don't have any declarations
                    const arbitraryDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.tryGetADeclaration(followedSymbol);
                    if (arbitraryDeclaration) {
                        const astSymbol = this._astSymbolTable.fetchAstSymbol({
                            followedSymbol: followedSymbol,
                            isExternal: astModule.isExternal,
                            includeNominalAnalysis: true,
                            addIfMissing: true
                        });
                        if (!astSymbol) {
                            throw new Error(`Unsupported export ${JSON.stringify(exportedSymbol.name)}:\n` +
                                SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(arbitraryDeclaration));
                        }
                        astModule.cachedExportedEntities.set(exportedSymbol.name, astSymbol);
                    }
                }
            }
            else {
                // The module is part of the local project, so do the full analysis
                if (moduleSymbol.exports) {
                    // The "export * from 'module-name';" declarations are all attached to a single virtual symbol
                    // whose name is InternalSymbolName.ExportStar
                    const exportStarSymbol = moduleSymbol.exports.get(ts.InternalSymbolName.ExportStar);
                    if (exportStarSymbol) {
                        for (const exportStarDeclaration of exportStarSymbol.getDeclarations() || []) {
                            if (ts.isExportDeclaration(exportStarDeclaration)) {
                                const starExportedModule = this._fetchSpecifierAstModule(exportStarDeclaration, exportStarSymbol);
                                if (starExportedModule !== undefined) {
                                    astModule.starExportedModules.add(starExportedModule);
                                }
                            }
                            else {
                                // Ignore ExportDeclaration nodes that don't match the expected pattern
                                // TODO: Should we report a warning?
                            }
                        }
                    }
                }
            }
        }
        return astModule;
    }
    /**
     * Retrieves the symbol for the module corresponding to the ts.SourceFile that is being imported/exported.
     *
     * @remarks
     * The `module` keyword can be used to declare multiple TypeScript modules inside a single source file.
     * (This is a deprecated construct and mainly used for typings such as `@types/node`.)  In this situation,
     * `moduleReference` helps us to fish out the correct module symbol.
     */
    _getModuleSymbolFromSourceFile(sourceFile, moduleReference) {
        const moduleSymbol = TypeScriptInternals_1.TypeScriptInternals.tryGetSymbolForDeclaration(sourceFile, this._typeChecker);
        if (moduleSymbol !== undefined) {
            // This is the normal case.  The SourceFile acts is a module and has a symbol.
            return moduleSymbol;
        }
        if (moduleReference !== undefined) {
            // But there is also an elaborate case where the source file contains one or more "module" declarations,
            // and our moduleReference took us to one of those.
            // eslint-disable-next-line no-bitwise
            if ((moduleReference.moduleSpecifierSymbol.flags & ts.SymbolFlags.Alias) !== 0) {
                // Follow the import/export declaration to one hop the exported item inside the target module
                let followedSymbol = TypeScriptInternals_1.TypeScriptInternals.getImmediateAliasedSymbol(moduleReference.moduleSpecifierSymbol, this._typeChecker);
                if (followedSymbol === undefined) {
                    // This is a workaround for a compiler bug where getImmediateAliasedSymbol() sometimes returns undefined
                    followedSymbol = this._typeChecker.getAliasedSymbol(moduleReference.moduleSpecifierSymbol);
                }
                if (followedSymbol !== undefined && followedSymbol !== moduleReference.moduleSpecifierSymbol) {
                    // The parent of the exported symbol will be the module that we're importing from
                    const parent = TypeScriptInternals_1.TypeScriptInternals.getSymbolParent(followedSymbol);
                    if (parent !== undefined) {
                        // Make sure the thing we found is a module
                        // eslint-disable-next-line no-bitwise
                        if ((parent.flags & ts.SymbolFlags.ValueModule) !== 0) {
                            // Record that that this is an ambient module that can also be imported from
                            this._importableAmbientSourceFiles.add(sourceFile);
                            return parent;
                        }
                    }
                }
            }
        }
        throw new node_core_library_1.InternalError('Unable to determine module for: ' + sourceFile.fileName);
    }
    /**
     * Implementation of {@link AstSymbolTable.fetchAstModuleExportInfo}.
     */
    fetchAstModuleExportInfo(entryPointAstModule) {
        if (entryPointAstModule.isExternal) {
            throw new Error('fetchAstModuleExportInfo() is not supported for external modules');
        }
        if (entryPointAstModule.astModuleExportInfo === undefined) {
            const astModuleExportInfo = new AstModule_1.AstModuleExportInfo();
            this._collectAllExportsRecursive(astModuleExportInfo, entryPointAstModule, new Set());
            entryPointAstModule.astModuleExportInfo = astModuleExportInfo;
        }
        return entryPointAstModule.astModuleExportInfo;
    }
    /**
     * Returns true if the module specifier refers to an external package.  Ignores packages listed in the
     * "bundledPackages" setting from the api-extractor.json config file.
     */
    _isExternalModulePath(importOrExportDeclaration, moduleSpecifier) {
        var _a;
        const specifier = ts.isImportTypeNode(importOrExportDeclaration)
            ? importOrExportDeclaration.argument
            : importOrExportDeclaration.moduleSpecifier;
        const mode = specifier && ts.isStringLiteralLike(specifier)
            ? TypeScriptInternals_1.TypeScriptInternals.getModeForUsageLocation(importOrExportDeclaration.getSourceFile(), specifier)
            : undefined;
        const resolvedModule = TypeScriptInternals_1.TypeScriptInternals.getResolvedModule(importOrExportDeclaration.getSourceFile(), moduleSpecifier, mode);
        if (resolvedModule === undefined) {
            // The TS compiler API `getResolvedModule` cannot resolve ambient modules. Thus, to match API Extractor's
            // previous behavior, simply treat all ambient modules as external. This bug is tracked by
            // https://github.com/microsoft/rushstack/issues/3335.
            return true;
        }
        // Either something like `jquery` or `@microsoft/api-extractor`.
        const packageName = (_a = resolvedModule.packageId) === null || _a === void 0 ? void 0 : _a.name;
        if (packageName !== undefined && this._bundledPackageNames.has(packageName)) {
            return false;
        }
        if (resolvedModule.isExternalLibraryImport === undefined) {
            // This presumably means the compiler couldn't figure out whether the module was external, but we're not
            // sure how this can happen.
            throw new node_core_library_1.InternalError(`Cannot determine whether the module ${JSON.stringify(moduleSpecifier)} is external\n` +
                SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(importOrExportDeclaration));
        }
        return resolvedModule.isExternalLibraryImport;
    }
    /**
     * Returns true if when we analyzed sourceFile, we found that it contains an "export=" statement that allows
     * it to behave /either/ as an ambient module /or/ as a regular importable module.  In this case,
     * `AstSymbolTable._fetchAstSymbol()` will analyze its symbols even though `TypeScriptHelpers.isAmbient()`
     * returns true.
     */
    isImportableAmbientSourceFile(sourceFile) {
        return this._importableAmbientSourceFiles.has(sourceFile);
    }
    _collectAllExportsRecursive(astModuleExportInfo, astModule, visitedAstModules) {
        if (visitedAstModules.has(astModule)) {
            return;
        }
        visitedAstModules.add(astModule);
        if (astModule.isExternal) {
            astModuleExportInfo.starExportedExternalModules.add(astModule);
        }
        else {
            // Fetch each of the explicit exports for this module
            if (astModule.moduleSymbol.exports) {
                astModule.moduleSymbol.exports.forEach((exportSymbol, exportName) => {
                    switch (exportName) {
                        case ts.InternalSymbolName.ExportStar:
                        case ts.InternalSymbolName.ExportEquals:
                            break;
                        default:
                            // Don't collect the "export default" symbol unless this is the entry point module
                            if (exportName !== ts.InternalSymbolName.Default || visitedAstModules.size === 1) {
                                if (!astModuleExportInfo.exportedLocalEntities.has(exportSymbol.name)) {
                                    const astEntity = this._getExportOfAstModule(exportSymbol.name, astModule);
                                    if (astEntity instanceof AstSymbol_1.AstSymbol && !astEntity.isExternal) {
                                        this._astSymbolTable.analyze(astEntity);
                                    }
                                    if (astEntity instanceof AstNamespaceImport_1.AstNamespaceImport && !astEntity.astModule.isExternal) {
                                        this._astSymbolTable.analyze(astEntity);
                                    }
                                    astModuleExportInfo.exportedLocalEntities.set(exportSymbol.name, astEntity);
                                }
                            }
                            break;
                    }
                });
            }
            for (const starExportedModule of astModule.starExportedModules) {
                this._collectAllExportsRecursive(astModuleExportInfo, starExportedModule, visitedAstModules);
            }
        }
    }
    /**
     * For a given symbol (which was encountered in the specified sourceFile), this fetches the AstEntity that it
     * refers to.  For example, if a particular interface describes the return value of a function, this API can help
     * us determine a TSDoc declaration reference for that symbol (if the symbol is exported).
     */
    fetchReferencedAstEntity(symbol, referringModuleIsExternal) {
        // eslint-disable-next-line no-bitwise
        if ((symbol.flags & ts.SymbolFlags.FunctionScopedVariable) !== 0) {
            // If a symbol refers back to part of its own definition, don't follow that rabbit hole
            // Example:
            //
            // function f(x: number): typeof x {
            //    return 123;
            // }
            return undefined;
        }
        let current = symbol;
        if (referringModuleIsExternal) {
            current = TypeScriptHelpers_1.TypeScriptHelpers.followAliases(symbol, this._typeChecker);
        }
        else {
            for (;;) {
                // Is this symbol an import/export that we need to follow to find the real declaration?
                for (const declaration of current.declarations || []) {
                    let matchedAstEntity;
                    matchedAstEntity = this._tryMatchExportDeclaration(declaration, current);
                    if (matchedAstEntity !== undefined) {
                        return matchedAstEntity;
                    }
                    matchedAstEntity = this._tryMatchImportDeclaration(declaration, current);
                    if (matchedAstEntity !== undefined) {
                        return matchedAstEntity;
                    }
                }
                // eslint-disable-next-line no-bitwise
                if (!(current.flags & ts.SymbolFlags.Alias)) {
                    break;
                }
                const currentAlias = TypeScriptInternals_1.TypeScriptInternals.getImmediateAliasedSymbol(current, this._typeChecker);
                // Stop if we reach the end of the chain
                if (!currentAlias || currentAlias === current) {
                    break;
                }
                current = currentAlias;
            }
        }
        // Otherwise, assume it is a normal declaration
        const astSymbol = this._astSymbolTable.fetchAstSymbol({
            followedSymbol: current,
            isExternal: referringModuleIsExternal,
            includeNominalAnalysis: false,
            addIfMissing: true
        });
        return astSymbol;
    }
    fetchReferencedAstEntityFromImportTypeNode(node, referringModuleIsExternal) {
        const externalModulePath = this._tryGetExternalModulePath(node);
        if (externalModulePath) {
            let exportName;
            if (node.qualifier) {
                // Example input:
                //   import('api-extractor-lib1-test').Lib1GenericType<number>
                //
                // Extracted qualifier:
                //   Lib1GenericType
                exportName = node.qualifier.getText().trim();
            }
            else {
                // Example input:
                //   import('api-extractor-lib1-test')
                //
                // Extracted qualifier:
                //   apiExtractorLib1Test
                exportName = SyntaxHelpers_1.SyntaxHelpers.makeCamelCaseIdentifier(externalModulePath);
            }
            return this._fetchAstImport(undefined, {
                importKind: AstImport_1.AstImportKind.ImportType,
                exportName: exportName,
                modulePath: externalModulePath,
                isTypeOnly: false
            });
        }
        // Internal reference: AstSymbol
        const rightMostToken = node.qualifier
            ? node.qualifier.kind === ts.SyntaxKind.QualifiedName
                ? node.qualifier.right
                : node.qualifier
            : node;
        // There is no symbol property in a ImportTypeNode, obtain the associated export symbol
        const exportSymbol = this._typeChecker.getSymbolAtLocation(rightMostToken);
        if (!exportSymbol) {
            throw new node_core_library_1.InternalError(`Symbol not found for identifier: ${node.getText()}\n` +
                SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(node));
        }
        let followedSymbol = exportSymbol;
        for (;;) {
            const referencedAstEntity = this.fetchReferencedAstEntity(followedSymbol, referringModuleIsExternal);
            if (referencedAstEntity) {
                return referencedAstEntity;
            }
            const followedSymbolNode = followedSymbol.declarations && followedSymbol.declarations[0];
            if (followedSymbolNode && followedSymbolNode.kind === ts.SyntaxKind.ImportType) {
                return this.fetchReferencedAstEntityFromImportTypeNode(followedSymbolNode, referringModuleIsExternal);
            }
            // eslint-disable-next-line no-bitwise
            if (!(followedSymbol.flags & ts.SymbolFlags.Alias)) {
                break;
            }
            const currentAlias = this._typeChecker.getAliasedSymbol(followedSymbol);
            if (!currentAlias || currentAlias === followedSymbol) {
                break;
            }
            followedSymbol = currentAlias;
        }
        const astSymbol = this._astSymbolTable.fetchAstSymbol({
            followedSymbol: followedSymbol,
            isExternal: referringModuleIsExternal,
            includeNominalAnalysis: false,
            addIfMissing: true
        });
        return astSymbol;
    }
    _tryMatchExportDeclaration(declaration, declarationSymbol) {
        const exportDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.findFirstParent(declaration, ts.SyntaxKind.ExportDeclaration);
        if (exportDeclaration) {
            let exportName = undefined;
            if (declaration.kind === ts.SyntaxKind.ExportSpecifier) {
                // EXAMPLE:
                // "export { A } from './file-a';"
                //
                // ExportDeclaration:
                //   ExportKeyword:  pre=[export] sep=[ ]
                //   NamedExports:
                //     FirstPunctuation:  pre=[{] sep=[ ]
                //     SyntaxList:
                //       ExportSpecifier:  <------------- declaration
                //         Identifier:  pre=[A] sep=[ ]
                //     CloseBraceToken:  pre=[}] sep=[ ]
                //   FromKeyword:  pre=[from] sep=[ ]
                //   StringLiteral:  pre=['./file-a']
                //   SemicolonToken:  pre=[;]
                // Example: " ExportName as RenamedName"
                const exportSpecifier = declaration;
                exportName = (exportSpecifier.propertyName || exportSpecifier.name).getText().trim();
            }
            else if (declaration.kind === ts.SyntaxKind.NamespaceExport) {
                // EXAMPLE:
                // "export * as theLib from 'the-lib';"
                //
                // ExportDeclaration:
                //   ExportKeyword:  pre=[export] sep=[ ]
                //   NamespaceExport:
                //     AsteriskToken:  pre=[*] sep=[ ]
                //     AsKeyword:  pre=[as] sep=[ ]
                //     Identifier:  pre=[theLib] sep=[ ]
                //   FromKeyword:  pre=[from] sep=[ ]
                //   StringLiteral:  pre=['the-lib']
                //   SemicolonToken:  pre=[;]
                // Issue tracking this feature: https://github.com/microsoft/rushstack/issues/2780
                throw new Error(`The "export * as ___" syntax is not supported yet; as a workaround,` +
                    ` use "import * as ___" with a separate "export { ___ }" declaration\n` +
                    SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(declaration));
            }
            else {
                throw new node_core_library_1.InternalError(`Unimplemented export declaration kind: ${declaration.getText()}\n` +
                    SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(declaration));
            }
            // Ignore "export { A }" without a module specifier
            if (exportDeclaration.moduleSpecifier) {
                const externalModulePath = this._tryGetExternalModulePath(exportDeclaration);
                if (externalModulePath !== undefined) {
                    return this._fetchAstImport(declarationSymbol, {
                        importKind: AstImport_1.AstImportKind.NamedImport,
                        modulePath: externalModulePath,
                        exportName: exportName,
                        isTypeOnly: false
                    });
                }
                return this._getExportOfSpecifierAstModule(exportName, exportDeclaration, declarationSymbol);
            }
        }
        return undefined;
    }
    _tryMatchImportDeclaration(declaration, declarationSymbol) {
        const importDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.findFirstParent(declaration, ts.SyntaxKind.ImportDeclaration);
        if (importDeclaration) {
            const externalModulePath = this._tryGetExternalModulePath(importDeclaration);
            if (declaration.kind === ts.SyntaxKind.NamespaceImport) {
                // EXAMPLE:
                // "import * as theLib from 'the-lib';"
                //
                // ImportDeclaration:
                //   ImportKeyword:  pre=[import] sep=[ ]
                //   ImportClause:
                //     NamespaceImport:  <------------- declaration
                //       AsteriskToken:  pre=[*] sep=[ ]
                //       AsKeyword:  pre=[as] sep=[ ]
                //       Identifier:  pre=[theLib] sep=[ ]
                //   FromKeyword:  pre=[from] sep=[ ]
                //   StringLiteral:  pre=['the-lib']
                //   SemicolonToken:  pre=[;]
                if (externalModulePath === undefined) {
                    const astModule = this._fetchSpecifierAstModule(importDeclaration, declarationSymbol);
                    let namespaceImport = this._astNamespaceImportByModule.get(astModule);
                    if (namespaceImport === undefined) {
                        namespaceImport = new AstNamespaceImport_1.AstNamespaceImport({
                            namespaceName: declarationSymbol.name,
                            astModule: astModule,
                            declaration: declaration,
                            symbol: declarationSymbol
                        });
                        this._astNamespaceImportByModule.set(astModule, namespaceImport);
                    }
                    return namespaceImport;
                }
                // Here importSymbol=undefined because {@inheritDoc} and such are not going to work correctly for
                // a package or source file.
                return this._fetchAstImport(undefined, {
                    importKind: AstImport_1.AstImportKind.StarImport,
                    exportName: declarationSymbol.name,
                    modulePath: externalModulePath,
                    isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
                });
            }
            if (declaration.kind === ts.SyntaxKind.ImportSpecifier) {
                // EXAMPLE:
                // "import { A, B } from 'the-lib';"
                //
                // ImportDeclaration:
                //   ImportKeyword:  pre=[import] sep=[ ]
                //   ImportClause:
                //     NamedImports:
                //       FirstPunctuation:  pre=[{] sep=[ ]
                //       SyntaxList:
                //         ImportSpecifier:  <------------- declaration
                //           Identifier:  pre=[A]
                //         CommaToken:  pre=[,] sep=[ ]
                //         ImportSpecifier:
                //           Identifier:  pre=[B] sep=[ ]
                //       CloseBraceToken:  pre=[}] sep=[ ]
                //   FromKeyword:  pre=[from] sep=[ ]
                //   StringLiteral:  pre=['the-lib']
                //   SemicolonToken:  pre=[;]
                // Example: " ExportName as RenamedName"
                const importSpecifier = declaration;
                const exportName = (importSpecifier.propertyName || importSpecifier.name).getText().trim();
                if (externalModulePath !== undefined) {
                    return this._fetchAstImport(declarationSymbol, {
                        importKind: AstImport_1.AstImportKind.NamedImport,
                        modulePath: externalModulePath,
                        exportName: exportName,
                        isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
                    });
                }
                return this._getExportOfSpecifierAstModule(exportName, importDeclaration, declarationSymbol);
            }
            else if (declaration.kind === ts.SyntaxKind.ImportClause) {
                // EXAMPLE:
                // "import A, { B } from './A';"
                //
                // ImportDeclaration:
                //   ImportKeyword:  pre=[import] sep=[ ]
                //   ImportClause:  <------------- declaration (referring to A)
                //     Identifier:  pre=[A]
                //     CommaToken:  pre=[,] sep=[ ]
                //     NamedImports:
                //       FirstPunctuation:  pre=[{] sep=[ ]
                //       SyntaxList:
                //         ImportSpecifier:
                //           Identifier:  pre=[B] sep=[ ]
                //       CloseBraceToken:  pre=[}] sep=[ ]
                //   FromKeyword:  pre=[from] sep=[ ]
                //   StringLiteral:  pre=['./A']
                //   SemicolonToken:  pre=[;]
                const importClause = declaration;
                const exportName = importClause.name
                    ? importClause.name.getText().trim()
                    : ts.InternalSymbolName.Default;
                if (externalModulePath !== undefined) {
                    return this._fetchAstImport(declarationSymbol, {
                        importKind: AstImport_1.AstImportKind.DefaultImport,
                        modulePath: externalModulePath,
                        exportName,
                        isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
                    });
                }
                return this._getExportOfSpecifierAstModule(ts.InternalSymbolName.Default, importDeclaration, declarationSymbol);
            }
            else {
                throw new node_core_library_1.InternalError(`Unimplemented import declaration kind: ${declaration.getText()}\n` +
                    SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(declaration));
            }
        }
        if (ts.isImportEqualsDeclaration(declaration)) {
            // EXAMPLE:
            // import myLib = require('my-lib');
            //
            // ImportEqualsDeclaration:
            //   ImportKeyword:  pre=[import] sep=[ ]
            //   Identifier:  pre=[myLib] sep=[ ]
            //   FirstAssignment:  pre=[=] sep=[ ]
            //   ExternalModuleReference:
            //     RequireKeyword:  pre=[require]
            //     OpenParenToken:  pre=[(]
            //     StringLiteral:  pre=['my-lib']
            //     CloseParenToken:  pre=[)]
            //   SemicolonToken:  pre=[;]
            if (ts.isExternalModuleReference(declaration.moduleReference)) {
                if (ts.isStringLiteralLike(declaration.moduleReference.expression)) {
                    const variableName = TypeScriptInternals_1.TypeScriptInternals.getTextOfIdentifierOrLiteral(declaration.name);
                    const externalModuleName = TypeScriptInternals_1.TypeScriptInternals.getTextOfIdentifierOrLiteral(declaration.moduleReference.expression);
                    return this._fetchAstImport(declarationSymbol, {
                        importKind: AstImport_1.AstImportKind.EqualsImport,
                        modulePath: externalModuleName,
                        exportName: variableName,
                        isTypeOnly: false
                    });
                }
            }
        }
        return undefined;
    }
    static _getIsTypeOnly(importDeclaration) {
        if (importDeclaration.importClause) {
            return !!importDeclaration.importClause.isTypeOnly;
        }
        return false;
    }
    _getExportOfSpecifierAstModule(exportName, importOrExportDeclaration, exportSymbol) {
        const specifierAstModule = this._fetchSpecifierAstModule(importOrExportDeclaration, exportSymbol);
        const astEntity = this._getExportOfAstModule(exportName, specifierAstModule);
        return astEntity;
    }
    _getExportOfAstModule(exportName, astModule) {
        const visitedAstModules = new Set();
        const astEntity = this._tryGetExportOfAstModule(exportName, astModule, visitedAstModules);
        if (astEntity === undefined) {
            throw new node_core_library_1.InternalError(`Unable to analyze the export ${JSON.stringify(exportName)} in\n` + astModule.sourceFile.fileName);
        }
        return astEntity;
    }
    /**
     * Implementation of {@link AstSymbolTable.tryGetExportOfAstModule}.
     */
    tryGetExportOfAstModule(exportName, astModule) {
        const visitedAstModules = new Set();
        return this._tryGetExportOfAstModule(exportName, astModule, visitedAstModules);
    }
    _tryGetExportOfAstModule(exportName, astModule, visitedAstModules) {
        if (visitedAstModules.has(astModule)) {
            return undefined;
        }
        visitedAstModules.add(astModule);
        let astEntity = astModule.cachedExportedEntities.get(exportName);
        if (astEntity !== undefined) {
            return astEntity;
        }
        // Try the explicit exports
        const escapedExportName = ts.escapeLeadingUnderscores(exportName);
        if (astModule.moduleSymbol.exports) {
            const exportSymbol = astModule.moduleSymbol.exports.get(escapedExportName);
            if (exportSymbol) {
                astEntity = this.fetchReferencedAstEntity(exportSymbol, astModule.isExternal);
                if (astEntity !== undefined) {
                    astModule.cachedExportedEntities.set(exportName, astEntity); // cache for next time
                    return astEntity;
                }
            }
        }
        // Try each of the star imports
        for (const starExportedModule of astModule.starExportedModules) {
            astEntity = this._tryGetExportOfAstModule(exportName, starExportedModule, visitedAstModules);
            if (astEntity !== undefined) {
                if (starExportedModule.externalModulePath !== undefined) {
                    // This entity was obtained from an external module, so return an AstImport instead
                    const astSymbol = astEntity;
                    return this._fetchAstImport(astSymbol.followedSymbol, {
                        importKind: AstImport_1.AstImportKind.NamedImport,
                        modulePath: starExportedModule.externalModulePath,
                        exportName: exportName,
                        isTypeOnly: false
                    });
                }
                return astEntity;
            }
        }
        return undefined;
    }
    _tryGetExternalModulePath(importOrExportDeclaration) {
        const moduleSpecifier = this._getModuleSpecifier(importOrExportDeclaration);
        if (this._isExternalModulePath(importOrExportDeclaration, moduleSpecifier)) {
            return moduleSpecifier;
        }
        return undefined;
    }
    /**
     * Given an ImportDeclaration of the form `export { X } from "___";`, this interprets the module specifier (`"___"`)
     * and fetches the corresponding AstModule object.
     */
    _fetchSpecifierAstModule(importOrExportDeclaration, exportSymbol) {
        const moduleSpecifier = this._getModuleSpecifier(importOrExportDeclaration);
        const mode = importOrExportDeclaration.moduleSpecifier &&
            ts.isStringLiteralLike(importOrExportDeclaration.moduleSpecifier)
            ? TypeScriptInternals_1.TypeScriptInternals.getModeForUsageLocation(importOrExportDeclaration.getSourceFile(), importOrExportDeclaration.moduleSpecifier)
            : undefined;
        const resolvedModule = TypeScriptInternals_1.TypeScriptInternals.getResolvedModule(importOrExportDeclaration.getSourceFile(), moduleSpecifier, mode);
        if (resolvedModule === undefined) {
            // Encountered in https://github.com/microsoft/rushstack/issues/1914.
            //
            // It's also possible for this to occur with ambient modules. However, in practice this doesn't happen
            // as API Extractor treats all ambient modules as external per the logic in `_isExternalModulePath`, and
            // thus this code path is never reached for ambient modules.
            throw new node_core_library_1.InternalError(`getResolvedModule() could not resolve module name ${JSON.stringify(moduleSpecifier)}\n` +
                SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(importOrExportDeclaration));
        }
        // Map the filename back to the corresponding SourceFile. This circuitous approach is needed because
        // we have no way to access the compiler's internal resolveExternalModuleName() function
        const moduleSourceFile = this._program.getSourceFile(resolvedModule.resolvedFileName);
        if (!moduleSourceFile) {
            // This should not happen, since getResolvedModule() specifically looks up names that the compiler
            // found in export declarations for this source file
            throw new node_core_library_1.InternalError(`getSourceFile() failed to locate ${JSON.stringify(resolvedModule.resolvedFileName)}\n` +
                SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(importOrExportDeclaration));
        }
        const isExternal = this._isExternalModulePath(importOrExportDeclaration, moduleSpecifier);
        const moduleReference = {
            moduleSpecifier: moduleSpecifier,
            moduleSpecifierSymbol: exportSymbol
        };
        const specifierAstModule = this.fetchAstModuleFromSourceFile(moduleSourceFile, moduleReference, isExternal);
        return specifierAstModule;
    }
    _fetchAstImport(importSymbol, options) {
        const key = AstImport_1.AstImport.getKey(options);
        let astImport = this._astImportsByKey.get(key);
        if (!astImport) {
            astImport = new AstImport_1.AstImport(options);
            this._astImportsByKey.set(key, astImport);
            if (importSymbol) {
                const followedSymbol = TypeScriptHelpers_1.TypeScriptHelpers.followAliases(importSymbol, this._typeChecker);
                astImport.astSymbol = this._astSymbolTable.fetchAstSymbol({
                    followedSymbol: followedSymbol,
                    isExternal: true,
                    includeNominalAnalysis: false,
                    addIfMissing: true
                });
            }
        }
        else {
            // If we encounter at least one import that does not use the type-only form,
            // then the .d.ts rollup will NOT use "import type".
            if (!options.isTypeOnly) {
                astImport.isTypeOnlyEverywhere = false;
            }
        }
        return astImport;
    }
    _getModuleSpecifier(importOrExportDeclaration) {
        // The name of the module, which could be like "./SomeLocalFile' or like 'external-package/entry/point'
        const moduleSpecifier = TypeScriptHelpers_1.TypeScriptHelpers.getModuleSpecifier(importOrExportDeclaration);
        if (!moduleSpecifier) {
            throw new node_core_library_1.InternalError('Unable to parse module specifier\n' +
                SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(importOrExportDeclaration));
        }
        return moduleSpecifier;
    }
}
exports.ExportAnalyzer = ExportAnalyzer;
//# sourceMappingURL=ExportAnalyzer.js.map