From b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c Mon Sep 17 00:00:00 2001 From: Philipp Tanlak Date: Mon, 24 Nov 2025 20:54:57 +0100 Subject: Docs --- .../dist/esm/transformers/CJSImportTransformer.js | 916 +++++++++++++++++++++ 1 file changed, 916 insertions(+) create mode 100644 node_modules/sucrase/dist/esm/transformers/CJSImportTransformer.js (limited to 'node_modules/sucrase/dist/esm/transformers/CJSImportTransformer.js') diff --git a/node_modules/sucrase/dist/esm/transformers/CJSImportTransformer.js b/node_modules/sucrase/dist/esm/transformers/CJSImportTransformer.js new file mode 100644 index 0000000..78cf896 --- /dev/null +++ b/node_modules/sucrase/dist/esm/transformers/CJSImportTransformer.js @@ -0,0 +1,916 @@ + + + +import {IdentifierRole, isDeclaration, isObjectShorthandDeclaration} from "../parser/tokenizer"; +import {ContextualKeyword} from "../parser/tokenizer/keywords"; +import {TokenType as tt} from "../parser/tokenizer/types"; + +import elideImportEquals from "../util/elideImportEquals"; +import getDeclarationInfo, { + + EMPTY_DECLARATION_INFO, +} from "../util/getDeclarationInfo"; +import getImportExportSpecifierInfo from "../util/getImportExportSpecifierInfo"; +import isExportFrom from "../util/isExportFrom"; +import {removeMaybeImportAttributes} from "../util/removeMaybeImportAttributes"; +import shouldElideDefaultExport from "../util/shouldElideDefaultExport"; + + +import Transformer from "./Transformer"; + +/** + * Class for editing import statements when we are transforming to commonjs. + */ +export default class CJSImportTransformer extends Transformer { + __init() {this.hadExport = false} + __init2() {this.hadNamedExport = false} + __init3() {this.hadDefaultExport = false} + + + constructor( + rootTransformer, + tokens, + importProcessor, + nameManager, + helperManager, + reactHotLoaderTransformer, + enableLegacyBabel5ModuleInterop, + enableLegacyTypeScriptModuleInterop, + isTypeScriptTransformEnabled, + isFlowTransformEnabled, + preserveDynamicImport, + keepUnusedImports, + ) { + super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.importProcessor = importProcessor;this.nameManager = nameManager;this.helperManager = helperManager;this.reactHotLoaderTransformer = reactHotLoaderTransformer;this.enableLegacyBabel5ModuleInterop = enableLegacyBabel5ModuleInterop;this.enableLegacyTypeScriptModuleInterop = enableLegacyTypeScriptModuleInterop;this.isTypeScriptTransformEnabled = isTypeScriptTransformEnabled;this.isFlowTransformEnabled = isFlowTransformEnabled;this.preserveDynamicImport = preserveDynamicImport;this.keepUnusedImports = keepUnusedImports;CJSImportTransformer.prototype.__init.call(this);CJSImportTransformer.prototype.__init2.call(this);CJSImportTransformer.prototype.__init3.call(this);; + this.declarationInfo = isTypeScriptTransformEnabled + ? getDeclarationInfo(tokens) + : EMPTY_DECLARATION_INFO; + } + + getPrefixCode() { + let prefix = ""; + if (this.hadExport) { + prefix += 'Object.defineProperty(exports, "__esModule", {value: true});'; + } + return prefix; + } + + getSuffixCode() { + if (this.enableLegacyBabel5ModuleInterop && this.hadDefaultExport && !this.hadNamedExport) { + return "\nmodule.exports = exports.default;\n"; + } + return ""; + } + + process() { + // TypeScript `import foo = require('foo');` should always just be translated to plain require. + if (this.tokens.matches3(tt._import, tt.name, tt.eq)) { + return this.processImportEquals(); + } + if (this.tokens.matches1(tt._import)) { + this.processImport(); + return true; + } + if (this.tokens.matches2(tt._export, tt.eq)) { + this.tokens.replaceToken("module.exports"); + return true; + } + if (this.tokens.matches1(tt._export) && !this.tokens.currentToken().isType) { + this.hadExport = true; + return this.processExport(); + } + if (this.tokens.matches2(tt.name, tt.postIncDec)) { + // Fall through to normal identifier matching if this doesn't apply. + if (this.processPostIncDec()) { + return true; + } + } + if (this.tokens.matches1(tt.name) || this.tokens.matches1(tt.jsxName)) { + return this.processIdentifier(); + } + if (this.tokens.matches1(tt.eq)) { + return this.processAssignment(); + } + if (this.tokens.matches1(tt.assign)) { + return this.processComplexAssignment(); + } + if (this.tokens.matches1(tt.preIncDec)) { + return this.processPreIncDec(); + } + return false; + } + + processImportEquals() { + const importName = this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 1); + if (this.importProcessor.shouldAutomaticallyElideImportedName(importName)) { + // If this name is only used as a type, elide the whole import. + elideImportEquals(this.tokens); + } else { + // Otherwise, switch `import` to `const`. + this.tokens.replaceToken("const"); + } + return true; + } + + /** + * Transform this: + * import foo, {bar} from 'baz'; + * into + * var _baz = require('baz'); var _baz2 = _interopRequireDefault(_baz); + * + * The import code was already generated in the import preprocessing step, so + * we just need to look it up. + */ + processImport() { + if (this.tokens.matches2(tt._import, tt.parenL)) { + if (this.preserveDynamicImport) { + // Bail out, only making progress for this one token. + this.tokens.copyToken(); + return; + } + const requireWrapper = this.enableLegacyTypeScriptModuleInterop + ? "" + : `${this.helperManager.getHelperName("interopRequireWildcard")}(`; + this.tokens.replaceToken(`Promise.resolve().then(() => ${requireWrapper}require`); + const contextId = this.tokens.currentToken().contextId; + if (contextId == null) { + throw new Error("Expected context ID on dynamic import invocation."); + } + this.tokens.copyToken(); + while (!this.tokens.matchesContextIdAndLabel(tt.parenR, contextId)) { + this.rootTransformer.processToken(); + } + this.tokens.replaceToken(requireWrapper ? ")))" : "))"); + return; + } + + const shouldElideImport = this.removeImportAndDetectIfShouldElide(); + if (shouldElideImport) { + this.tokens.removeToken(); + } else { + const path = this.tokens.stringValue(); + this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path)); + this.tokens.appendCode(this.importProcessor.claimImportCode(path)); + } + removeMaybeImportAttributes(this.tokens); + if (this.tokens.matches1(tt.semi)) { + this.tokens.removeToken(); + } + } + + /** + * Erase this import (since any CJS output would be completely different), and + * return true if this import is should be elided due to being a type-only + * import. Such imports will not be emitted at all to avoid side effects. + * + * Import elision only happens with the TypeScript or Flow transforms enabled. + * + * TODO: This function has some awkward overlap with + * CJSImportProcessor.pruneTypeOnlyImports , and the two should be unified. + * That function handles TypeScript implicit import name elision, and removes + * an import if all typical imported names (without `type`) are removed due + * to being type-only imports. This function handles Flow import removal and + * properly distinguishes `import 'foo'` from `import {} from 'foo'` for TS + * purposes. + * + * The position should end at the import string. + */ + removeImportAndDetectIfShouldElide() { + this.tokens.removeInitialToken(); + if ( + this.tokens.matchesContextual(ContextualKeyword._type) && + !this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, tt.comma) && + !this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, ContextualKeyword._from) + ) { + // This is an "import type" statement, so exit early. + this.removeRemainingImport(); + return true; + } + + if (this.tokens.matches1(tt.name) || this.tokens.matches1(tt.star)) { + // We have a default import or namespace import, so there must be some + // non-type import. + this.removeRemainingImport(); + return false; + } + + if (this.tokens.matches1(tt.string)) { + // This is a bare import, so we should proceed with the import. + return false; + } + + let foundNonTypeImport = false; + let foundAnyNamedImport = false; + while (!this.tokens.matches1(tt.string)) { + // Check if any named imports are of the form "foo" or "foo as bar", with + // no leading "type". + if ( + (!foundNonTypeImport && this.tokens.matches1(tt.braceL)) || + this.tokens.matches1(tt.comma) + ) { + this.tokens.removeToken(); + if (!this.tokens.matches1(tt.braceR)) { + foundAnyNamedImport = true; + } + if ( + this.tokens.matches2(tt.name, tt.comma) || + this.tokens.matches2(tt.name, tt.braceR) || + this.tokens.matches4(tt.name, tt.name, tt.name, tt.comma) || + this.tokens.matches4(tt.name, tt.name, tt.name, tt.braceR) + ) { + foundNonTypeImport = true; + } + } + this.tokens.removeToken(); + } + if (this.keepUnusedImports) { + return false; + } + if (this.isTypeScriptTransformEnabled) { + return !foundNonTypeImport; + } else if (this.isFlowTransformEnabled) { + // In Flow, unlike TS, `import {} from 'foo';` preserves the import. + return foundAnyNamedImport && !foundNonTypeImport; + } else { + return false; + } + } + + removeRemainingImport() { + while (!this.tokens.matches1(tt.string)) { + this.tokens.removeToken(); + } + } + + processIdentifier() { + const token = this.tokens.currentToken(); + if (token.shadowsGlobal) { + return false; + } + + if (token.identifierRole === IdentifierRole.ObjectShorthand) { + return this.processObjectShorthand(); + } + + if (token.identifierRole !== IdentifierRole.Access) { + return false; + } + const replacement = this.importProcessor.getIdentifierReplacement( + this.tokens.identifierNameForToken(token), + ); + if (!replacement) { + return false; + } + // Tolerate any number of closing parens while looking for an opening paren + // that indicates a function call. + let possibleOpenParenIndex = this.tokens.currentIndex() + 1; + while ( + possibleOpenParenIndex < this.tokens.tokens.length && + this.tokens.tokens[possibleOpenParenIndex].type === tt.parenR + ) { + possibleOpenParenIndex++; + } + // Avoid treating imported functions as methods of their `exports` object + // by using `(0, f)` when the identifier is in a paren expression. Else + // use `Function.prototype.call` when the identifier is a guaranteed + // function call. When using `call`, pass undefined as the context. + if (this.tokens.tokens[possibleOpenParenIndex].type === tt.parenL) { + if ( + this.tokens.tokenAtRelativeIndex(1).type === tt.parenL && + this.tokens.tokenAtRelativeIndex(-1).type !== tt._new + ) { + this.tokens.replaceToken(`${replacement}.call(void 0, `); + // Remove the old paren. + this.tokens.removeToken(); + // Balance out the new paren. + this.rootTransformer.processBalancedCode(); + this.tokens.copyExpectedToken(tt.parenR); + } else { + // See here: http://2ality.com/2015/12/references.html + this.tokens.replaceToken(`(0, ${replacement})`); + } + } else { + this.tokens.replaceToken(replacement); + } + return true; + } + + processObjectShorthand() { + const identifier = this.tokens.identifierName(); + const replacement = this.importProcessor.getIdentifierReplacement(identifier); + if (!replacement) { + return false; + } + this.tokens.replaceToken(`${identifier}: ${replacement}`); + return true; + } + + processExport() { + if ( + this.tokens.matches2(tt._export, tt._enum) || + this.tokens.matches3(tt._export, tt._const, tt._enum) + ) { + this.hadNamedExport = true; + // Let the TypeScript transform handle it. + return false; + } + if (this.tokens.matches2(tt._export, tt._default)) { + if (this.tokens.matches3(tt._export, tt._default, tt._enum)) { + this.hadDefaultExport = true; + // Flow export default enums need some special handling, so handle them + // in that tranform rather than this one. + return false; + } + this.processExportDefault(); + return true; + } else if (this.tokens.matches2(tt._export, tt.braceL)) { + this.processExportBindings(); + return true; + } else if ( + this.tokens.matches2(tt._export, tt.name) && + this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, ContextualKeyword._type) + ) { + // export type {a}; + // export type {a as b}; + // export type {a} from './b'; + // export type * from './b'; + // export type * as ns from './b'; + this.tokens.removeInitialToken(); + this.tokens.removeToken(); + if (this.tokens.matches1(tt.braceL)) { + while (!this.tokens.matches1(tt.braceR)) { + this.tokens.removeToken(); + } + this.tokens.removeToken(); + } else { + // * + this.tokens.removeToken(); + if (this.tokens.matches1(tt._as)) { + // as + this.tokens.removeToken(); + // ns + this.tokens.removeToken(); + } + } + // Remove type re-export `... } from './T'` + if ( + this.tokens.matchesContextual(ContextualKeyword._from) && + this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, tt.string) + ) { + this.tokens.removeToken(); + this.tokens.removeToken(); + removeMaybeImportAttributes(this.tokens); + } + return true; + } + this.hadNamedExport = true; + if ( + this.tokens.matches2(tt._export, tt._var) || + this.tokens.matches2(tt._export, tt._let) || + this.tokens.matches2(tt._export, tt._const) + ) { + this.processExportVar(); + return true; + } else if ( + this.tokens.matches2(tt._export, tt._function) || + // export async function + this.tokens.matches3(tt._export, tt.name, tt._function) + ) { + this.processExportFunction(); + return true; + } else if ( + this.tokens.matches2(tt._export, tt._class) || + this.tokens.matches3(tt._export, tt._abstract, tt._class) || + this.tokens.matches2(tt._export, tt.at) + ) { + this.processExportClass(); + return true; + } else if (this.tokens.matches2(tt._export, tt.star)) { + this.processExportStar(); + return true; + } else { + throw new Error("Unrecognized export syntax."); + } + } + + processAssignment() { + const index = this.tokens.currentIndex(); + const identifierToken = this.tokens.tokens[index - 1]; + // If the LHS is a type identifier, this must be a declaration like `let a: b = c;`, + // with `b` as the identifier, so nothing needs to be done in that case. + if (identifierToken.isType || identifierToken.type !== tt.name) { + return false; + } + if (identifierToken.shadowsGlobal) { + return false; + } + if (index >= 2 && this.tokens.matches1AtIndex(index - 2, tt.dot)) { + return false; + } + if (index >= 2 && [tt._var, tt._let, tt._const].includes(this.tokens.tokens[index - 2].type)) { + // Declarations don't need an extra assignment. This doesn't avoid the + // assignment for comma-separated declarations, but it's still correct + // since the assignment is just redundant. + return false; + } + const assignmentSnippet = this.importProcessor.resolveExportBinding( + this.tokens.identifierNameForToken(identifierToken), + ); + if (!assignmentSnippet) { + return false; + } + this.tokens.copyToken(); + this.tokens.appendCode(` ${assignmentSnippet} =`); + return true; + } + + /** + * Process something like `a += 3`, where `a` might be an exported value. + */ + processComplexAssignment() { + const index = this.tokens.currentIndex(); + const identifierToken = this.tokens.tokens[index - 1]; + if (identifierToken.type !== tt.name) { + return false; + } + if (identifierToken.shadowsGlobal) { + return false; + } + if (index >= 2 && this.tokens.matches1AtIndex(index - 2, tt.dot)) { + return false; + } + const assignmentSnippet = this.importProcessor.resolveExportBinding( + this.tokens.identifierNameForToken(identifierToken), + ); + if (!assignmentSnippet) { + return false; + } + this.tokens.appendCode(` = ${assignmentSnippet}`); + this.tokens.copyToken(); + return true; + } + + /** + * Process something like `++a`, where `a` might be an exported value. + */ + processPreIncDec() { + const index = this.tokens.currentIndex(); + const identifierToken = this.tokens.tokens[index + 1]; + if (identifierToken.type !== tt.name) { + return false; + } + if (identifierToken.shadowsGlobal) { + return false; + } + // Ignore things like ++a.b and ++a[b] and ++a().b. + if ( + index + 2 < this.tokens.tokens.length && + (this.tokens.matches1AtIndex(index + 2, tt.dot) || + this.tokens.matches1AtIndex(index + 2, tt.bracketL) || + this.tokens.matches1AtIndex(index + 2, tt.parenL)) + ) { + return false; + } + const identifierName = this.tokens.identifierNameForToken(identifierToken); + const assignmentSnippet = this.importProcessor.resolveExportBinding(identifierName); + if (!assignmentSnippet) { + return false; + } + this.tokens.appendCode(`${assignmentSnippet} = `); + this.tokens.copyToken(); + return true; + } + + /** + * Process something like `a++`, where `a` might be an exported value. + * This starts at the `a`, not at the `++`. + */ + processPostIncDec() { + const index = this.tokens.currentIndex(); + const identifierToken = this.tokens.tokens[index]; + const operatorToken = this.tokens.tokens[index + 1]; + if (identifierToken.type !== tt.name) { + return false; + } + if (identifierToken.shadowsGlobal) { + return false; + } + if (index >= 1 && this.tokens.matches1AtIndex(index - 1, tt.dot)) { + return false; + } + const identifierName = this.tokens.identifierNameForToken(identifierToken); + const assignmentSnippet = this.importProcessor.resolveExportBinding(identifierName); + if (!assignmentSnippet) { + return false; + } + const operatorCode = this.tokens.rawCodeForToken(operatorToken); + // We might also replace the identifier with something like exports.x, so + // do that replacement here as well. + const base = this.importProcessor.getIdentifierReplacement(identifierName) || identifierName; + if (operatorCode === "++") { + this.tokens.replaceToken(`(${base} = ${assignmentSnippet} = ${base} + 1, ${base} - 1)`); + } else if (operatorCode === "--") { + this.tokens.replaceToken(`(${base} = ${assignmentSnippet} = ${base} - 1, ${base} + 1)`); + } else { + throw new Error(`Unexpected operator: ${operatorCode}`); + } + this.tokens.removeToken(); + return true; + } + + processExportDefault() { + let exportedRuntimeValue = true; + if ( + this.tokens.matches4(tt._export, tt._default, tt._function, tt.name) || + // export default async function + (this.tokens.matches5(tt._export, tt._default, tt.name, tt._function, tt.name) && + this.tokens.matchesContextualAtIndex( + this.tokens.currentIndex() + 2, + ContextualKeyword._async, + )) + ) { + this.tokens.removeInitialToken(); + this.tokens.removeToken(); + // Named function export case: change it to a top-level function + // declaration followed by exports statement. + const name = this.processNamedFunction(); + this.tokens.appendCode(` exports.default = ${name};`); + } else if ( + this.tokens.matches4(tt._export, tt._default, tt._class, tt.name) || + this.tokens.matches5(tt._export, tt._default, tt._abstract, tt._class, tt.name) || + this.tokens.matches3(tt._export, tt._default, tt.at) + ) { + this.tokens.removeInitialToken(); + this.tokens.removeToken(); + this.copyDecorators(); + if (this.tokens.matches1(tt._abstract)) { + this.tokens.removeToken(); + } + const name = this.rootTransformer.processNamedClass(); + this.tokens.appendCode(` exports.default = ${name};`); + // After this point, this is a plain "export default E" statement. + } else if ( + shouldElideDefaultExport( + this.isTypeScriptTransformEnabled, + this.keepUnusedImports, + this.tokens, + this.declarationInfo, + ) + ) { + // If the exported value is just an identifier and should be elided by TypeScript + // rules, then remove it entirely. It will always have the form `export default e`, + // where `e` is an identifier. + exportedRuntimeValue = false; + this.tokens.removeInitialToken(); + this.tokens.removeToken(); + this.tokens.removeToken(); + } else if (this.reactHotLoaderTransformer) { + // We need to assign E to a variable. Change "export default E" to + // "let _default; exports.default = _default = E" + const defaultVarName = this.nameManager.claimFreeName("_default"); + this.tokens.replaceToken(`let ${defaultVarName}; exports.`); + this.tokens.copyToken(); + this.tokens.appendCode(` = ${defaultVarName} =`); + this.reactHotLoaderTransformer.setExtractedDefaultExportName(defaultVarName); + } else { + // Change "export default E" to "exports.default = E" + this.tokens.replaceToken("exports."); + this.tokens.copyToken(); + this.tokens.appendCode(" ="); + } + if (exportedRuntimeValue) { + this.hadDefaultExport = true; + } + } + + copyDecorators() { + while (this.tokens.matches1(tt.at)) { + this.tokens.copyToken(); + if (this.tokens.matches1(tt.parenL)) { + this.tokens.copyExpectedToken(tt.parenL); + this.rootTransformer.processBalancedCode(); + this.tokens.copyExpectedToken(tt.parenR); + } else { + this.tokens.copyExpectedToken(tt.name); + while (this.tokens.matches1(tt.dot)) { + this.tokens.copyExpectedToken(tt.dot); + this.tokens.copyExpectedToken(tt.name); + } + if (this.tokens.matches1(tt.parenL)) { + this.tokens.copyExpectedToken(tt.parenL); + this.rootTransformer.processBalancedCode(); + this.tokens.copyExpectedToken(tt.parenR); + } + } + } + } + + /** + * Transform a declaration like `export var`, `export let`, or `export const`. + */ + processExportVar() { + if (this.isSimpleExportVar()) { + this.processSimpleExportVar(); + } else { + this.processComplexExportVar(); + } + } + + /** + * Determine if the export is of the form: + * export var/let/const [varName] = [expr]; + * In other words, determine if function name inference might apply. + */ + isSimpleExportVar() { + let tokenIndex = this.tokens.currentIndex(); + // export + tokenIndex++; + // var/let/const + tokenIndex++; + if (!this.tokens.matches1AtIndex(tokenIndex, tt.name)) { + return false; + } + tokenIndex++; + while (tokenIndex < this.tokens.tokens.length && this.tokens.tokens[tokenIndex].isType) { + tokenIndex++; + } + if (!this.tokens.matches1AtIndex(tokenIndex, tt.eq)) { + return false; + } + return true; + } + + /** + * Transform an `export var` declaration initializing a single variable. + * + * For example, this: + * export const f = () => {}; + * becomes this: + * const f = () => {}; exports.f = f; + * + * The variable is unused (e.g. exports.f has the true value of the export). + * We need to produce an assignment of this form so that the function will + * have an inferred name of "f", which wouldn't happen in the more general + * case below. + */ + processSimpleExportVar() { + // export + this.tokens.removeInitialToken(); + // var/let/const + this.tokens.copyToken(); + const varName = this.tokens.identifierName(); + // x: number -> x + while (!this.tokens.matches1(tt.eq)) { + this.rootTransformer.processToken(); + } + const endIndex = this.tokens.currentToken().rhsEndIndex; + if (endIndex == null) { + throw new Error("Expected = token with an end index."); + } + while (this.tokens.currentIndex() < endIndex) { + this.rootTransformer.processToken(); + } + this.tokens.appendCode(`; exports.${varName} = ${varName}`); + } + + /** + * Transform normal declaration exports, including handling destructuring. + * For example, this: + * export const {x: [a = 2, b], c} = d; + * becomes this: + * ({x: [exports.a = 2, exports.b], c: exports.c} = d;) + */ + processComplexExportVar() { + this.tokens.removeInitialToken(); + this.tokens.removeToken(); + const needsParens = this.tokens.matches1(tt.braceL); + if (needsParens) { + this.tokens.appendCode("("); + } + + let depth = 0; + while (true) { + if ( + this.tokens.matches1(tt.braceL) || + this.tokens.matches1(tt.dollarBraceL) || + this.tokens.matches1(tt.bracketL) + ) { + depth++; + this.tokens.copyToken(); + } else if (this.tokens.matches1(tt.braceR) || this.tokens.matches1(tt.bracketR)) { + depth--; + this.tokens.copyToken(); + } else if ( + depth === 0 && + !this.tokens.matches1(tt.name) && + !this.tokens.currentToken().isType + ) { + break; + } else if (this.tokens.matches1(tt.eq)) { + // Default values might have assignments in the RHS that we want to ignore, so skip past + // them. + const endIndex = this.tokens.currentToken().rhsEndIndex; + if (endIndex == null) { + throw new Error("Expected = token with an end index."); + } + while (this.tokens.currentIndex() < endIndex) { + this.rootTransformer.processToken(); + } + } else { + const token = this.tokens.currentToken(); + if (isDeclaration(token)) { + const name = this.tokens.identifierName(); + let replacement = this.importProcessor.getIdentifierReplacement(name); + if (replacement === null) { + throw new Error(`Expected a replacement for ${name} in \`export var\` syntax.`); + } + if (isObjectShorthandDeclaration(token)) { + replacement = `${name}: ${replacement}`; + } + this.tokens.replaceToken(replacement); + } else { + this.rootTransformer.processToken(); + } + } + } + + if (needsParens) { + // Seek to the end of the RHS. + const endIndex = this.tokens.currentToken().rhsEndIndex; + if (endIndex == null) { + throw new Error("Expected = token with an end index."); + } + while (this.tokens.currentIndex() < endIndex) { + this.rootTransformer.processToken(); + } + this.tokens.appendCode(")"); + } + } + + /** + * Transform this: + * export function foo() {} + * into this: + * function foo() {} exports.foo = foo; + */ + processExportFunction() { + this.tokens.replaceToken(""); + const name = this.processNamedFunction(); + this.tokens.appendCode(` exports.${name} = ${name};`); + } + + /** + * Skip past a function with a name and return that name. + */ + processNamedFunction() { + if (this.tokens.matches1(tt._function)) { + this.tokens.copyToken(); + } else if (this.tokens.matches2(tt.name, tt._function)) { + if (!this.tokens.matchesContextual(ContextualKeyword._async)) { + throw new Error("Expected async keyword in function export."); + } + this.tokens.copyToken(); + this.tokens.copyToken(); + } + if (this.tokens.matches1(tt.star)) { + this.tokens.copyToken(); + } + if (!this.tokens.matches1(tt.name)) { + throw new Error("Expected identifier for exported function name."); + } + const name = this.tokens.identifierName(); + this.tokens.copyToken(); + if (this.tokens.currentToken().isType) { + this.tokens.removeInitialToken(); + while (this.tokens.currentToken().isType) { + this.tokens.removeToken(); + } + } + this.tokens.copyExpectedToken(tt.parenL); + this.rootTransformer.processBalancedCode(); + this.tokens.copyExpectedToken(tt.parenR); + this.rootTransformer.processPossibleTypeRange(); + this.tokens.copyExpectedToken(tt.braceL); + this.rootTransformer.processBalancedCode(); + this.tokens.copyExpectedToken(tt.braceR); + return name; + } + + /** + * Transform this: + * export class A {} + * into this: + * class A {} exports.A = A; + */ + processExportClass() { + this.tokens.removeInitialToken(); + this.copyDecorators(); + if (this.tokens.matches1(tt._abstract)) { + this.tokens.removeToken(); + } + const name = this.rootTransformer.processNamedClass(); + this.tokens.appendCode(` exports.${name} = ${name};`); + } + + /** + * Transform this: + * export {a, b as c}; + * into this: + * exports.a = a; exports.c = b; + * + * OR + * + * Transform this: + * export {a, b as c} from './foo'; + * into the pre-generated Object.defineProperty code from the ImportProcessor. + * + * For the first case, if the TypeScript transform is enabled, we need to skip + * exports that are only defined as types. + */ + processExportBindings() { + this.tokens.removeInitialToken(); + this.tokens.removeToken(); + + const isReExport = isExportFrom(this.tokens); + + const exportStatements = []; + while (true) { + if (this.tokens.matches1(tt.braceR)) { + this.tokens.removeToken(); + break; + } + + const specifierInfo = getImportExportSpecifierInfo(this.tokens); + + while (this.tokens.currentIndex() < specifierInfo.endIndex) { + this.tokens.removeToken(); + } + + const shouldRemoveExport = + specifierInfo.isType || + (!isReExport && this.shouldElideExportedIdentifier(specifierInfo.leftName)); + if (!shouldRemoveExport) { + const exportedName = specifierInfo.rightName; + if (exportedName === "default") { + this.hadDefaultExport = true; + } else { + this.hadNamedExport = true; + } + const localName = specifierInfo.leftName; + const newLocalName = this.importProcessor.getIdentifierReplacement(localName); + exportStatements.push(`exports.${exportedName} = ${newLocalName || localName};`); + } + + if (this.tokens.matches1(tt.braceR)) { + this.tokens.removeToken(); + break; + } + if (this.tokens.matches2(tt.comma, tt.braceR)) { + this.tokens.removeToken(); + this.tokens.removeToken(); + break; + } else if (this.tokens.matches1(tt.comma)) { + this.tokens.removeToken(); + } else { + throw new Error(`Unexpected token: ${JSON.stringify(this.tokens.currentToken())}`); + } + } + + if (this.tokens.matchesContextual(ContextualKeyword._from)) { + // This is an export...from, so throw away the normal named export code + // and use the Object.defineProperty code from ImportProcessor. + this.tokens.removeToken(); + const path = this.tokens.stringValue(); + this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path)); + removeMaybeImportAttributes(this.tokens); + } else { + // This is a normal named export, so use that. + this.tokens.appendCode(exportStatements.join(" ")); + } + + if (this.tokens.matches1(tt.semi)) { + this.tokens.removeToken(); + } + } + + processExportStar() { + this.tokens.removeInitialToken(); + while (!this.tokens.matches1(tt.string)) { + this.tokens.removeToken(); + } + const path = this.tokens.stringValue(); + this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path)); + removeMaybeImportAttributes(this.tokens); + if (this.tokens.matches1(tt.semi)) { + this.tokens.removeToken(); + } + } + + shouldElideExportedIdentifier(name) { + return ( + this.isTypeScriptTransformEnabled && + !this.keepUnusedImports && + !this.declarationInfo.valueDeclarations.has(name) + ); + } +} -- cgit v1.2.3