UNPKG

7.95 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = convertFunctionRest;
7
8var _core = require("@babel/core");
9
10const buildRest = (0, _core.template)(`
11 for (var LEN = ARGUMENTS.length,
12 ARRAY = new Array(ARRAY_LEN),
13 KEY = START;
14 KEY < LEN;
15 KEY++) {
16 ARRAY[ARRAY_KEY] = ARGUMENTS[KEY];
17 }
18`);
19const restIndex = (0, _core.template)(`
20 (INDEX < OFFSET || ARGUMENTS.length <= INDEX) ? undefined : ARGUMENTS[INDEX]
21`);
22const restIndexImpure = (0, _core.template)(`
23 REF = INDEX, (REF < OFFSET || ARGUMENTS.length <= REF) ? undefined : ARGUMENTS[REF]
24`);
25const restLength = (0, _core.template)(`
26 ARGUMENTS.length <= OFFSET ? 0 : ARGUMENTS.length - OFFSET
27`);
28
29function referencesRest(path, state) {
30 if (path.node.name === state.name) {
31 return path.scope.bindingIdentifierEquals(state.name, state.outerBinding);
32 }
33
34 return false;
35}
36
37const memberExpressionOptimisationVisitor = {
38 Scope(path, state) {
39 if (!path.scope.bindingIdentifierEquals(state.name, state.outerBinding)) {
40 path.skip();
41 }
42 },
43
44 Flow(path) {
45 if (path.isTypeCastExpression()) return;
46 path.skip();
47 },
48
49 Function(path, state) {
50 const oldNoOptimise = state.noOptimise;
51 state.noOptimise = true;
52 path.traverse(memberExpressionOptimisationVisitor, state);
53 state.noOptimise = oldNoOptimise;
54 path.skip();
55 },
56
57 ReferencedIdentifier(path, state) {
58 const {
59 node
60 } = path;
61
62 if (node.name === "arguments") {
63 state.deopted = true;
64 }
65
66 if (!referencesRest(path, state)) return;
67
68 if (state.noOptimise) {
69 state.deopted = true;
70 } else {
71 const {
72 parentPath
73 } = path;
74
75 if (parentPath.listKey === "params" && parentPath.key < state.offset) {
76 return;
77 }
78
79 if (parentPath.isMemberExpression({
80 object: node
81 })) {
82 const grandparentPath = parentPath.parentPath;
83 const argsOptEligible = !state.deopted && !(grandparentPath.isAssignmentExpression() && parentPath.node === grandparentPath.node.left || grandparentPath.isLVal() || grandparentPath.isForXStatement() || grandparentPath.isUpdateExpression() || grandparentPath.isUnaryExpression({
84 operator: "delete"
85 }) || (grandparentPath.isCallExpression() || grandparentPath.isNewExpression()) && parentPath.node === grandparentPath.node.callee);
86
87 if (argsOptEligible) {
88 if (parentPath.node.computed) {
89 if (parentPath.get("property").isBaseType("number")) {
90 state.candidates.push({
91 cause: "indexGetter",
92 path
93 });
94 return;
95 }
96 } else if (parentPath.node.property.name === "length") {
97 state.candidates.push({
98 cause: "lengthGetter",
99 path
100 });
101 return;
102 }
103 }
104 }
105
106 if (state.offset === 0 && parentPath.isSpreadElement()) {
107 const call = parentPath.parentPath;
108
109 if (call.isCallExpression() && call.node.arguments.length === 1) {
110 state.candidates.push({
111 cause: "argSpread",
112 path
113 });
114 return;
115 }
116 }
117
118 state.references.push(path);
119 }
120 },
121
122 BindingIdentifier(path, state) {
123 if (referencesRest(path, state)) {
124 state.deopted = true;
125 }
126 }
127
128};
129
130function getParamsCount(node) {
131 let count = node.params.length;
132
133 if (count > 0 && _core.types.isIdentifier(node.params[0], {
134 name: "this"
135 })) {
136 count -= 1;
137 }
138
139 return count;
140}
141
142function hasRest(node) {
143 const length = node.params.length;
144 return length > 0 && _core.types.isRestElement(node.params[length - 1]);
145}
146
147function optimiseIndexGetter(path, argsId, offset) {
148 const offsetLiteral = _core.types.numericLiteral(offset);
149
150 let index;
151
152 if (_core.types.isNumericLiteral(path.parent.property)) {
153 index = _core.types.numericLiteral(path.parent.property.value + offset);
154 } else if (offset === 0) {
155 index = path.parent.property;
156 } else {
157 index = _core.types.binaryExpression("+", path.parent.property, _core.types.cloneNode(offsetLiteral));
158 }
159
160 const {
161 scope
162 } = path;
163
164 if (!scope.isPure(index)) {
165 const temp = scope.generateUidIdentifierBasedOnNode(index);
166 scope.push({
167 id: temp,
168 kind: "var"
169 });
170 path.parentPath.replaceWith(restIndexImpure({
171 ARGUMENTS: argsId,
172 OFFSET: offsetLiteral,
173 INDEX: index,
174 REF: _core.types.cloneNode(temp)
175 }));
176 } else {
177 const parentPath = path.parentPath;
178 parentPath.replaceWith(restIndex({
179 ARGUMENTS: argsId,
180 OFFSET: offsetLiteral,
181 INDEX: index
182 }));
183 const offsetTestPath = parentPath.get("test").get("left");
184 const valRes = offsetTestPath.evaluate();
185
186 if (valRes.confident) {
187 if (valRes.value === true) {
188 parentPath.replaceWith(parentPath.scope.buildUndefinedNode());
189 } else {
190 parentPath.get("test").replaceWith(parentPath.get("test").get("right"));
191 }
192 }
193 }
194}
195
196function optimiseLengthGetter(path, argsId, offset) {
197 if (offset) {
198 path.parentPath.replaceWith(restLength({
199 ARGUMENTS: argsId,
200 OFFSET: _core.types.numericLiteral(offset)
201 }));
202 } else {
203 path.replaceWith(argsId);
204 }
205}
206
207function convertFunctionRest(path) {
208 const {
209 node,
210 scope
211 } = path;
212 if (!hasRest(node)) return false;
213 let rest = node.params.pop().argument;
214
215 const argsId = _core.types.identifier("arguments");
216
217 if (_core.types.isPattern(rest)) {
218 const pattern = rest;
219 rest = scope.generateUidIdentifier("ref");
220
221 const declar = _core.types.variableDeclaration("let", [_core.types.variableDeclarator(pattern, rest)]);
222
223 node.body.body.unshift(declar);
224 }
225
226 const paramsCount = getParamsCount(node);
227 const state = {
228 references: [],
229 offset: paramsCount,
230 argumentsNode: argsId,
231 outerBinding: scope.getBindingIdentifier(rest.name),
232 candidates: [],
233 name: rest.name,
234 deopted: false
235 };
236 path.traverse(memberExpressionOptimisationVisitor, state);
237
238 if (!state.deopted && !state.references.length) {
239 for (const {
240 path,
241 cause
242 } of state.candidates) {
243 const clonedArgsId = _core.types.cloneNode(argsId);
244
245 switch (cause) {
246 case "indexGetter":
247 optimiseIndexGetter(path, clonedArgsId, state.offset);
248 break;
249
250 case "lengthGetter":
251 optimiseLengthGetter(path, clonedArgsId, state.offset);
252 break;
253
254 default:
255 path.replaceWith(clonedArgsId);
256 }
257 }
258
259 return true;
260 }
261
262 state.references = state.references.concat(state.candidates.map(({
263 path
264 }) => path));
265
266 const start = _core.types.numericLiteral(paramsCount);
267
268 const key = scope.generateUidIdentifier("key");
269 const len = scope.generateUidIdentifier("len");
270 let arrKey, arrLen;
271
272 if (paramsCount) {
273 arrKey = _core.types.binaryExpression("-", _core.types.cloneNode(key), _core.types.cloneNode(start));
274 arrLen = _core.types.conditionalExpression(_core.types.binaryExpression(">", _core.types.cloneNode(len), _core.types.cloneNode(start)), _core.types.binaryExpression("-", _core.types.cloneNode(len), _core.types.cloneNode(start)), _core.types.numericLiteral(0));
275 } else {
276 arrKey = _core.types.identifier(key.name);
277 arrLen = _core.types.identifier(len.name);
278 }
279
280 const loop = buildRest({
281 ARGUMENTS: argsId,
282 ARRAY_KEY: arrKey,
283 ARRAY_LEN: arrLen,
284 START: start,
285 ARRAY: rest,
286 KEY: key,
287 LEN: len
288 });
289
290 if (state.deopted) {
291 node.body.body.unshift(loop);
292 } else {
293 let target = path.getEarliestCommonAncestorFrom(state.references).getStatementParent();
294 target.findParent(path => {
295 if (path.isLoop()) {
296 target = path;
297 } else {
298 return path.isFunction();
299 }
300 });
301 target.insertBefore(loop);
302 }
303
304 return true;
305}
\No newline at end of file