暂无描述

CJSImportTransformer.js 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. import {IdentifierRole, isDeclaration, isObjectShorthandDeclaration} from "../parser/tokenizer";
  2. import {ContextualKeyword} from "../parser/tokenizer/keywords";
  3. import {TokenType as tt} from "../parser/tokenizer/types";
  4. import elideImportEquals from "../util/elideImportEquals";
  5. import getDeclarationInfo, {
  6. EMPTY_DECLARATION_INFO,
  7. } from "../util/getDeclarationInfo";
  8. import getImportExportSpecifierInfo from "../util/getImportExportSpecifierInfo";
  9. import {removeMaybeImportAssertion} from "../util/removeMaybeImportAssertion";
  10. import shouldElideDefaultExport from "../util/shouldElideDefaultExport";
  11. import Transformer from "./Transformer";
  12. /**
  13. * Class for editing import statements when we are transforming to commonjs.
  14. */
  15. export default class CJSImportTransformer extends Transformer {
  16. __init() {this.hadExport = false}
  17. __init2() {this.hadNamedExport = false}
  18. __init3() {this.hadDefaultExport = false}
  19. constructor(
  20. rootTransformer,
  21. tokens,
  22. importProcessor,
  23. nameManager,
  24. helperManager,
  25. reactHotLoaderTransformer,
  26. enableLegacyBabel5ModuleInterop,
  27. enableLegacyTypeScriptModuleInterop,
  28. isTypeScriptTransformEnabled,
  29. preserveDynamicImport,
  30. ) {
  31. 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.preserveDynamicImport = preserveDynamicImport;CJSImportTransformer.prototype.__init.call(this);CJSImportTransformer.prototype.__init2.call(this);CJSImportTransformer.prototype.__init3.call(this);;
  32. this.declarationInfo = isTypeScriptTransformEnabled
  33. ? getDeclarationInfo(tokens)
  34. : EMPTY_DECLARATION_INFO;
  35. }
  36. getPrefixCode() {
  37. let prefix = "";
  38. if (this.hadExport) {
  39. prefix += 'Object.defineProperty(exports, "__esModule", {value: true});';
  40. }
  41. return prefix;
  42. }
  43. getSuffixCode() {
  44. if (this.enableLegacyBabel5ModuleInterop && this.hadDefaultExport && !this.hadNamedExport) {
  45. return "\nmodule.exports = exports.default;\n";
  46. }
  47. return "";
  48. }
  49. process() {
  50. // TypeScript `import foo = require('foo');` should always just be translated to plain require.
  51. if (this.tokens.matches3(tt._import, tt.name, tt.eq)) {
  52. return this.processImportEquals();
  53. }
  54. if (this.tokens.matches1(tt._import)) {
  55. this.processImport();
  56. return true;
  57. }
  58. if (this.tokens.matches2(tt._export, tt.eq)) {
  59. this.tokens.replaceToken("module.exports");
  60. return true;
  61. }
  62. if (this.tokens.matches1(tt._export) && !this.tokens.currentToken().isType) {
  63. this.hadExport = true;
  64. return this.processExport();
  65. }
  66. if (this.tokens.matches2(tt.name, tt.postIncDec)) {
  67. // Fall through to normal identifier matching if this doesn't apply.
  68. if (this.processPostIncDec()) {
  69. return true;
  70. }
  71. }
  72. if (this.tokens.matches1(tt.name) || this.tokens.matches1(tt.jsxName)) {
  73. return this.processIdentifier();
  74. }
  75. if (this.tokens.matches1(tt.eq)) {
  76. return this.processAssignment();
  77. }
  78. if (this.tokens.matches1(tt.assign)) {
  79. return this.processComplexAssignment();
  80. }
  81. if (this.tokens.matches1(tt.preIncDec)) {
  82. return this.processPreIncDec();
  83. }
  84. return false;
  85. }
  86. processImportEquals() {
  87. const importName = this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 1);
  88. if (this.importProcessor.isTypeName(importName)) {
  89. // If this name is only used as a type, elide the whole import.
  90. elideImportEquals(this.tokens);
  91. } else {
  92. // Otherwise, switch `import` to `const`.
  93. this.tokens.replaceToken("const");
  94. }
  95. return true;
  96. }
  97. /**
  98. * Transform this:
  99. * import foo, {bar} from 'baz';
  100. * into
  101. * var _baz = require('baz'); var _baz2 = _interopRequireDefault(_baz);
  102. *
  103. * The import code was already generated in the import preprocessing step, so
  104. * we just need to look it up.
  105. */
  106. processImport() {
  107. if (this.tokens.matches2(tt._import, tt.parenL)) {
  108. if (this.preserveDynamicImport) {
  109. // Bail out, only making progress for this one token.
  110. this.tokens.copyToken();
  111. return;
  112. }
  113. const requireWrapper = this.enableLegacyTypeScriptModuleInterop
  114. ? ""
  115. : `${this.helperManager.getHelperName("interopRequireWildcard")}(`;
  116. this.tokens.replaceToken(`Promise.resolve().then(() => ${requireWrapper}require`);
  117. const contextId = this.tokens.currentToken().contextId;
  118. if (contextId == null) {
  119. throw new Error("Expected context ID on dynamic import invocation.");
  120. }
  121. this.tokens.copyToken();
  122. while (!this.tokens.matchesContextIdAndLabel(tt.parenR, contextId)) {
  123. this.rootTransformer.processToken();
  124. }
  125. this.tokens.replaceToken(requireWrapper ? ")))" : "))");
  126. return;
  127. }
  128. const wasOnlyTypes = this.removeImportAndDetectIfType();
  129. if (wasOnlyTypes) {
  130. this.tokens.removeToken();
  131. } else {
  132. const path = this.tokens.stringValue();
  133. this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path));
  134. this.tokens.appendCode(this.importProcessor.claimImportCode(path));
  135. }
  136. removeMaybeImportAssertion(this.tokens);
  137. if (this.tokens.matches1(tt.semi)) {
  138. this.tokens.removeToken();
  139. }
  140. }
  141. /**
  142. * Erase this import, and return true if it was either of the form "import type" or contained only
  143. * "type" named imports. Such imports should not even do a side-effect import.
  144. *
  145. * The position should end at the import string.
  146. */
  147. removeImportAndDetectIfType() {
  148. this.tokens.removeInitialToken();
  149. if (
  150. this.tokens.matchesContextual(ContextualKeyword._type) &&
  151. !this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, tt.comma) &&
  152. !this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, ContextualKeyword._from)
  153. ) {
  154. // This is an "import type" statement, so exit early.
  155. this.removeRemainingImport();
  156. return true;
  157. }
  158. if (this.tokens.matches1(tt.name) || this.tokens.matches1(tt.star)) {
  159. // We have a default import or namespace import, so there must be some
  160. // non-type import.
  161. this.removeRemainingImport();
  162. return false;
  163. }
  164. if (this.tokens.matches1(tt.string)) {
  165. // This is a bare import, so we should proceed with the import.
  166. return false;
  167. }
  168. let foundNonType = false;
  169. while (!this.tokens.matches1(tt.string)) {
  170. // Check if any named imports are of the form "foo" or "foo as bar", with
  171. // no leading "type".
  172. if ((!foundNonType && this.tokens.matches1(tt.braceL)) || this.tokens.matches1(tt.comma)) {
  173. this.tokens.removeToken();
  174. if (
  175. this.tokens.matches2(tt.name, tt.comma) ||
  176. this.tokens.matches2(tt.name, tt.braceR) ||
  177. this.tokens.matches4(tt.name, tt.name, tt.name, tt.comma) ||
  178. this.tokens.matches4(tt.name, tt.name, tt.name, tt.braceR)
  179. ) {
  180. foundNonType = true;
  181. }
  182. }
  183. this.tokens.removeToken();
  184. }
  185. return !foundNonType;
  186. }
  187. removeRemainingImport() {
  188. while (!this.tokens.matches1(tt.string)) {
  189. this.tokens.removeToken();
  190. }
  191. }
  192. processIdentifier() {
  193. const token = this.tokens.currentToken();
  194. if (token.shadowsGlobal) {
  195. return false;
  196. }
  197. if (token.identifierRole === IdentifierRole.ObjectShorthand) {
  198. return this.processObjectShorthand();
  199. }
  200. if (token.identifierRole !== IdentifierRole.Access) {
  201. return false;
  202. }
  203. const replacement = this.importProcessor.getIdentifierReplacement(
  204. this.tokens.identifierNameForToken(token),
  205. );
  206. if (!replacement) {
  207. return false;
  208. }
  209. // Tolerate any number of closing parens while looking for an opening paren
  210. // that indicates a function call.
  211. let possibleOpenParenIndex = this.tokens.currentIndex() + 1;
  212. while (
  213. possibleOpenParenIndex < this.tokens.tokens.length &&
  214. this.tokens.tokens[possibleOpenParenIndex].type === tt.parenR
  215. ) {
  216. possibleOpenParenIndex++;
  217. }
  218. // Avoid treating imported functions as methods of their `exports` object
  219. // by using `(0, f)` when the identifier is in a paren expression. Else
  220. // use `Function.prototype.call` when the identifier is a guaranteed
  221. // function call. When using `call`, pass undefined as the context.
  222. if (this.tokens.tokens[possibleOpenParenIndex].type === tt.parenL) {
  223. if (
  224. this.tokens.tokenAtRelativeIndex(1).type === tt.parenL &&
  225. this.tokens.tokenAtRelativeIndex(-1).type !== tt._new
  226. ) {
  227. this.tokens.replaceToken(`${replacement}.call(void 0, `);
  228. // Remove the old paren.
  229. this.tokens.removeToken();
  230. // Balance out the new paren.
  231. this.rootTransformer.processBalancedCode();
  232. this.tokens.copyExpectedToken(tt.parenR);
  233. } else {
  234. // See here: http://2ality.com/2015/12/references.html
  235. this.tokens.replaceToken(`(0, ${replacement})`);
  236. }
  237. } else {
  238. this.tokens.replaceToken(replacement);
  239. }
  240. return true;
  241. }
  242. processObjectShorthand() {
  243. const identifier = this.tokens.identifierName();
  244. const replacement = this.importProcessor.getIdentifierReplacement(identifier);
  245. if (!replacement) {
  246. return false;
  247. }
  248. this.tokens.replaceToken(`${identifier}: ${replacement}`);
  249. return true;
  250. }
  251. processExport() {
  252. if (
  253. this.tokens.matches2(tt._export, tt._enum) ||
  254. this.tokens.matches3(tt._export, tt._const, tt._enum)
  255. ) {
  256. // Let the TypeScript transform handle it.
  257. return false;
  258. }
  259. if (this.tokens.matches2(tt._export, tt._default)) {
  260. this.hadDefaultExport = true;
  261. if (this.tokens.matches3(tt._export, tt._default, tt._enum)) {
  262. // Flow export default enums need some special handling, so handle them
  263. // in that tranform rather than this one.
  264. return false;
  265. }
  266. this.processExportDefault();
  267. return true;
  268. }
  269. this.hadNamedExport = true;
  270. if (
  271. this.tokens.matches2(tt._export, tt._var) ||
  272. this.tokens.matches2(tt._export, tt._let) ||
  273. this.tokens.matches2(tt._export, tt._const)
  274. ) {
  275. this.processExportVar();
  276. return true;
  277. } else if (
  278. this.tokens.matches2(tt._export, tt._function) ||
  279. // export async function
  280. this.tokens.matches3(tt._export, tt.name, tt._function)
  281. ) {
  282. this.processExportFunction();
  283. return true;
  284. } else if (
  285. this.tokens.matches2(tt._export, tt._class) ||
  286. this.tokens.matches3(tt._export, tt._abstract, tt._class) ||
  287. this.tokens.matches2(tt._export, tt.at)
  288. ) {
  289. this.processExportClass();
  290. return true;
  291. } else if (this.tokens.matches2(tt._export, tt.braceL)) {
  292. this.processExportBindings();
  293. return true;
  294. } else if (this.tokens.matches2(tt._export, tt.star)) {
  295. this.processExportStar();
  296. return true;
  297. } else if (
  298. this.tokens.matches2(tt._export, tt.name) &&
  299. this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, ContextualKeyword._type)
  300. ) {
  301. // export type {a};
  302. // export type {a as b};
  303. // export type {a} from './b';
  304. // export type * from './b';
  305. // export type * as ns from './b';
  306. this.tokens.removeInitialToken();
  307. this.tokens.removeToken();
  308. if (this.tokens.matches1(tt.braceL)) {
  309. while (!this.tokens.matches1(tt.braceR)) {
  310. this.tokens.removeToken();
  311. }
  312. this.tokens.removeToken();
  313. } else {
  314. // *
  315. this.tokens.removeToken();
  316. if (this.tokens.matches1(tt._as)) {
  317. // as
  318. this.tokens.removeToken();
  319. // ns
  320. this.tokens.removeToken();
  321. }
  322. }
  323. // Remove type re-export `... } from './T'`
  324. if (
  325. this.tokens.matchesContextual(ContextualKeyword._from) &&
  326. this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, tt.string)
  327. ) {
  328. this.tokens.removeToken();
  329. this.tokens.removeToken();
  330. removeMaybeImportAssertion(this.tokens);
  331. }
  332. return true;
  333. } else {
  334. throw new Error("Unrecognized export syntax.");
  335. }
  336. }
  337. processAssignment() {
  338. const index = this.tokens.currentIndex();
  339. const identifierToken = this.tokens.tokens[index - 1];
  340. // If the LHS is a type identifier, this must be a declaration like `let a: b = c;`,
  341. // with `b` as the identifier, so nothing needs to be done in that case.
  342. if (identifierToken.isType || identifierToken.type !== tt.name) {
  343. return false;
  344. }
  345. if (identifierToken.shadowsGlobal) {
  346. return false;
  347. }
  348. if (index >= 2 && this.tokens.matches1AtIndex(index - 2, tt.dot)) {
  349. return false;
  350. }
  351. if (index >= 2 && [tt._var, tt._let, tt._const].includes(this.tokens.tokens[index - 2].type)) {
  352. // Declarations don't need an extra assignment. This doesn't avoid the
  353. // assignment for comma-separated declarations, but it's still correct
  354. // since the assignment is just redundant.
  355. return false;
  356. }
  357. const assignmentSnippet = this.importProcessor.resolveExportBinding(
  358. this.tokens.identifierNameForToken(identifierToken),
  359. );
  360. if (!assignmentSnippet) {
  361. return false;
  362. }
  363. this.tokens.copyToken();
  364. this.tokens.appendCode(` ${assignmentSnippet} =`);
  365. return true;
  366. }
  367. /**
  368. * Process something like `a += 3`, where `a` might be an exported value.
  369. */
  370. processComplexAssignment() {
  371. const index = this.tokens.currentIndex();
  372. const identifierToken = this.tokens.tokens[index - 1];
  373. if (identifierToken.type !== tt.name) {
  374. return false;
  375. }
  376. if (identifierToken.shadowsGlobal) {
  377. return false;
  378. }
  379. if (index >= 2 && this.tokens.matches1AtIndex(index - 2, tt.dot)) {
  380. return false;
  381. }
  382. const assignmentSnippet = this.importProcessor.resolveExportBinding(
  383. this.tokens.identifierNameForToken(identifierToken),
  384. );
  385. if (!assignmentSnippet) {
  386. return false;
  387. }
  388. this.tokens.appendCode(` = ${assignmentSnippet}`);
  389. this.tokens.copyToken();
  390. return true;
  391. }
  392. /**
  393. * Process something like `++a`, where `a` might be an exported value.
  394. */
  395. processPreIncDec() {
  396. const index = this.tokens.currentIndex();
  397. const identifierToken = this.tokens.tokens[index + 1];
  398. if (identifierToken.type !== tt.name) {
  399. return false;
  400. }
  401. if (identifierToken.shadowsGlobal) {
  402. return false;
  403. }
  404. // Ignore things like ++a.b and ++a[b] and ++a().b.
  405. if (
  406. index + 2 < this.tokens.tokens.length &&
  407. (this.tokens.matches1AtIndex(index + 2, tt.dot) ||
  408. this.tokens.matches1AtIndex(index + 2, tt.bracketL) ||
  409. this.tokens.matches1AtIndex(index + 2, tt.parenL))
  410. ) {
  411. return false;
  412. }
  413. const identifierName = this.tokens.identifierNameForToken(identifierToken);
  414. const assignmentSnippet = this.importProcessor.resolveExportBinding(identifierName);
  415. if (!assignmentSnippet) {
  416. return false;
  417. }
  418. this.tokens.appendCode(`${assignmentSnippet} = `);
  419. this.tokens.copyToken();
  420. return true;
  421. }
  422. /**
  423. * Process something like `a++`, where `a` might be an exported value.
  424. * This starts at the `a`, not at the `++`.
  425. */
  426. processPostIncDec() {
  427. const index = this.tokens.currentIndex();
  428. const identifierToken = this.tokens.tokens[index];
  429. const operatorToken = this.tokens.tokens[index + 1];
  430. if (identifierToken.type !== tt.name) {
  431. return false;
  432. }
  433. if (identifierToken.shadowsGlobal) {
  434. return false;
  435. }
  436. if (index >= 1 && this.tokens.matches1AtIndex(index - 1, tt.dot)) {
  437. return false;
  438. }
  439. const identifierName = this.tokens.identifierNameForToken(identifierToken);
  440. const assignmentSnippet = this.importProcessor.resolveExportBinding(identifierName);
  441. if (!assignmentSnippet) {
  442. return false;
  443. }
  444. const operatorCode = this.tokens.rawCodeForToken(operatorToken);
  445. // We might also replace the identifier with something like exports.x, so
  446. // do that replacement here as well.
  447. const base = this.importProcessor.getIdentifierReplacement(identifierName) || identifierName;
  448. if (operatorCode === "++") {
  449. this.tokens.replaceToken(`(${base} = ${assignmentSnippet} = ${base} + 1, ${base} - 1)`);
  450. } else if (operatorCode === "--") {
  451. this.tokens.replaceToken(`(${base} = ${assignmentSnippet} = ${base} - 1, ${base} + 1)`);
  452. } else {
  453. throw new Error(`Unexpected operator: ${operatorCode}`);
  454. }
  455. this.tokens.removeToken();
  456. return true;
  457. }
  458. processExportDefault() {
  459. if (
  460. this.tokens.matches4(tt._export, tt._default, tt._function, tt.name) ||
  461. // export default async function
  462. (this.tokens.matches5(tt._export, tt._default, tt.name, tt._function, tt.name) &&
  463. this.tokens.matchesContextualAtIndex(
  464. this.tokens.currentIndex() + 2,
  465. ContextualKeyword._async,
  466. ))
  467. ) {
  468. this.tokens.removeInitialToken();
  469. this.tokens.removeToken();
  470. // Named function export case: change it to a top-level function
  471. // declaration followed by exports statement.
  472. const name = this.processNamedFunction();
  473. this.tokens.appendCode(` exports.default = ${name};`);
  474. } else if (
  475. this.tokens.matches4(tt._export, tt._default, tt._class, tt.name) ||
  476. this.tokens.matches5(tt._export, tt._default, tt._abstract, tt._class, tt.name) ||
  477. this.tokens.matches3(tt._export, tt._default, tt.at)
  478. ) {
  479. this.tokens.removeInitialToken();
  480. this.tokens.removeToken();
  481. this.copyDecorators();
  482. if (this.tokens.matches1(tt._abstract)) {
  483. this.tokens.removeToken();
  484. }
  485. const name = this.rootTransformer.processNamedClass();
  486. this.tokens.appendCode(` exports.default = ${name};`);
  487. // After this point, this is a plain "export default E" statement.
  488. } else if (
  489. shouldElideDefaultExport(this.isTypeScriptTransformEnabled, this.tokens, this.declarationInfo)
  490. ) {
  491. // If the exported value is just an identifier and should be elided by TypeScript
  492. // rules, then remove it entirely. It will always have the form `export default e`,
  493. // where `e` is an identifier.
  494. this.tokens.removeInitialToken();
  495. this.tokens.removeToken();
  496. this.tokens.removeToken();
  497. } else if (this.reactHotLoaderTransformer) {
  498. // We need to assign E to a variable. Change "export default E" to
  499. // "let _default; exports.default = _default = E"
  500. const defaultVarName = this.nameManager.claimFreeName("_default");
  501. this.tokens.replaceToken(`let ${defaultVarName}; exports.`);
  502. this.tokens.copyToken();
  503. this.tokens.appendCode(` = ${defaultVarName} =`);
  504. this.reactHotLoaderTransformer.setExtractedDefaultExportName(defaultVarName);
  505. } else {
  506. // Change "export default E" to "exports.default = E"
  507. this.tokens.replaceToken("exports.");
  508. this.tokens.copyToken();
  509. this.tokens.appendCode(" =");
  510. }
  511. }
  512. copyDecorators() {
  513. while (this.tokens.matches1(tt.at)) {
  514. this.tokens.copyToken();
  515. if (this.tokens.matches1(tt.parenL)) {
  516. this.tokens.copyExpectedToken(tt.parenL);
  517. this.rootTransformer.processBalancedCode();
  518. this.tokens.copyExpectedToken(tt.parenR);
  519. } else {
  520. this.tokens.copyExpectedToken(tt.name);
  521. while (this.tokens.matches1(tt.dot)) {
  522. this.tokens.copyExpectedToken(tt.dot);
  523. this.tokens.copyExpectedToken(tt.name);
  524. }
  525. if (this.tokens.matches1(tt.parenL)) {
  526. this.tokens.copyExpectedToken(tt.parenL);
  527. this.rootTransformer.processBalancedCode();
  528. this.tokens.copyExpectedToken(tt.parenR);
  529. }
  530. }
  531. }
  532. }
  533. /**
  534. * Transform a declaration like `export var`, `export let`, or `export const`.
  535. */
  536. processExportVar() {
  537. if (this.isSimpleExportVar()) {
  538. this.processSimpleExportVar();
  539. } else {
  540. this.processComplexExportVar();
  541. }
  542. }
  543. /**
  544. * Determine if the export is of the form:
  545. * export var/let/const [varName] = [expr];
  546. * In other words, determine if function name inference might apply.
  547. */
  548. isSimpleExportVar() {
  549. let tokenIndex = this.tokens.currentIndex();
  550. // export
  551. tokenIndex++;
  552. // var/let/const
  553. tokenIndex++;
  554. if (!this.tokens.matches1AtIndex(tokenIndex, tt.name)) {
  555. return false;
  556. }
  557. tokenIndex++;
  558. while (tokenIndex < this.tokens.tokens.length && this.tokens.tokens[tokenIndex].isType) {
  559. tokenIndex++;
  560. }
  561. if (!this.tokens.matches1AtIndex(tokenIndex, tt.eq)) {
  562. return false;
  563. }
  564. return true;
  565. }
  566. /**
  567. * Transform an `export var` declaration initializing a single variable.
  568. *
  569. * For example, this:
  570. * export const f = () => {};
  571. * becomes this:
  572. * const f = () => {}; exports.f = f;
  573. *
  574. * The variable is unused (e.g. exports.f has the true value of the export).
  575. * We need to produce an assignment of this form so that the function will
  576. * have an inferred name of "f", which wouldn't happen in the more general
  577. * case below.
  578. */
  579. processSimpleExportVar() {
  580. // export
  581. this.tokens.removeInitialToken();
  582. // var/let/const
  583. this.tokens.copyToken();
  584. const varName = this.tokens.identifierName();
  585. // x: number -> x
  586. while (!this.tokens.matches1(tt.eq)) {
  587. this.rootTransformer.processToken();
  588. }
  589. const endIndex = this.tokens.currentToken().rhsEndIndex;
  590. if (endIndex == null) {
  591. throw new Error("Expected = token with an end index.");
  592. }
  593. while (this.tokens.currentIndex() < endIndex) {
  594. this.rootTransformer.processToken();
  595. }
  596. this.tokens.appendCode(`; exports.${varName} = ${varName}`);
  597. }
  598. /**
  599. * Transform normal declaration exports, including handling destructuring.
  600. * For example, this:
  601. * export const {x: [a = 2, b], c} = d;
  602. * becomes this:
  603. * ({x: [exports.a = 2, exports.b], c: exports.c} = d;)
  604. */
  605. processComplexExportVar() {
  606. this.tokens.removeInitialToken();
  607. this.tokens.removeToken();
  608. const needsParens = this.tokens.matches1(tt.braceL);
  609. if (needsParens) {
  610. this.tokens.appendCode("(");
  611. }
  612. let depth = 0;
  613. while (true) {
  614. if (
  615. this.tokens.matches1(tt.braceL) ||
  616. this.tokens.matches1(tt.dollarBraceL) ||
  617. this.tokens.matches1(tt.bracketL)
  618. ) {
  619. depth++;
  620. this.tokens.copyToken();
  621. } else if (this.tokens.matches1(tt.braceR) || this.tokens.matches1(tt.bracketR)) {
  622. depth--;
  623. this.tokens.copyToken();
  624. } else if (
  625. depth === 0 &&
  626. !this.tokens.matches1(tt.name) &&
  627. !this.tokens.currentToken().isType
  628. ) {
  629. break;
  630. } else if (this.tokens.matches1(tt.eq)) {
  631. // Default values might have assignments in the RHS that we want to ignore, so skip past
  632. // them.
  633. const endIndex = this.tokens.currentToken().rhsEndIndex;
  634. if (endIndex == null) {
  635. throw new Error("Expected = token with an end index.");
  636. }
  637. while (this.tokens.currentIndex() < endIndex) {
  638. this.rootTransformer.processToken();
  639. }
  640. } else {
  641. const token = this.tokens.currentToken();
  642. if (isDeclaration(token)) {
  643. const name = this.tokens.identifierName();
  644. let replacement = this.importProcessor.getIdentifierReplacement(name);
  645. if (replacement === null) {
  646. throw new Error(`Expected a replacement for ${name} in \`export var\` syntax.`);
  647. }
  648. if (isObjectShorthandDeclaration(token)) {
  649. replacement = `${name}: ${replacement}`;
  650. }
  651. this.tokens.replaceToken(replacement);
  652. } else {
  653. this.rootTransformer.processToken();
  654. }
  655. }
  656. }
  657. if (needsParens) {
  658. // Seek to the end of the RHS.
  659. const endIndex = this.tokens.currentToken().rhsEndIndex;
  660. if (endIndex == null) {
  661. throw new Error("Expected = token with an end index.");
  662. }
  663. while (this.tokens.currentIndex() < endIndex) {
  664. this.rootTransformer.processToken();
  665. }
  666. this.tokens.appendCode(")");
  667. }
  668. }
  669. /**
  670. * Transform this:
  671. * export function foo() {}
  672. * into this:
  673. * function foo() {} exports.foo = foo;
  674. */
  675. processExportFunction() {
  676. this.tokens.replaceToken("");
  677. const name = this.processNamedFunction();
  678. this.tokens.appendCode(` exports.${name} = ${name};`);
  679. }
  680. /**
  681. * Skip past a function with a name and return that name.
  682. */
  683. processNamedFunction() {
  684. if (this.tokens.matches1(tt._function)) {
  685. this.tokens.copyToken();
  686. } else if (this.tokens.matches2(tt.name, tt._function)) {
  687. if (!this.tokens.matchesContextual(ContextualKeyword._async)) {
  688. throw new Error("Expected async keyword in function export.");
  689. }
  690. this.tokens.copyToken();
  691. this.tokens.copyToken();
  692. }
  693. if (this.tokens.matches1(tt.star)) {
  694. this.tokens.copyToken();
  695. }
  696. if (!this.tokens.matches1(tt.name)) {
  697. throw new Error("Expected identifier for exported function name.");
  698. }
  699. const name = this.tokens.identifierName();
  700. this.tokens.copyToken();
  701. if (this.tokens.currentToken().isType) {
  702. this.tokens.removeInitialToken();
  703. while (this.tokens.currentToken().isType) {
  704. this.tokens.removeToken();
  705. }
  706. }
  707. this.tokens.copyExpectedToken(tt.parenL);
  708. this.rootTransformer.processBalancedCode();
  709. this.tokens.copyExpectedToken(tt.parenR);
  710. this.rootTransformer.processPossibleTypeRange();
  711. this.tokens.copyExpectedToken(tt.braceL);
  712. this.rootTransformer.processBalancedCode();
  713. this.tokens.copyExpectedToken(tt.braceR);
  714. return name;
  715. }
  716. /**
  717. * Transform this:
  718. * export class A {}
  719. * into this:
  720. * class A {} exports.A = A;
  721. */
  722. processExportClass() {
  723. this.tokens.removeInitialToken();
  724. this.copyDecorators();
  725. if (this.tokens.matches1(tt._abstract)) {
  726. this.tokens.removeToken();
  727. }
  728. const name = this.rootTransformer.processNamedClass();
  729. this.tokens.appendCode(` exports.${name} = ${name};`);
  730. }
  731. /**
  732. * Transform this:
  733. * export {a, b as c};
  734. * into this:
  735. * exports.a = a; exports.c = b;
  736. *
  737. * OR
  738. *
  739. * Transform this:
  740. * export {a, b as c} from './foo';
  741. * into the pre-generated Object.defineProperty code from the ImportProcessor.
  742. *
  743. * For the first case, if the TypeScript transform is enabled, we need to skip
  744. * exports that are only defined as types.
  745. */
  746. processExportBindings() {
  747. this.tokens.removeInitialToken();
  748. this.tokens.removeToken();
  749. const exportStatements = [];
  750. while (true) {
  751. if (this.tokens.matches1(tt.braceR)) {
  752. this.tokens.removeToken();
  753. break;
  754. }
  755. const specifierInfo = getImportExportSpecifierInfo(this.tokens);
  756. while (this.tokens.currentIndex() < specifierInfo.endIndex) {
  757. this.tokens.removeToken();
  758. }
  759. if (!specifierInfo.isType && !this.shouldElideExportedIdentifier(specifierInfo.leftName)) {
  760. const localName = specifierInfo.leftName;
  761. const exportedName = specifierInfo.rightName;
  762. const newLocalName = this.importProcessor.getIdentifierReplacement(localName);
  763. exportStatements.push(`exports.${exportedName} = ${newLocalName || localName};`);
  764. }
  765. if (this.tokens.matches1(tt.braceR)) {
  766. this.tokens.removeToken();
  767. break;
  768. }
  769. if (this.tokens.matches2(tt.comma, tt.braceR)) {
  770. this.tokens.removeToken();
  771. this.tokens.removeToken();
  772. break;
  773. } else if (this.tokens.matches1(tt.comma)) {
  774. this.tokens.removeToken();
  775. } else {
  776. throw new Error(`Unexpected token: ${JSON.stringify(this.tokens.currentToken())}`);
  777. }
  778. }
  779. if (this.tokens.matchesContextual(ContextualKeyword._from)) {
  780. // This is an export...from, so throw away the normal named export code
  781. // and use the Object.defineProperty code from ImportProcessor.
  782. this.tokens.removeToken();
  783. const path = this.tokens.stringValue();
  784. this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path));
  785. removeMaybeImportAssertion(this.tokens);
  786. } else {
  787. // This is a normal named export, so use that.
  788. this.tokens.appendCode(exportStatements.join(" "));
  789. }
  790. if (this.tokens.matches1(tt.semi)) {
  791. this.tokens.removeToken();
  792. }
  793. }
  794. processExportStar() {
  795. this.tokens.removeInitialToken();
  796. while (!this.tokens.matches1(tt.string)) {
  797. this.tokens.removeToken();
  798. }
  799. const path = this.tokens.stringValue();
  800. this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path));
  801. removeMaybeImportAssertion(this.tokens);
  802. if (this.tokens.matches1(tt.semi)) {
  803. this.tokens.removeToken();
  804. }
  805. }
  806. shouldElideExportedIdentifier(name) {
  807. return this.isTypeScriptTransformEnabled && !this.declarationInfo.valueDeclarations.has(name);
  808. }
  809. }