index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. exports.ensure = ensure;
  7. exports.get = get;
  8. exports.getDependencies = getDependencies;
  9. exports.list = void 0;
  10. exports.minVersion = minVersion;
  11. var _traverse = require("@babel/traverse");
  12. var _t = require("@babel/types");
  13. var _helpers = require("./helpers");
  14. const {
  15. assignmentExpression,
  16. cloneNode,
  17. expressionStatement,
  18. file,
  19. identifier
  20. } = _t;
  21. function makePath(path) {
  22. const parts = [];
  23. for (; path.parentPath; path = path.parentPath) {
  24. parts.push(path.key);
  25. if (path.inList) parts.push(path.listKey);
  26. }
  27. return parts.reverse().join(".");
  28. }
  29. let FileClass = undefined;
  30. function getHelperMetadata(file) {
  31. const globals = new Set();
  32. const localBindingNames = new Set();
  33. const dependencies = new Map();
  34. let exportName;
  35. let exportPath;
  36. const exportBindingAssignments = [];
  37. const importPaths = [];
  38. const importBindingsReferences = [];
  39. const dependencyVisitor = {
  40. ImportDeclaration(child) {
  41. const name = child.node.source.value;
  42. if (!_helpers.default[name]) {
  43. throw child.buildCodeFrameError(`Unknown helper ${name}`);
  44. }
  45. if (child.get("specifiers").length !== 1 || !child.get("specifiers.0").isImportDefaultSpecifier()) {
  46. throw child.buildCodeFrameError("Helpers can only import a default value");
  47. }
  48. const bindingIdentifier = child.node.specifiers[0].local;
  49. dependencies.set(bindingIdentifier, name);
  50. importPaths.push(makePath(child));
  51. },
  52. ExportDefaultDeclaration(child) {
  53. const decl = child.get("declaration");
  54. if (!decl.isFunctionDeclaration() || !decl.node.id) {
  55. throw decl.buildCodeFrameError("Helpers can only export named function declarations");
  56. }
  57. exportName = decl.node.id.name;
  58. exportPath = makePath(child);
  59. },
  60. ExportAllDeclaration(child) {
  61. throw child.buildCodeFrameError("Helpers can only export default");
  62. },
  63. ExportNamedDeclaration(child) {
  64. throw child.buildCodeFrameError("Helpers can only export default");
  65. },
  66. Statement(child) {
  67. if (child.isModuleDeclaration()) return;
  68. child.skip();
  69. }
  70. };
  71. const referenceVisitor = {
  72. Program(path) {
  73. const bindings = path.scope.getAllBindings();
  74. Object.keys(bindings).forEach(name => {
  75. if (name === exportName) return;
  76. if (dependencies.has(bindings[name].identifier)) return;
  77. localBindingNames.add(name);
  78. });
  79. },
  80. ReferencedIdentifier(child) {
  81. const name = child.node.name;
  82. const binding = child.scope.getBinding(name);
  83. if (!binding) {
  84. globals.add(name);
  85. } else if (dependencies.has(binding.identifier)) {
  86. importBindingsReferences.push(makePath(child));
  87. }
  88. },
  89. AssignmentExpression(child) {
  90. const left = child.get("left");
  91. if (!(exportName in left.getBindingIdentifiers())) return;
  92. if (!left.isIdentifier()) {
  93. throw left.buildCodeFrameError("Only simple assignments to exports are allowed in helpers");
  94. }
  95. const binding = child.scope.getBinding(exportName);
  96. if (binding != null && binding.scope.path.isProgram()) {
  97. exportBindingAssignments.push(makePath(child));
  98. }
  99. }
  100. };
  101. (0, _traverse.default)(file.ast, dependencyVisitor, file.scope);
  102. (0, _traverse.default)(file.ast, referenceVisitor, file.scope);
  103. if (!exportPath) throw new Error("Helpers must have a default export.");
  104. exportBindingAssignments.reverse();
  105. return {
  106. globals: Array.from(globals),
  107. localBindingNames: Array.from(localBindingNames),
  108. dependencies,
  109. exportBindingAssignments,
  110. exportPath,
  111. exportName,
  112. importBindingsReferences,
  113. importPaths
  114. };
  115. }
  116. function permuteHelperAST(file, metadata, id, localBindings, getDependency) {
  117. if (localBindings && !id) {
  118. throw new Error("Unexpected local bindings for module-based helpers.");
  119. }
  120. if (!id) return;
  121. const {
  122. localBindingNames,
  123. dependencies,
  124. exportBindingAssignments,
  125. exportPath,
  126. exportName,
  127. importBindingsReferences,
  128. importPaths
  129. } = metadata;
  130. const dependenciesRefs = {};
  131. dependencies.forEach((name, id) => {
  132. dependenciesRefs[id.name] = typeof getDependency === "function" && getDependency(name) || id;
  133. });
  134. const toRename = {};
  135. const bindings = new Set(localBindings || []);
  136. localBindingNames.forEach(name => {
  137. let newName = name;
  138. while (bindings.has(newName)) newName = "_" + newName;
  139. if (newName !== name) toRename[name] = newName;
  140. });
  141. if (id.type === "Identifier" && exportName !== id.name) {
  142. toRename[exportName] = id.name;
  143. }
  144. const {
  145. path
  146. } = file;
  147. const exp = path.get(exportPath);
  148. const imps = importPaths.map(p => path.get(p));
  149. const impsBindingRefs = importBindingsReferences.map(p => path.get(p));
  150. const decl = exp.get("declaration");
  151. if (id.type === "Identifier") {
  152. exp.replaceWith(decl);
  153. } else if (id.type === "MemberExpression") {
  154. exportBindingAssignments.forEach(assignPath => {
  155. const assign = path.get(assignPath);
  156. assign.replaceWith(assignmentExpression("=", id, assign.node));
  157. });
  158. exp.replaceWith(decl);
  159. path.pushContainer("body", expressionStatement(assignmentExpression("=", id, identifier(exportName))));
  160. } else {
  161. throw new Error("Unexpected helper format.");
  162. }
  163. Object.keys(toRename).forEach(name => {
  164. path.scope.rename(name, toRename[name]);
  165. });
  166. for (const path of imps) path.remove();
  167. for (const path of impsBindingRefs) {
  168. const node = cloneNode(dependenciesRefs[path.node.name]);
  169. path.replaceWith(node);
  170. }
  171. }
  172. const helperData = Object.create(null);
  173. function loadHelper(name) {
  174. if (!helperData[name]) {
  175. const helper = _helpers.default[name];
  176. if (!helper) {
  177. throw Object.assign(new ReferenceError(`Unknown helper ${name}`), {
  178. code: "BABEL_HELPER_UNKNOWN",
  179. helper: name
  180. });
  181. }
  182. const fn = () => {
  183. {
  184. if (!FileClass) {
  185. const fakeFile = {
  186. ast: file(helper.ast()),
  187. path: null
  188. };
  189. (0, _traverse.default)(fakeFile.ast, {
  190. Program: path => (fakeFile.path = path).stop()
  191. });
  192. return fakeFile;
  193. }
  194. }
  195. return new FileClass({
  196. filename: `babel-helper://${name}`
  197. }, {
  198. ast: file(helper.ast()),
  199. code: "[internal Babel helper code]",
  200. inputMap: null
  201. });
  202. };
  203. let metadata = null;
  204. helperData[name] = {
  205. minVersion: helper.minVersion,
  206. build(getDependency, id, localBindings) {
  207. const file = fn();
  208. metadata || (metadata = getHelperMetadata(file));
  209. permuteHelperAST(file, metadata, id, localBindings, getDependency);
  210. return {
  211. nodes: file.ast.program.body,
  212. globals: metadata.globals
  213. };
  214. },
  215. getDependencies() {
  216. metadata || (metadata = getHelperMetadata(fn()));
  217. return Array.from(metadata.dependencies.values());
  218. }
  219. };
  220. }
  221. return helperData[name];
  222. }
  223. function get(name, getDependency, id, localBindings) {
  224. return loadHelper(name).build(getDependency, id, localBindings);
  225. }
  226. function minVersion(name) {
  227. return loadHelper(name).minVersion;
  228. }
  229. function getDependencies(name) {
  230. return loadHelper(name).getDependencies();
  231. }
  232. function ensure(name, newFileClass) {
  233. FileClass || (FileClass = newFileClass);
  234. loadHelper(name);
  235. }
  236. const list = Object.keys(_helpers.default).map(name => name.replace(/^_/, ""));
  237. exports.list = list;
  238. var _default = get;
  239. exports.default = _default;