123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- import {isDeclaration} from "./parser/tokenizer";
- import {ContextualKeyword} from "./parser/tokenizer/keywords";
- import {TokenType as tt} from "./parser/tokenizer/types";
- import getImportExportSpecifierInfo from "./util/getImportExportSpecifierInfo";
- import {getNonTypeIdentifiers} from "./util/getNonTypeIdentifiers";
- /**
- * Class responsible for preprocessing and bookkeeping import and export declarations within the
- * file.
- *
- * TypeScript uses a simpler mechanism that does not use functions like interopRequireDefault and
- * interopRequireWildcard, so we also allow that mode for compatibility.
- */
- export default class CJSImportProcessor {
- __init() {this.nonTypeIdentifiers = new Set()}
- __init2() {this.importInfoByPath = new Map()}
- __init3() {this.importsToReplace = new Map()}
- __init4() {this.identifierReplacements = new Map()}
- __init5() {this.exportBindingsByLocalName = new Map()}
- constructor(
- nameManager,
- tokens,
- enableLegacyTypeScriptModuleInterop,
- options,
- isTypeScriptTransformEnabled,
- keepUnusedImports,
- helperManager,
- ) {;this.nameManager = nameManager;this.tokens = tokens;this.enableLegacyTypeScriptModuleInterop = enableLegacyTypeScriptModuleInterop;this.options = options;this.isTypeScriptTransformEnabled = isTypeScriptTransformEnabled;this.keepUnusedImports = keepUnusedImports;this.helperManager = helperManager;CJSImportProcessor.prototype.__init.call(this);CJSImportProcessor.prototype.__init2.call(this);CJSImportProcessor.prototype.__init3.call(this);CJSImportProcessor.prototype.__init4.call(this);CJSImportProcessor.prototype.__init5.call(this);}
- preprocessTokens() {
- for (let i = 0; i < this.tokens.tokens.length; i++) {
- if (
- this.tokens.matches1AtIndex(i, tt._import) &&
- !this.tokens.matches3AtIndex(i, tt._import, tt.name, tt.eq)
- ) {
- this.preprocessImportAtIndex(i);
- }
- if (
- this.tokens.matches1AtIndex(i, tt._export) &&
- !this.tokens.matches2AtIndex(i, tt._export, tt.eq)
- ) {
- this.preprocessExportAtIndex(i);
- }
- }
- this.generateImportReplacements();
- }
- /**
- * In TypeScript, import statements that only import types should be removed.
- * This includes `import {} from 'foo';`, but not `import 'foo';`.
- */
- pruneTypeOnlyImports() {
- this.nonTypeIdentifiers = getNonTypeIdentifiers(this.tokens, this.options);
- for (const [path, importInfo] of this.importInfoByPath.entries()) {
- if (
- importInfo.hasBareImport ||
- importInfo.hasStarExport ||
- importInfo.exportStarNames.length > 0 ||
- importInfo.namedExports.length > 0
- ) {
- continue;
- }
- const names = [
- ...importInfo.defaultNames,
- ...importInfo.wildcardNames,
- ...importInfo.namedImports.map(({localName}) => localName),
- ];
- if (names.every((name) => this.shouldAutomaticallyElideImportedName(name))) {
- this.importsToReplace.set(path, "");
- }
- }
- }
- shouldAutomaticallyElideImportedName(name) {
- return (
- this.isTypeScriptTransformEnabled &&
- !this.keepUnusedImports &&
- !this.nonTypeIdentifiers.has(name)
- );
- }
- generateImportReplacements() {
- for (const [path, importInfo] of this.importInfoByPath.entries()) {
- const {
- defaultNames,
- wildcardNames,
- namedImports,
- namedExports,
- exportStarNames,
- hasStarExport,
- } = importInfo;
- if (
- defaultNames.length === 0 &&
- wildcardNames.length === 0 &&
- namedImports.length === 0 &&
- namedExports.length === 0 &&
- exportStarNames.length === 0 &&
- !hasStarExport
- ) {
- // Import is never used, so don't even assign a name.
- this.importsToReplace.set(path, `require('${path}');`);
- continue;
- }
- const primaryImportName = this.getFreeIdentifierForPath(path);
- let secondaryImportName;
- if (this.enableLegacyTypeScriptModuleInterop) {
- secondaryImportName = primaryImportName;
- } else {
- secondaryImportName =
- wildcardNames.length > 0 ? wildcardNames[0] : this.getFreeIdentifierForPath(path);
- }
- let requireCode = `var ${primaryImportName} = require('${path}');`;
- if (wildcardNames.length > 0) {
- for (const wildcardName of wildcardNames) {
- const moduleExpr = this.enableLegacyTypeScriptModuleInterop
- ? primaryImportName
- : `${this.helperManager.getHelperName("interopRequireWildcard")}(${primaryImportName})`;
- requireCode += ` var ${wildcardName} = ${moduleExpr};`;
- }
- } else if (exportStarNames.length > 0 && secondaryImportName !== primaryImportName) {
- requireCode += ` var ${secondaryImportName} = ${this.helperManager.getHelperName(
- "interopRequireWildcard",
- )}(${primaryImportName});`;
- } else if (defaultNames.length > 0 && secondaryImportName !== primaryImportName) {
- requireCode += ` var ${secondaryImportName} = ${this.helperManager.getHelperName(
- "interopRequireDefault",
- )}(${primaryImportName});`;
- }
- for (const {importedName, localName} of namedExports) {
- requireCode += ` ${this.helperManager.getHelperName(
- "createNamedExportFrom",
- )}(${primaryImportName}, '${localName}', '${importedName}');`;
- }
- for (const exportStarName of exportStarNames) {
- requireCode += ` exports.${exportStarName} = ${secondaryImportName};`;
- }
- if (hasStarExport) {
- requireCode += ` ${this.helperManager.getHelperName(
- "createStarExport",
- )}(${primaryImportName});`;
- }
- this.importsToReplace.set(path, requireCode);
- for (const defaultName of defaultNames) {
- this.identifierReplacements.set(defaultName, `${secondaryImportName}.default`);
- }
- for (const {importedName, localName} of namedImports) {
- this.identifierReplacements.set(localName, `${primaryImportName}.${importedName}`);
- }
- }
- }
- getFreeIdentifierForPath(path) {
- const components = path.split("/");
- const lastComponent = components[components.length - 1];
- const baseName = lastComponent.replace(/\W/g, "");
- return this.nameManager.claimFreeName(`_${baseName}`);
- }
- preprocessImportAtIndex(index) {
- const defaultNames = [];
- const wildcardNames = [];
- const namedImports = [];
- index++;
- if (
- (this.tokens.matchesContextualAtIndex(index, ContextualKeyword._type) ||
- this.tokens.matches1AtIndex(index, tt._typeof)) &&
- !this.tokens.matches1AtIndex(index + 1, tt.comma) &&
- !this.tokens.matchesContextualAtIndex(index + 1, ContextualKeyword._from)
- ) {
- // import type declaration, so no need to process anything.
- return;
- }
- if (this.tokens.matches1AtIndex(index, tt.parenL)) {
- // Dynamic import, so nothing to do
- return;
- }
- if (this.tokens.matches1AtIndex(index, tt.name)) {
- defaultNames.push(this.tokens.identifierNameAtIndex(index));
- index++;
- if (this.tokens.matches1AtIndex(index, tt.comma)) {
- index++;
- }
- }
- if (this.tokens.matches1AtIndex(index, tt.star)) {
- // * as
- index += 2;
- wildcardNames.push(this.tokens.identifierNameAtIndex(index));
- index++;
- }
- if (this.tokens.matches1AtIndex(index, tt.braceL)) {
- const result = this.getNamedImports(index + 1);
- index = result.newIndex;
- for (const namedImport of result.namedImports) {
- // Treat {default as X} as a default import to ensure usage of require interop helper
- if (namedImport.importedName === "default") {
- defaultNames.push(namedImport.localName);
- } else {
- namedImports.push(namedImport);
- }
- }
- }
- if (this.tokens.matchesContextualAtIndex(index, ContextualKeyword._from)) {
- index++;
- }
- if (!this.tokens.matches1AtIndex(index, tt.string)) {
- throw new Error("Expected string token at the end of import statement.");
- }
- const path = this.tokens.stringValueAtIndex(index);
- const importInfo = this.getImportInfo(path);
- importInfo.defaultNames.push(...defaultNames);
- importInfo.wildcardNames.push(...wildcardNames);
- importInfo.namedImports.push(...namedImports);
- if (defaultNames.length === 0 && wildcardNames.length === 0 && namedImports.length === 0) {
- importInfo.hasBareImport = true;
- }
- }
- preprocessExportAtIndex(index) {
- if (
- this.tokens.matches2AtIndex(index, tt._export, tt._var) ||
- this.tokens.matches2AtIndex(index, tt._export, tt._let) ||
- this.tokens.matches2AtIndex(index, tt._export, tt._const)
- ) {
- this.preprocessVarExportAtIndex(index);
- } else if (
- this.tokens.matches2AtIndex(index, tt._export, tt._function) ||
- this.tokens.matches2AtIndex(index, tt._export, tt._class)
- ) {
- const exportName = this.tokens.identifierNameAtIndex(index + 2);
- this.addExportBinding(exportName, exportName);
- } else if (this.tokens.matches3AtIndex(index, tt._export, tt.name, tt._function)) {
- const exportName = this.tokens.identifierNameAtIndex(index + 3);
- this.addExportBinding(exportName, exportName);
- } else if (this.tokens.matches2AtIndex(index, tt._export, tt.braceL)) {
- this.preprocessNamedExportAtIndex(index);
- } else if (this.tokens.matches2AtIndex(index, tt._export, tt.star)) {
- this.preprocessExportStarAtIndex(index);
- }
- }
- preprocessVarExportAtIndex(index) {
- let depth = 0;
- // Handle cases like `export let {x} = y;`, starting at the open-brace in that case.
- for (let i = index + 2; ; i++) {
- if (
- this.tokens.matches1AtIndex(i, tt.braceL) ||
- this.tokens.matches1AtIndex(i, tt.dollarBraceL) ||
- this.tokens.matches1AtIndex(i, tt.bracketL)
- ) {
- depth++;
- } else if (
- this.tokens.matches1AtIndex(i, tt.braceR) ||
- this.tokens.matches1AtIndex(i, tt.bracketR)
- ) {
- depth--;
- } else if (depth === 0 && !this.tokens.matches1AtIndex(i, tt.name)) {
- break;
- } else if (this.tokens.matches1AtIndex(1, tt.eq)) {
- const endIndex = this.tokens.currentToken().rhsEndIndex;
- if (endIndex == null) {
- throw new Error("Expected = token with an end index.");
- }
- i = endIndex - 1;
- } else {
- const token = this.tokens.tokens[i];
- if (isDeclaration(token)) {
- const exportName = this.tokens.identifierNameAtIndex(i);
- this.identifierReplacements.set(exportName, `exports.${exportName}`);
- }
- }
- }
- }
- /**
- * Walk this export statement just in case it's an export...from statement.
- * If it is, combine it into the import info for that path. Otherwise, just
- * bail out; it'll be handled later.
- */
- preprocessNamedExportAtIndex(index) {
- // export {
- index += 2;
- const {newIndex, namedImports} = this.getNamedImports(index);
- index = newIndex;
- if (this.tokens.matchesContextualAtIndex(index, ContextualKeyword._from)) {
- index++;
- } else {
- // Reinterpret "a as b" to be local/exported rather than imported/local.
- for (const {importedName: localName, localName: exportedName} of namedImports) {
- this.addExportBinding(localName, exportedName);
- }
- return;
- }
- if (!this.tokens.matches1AtIndex(index, tt.string)) {
- throw new Error("Expected string token at the end of import statement.");
- }
- const path = this.tokens.stringValueAtIndex(index);
- const importInfo = this.getImportInfo(path);
- importInfo.namedExports.push(...namedImports);
- }
- preprocessExportStarAtIndex(index) {
- let exportedName = null;
- if (this.tokens.matches3AtIndex(index, tt._export, tt.star, tt._as)) {
- // export * as
- index += 3;
- exportedName = this.tokens.identifierNameAtIndex(index);
- // foo from
- index += 2;
- } else {
- // export * from
- index += 3;
- }
- if (!this.tokens.matches1AtIndex(index, tt.string)) {
- throw new Error("Expected string token at the end of star export statement.");
- }
- const path = this.tokens.stringValueAtIndex(index);
- const importInfo = this.getImportInfo(path);
- if (exportedName !== null) {
- importInfo.exportStarNames.push(exportedName);
- } else {
- importInfo.hasStarExport = true;
- }
- }
- getNamedImports(index) {
- const namedImports = [];
- while (true) {
- if (this.tokens.matches1AtIndex(index, tt.braceR)) {
- index++;
- break;
- }
- const specifierInfo = getImportExportSpecifierInfo(this.tokens, index);
- index = specifierInfo.endIndex;
- if (!specifierInfo.isType) {
- namedImports.push({
- importedName: specifierInfo.leftName,
- localName: specifierInfo.rightName,
- });
- }
- if (this.tokens.matches2AtIndex(index, tt.comma, tt.braceR)) {
- index += 2;
- break;
- } else if (this.tokens.matches1AtIndex(index, tt.braceR)) {
- index++;
- break;
- } else if (this.tokens.matches1AtIndex(index, tt.comma)) {
- index++;
- } else {
- throw new Error(`Unexpected token: ${JSON.stringify(this.tokens.tokens[index])}`);
- }
- }
- return {newIndex: index, namedImports};
- }
- /**
- * Get a mutable import info object for this path, creating one if it doesn't
- * exist yet.
- */
- getImportInfo(path) {
- const existingInfo = this.importInfoByPath.get(path);
- if (existingInfo) {
- return existingInfo;
- }
- const newInfo = {
- defaultNames: [],
- wildcardNames: [],
- namedImports: [],
- namedExports: [],
- hasBareImport: false,
- exportStarNames: [],
- hasStarExport: false,
- };
- this.importInfoByPath.set(path, newInfo);
- return newInfo;
- }
- addExportBinding(localName, exportedName) {
- if (!this.exportBindingsByLocalName.has(localName)) {
- this.exportBindingsByLocalName.set(localName, []);
- }
- this.exportBindingsByLocalName.get(localName).push(exportedName);
- }
- /**
- * Return the code to use for the import for this path, or the empty string if
- * the code has already been "claimed" by a previous import.
- */
- claimImportCode(importPath) {
- const result = this.importsToReplace.get(importPath);
- this.importsToReplace.set(importPath, "");
- return result || "";
- }
- getIdentifierReplacement(identifierName) {
- return this.identifierReplacements.get(identifierName) || null;
- }
- /**
- * Return a string like `exports.foo = exports.bar`.
- */
- resolveExportBinding(assignedName) {
- const exportedNames = this.exportBindingsByLocalName.get(assignedName);
- if (!exportedNames || exportedNames.length === 0) {
- return null;
- }
- return exportedNames.map((exportedName) => `exports.${exportedName}`).join(" = ");
- }
- /**
- * Return all imported/exported names where we might be interested in whether usages of those
- * names are shadowed.
- */
- getGlobalNames() {
- return new Set([
- ...this.identifierReplacements.keys(),
- ...this.exportBindingsByLocalName.keys(),
- ]);
- }
- }
|