Revision 5f64ae878e892ab293e54e779f470fd0ccc0db2c authored by TypeScript Bot on 10 August 2022, 06:07:48 UTC, committed by TypeScript Bot on 10 August 2022, 06:07:48 UTC
1 parent 35c6fbf
importTracker.ts
/* Code for finding imports of an exported symbol. Used only by FindAllReferences. */
/* @internal */
namespace ts.FindAllReferences {
export interface ImportsResult {
/** For every import of the symbol, the location and local symbol for the import. */
importSearches: readonly [Identifier, Symbol][];
/** For rename imports/exports `{ foo as bar }`, `foo` is not a local, so it may be added as a reference immediately without further searching. */
singleReferences: readonly (Identifier | StringLiteral)[];
/** List of source files that may (or may not) use the symbol via a namespace. (For UMD modules this is every file.) */
indirectUsers: readonly SourceFile[];
}
export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult;
/** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */
export function createImportTracker(sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string>, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker {
const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken);
return (exportSymbol, exportInfo, isForRename) => {
const { directImports, indirectUsers } = getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker, cancellationToken);
return { indirectUsers, ...getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename) };
};
}
/** Info about an exported symbol to perform recursive search on. */
export interface ExportInfo {
exportingModuleSymbol: Symbol;
exportKind: ExportKind;
}
export const enum ExportKind { Named, Default, ExportEquals }
export const enum ImportExport { Import, Export }
interface AmbientModuleDeclaration extends ModuleDeclaration { body?: ModuleBlock; }
type SourceFileLike = SourceFile | AmbientModuleDeclaration;
// Identifier for the case of `const x = require("y")`.
type Importer = AnyImportOrReExport | ValidImportTypeNode | Identifier;
type ImporterOrCallExpression = Importer | CallExpression;
/** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */
function getImportersForExport(
sourceFiles: readonly SourceFile[],
sourceFilesSet: ReadonlySet<string>,
allDirectImports: ESMap<string, ImporterOrCallExpression[]>,
{ exportingModuleSymbol, exportKind }: ExportInfo,
checker: TypeChecker,
cancellationToken: CancellationToken | undefined,
): { directImports: Importer[], indirectUsers: readonly SourceFile[] } {
const markSeenDirectImport = nodeSeenTracker<ImporterOrCallExpression>();
const markSeenIndirectUser = nodeSeenTracker<SourceFileLike>();
const directImports: Importer[] = [];
const isAvailableThroughGlobal = !!exportingModuleSymbol.globalExports;
const indirectUserDeclarations: SourceFileLike[] | undefined = isAvailableThroughGlobal ? undefined : [];
handleDirectImports(exportingModuleSymbol);
return { directImports, indirectUsers: getIndirectUsers() };
function getIndirectUsers(): readonly SourceFile[] {
if (isAvailableThroughGlobal) {
// It has `export as namespace`, so anything could potentially use it.
return sourceFiles;
}
// Module augmentations may use this module's exports without importing it.
if (exportingModuleSymbol.declarations) {
for (const decl of exportingModuleSymbol.declarations) {
if (isExternalModuleAugmentation(decl) && sourceFilesSet.has(decl.getSourceFile().fileName)) {
addIndirectUser(decl);
}
}
}
// 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.
return indirectUserDeclarations!.map<SourceFile>(getSourceFileOfNode);
}
function handleDirectImports(exportingModuleSymbol: Symbol): void {
const theseDirectImports = getDirectImports(exportingModuleSymbol);
if (theseDirectImports) {
for (const direct of theseDirectImports) {
if (!markSeenDirectImport(direct)) {
continue;
}
if (cancellationToken) cancellationToken.throwIfCancellationRequested();
switch (direct.kind) {
case SyntaxKind.CallExpression:
if (isImportCall(direct)) {
handleImportCall(direct);
break;
}
if (!isAvailableThroughGlobal) {
const parent = direct.parent;
if (exportKind === ExportKind.ExportEquals && parent.kind === SyntaxKind.VariableDeclaration) {
const { name } = parent as VariableDeclaration;
if (name.kind === SyntaxKind.Identifier) {
directImports.push(name);
break;
}
}
}
break;
case SyntaxKind.Identifier: // for 'const x = require("y");
break; // TODO: GH#23879
case SyntaxKind.ImportEqualsDeclaration:
handleNamespaceImport(direct, direct.name, hasSyntacticModifier(direct, ModifierFlags.Export), /*alreadyAddedDirect*/ false);
break;
case SyntaxKind.ImportDeclaration:
directImports.push(direct);
const namedBindings = direct.importClause && direct.importClause.namedBindings;
if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) {
handleNamespaceImport(direct, namedBindings.name, /*isReExport*/ false, /*alreadyAddedDirect*/ true);
}
else if (!isAvailableThroughGlobal && isDefaultImport(direct)) {
addIndirectUser(getSourceFileLikeForImportDeclaration(direct)); // Add a check for indirect uses to handle synthetic default imports
}
break;
case SyntaxKind.ExportDeclaration:
if (!direct.exportClause) {
// This is `export * from "foo"`, so imports of this module may import the export too.
handleDirectImports(getContainingModuleSymbol(direct, checker));
}
else if (direct.exportClause.kind === SyntaxKind.NamespaceExport) {
// `export * as foo from "foo"` add to indirect uses
addIndirectUser(getSourceFileLikeForImportDeclaration(direct), /** addTransitiveDependencies */ true);
}
else {
// This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports.
directImports.push(direct);
}
break;
case SyntaxKind.ImportType:
// Only check for typeof import('xyz')
if (direct.isTypeOf && !direct.qualifier && isExported(direct)) {
addIndirectUser(direct.getSourceFile(), /** addTransitiveDependencies */ true);
}
directImports.push(direct);
break;
default:
Debug.failBadSyntaxKind(direct, "Unexpected import kind.");
}
}
}
}
function handleImportCall(importCall: ImportCall) {
const top = findAncestor(importCall, isAmbientModuleDeclaration) || importCall.getSourceFile();
addIndirectUser(top, /** addTransitiveDependencies */ !!isExported(importCall, /** stopAtAmbientModule */ true));
}
function isExported(node: Node, stopAtAmbientModule = false) {
return findAncestor(node, node => {
if (stopAtAmbientModule && isAmbientModuleDeclaration(node)) return "quit";
return canHaveModifiers(node) && some(node.modifiers, isExportModifier);
});
}
function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport: boolean, alreadyAddedDirect: boolean): void {
if (exportKind === ExportKind.ExportEquals) {
// This is a direct import, not import-as-namespace.
if (!alreadyAddedDirect) directImports.push(importDeclaration);
}
else if (!isAvailableThroughGlobal) {
const sourceFileLike = getSourceFileLikeForImportDeclaration(importDeclaration);
Debug.assert(sourceFileLike.kind === SyntaxKind.SourceFile || sourceFileLike.kind === SyntaxKind.ModuleDeclaration);
if (isReExport || findNamespaceReExports(sourceFileLike, name, checker)) {
addIndirectUser(sourceFileLike, /** addTransitiveDependencies */ true);
}
else {
addIndirectUser(sourceFileLike);
}
}
}
/** Adds a module and all of its transitive dependencies as possible indirect users. */
function addIndirectUser(sourceFileLike: SourceFileLike, addTransitiveDependencies = false): void {
Debug.assert(!isAvailableThroughGlobal);
const isNew = markSeenIndirectUser(sourceFileLike);
if (!isNew) return;
indirectUserDeclarations!.push(sourceFileLike); // TODO: GH#18217
if (!addTransitiveDependencies) return;
const moduleSymbol = checker.getMergedSymbol(sourceFileLike.symbol);
if (!moduleSymbol) return;
Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module));
const directImports = getDirectImports(moduleSymbol);
if (directImports) {
for (const directImport of directImports) {
if (!isImportTypeNode(directImport)) {
addIndirectUser(getSourceFileLikeForImportDeclaration(directImport), /** addTransitiveDependencies */ true);
}
}
}
}
function getDirectImports(moduleSymbol: Symbol): ImporterOrCallExpression[] | undefined {
return allDirectImports.get(getSymbolId(moduleSymbol).toString());
}
}
/**
* Given the set of direct imports of a module, we need to find which ones import the particular exported symbol.
* The returned `importSearches` will result in the entire source file being searched.
* But re-exports will be placed in 'singleReferences' since they cannot be locally referenced.
*/
function getSearchesFromDirectImports(directImports: Importer[], exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker, isForRename: boolean): Pick<ImportsResult, "importSearches" | "singleReferences"> {
const importSearches: [Identifier, Symbol][] = [];
const singleReferences: (Identifier | StringLiteral)[] = [];
function addSearch(location: Identifier, symbol: Symbol): void {
importSearches.push([location, symbol]);
}
if (directImports) {
for (const decl of directImports) {
handleImport(decl);
}
}
return { importSearches, singleReferences };
function handleImport(decl: Importer): void {
if (decl.kind === SyntaxKind.ImportEqualsDeclaration) {
if (isExternalModuleImportEquals(decl)) {
handleNamespaceImportLike(decl.name);
}
return;
}
if (decl.kind === SyntaxKind.Identifier) {
handleNamespaceImportLike(decl);
return;
}
if (decl.kind === SyntaxKind.ImportType) {
if (decl.qualifier) {
const firstIdentifier = getFirstIdentifier(decl.qualifier);
if (firstIdentifier.escapedText === symbolName(exportSymbol)) {
singleReferences.push(firstIdentifier);
}
}
else if (exportKind === ExportKind.ExportEquals) {
singleReferences.push(decl.argument.literal);
}
return;
}
// Ignore if there's a grammar error
if (decl.moduleSpecifier!.kind !== SyntaxKind.StringLiteral) {
return;
}
if (decl.kind === SyntaxKind.ExportDeclaration) {
if (decl.exportClause && isNamedExports(decl.exportClause)) {
searchForNamedImport(decl.exportClause);
}
return;
}
const { name, namedBindings } = decl.importClause || { name: undefined, namedBindings: undefined };
if (namedBindings) {
switch (namedBindings.kind) {
case SyntaxKind.NamespaceImport:
handleNamespaceImportLike(namedBindings.name);
break;
case SyntaxKind.NamedImports:
// 'default' might be accessed as a named import `{ default as foo }`.
if (exportKind === ExportKind.Named || exportKind === ExportKind.Default) {
searchForNamedImport(namedBindings);
}
break;
default:
Debug.assertNever(namedBindings);
}
}
// `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals.
// If a default import has the same name as the default export, allow to rename it.
// Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that.
if (name && (exportKind === ExportKind.Default || exportKind === ExportKind.ExportEquals) && (!isForRename || name.escapedText === symbolEscapedNameNoDefault(exportSymbol))) {
const defaultImportAlias = checker.getSymbolAtLocation(name)!;
addSearch(name, defaultImportAlias);
}
}
/**
* `import x = require("./x")` or `import * as x from "./x"`.
* An `export =` may be imported by this syntax, so it may be a direct import.
* If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here.
*/
function handleNamespaceImportLike(importName: Identifier): void {
// Don't rename an import that already has a different name than the export.
if (exportKind === ExportKind.ExportEquals && (!isForRename || isNameMatch(importName.escapedText))) {
addSearch(importName, checker.getSymbolAtLocation(importName)!);
}
}
function searchForNamedImport(namedBindings: NamedImportsOrExports | undefined): void {
if (!namedBindings) {
return;
}
for (const element of namedBindings.elements) {
const { name, propertyName } = element;
if (!isNameMatch((propertyName || name).escapedText)) {
continue;
}
if (propertyName) {
// 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.
singleReferences.push(propertyName);
// If renaming `{ foo as bar }`, don't touch `bar`, just `foo`.
// But do rename `foo` in ` { default as foo }` if that's the original export name.
if (!isForRename || name.escapedText === exportSymbol.escapedName) {
// Search locally for `bar`.
addSearch(name, checker.getSymbolAtLocation(name)!);
}
}
else {
const localSymbol = element.kind === SyntaxKind.ExportSpecifier && element.propertyName
? checker.getExportSpecifierLocalTargetSymbol(element)! // For re-exporting under a different name, we want to get the re-exported symbol.
: checker.getSymbolAtLocation(name)!;
addSearch(name, localSymbol);
}
}
}
function isNameMatch(name: __String): boolean {
// Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports
return name === exportSymbol.escapedName || exportKind !== ExportKind.Named && name === InternalSymbolName.Default;
}
}
/** Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally. */
function findNamespaceReExports(sourceFileLike: SourceFileLike, name: Identifier, checker: TypeChecker): boolean {
const namespaceImportSymbol = checker.getSymbolAtLocation(name);
return !!forEachPossibleImportOrExportStatement(sourceFileLike, statement => {
if (!isExportDeclaration(statement)) return;
const { exportClause, moduleSpecifier } = statement;
return !moduleSpecifier && exportClause && isNamedExports(exportClause) &&
exportClause.elements.some(element => checker.getExportSpecifierLocalTargetSymbol(element) === namespaceImportSymbol);
});
}
export type ModuleReference =
/** "import" also includes require() calls. */
| { kind: "import", literal: StringLiteralLike }
/** <reference path> or <reference types> */
| { kind: "reference", referencingFile: SourceFile, ref: FileReference };
export function findModuleReferences(program: Program, sourceFiles: readonly SourceFile[], searchModuleSymbol: Symbol): ModuleReference[] {
const refs: ModuleReference[] = [];
const checker = program.getTypeChecker();
for (const referencingFile of sourceFiles) {
const searchSourceFile = searchModuleSymbol.valueDeclaration;
if (searchSourceFile?.kind === SyntaxKind.SourceFile) {
for (const ref of referencingFile.referencedFiles) {
if (program.getSourceFileFromReference(referencingFile, ref) === searchSourceFile) {
refs.push({ kind: "reference", referencingFile, ref });
}
}
for (const ref of referencingFile.typeReferenceDirectives) {
const referenced = program.getResolvedTypeReferenceDirectives().get(ref.fileName, ref.resolutionMode || referencingFile.impliedNodeFormat);
if (referenced !== undefined && referenced.resolvedFileName === (searchSourceFile as SourceFile).fileName) {
refs.push({ kind: "reference", referencingFile, ref });
}
}
}
forEachImport(referencingFile, (_importDecl, moduleSpecifier) => {
const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier);
if (moduleSymbol === searchModuleSymbol) {
refs.push({ kind: "import", literal: moduleSpecifier });
}
});
}
return refs;
}
/** Returns a map from a module symbol Id to all import statements that directly reference the module. */
function getDirectImportsMap(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken | undefined): ESMap<string, ImporterOrCallExpression[]> {
const map = new Map<string, ImporterOrCallExpression[]>();
for (const sourceFile of sourceFiles) {
if (cancellationToken) cancellationToken.throwIfCancellationRequested();
forEachImport(sourceFile, (importDecl, moduleSpecifier) => {
const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier);
if (moduleSymbol) {
const id = getSymbolId(moduleSymbol).toString();
let imports = map.get(id);
if (!imports) {
map.set(id, imports = []);
}
imports.push(importDecl);
}
});
}
return map;
}
/** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */
function forEachPossibleImportOrExportStatement<T>(sourceFileLike: SourceFileLike, action: (statement: Statement) => T) {
return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body!.statements, statement => // TODO: GH#18217
action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action)));
}
/** Calls `action` for each import, re-export, or require() in a file. */
function forEachImport(sourceFile: SourceFile, action: (importStatement: ImporterOrCallExpression, imported: StringLiteralLike) => void): void {
if (sourceFile.externalModuleIndicator || sourceFile.imports !== undefined) {
for (const i of sourceFile.imports) {
action(importFromModuleSpecifier(i), i);
}
}
else {
forEachPossibleImportOrExportStatement(sourceFile, statement => {
switch (statement.kind) {
case SyntaxKind.ExportDeclaration:
case SyntaxKind.ImportDeclaration: {
const decl = statement as ImportDeclaration | ExportDeclaration;
if (decl.moduleSpecifier && isStringLiteral(decl.moduleSpecifier)) {
action(decl, decl.moduleSpecifier);
}
break;
}
case SyntaxKind.ImportEqualsDeclaration: {
const decl = statement as ImportEqualsDeclaration;
if (isExternalModuleImportEquals(decl)) {
action(decl, decl.moduleReference.expression);
}
break;
}
}
});
}
}
export interface ImportedSymbol {
kind: ImportExport.Import;
symbol: Symbol;
}
export interface ExportedSymbol {
kind: ImportExport.Export;
symbol: Symbol;
exportInfo: ExportInfo;
}
/**
* Given a local reference, we might notice that it's an import/export and recursively search for references of that.
* If at an import, look locally for the symbol it imports.
* If at an export, look for all imports of it.
* This doesn't handle export specifiers; that is done in `getReferencesAtExportSpecifier`.
* @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.
*/
export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined {
return comingFromExport ? getExport() : getExport() || getImport();
function getExport(): ExportedSymbol | ImportedSymbol | undefined {
const { parent } = node;
const grandparent = parent.parent;
if (symbol.exportSymbol) {
if (parent.kind === SyntaxKind.PropertyAccessExpression) {
// 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.
// So check that we are at the declaration.
return symbol.declarations?.some(d => d === parent) && isBinaryExpression(grandparent)
? getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ false)
: undefined;
}
else {
return exportInfo(symbol.exportSymbol, getExportKindForDeclaration(parent));
}
}
else {
const exportNode = getExportNode(parent, node);
if (exportNode && hasSyntacticModifier(exportNode, ModifierFlags.Export)) {
if (isImportEqualsDeclaration(exportNode) && exportNode.moduleReference === node) {
// 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.
if (comingFromExport) {
return undefined;
}
const lhsSymbol = checker.getSymbolAtLocation(exportNode.name)!;
return { kind: ImportExport.Import, symbol: lhsSymbol };
}
else {
return exportInfo(symbol, getExportKindForDeclaration(exportNode));
}
}
else if (isNamespaceExport(parent)) {
return exportInfo(symbol, ExportKind.Named);
}
// If we are in `export = a;` or `export default a;`, `parent` is the export assignment.
else if (isExportAssignment(parent)) {
return getExportAssignmentExport(parent);
}
// If we are in `export = class A {};` (or `export = class A {};`) at `A`, `parent.parent` is the export assignment.
else if (isExportAssignment(grandparent)) {
return getExportAssignmentExport(grandparent);
}
// Similar for `module.exports =` and `exports.A =`.
else if (isBinaryExpression(parent)) {
return getSpecialPropertyExport(parent, /*useLhsSymbol*/ true);
}
else if (isBinaryExpression(grandparent)) {
return getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ true);
}
else if (isJSDocTypedefTag(parent)) {
return exportInfo(symbol, ExportKind.Named);
}
}
function getExportAssignmentExport(ex: ExportAssignment): ExportedSymbol | undefined {
// Get the symbol for the `export =` node; its parent is the module it's the export of.
if (!ex.symbol.parent) return undefined;
const exportKind = ex.isExportEquals ? ExportKind.ExportEquals : ExportKind.Default;
return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol: ex.symbol.parent, exportKind } };
}
function getSpecialPropertyExport(node: BinaryExpression, useLhsSymbol: boolean): ExportedSymbol | undefined {
let kind: ExportKind;
switch (getAssignmentDeclarationKind(node)) {
case AssignmentDeclarationKind.ExportsProperty:
kind = ExportKind.Named;
break;
case AssignmentDeclarationKind.ModuleExports:
kind = ExportKind.ExportEquals;
break;
default:
return undefined;
}
const sym = useLhsSymbol ? checker.getSymbolAtLocation(getNameOfAccessExpression(cast(node.left, isAccessExpression))) : symbol;
return sym && exportInfo(sym, kind);
}
}
function getImport(): ImportedSymbol | undefined {
const isImport = isNodeImport(node);
if (!isImport) return undefined;
// A symbol being imported is always an alias. So get what that aliases to find the local symbol.
let importedSymbol = checker.getImmediateAliasedSymbol(symbol);
if (!importedSymbol) return undefined;
// Search on the local symbol in the exporting module, not the exported symbol.
importedSymbol = skipExportSpecifierSymbol(importedSymbol, checker);
// Similarly, skip past the symbol for 'export ='
if (importedSymbol.escapedName === "export=") {
importedSymbol = getExportEqualsLocalSymbol(importedSymbol, checker);
}
// If the import has a different name than the export, do not continue searching.
// If `importedName` is undefined, do continue searching as the export is anonymous.
// (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.)
const importedName = symbolEscapedNameNoDefault(importedSymbol);
if (importedName === undefined || importedName === InternalSymbolName.Default || importedName === symbol.escapedName) {
return { kind: ImportExport.Import, symbol: importedSymbol };
}
}
function exportInfo(symbol: Symbol, kind: ExportKind): ExportedSymbol | undefined {
const exportInfo = getExportInfo(symbol, kind, checker);
return exportInfo && { kind: ImportExport.Export, symbol, exportInfo };
}
// Not meant for use with export specifiers or export assignment.
function getExportKindForDeclaration(node: Node): ExportKind {
return hasSyntacticModifier(node, ModifierFlags.Default) ? ExportKind.Default : ExportKind.Named;
}
}
function getExportEqualsLocalSymbol(importedSymbol: Symbol, checker: TypeChecker): Symbol {
if (importedSymbol.flags & SymbolFlags.Alias) {
return Debug.checkDefined(checker.getImmediateAliasedSymbol(importedSymbol));
}
const decl = Debug.checkDefined(importedSymbol.valueDeclaration);
if (isExportAssignment(decl)) { // `export = class {}`
return Debug.checkDefined(decl.expression.symbol);
}
else if (isBinaryExpression(decl)) { // `module.exports = class {}`
return Debug.checkDefined(decl.right.symbol);
}
else if (isSourceFile(decl)) { // json module
return Debug.checkDefined(decl.symbol);
}
return Debug.fail();
}
// If a reference is a class expression, the exported node would be its parent.
// If a reference is a variable declaration, the exported node would be the variable statement.
function getExportNode(parent: Node, node: Node): Node | undefined {
const declaration = isVariableDeclaration(parent) ? parent : isBindingElement(parent) ? walkUpBindingElementsAndPatterns(parent) : undefined;
if (declaration) {
return (parent as VariableDeclaration | BindingElement).name !== node ? undefined :
isCatchClause(declaration.parent) ? undefined : isVariableStatement(declaration.parent.parent) ? declaration.parent.parent : undefined;
}
else {
return parent;
}
}
function isNodeImport(node: Node): boolean {
const { parent } = node;
switch (parent.kind) {
case SyntaxKind.ImportEqualsDeclaration:
return (parent as ImportEqualsDeclaration).name === node && isExternalModuleImportEquals(parent as ImportEqualsDeclaration);
case SyntaxKind.ImportSpecifier:
// For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`.
return !(parent as ImportSpecifier).propertyName;
case SyntaxKind.ImportClause:
case SyntaxKind.NamespaceImport:
Debug.assert((parent as ImportClause | NamespaceImport).name === node);
return true;
case SyntaxKind.BindingElement:
return isInJSFile(node) && isVariableDeclarationInitializedToBareOrAccessedRequire(parent.parent.parent);
default:
return false;
}
}
export function getExportInfo(exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker): ExportInfo | undefined {
const moduleSymbol = exportSymbol.parent;
if (!moduleSymbol) return undefined; // This can happen if an `export` is not at the top-level (which is a compile error).
const exportingModuleSymbol = checker.getMergedSymbol(moduleSymbol); // Need to get merged symbol in case there's an augmentation.
// `export` may appear in a namespace. In that case, just rely on global search.
return isExternalModuleSymbol(exportingModuleSymbol) ? { exportingModuleSymbol, exportKind } : undefined;
}
/** If at an export specifier, go to the symbol it refers to. */
function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol {
// For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does.
if (symbol.declarations) {
for (const declaration of symbol.declarations) {
if (isExportSpecifier(declaration) && !declaration.propertyName && !declaration.parent.parent.moduleSpecifier) {
return checker.getExportSpecifierLocalTargetSymbol(declaration)!;
}
else if (isPropertyAccessExpression(declaration) && isModuleExportsAccessExpression(declaration.expression) && !isPrivateIdentifier(declaration.name)) {
// Export of form 'module.exports.propName = expr';
return checker.getSymbolAtLocation(declaration)!;
}
else if (isShorthandPropertyAssignment(declaration)
&& isBinaryExpression(declaration.parent.parent)
&& getAssignmentDeclarationKind(declaration.parent.parent) === AssignmentDeclarationKind.ModuleExports) {
return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!;
}
}
}
return symbol;
}
function getContainingModuleSymbol(importer: Importer, checker: TypeChecker): Symbol {
return checker.getMergedSymbol(getSourceFileLikeForImportDeclaration(importer).symbol);
}
function getSourceFileLikeForImportDeclaration(node: ImporterOrCallExpression): SourceFileLike {
if (node.kind === SyntaxKind.CallExpression) {
return node.getSourceFile();
}
const { parent } = node;
if (parent.kind === SyntaxKind.SourceFile) {
return parent as SourceFile;
}
Debug.assert(parent.kind === SyntaxKind.ModuleBlock);
return cast(parent.parent, isAmbientModuleDeclaration);
}
function isAmbientModuleDeclaration(node: Node): node is AmbientModuleDeclaration {
return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).name.kind === SyntaxKind.StringLiteral;
}
function isExternalModuleImportEquals(eq: ImportEqualsDeclaration): eq is ImportEqualsDeclaration & { moduleReference: { expression: StringLiteral } } {
return eq.moduleReference.kind === SyntaxKind.ExternalModuleReference && eq.moduleReference.expression.kind === SyntaxKind.StringLiteral;
}
}
Computing file changes ...