CJSImportProcessor.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  2. var _tokenizer = require('./parser/tokenizer');
  3. var _keywords = require('./parser/tokenizer/keywords');
  4. var _types = require('./parser/tokenizer/types');
  5. var _getImportExportSpecifierInfo = require('./util/getImportExportSpecifierInfo'); var _getImportExportSpecifierInfo2 = _interopRequireDefault(_getImportExportSpecifierInfo);
  6. var _getNonTypeIdentifiers = require('./util/getNonTypeIdentifiers');
  7. /**
  8. * Class responsible for preprocessing and bookkeeping import and export declarations within the
  9. * file.
  10. *
  11. * TypeScript uses a simpler mechanism that does not use functions like interopRequireDefault and
  12. * interopRequireWildcard, so we also allow that mode for compatibility.
  13. */
  14. class CJSImportProcessor {
  15. __init() {this.nonTypeIdentifiers = new Set()}
  16. __init2() {this.importInfoByPath = new Map()}
  17. __init3() {this.importsToReplace = new Map()}
  18. __init4() {this.identifierReplacements = new Map()}
  19. __init5() {this.exportBindingsByLocalName = new Map()}
  20. constructor(
  21. nameManager,
  22. tokens,
  23. enableLegacyTypeScriptModuleInterop,
  24. options,
  25. isTypeScriptTransformEnabled,
  26. keepUnusedImports,
  27. helperManager,
  28. ) {;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);}
  29. preprocessTokens() {
  30. for (let i = 0; i < this.tokens.tokens.length; i++) {
  31. if (
  32. this.tokens.matches1AtIndex(i, _types.TokenType._import) &&
  33. !this.tokens.matches3AtIndex(i, _types.TokenType._import, _types.TokenType.name, _types.TokenType.eq)
  34. ) {
  35. this.preprocessImportAtIndex(i);
  36. }
  37. if (
  38. this.tokens.matches1AtIndex(i, _types.TokenType._export) &&
  39. !this.tokens.matches2AtIndex(i, _types.TokenType._export, _types.TokenType.eq)
  40. ) {
  41. this.preprocessExportAtIndex(i);
  42. }
  43. }
  44. this.generateImportReplacements();
  45. }
  46. /**
  47. * In TypeScript, import statements that only import types should be removed.
  48. * This includes `import {} from 'foo';`, but not `import 'foo';`.
  49. */
  50. pruneTypeOnlyImports() {
  51. this.nonTypeIdentifiers = _getNonTypeIdentifiers.getNonTypeIdentifiers.call(void 0, this.tokens, this.options);
  52. for (const [path, importInfo] of this.importInfoByPath.entries()) {
  53. if (
  54. importInfo.hasBareImport ||
  55. importInfo.hasStarExport ||
  56. importInfo.exportStarNames.length > 0 ||
  57. importInfo.namedExports.length > 0
  58. ) {
  59. continue;
  60. }
  61. const names = [
  62. ...importInfo.defaultNames,
  63. ...importInfo.wildcardNames,
  64. ...importInfo.namedImports.map(({localName}) => localName),
  65. ];
  66. if (names.every((name) => this.shouldAutomaticallyElideImportedName(name))) {
  67. this.importsToReplace.set(path, "");
  68. }
  69. }
  70. }
  71. shouldAutomaticallyElideImportedName(name) {
  72. return (
  73. this.isTypeScriptTransformEnabled &&
  74. !this.keepUnusedImports &&
  75. !this.nonTypeIdentifiers.has(name)
  76. );
  77. }
  78. generateImportReplacements() {
  79. for (const [path, importInfo] of this.importInfoByPath.entries()) {
  80. const {
  81. defaultNames,
  82. wildcardNames,
  83. namedImports,
  84. namedExports,
  85. exportStarNames,
  86. hasStarExport,
  87. } = importInfo;
  88. if (
  89. defaultNames.length === 0 &&
  90. wildcardNames.length === 0 &&
  91. namedImports.length === 0 &&
  92. namedExports.length === 0 &&
  93. exportStarNames.length === 0 &&
  94. !hasStarExport
  95. ) {
  96. // Import is never used, so don't even assign a name.
  97. this.importsToReplace.set(path, `require('${path}');`);
  98. continue;
  99. }
  100. const primaryImportName = this.getFreeIdentifierForPath(path);
  101. let secondaryImportName;
  102. if (this.enableLegacyTypeScriptModuleInterop) {
  103. secondaryImportName = primaryImportName;
  104. } else {
  105. secondaryImportName =
  106. wildcardNames.length > 0 ? wildcardNames[0] : this.getFreeIdentifierForPath(path);
  107. }
  108. let requireCode = `var ${primaryImportName} = require('${path}');`;
  109. if (wildcardNames.length > 0) {
  110. for (const wildcardName of wildcardNames) {
  111. const moduleExpr = this.enableLegacyTypeScriptModuleInterop
  112. ? primaryImportName
  113. : `${this.helperManager.getHelperName("interopRequireWildcard")}(${primaryImportName})`;
  114. requireCode += ` var ${wildcardName} = ${moduleExpr};`;
  115. }
  116. } else if (exportStarNames.length > 0 && secondaryImportName !== primaryImportName) {
  117. requireCode += ` var ${secondaryImportName} = ${this.helperManager.getHelperName(
  118. "interopRequireWildcard",
  119. )}(${primaryImportName});`;
  120. } else if (defaultNames.length > 0 && secondaryImportName !== primaryImportName) {
  121. requireCode += ` var ${secondaryImportName} = ${this.helperManager.getHelperName(
  122. "interopRequireDefault",
  123. )}(${primaryImportName});`;
  124. }
  125. for (const {importedName, localName} of namedExports) {
  126. requireCode += ` ${this.helperManager.getHelperName(
  127. "createNamedExportFrom",
  128. )}(${primaryImportName}, '${localName}', '${importedName}');`;
  129. }
  130. for (const exportStarName of exportStarNames) {
  131. requireCode += ` exports.${exportStarName} = ${secondaryImportName};`;
  132. }
  133. if (hasStarExport) {
  134. requireCode += ` ${this.helperManager.getHelperName(
  135. "createStarExport",
  136. )}(${primaryImportName});`;
  137. }
  138. this.importsToReplace.set(path, requireCode);
  139. for (const defaultName of defaultNames) {
  140. this.identifierReplacements.set(defaultName, `${secondaryImportName}.default`);
  141. }
  142. for (const {importedName, localName} of namedImports) {
  143. this.identifierReplacements.set(localName, `${primaryImportName}.${importedName}`);
  144. }
  145. }
  146. }
  147. getFreeIdentifierForPath(path) {
  148. const components = path.split("/");
  149. const lastComponent = components[components.length - 1];
  150. const baseName = lastComponent.replace(/\W/g, "");
  151. return this.nameManager.claimFreeName(`_${baseName}`);
  152. }
  153. preprocessImportAtIndex(index) {
  154. const defaultNames = [];
  155. const wildcardNames = [];
  156. const namedImports = [];
  157. index++;
  158. if (
  159. (this.tokens.matchesContextualAtIndex(index, _keywords.ContextualKeyword._type) ||
  160. this.tokens.matches1AtIndex(index, _types.TokenType._typeof)) &&
  161. !this.tokens.matches1AtIndex(index + 1, _types.TokenType.comma) &&
  162. !this.tokens.matchesContextualAtIndex(index + 1, _keywords.ContextualKeyword._from)
  163. ) {
  164. // import type declaration, so no need to process anything.
  165. return;
  166. }
  167. if (this.tokens.matches1AtIndex(index, _types.TokenType.parenL)) {
  168. // Dynamic import, so nothing to do
  169. return;
  170. }
  171. if (this.tokens.matches1AtIndex(index, _types.TokenType.name)) {
  172. defaultNames.push(this.tokens.identifierNameAtIndex(index));
  173. index++;
  174. if (this.tokens.matches1AtIndex(index, _types.TokenType.comma)) {
  175. index++;
  176. }
  177. }
  178. if (this.tokens.matches1AtIndex(index, _types.TokenType.star)) {
  179. // * as
  180. index += 2;
  181. wildcardNames.push(this.tokens.identifierNameAtIndex(index));
  182. index++;
  183. }
  184. if (this.tokens.matches1AtIndex(index, _types.TokenType.braceL)) {
  185. const result = this.getNamedImports(index + 1);
  186. index = result.newIndex;
  187. for (const namedImport of result.namedImports) {
  188. // Treat {default as X} as a default import to ensure usage of require interop helper
  189. if (namedImport.importedName === "default") {
  190. defaultNames.push(namedImport.localName);
  191. } else {
  192. namedImports.push(namedImport);
  193. }
  194. }
  195. }
  196. if (this.tokens.matchesContextualAtIndex(index, _keywords.ContextualKeyword._from)) {
  197. index++;
  198. }
  199. if (!this.tokens.matches1AtIndex(index, _types.TokenType.string)) {
  200. throw new Error("Expected string token at the end of import statement.");
  201. }
  202. const path = this.tokens.stringValueAtIndex(index);
  203. const importInfo = this.getImportInfo(path);
  204. importInfo.defaultNames.push(...defaultNames);
  205. importInfo.wildcardNames.push(...wildcardNames);
  206. importInfo.namedImports.push(...namedImports);
  207. if (defaultNames.length === 0 && wildcardNames.length === 0 && namedImports.length === 0) {
  208. importInfo.hasBareImport = true;
  209. }
  210. }
  211. preprocessExportAtIndex(index) {
  212. if (
  213. this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._var) ||
  214. this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._let) ||
  215. this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._const)
  216. ) {
  217. this.preprocessVarExportAtIndex(index);
  218. } else if (
  219. this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._function) ||
  220. this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType._class)
  221. ) {
  222. const exportName = this.tokens.identifierNameAtIndex(index + 2);
  223. this.addExportBinding(exportName, exportName);
  224. } else if (this.tokens.matches3AtIndex(index, _types.TokenType._export, _types.TokenType.name, _types.TokenType._function)) {
  225. const exportName = this.tokens.identifierNameAtIndex(index + 3);
  226. this.addExportBinding(exportName, exportName);
  227. } else if (this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType.braceL)) {
  228. this.preprocessNamedExportAtIndex(index);
  229. } else if (this.tokens.matches2AtIndex(index, _types.TokenType._export, _types.TokenType.star)) {
  230. this.preprocessExportStarAtIndex(index);
  231. }
  232. }
  233. preprocessVarExportAtIndex(index) {
  234. let depth = 0;
  235. // Handle cases like `export let {x} = y;`, starting at the open-brace in that case.
  236. for (let i = index + 2; ; i++) {
  237. if (
  238. this.tokens.matches1AtIndex(i, _types.TokenType.braceL) ||
  239. this.tokens.matches1AtIndex(i, _types.TokenType.dollarBraceL) ||
  240. this.tokens.matches1AtIndex(i, _types.TokenType.bracketL)
  241. ) {
  242. depth++;
  243. } else if (
  244. this.tokens.matches1AtIndex(i, _types.TokenType.braceR) ||
  245. this.tokens.matches1AtIndex(i, _types.TokenType.bracketR)
  246. ) {
  247. depth--;
  248. } else if (depth === 0 && !this.tokens.matches1AtIndex(i, _types.TokenType.name)) {
  249. break;
  250. } else if (this.tokens.matches1AtIndex(1, _types.TokenType.eq)) {
  251. const endIndex = this.tokens.currentToken().rhsEndIndex;
  252. if (endIndex == null) {
  253. throw new Error("Expected = token with an end index.");
  254. }
  255. i = endIndex - 1;
  256. } else {
  257. const token = this.tokens.tokens[i];
  258. if (_tokenizer.isDeclaration.call(void 0, token)) {
  259. const exportName = this.tokens.identifierNameAtIndex(i);
  260. this.identifierReplacements.set(exportName, `exports.${exportName}`);
  261. }
  262. }
  263. }
  264. }
  265. /**
  266. * Walk this export statement just in case it's an export...from statement.
  267. * If it is, combine it into the import info for that path. Otherwise, just
  268. * bail out; it'll be handled later.
  269. */
  270. preprocessNamedExportAtIndex(index) {
  271. // export {
  272. index += 2;
  273. const {newIndex, namedImports} = this.getNamedImports(index);
  274. index = newIndex;
  275. if (this.tokens.matchesContextualAtIndex(index, _keywords.ContextualKeyword._from)) {
  276. index++;
  277. } else {
  278. // Reinterpret "a as b" to be local/exported rather than imported/local.
  279. for (const {importedName: localName, localName: exportedName} of namedImports) {
  280. this.addExportBinding(localName, exportedName);
  281. }
  282. return;
  283. }
  284. if (!this.tokens.matches1AtIndex(index, _types.TokenType.string)) {
  285. throw new Error("Expected string token at the end of import statement.");
  286. }
  287. const path = this.tokens.stringValueAtIndex(index);
  288. const importInfo = this.getImportInfo(path);
  289. importInfo.namedExports.push(...namedImports);
  290. }
  291. preprocessExportStarAtIndex(index) {
  292. let exportedName = null;
  293. if (this.tokens.matches3AtIndex(index, _types.TokenType._export, _types.TokenType.star, _types.TokenType._as)) {
  294. // export * as
  295. index += 3;
  296. exportedName = this.tokens.identifierNameAtIndex(index);
  297. // foo from
  298. index += 2;
  299. } else {
  300. // export * from
  301. index += 3;
  302. }
  303. if (!this.tokens.matches1AtIndex(index, _types.TokenType.string)) {
  304. throw new Error("Expected string token at the end of star export statement.");
  305. }
  306. const path = this.tokens.stringValueAtIndex(index);
  307. const importInfo = this.getImportInfo(path);
  308. if (exportedName !== null) {
  309. importInfo.exportStarNames.push(exportedName);
  310. } else {
  311. importInfo.hasStarExport = true;
  312. }
  313. }
  314. getNamedImports(index) {
  315. const namedImports = [];
  316. while (true) {
  317. if (this.tokens.matches1AtIndex(index, _types.TokenType.braceR)) {
  318. index++;
  319. break;
  320. }
  321. const specifierInfo = _getImportExportSpecifierInfo2.default.call(void 0, this.tokens, index);
  322. index = specifierInfo.endIndex;
  323. if (!specifierInfo.isType) {
  324. namedImports.push({
  325. importedName: specifierInfo.leftName,
  326. localName: specifierInfo.rightName,
  327. });
  328. }
  329. if (this.tokens.matches2AtIndex(index, _types.TokenType.comma, _types.TokenType.braceR)) {
  330. index += 2;
  331. break;
  332. } else if (this.tokens.matches1AtIndex(index, _types.TokenType.braceR)) {
  333. index++;
  334. break;
  335. } else if (this.tokens.matches1AtIndex(index, _types.TokenType.comma)) {
  336. index++;
  337. } else {
  338. throw new Error(`Unexpected token: ${JSON.stringify(this.tokens.tokens[index])}`);
  339. }
  340. }
  341. return {newIndex: index, namedImports};
  342. }
  343. /**
  344. * Get a mutable import info object for this path, creating one if it doesn't
  345. * exist yet.
  346. */
  347. getImportInfo(path) {
  348. const existingInfo = this.importInfoByPath.get(path);
  349. if (existingInfo) {
  350. return existingInfo;
  351. }
  352. const newInfo = {
  353. defaultNames: [],
  354. wildcardNames: [],
  355. namedImports: [],
  356. namedExports: [],
  357. hasBareImport: false,
  358. exportStarNames: [],
  359. hasStarExport: false,
  360. };
  361. this.importInfoByPath.set(path, newInfo);
  362. return newInfo;
  363. }
  364. addExportBinding(localName, exportedName) {
  365. if (!this.exportBindingsByLocalName.has(localName)) {
  366. this.exportBindingsByLocalName.set(localName, []);
  367. }
  368. this.exportBindingsByLocalName.get(localName).push(exportedName);
  369. }
  370. /**
  371. * Return the code to use for the import for this path, or the empty string if
  372. * the code has already been "claimed" by a previous import.
  373. */
  374. claimImportCode(importPath) {
  375. const result = this.importsToReplace.get(importPath);
  376. this.importsToReplace.set(importPath, "");
  377. return result || "";
  378. }
  379. getIdentifierReplacement(identifierName) {
  380. return this.identifierReplacements.get(identifierName) || null;
  381. }
  382. /**
  383. * Return a string like `exports.foo = exports.bar`.
  384. */
  385. resolveExportBinding(assignedName) {
  386. const exportedNames = this.exportBindingsByLocalName.get(assignedName);
  387. if (!exportedNames || exportedNames.length === 0) {
  388. return null;
  389. }
  390. return exportedNames.map((exportedName) => `exports.${exportedName}`).join(" = ");
  391. }
  392. /**
  393. * Return all imported/exported names where we might be interested in whether usages of those
  394. * names are shadowed.
  395. */
  396. getGlobalNames() {
  397. return new Set([
  398. ...this.identifierReplacements.keys(),
  399. ...this.exportBindingsByLocalName.keys(),
  400. ]);
  401. }
  402. } exports.default = CJSImportProcessor;