UNPKG

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