Brak opisu

CJSImportProcessor.js 16KB

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