index.js 7.7 KB

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