1 | var types = require("./types");
|
2 | var n = types.namedTypes;
|
3 | var b = types.builders;
|
4 | var isNumber = types.builtInTypes.number;
|
5 | var isArray = types.builtInTypes.array;
|
6 | var Path = require("./path");
|
7 | var Scope = require("./scope");
|
8 |
|
9 | function NodePath(value, parentPath, name) {
|
10 | if (!(this instanceof NodePath)) {
|
11 | throw new Error("NodePath constructor cannot be invoked without 'new'");
|
12 | }
|
13 | Path.call(this, value, parentPath, name);
|
14 | }
|
15 |
|
16 | var NPp = NodePath.prototype = Object.create(Path.prototype, {
|
17 | constructor: {
|
18 | value: NodePath,
|
19 | enumerable: false,
|
20 | writable: true,
|
21 | configurable: true
|
22 | }
|
23 | });
|
24 |
|
25 | Object.defineProperties(NPp, {
|
26 | node: {
|
27 | get: function() {
|
28 | Object.defineProperty(this, "node", {
|
29 | configurable: true,
|
30 | value: this._computeNode()
|
31 | });
|
32 |
|
33 | return this.node;
|
34 | }
|
35 | },
|
36 |
|
37 | parent: {
|
38 | get: function() {
|
39 | Object.defineProperty(this, "parent", {
|
40 | configurable: true,
|
41 | value: this._computeParent()
|
42 | });
|
43 |
|
44 | return this.parent;
|
45 | }
|
46 | },
|
47 |
|
48 | scope: {
|
49 | get: function() {
|
50 | Object.defineProperty(this, "scope", {
|
51 | configurable: true,
|
52 | value: this._computeScope()
|
53 | });
|
54 |
|
55 | return this.scope;
|
56 | }
|
57 | }
|
58 | });
|
59 |
|
60 | NPp.replace = function() {
|
61 | delete this.node;
|
62 | delete this.parent;
|
63 | delete this.scope;
|
64 | return Path.prototype.replace.apply(this, arguments);
|
65 | };
|
66 |
|
67 | NPp.prune = function() {
|
68 | var remainingNodePath = this.parent;
|
69 |
|
70 | this.replace();
|
71 |
|
72 | return cleanUpNodesAfterPrune(remainingNodePath);
|
73 | };
|
74 |
|
75 |
|
76 | NPp._computeNode = function() {
|
77 | var value = this.value;
|
78 | if (n.Node.check(value)) {
|
79 | return value;
|
80 | }
|
81 |
|
82 | var pp = this.parentPath;
|
83 | return pp && pp.node || null;
|
84 | };
|
85 |
|
86 |
|
87 | NPp._computeParent = function() {
|
88 | var value = this.value;
|
89 | var pp = this.parentPath;
|
90 |
|
91 | if (!n.Node.check(value)) {
|
92 | while (pp && !n.Node.check(pp.value)) {
|
93 | pp = pp.parentPath;
|
94 | }
|
95 |
|
96 | if (pp) {
|
97 | pp = pp.parentPath;
|
98 | }
|
99 | }
|
100 |
|
101 | while (pp && !n.Node.check(pp.value)) {
|
102 | pp = pp.parentPath;
|
103 | }
|
104 |
|
105 | return pp || null;
|
106 | };
|
107 |
|
108 |
|
109 | NPp._computeScope = function() {
|
110 | var value = this.value;
|
111 | var pp = this.parentPath;
|
112 | var scope = pp && pp.scope;
|
113 |
|
114 | if (n.Node.check(value) &&
|
115 | Scope.isEstablishedBy(value)) {
|
116 | scope = new Scope(this, scope);
|
117 | }
|
118 |
|
119 | return scope || null;
|
120 | };
|
121 |
|
122 | NPp.getValueProperty = function(name) {
|
123 | return types.getFieldValue(this.value, name);
|
124 | };
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | NPp.needsParens = function(assumeExpressionContext) {
|
140 | var pp = this.parentPath;
|
141 | if (!pp) {
|
142 | return false;
|
143 | }
|
144 |
|
145 | var node = this.value;
|
146 |
|
147 |
|
148 | if (!n.Expression.check(node)) {
|
149 | return false;
|
150 | }
|
151 |
|
152 |
|
153 | if (node.type === "Identifier") {
|
154 | return false;
|
155 | }
|
156 |
|
157 | while (!n.Node.check(pp.value)) {
|
158 | pp = pp.parentPath;
|
159 | if (!pp) {
|
160 | return false;
|
161 | }
|
162 | }
|
163 |
|
164 | var parent = pp.value;
|
165 |
|
166 | switch (node.type) {
|
167 | case "UnaryExpression":
|
168 | case "SpreadElement":
|
169 | case "SpreadProperty":
|
170 | return parent.type === "MemberExpression"
|
171 | && this.name === "object"
|
172 | && parent.object === node;
|
173 |
|
174 | case "BinaryExpression":
|
175 | case "LogicalExpression":
|
176 | switch (parent.type) {
|
177 | case "CallExpression":
|
178 | return this.name === "callee"
|
179 | && parent.callee === node;
|
180 |
|
181 | case "UnaryExpression":
|
182 | case "SpreadElement":
|
183 | case "SpreadProperty":
|
184 | return true;
|
185 |
|
186 | case "MemberExpression":
|
187 | return this.name === "object"
|
188 | && parent.object === node;
|
189 |
|
190 | case "BinaryExpression":
|
191 | case "LogicalExpression":
|
192 | var po = parent.operator;
|
193 | var pp = PRECEDENCE[po];
|
194 | var no = node.operator;
|
195 | var np = PRECEDENCE[no];
|
196 |
|
197 | if (pp > np) {
|
198 | return true;
|
199 | }
|
200 |
|
201 | if (pp === np && this.name === "right") {
|
202 | if (parent.right !== node) {
|
203 | throw new Error("Nodes must be equal");
|
204 | }
|
205 | return true;
|
206 | }
|
207 |
|
208 | default:
|
209 | return false;
|
210 | }
|
211 |
|
212 | case "SequenceExpression":
|
213 | switch (parent.type) {
|
214 | case "ForStatement":
|
215 |
|
216 |
|
217 |
|
218 |
|
219 | return false;
|
220 |
|
221 | case "ExpressionStatement":
|
222 | return this.name !== "expression";
|
223 |
|
224 | default:
|
225 |
|
226 |
|
227 | return true;
|
228 | }
|
229 |
|
230 | case "YieldExpression":
|
231 | switch (parent.type) {
|
232 | case "BinaryExpression":
|
233 | case "LogicalExpression":
|
234 | case "UnaryExpression":
|
235 | case "SpreadElement":
|
236 | case "SpreadProperty":
|
237 | case "CallExpression":
|
238 | case "MemberExpression":
|
239 | case "NewExpression":
|
240 | case "ConditionalExpression":
|
241 | case "YieldExpression":
|
242 | return true;
|
243 |
|
244 | default:
|
245 | return false;
|
246 | }
|
247 |
|
248 | case "Literal":
|
249 | return parent.type === "MemberExpression"
|
250 | && isNumber.check(node.value)
|
251 | && this.name === "object"
|
252 | && parent.object === node;
|
253 |
|
254 | case "AssignmentExpression":
|
255 | case "ConditionalExpression":
|
256 | switch (parent.type) {
|
257 | case "UnaryExpression":
|
258 | case "SpreadElement":
|
259 | case "SpreadProperty":
|
260 | case "BinaryExpression":
|
261 | case "LogicalExpression":
|
262 | return true;
|
263 |
|
264 | case "CallExpression":
|
265 | return this.name === "callee"
|
266 | && parent.callee === node;
|
267 |
|
268 | case "ConditionalExpression":
|
269 | return this.name === "test"
|
270 | && parent.test === node;
|
271 |
|
272 | case "MemberExpression":
|
273 | return this.name === "object"
|
274 | && parent.object === node;
|
275 |
|
276 | default:
|
277 | return false;
|
278 | }
|
279 |
|
280 | default:
|
281 | if (parent.type === "NewExpression" &&
|
282 | this.name === "callee" &&
|
283 | parent.callee === node) {
|
284 | return containsCallExpression(node);
|
285 | }
|
286 | }
|
287 |
|
288 | if (assumeExpressionContext !== true &&
|
289 | !this.canBeFirstInStatement() &&
|
290 | this.firstInStatement())
|
291 | return true;
|
292 |
|
293 | return false;
|
294 | };
|
295 |
|
296 | function isBinary(node) {
|
297 | return n.BinaryExpression.check(node)
|
298 | || n.LogicalExpression.check(node);
|
299 | }
|
300 |
|
301 | function isUnaryLike(node) {
|
302 | return n.UnaryExpression.check(node)
|
303 |
|
304 |
|
305 | || (n.SpreadElement && n.SpreadElement.check(node))
|
306 | || (n.SpreadProperty && n.SpreadProperty.check(node));
|
307 | }
|
308 |
|
309 | var PRECEDENCE = {};
|
310 | [["||"],
|
311 | ["&&"],
|
312 | ["|"],
|
313 | ["^"],
|
314 | ["&"],
|
315 | ["==", "===", "!=", "!=="],
|
316 | ["<", ">", "<=", ">=", "in", "instanceof"],
|
317 | [">>", "<<", ">>>"],
|
318 | ["+", "-"],
|
319 | ["*", "/", "%"]
|
320 | ].forEach(function(tier, i) {
|
321 | tier.forEach(function(op) {
|
322 | PRECEDENCE[op] = i;
|
323 | });
|
324 | });
|
325 |
|
326 | function containsCallExpression(node) {
|
327 | if (n.CallExpression.check(node)) {
|
328 | return true;
|
329 | }
|
330 |
|
331 | if (isArray.check(node)) {
|
332 | return node.some(containsCallExpression);
|
333 | }
|
334 |
|
335 | if (n.Node.check(node)) {
|
336 | return types.someField(node, function(name, child) {
|
337 | return containsCallExpression(child);
|
338 | });
|
339 | }
|
340 |
|
341 | return false;
|
342 | }
|
343 |
|
344 | NPp.canBeFirstInStatement = function() {
|
345 | var node = this.node;
|
346 | return !n.FunctionExpression.check(node)
|
347 | && !n.ObjectExpression.check(node);
|
348 | };
|
349 |
|
350 | NPp.firstInStatement = function() {
|
351 | return firstInStatement(this);
|
352 | };
|
353 |
|
354 | function firstInStatement(path) {
|
355 | for (var node, parent; path.parent; path = path.parent) {
|
356 | node = path.node;
|
357 | parent = path.parent.node;
|
358 |
|
359 | if (n.BlockStatement.check(parent) &&
|
360 | path.parent.name === "body" &&
|
361 | path.name === 0) {
|
362 | if (parent.body[0] !== node) {
|
363 | throw new Error("Nodes must be equal");
|
364 | }
|
365 | return true;
|
366 | }
|
367 |
|
368 | if (n.ExpressionStatement.check(parent) &&
|
369 | path.name === "expression") {
|
370 | if (parent.expression !== node) {
|
371 | throw new Error("Nodes must be equal");
|
372 | }
|
373 | return true;
|
374 | }
|
375 |
|
376 | if (n.SequenceExpression.check(parent) &&
|
377 | path.parent.name === "expressions" &&
|
378 | path.name === 0) {
|
379 | if (parent.expressions[0] !== node) {
|
380 | throw new Error("Nodes must be equal");
|
381 | }
|
382 | continue;
|
383 | }
|
384 |
|
385 | if (n.CallExpression.check(parent) &&
|
386 | path.name === "callee") {
|
387 | if (parent.callee !== node) {
|
388 | throw new Error("Nodes must be equal");
|
389 | }
|
390 | continue;
|
391 | }
|
392 |
|
393 | if (n.MemberExpression.check(parent) &&
|
394 | path.name === "object") {
|
395 | if (parent.object !== node) {
|
396 | throw new Error("Nodes must be equal");
|
397 | }
|
398 | continue;
|
399 | }
|
400 |
|
401 | if (n.ConditionalExpression.check(parent) &&
|
402 | path.name === "test") {
|
403 | if (parent.test !== node) {
|
404 | throw new Error("Nodes must be equal");
|
405 | }
|
406 | continue;
|
407 | }
|
408 |
|
409 | if (isBinary(parent) &&
|
410 | path.name === "left") {
|
411 | if (parent.left !== node) {
|
412 | throw new Error("Nodes must be equal");
|
413 | }
|
414 | continue;
|
415 | }
|
416 |
|
417 | if (n.UnaryExpression.check(parent) &&
|
418 | !parent.prefix &&
|
419 | path.name === "argument") {
|
420 | if (parent.argument !== node) {
|
421 | throw new Error("Nodes must be equal");
|
422 | }
|
423 | continue;
|
424 | }
|
425 |
|
426 | return false;
|
427 | }
|
428 |
|
429 | return true;
|
430 | }
|
431 |
|
432 |
|
433 |
|
434 |
|
435 | function cleanUpNodesAfterPrune(remainingNodePath) {
|
436 | if (n.VariableDeclaration.check(remainingNodePath.node)) {
|
437 | var declarations = remainingNodePath.get('declarations').value;
|
438 | if (!declarations || declarations.length === 0) {
|
439 | return remainingNodePath.prune();
|
440 | }
|
441 | } else if (n.ExpressionStatement.check(remainingNodePath.node)) {
|
442 | if (!remainingNodePath.get('expression').value) {
|
443 | return remainingNodePath.prune();
|
444 | }
|
445 | } else if (n.IfStatement.check(remainingNodePath.node)) {
|
446 | cleanUpIfStatementAfterPrune(remainingNodePath);
|
447 | }
|
448 |
|
449 | return remainingNodePath;
|
450 | }
|
451 |
|
452 | function cleanUpIfStatementAfterPrune(ifStatement) {
|
453 | var testExpression = ifStatement.get('test').value;
|
454 | var alternate = ifStatement.get('alternate').value;
|
455 | var consequent = ifStatement.get('consequent').value;
|
456 |
|
457 | if (!consequent && !alternate) {
|
458 | var testExpressionStatement = b.expressionStatement(testExpression);
|
459 |
|
460 | ifStatement.replace(testExpressionStatement);
|
461 | } else if (!consequent && alternate) {
|
462 | var negatedTestExpression = b.unaryExpression('!', testExpression, true);
|
463 |
|
464 | if (n.UnaryExpression.check(testExpression) && testExpression.operator === '!') {
|
465 | negatedTestExpression = testExpression.argument;
|
466 | }
|
467 |
|
468 | ifStatement.get("test").replace(negatedTestExpression);
|
469 | ifStatement.get("consequent").replace(alternate);
|
470 | ifStatement.get("alternate").replace();
|
471 | }
|
472 | }
|
473 |
|
474 | module.exports = NodePath;
|