UNPKG

12.7 kBJavaScriptView Raw
1var types = require("./types");
2var n = types.namedTypes;
3var b = types.builders;
4var isNumber = types.builtInTypes.number;
5var isArray = types.builtInTypes.array;
6var Path = require("./path");
7var Scope = require("./scope");
8
9function 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
16var NPp = NodePath.prototype = Object.create(Path.prototype, {
17 constructor: {
18 value: NodePath,
19 enumerable: false,
20 writable: true,
21 configurable: true
22 }
23});
24
25Object.defineProperties(NPp, {
26 node: {
27 get: function() {
28 Object.defineProperty(this, "node", {
29 configurable: true, // Enable deletion.
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, // Enable deletion.
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, // Enable deletion.
52 value: this._computeScope()
53 });
54
55 return this.scope;
56 }
57 }
58});
59
60NPp.replace = function() {
61 delete this.node;
62 delete this.parent;
63 delete this.scope;
64 return Path.prototype.replace.apply(this, arguments);
65};
66
67NPp.prune = function() {
68 var remainingNodePath = this.parent;
69
70 this.replace();
71
72 return cleanUpNodesAfterPrune(remainingNodePath);
73};
74
75// The value of the first ancestor Path whose value is a Node.
76NPp._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// The first ancestor Path whose value is a Node distinct from this.node.
87NPp._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// The closest enclosing scope that governs this node.
109NPp._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
122NPp.getValueProperty = function(name) {
123 return types.getFieldValue(this.value, name);
124};
125
126/**
127 * Determine whether this.node needs to be wrapped in parentheses in order
128 * for a parser to reproduce the same local AST structure.
129 *
130 * For instance, in the expression `(1 + 2) * 3`, the BinaryExpression
131 * whose operator is "+" needs parentheses, because `1 + 2 * 3` would
132 * parse differently.
133 *
134 * If assumeExpressionContext === true, we don't worry about edge cases
135 * like an anonymous FunctionExpression appearing lexically first in its
136 * enclosing statement and thus needing parentheses to avoid being parsed
137 * as a FunctionDeclaration with a missing name.
138 */
139NPp.needsParens = function(assumeExpressionContext) {
140 var pp = this.parentPath;
141 if (!pp) {
142 return false;
143 }
144
145 var node = this.value;
146
147 // Only expressions need parentheses.
148 if (!n.Expression.check(node)) {
149 return false;
150 }
151
152 // Identifiers never need parentheses.
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 // Although parentheses wouldn't hurt around sequence
216 // expressions in the head of for loops, traditional style
217 // dictates that e.g. i++, j++ should not be wrapped with
218 // parentheses.
219 return false;
220
221 case "ExpressionStatement":
222 return this.name !== "expression";
223
224 default:
225 // Otherwise err on the side of overparenthesization, adding
226 // explicit exceptions above if this proves overzealous.
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
296function isBinary(node) {
297 return n.BinaryExpression.check(node)
298 || n.LogicalExpression.check(node);
299}
300
301function isUnaryLike(node) {
302 return n.UnaryExpression.check(node)
303 // I considered making SpreadElement and SpreadProperty subtypes
304 // of UnaryExpression, but they're not really Expression nodes.
305 || (n.SpreadElement && n.SpreadElement.check(node))
306 || (n.SpreadProperty && n.SpreadProperty.check(node));
307}
308
309var 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
326function 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
344NPp.canBeFirstInStatement = function() {
345 var node = this.node;
346 return !n.FunctionExpression.check(node)
347 && !n.ObjectExpression.check(node);
348};
349
350NPp.firstInStatement = function() {
351 return firstInStatement(this);
352};
353
354function 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 * Pruning certain nodes will result in empty or incomplete nodes, here we clean those nodes up.
434 */
435function 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
452function 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
474module.exports = NodePath;