Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81169 views
1
var assert = require("assert");
2
var types = require("./types");
3
var n = types.namedTypes;
4
var Node = n.Node;
5
var isArray = types.builtInTypes.array;
6
var isNumber = types.builtInTypes.number;
7
8
function FastPath(value) {
9
assert.ok(this instanceof FastPath);
10
this.stack = [value];
11
}
12
13
var FPp = FastPath.prototype;
14
module.exports = FastPath;
15
16
// Static convenience function for coercing a value to a FastPath.
17
FastPath.from = function(obj) {
18
if (obj instanceof FastPath) {
19
// Return a defensive copy of any existing FastPath instances.
20
return obj.copy();
21
}
22
23
if (obj instanceof types.NodePath) {
24
// For backwards compatibility, unroll NodePath instances into
25
// lightweight FastPath [..., name, value] stacks.
26
var copy = Object.create(FastPath.prototype);
27
var stack = [obj.value];
28
for (var pp; (pp = obj.parentPath); obj = pp)
29
stack.push(obj.name, pp.value);
30
copy.stack = stack.reverse();
31
return copy;
32
}
33
34
// Otherwise use obj as the value of the new FastPath instance.
35
return new FastPath(obj);
36
};
37
38
FPp.copy = function copy() {
39
var copy = Object.create(FastPath.prototype);
40
copy.stack = this.stack.slice(0);
41
return copy;
42
};
43
44
// The name of the current property is always the penultimate element of
45
// this.stack, and always a String.
46
FPp.getName = function getName() {
47
var s = this.stack;
48
var len = s.length;
49
if (len > 1) {
50
return s[len - 2];
51
}
52
// Since the name is always a string, null is a safe sentinel value to
53
// return if we do not know the name of the (root) value.
54
return null;
55
};
56
57
// The value of the current property is always the final element of
58
// this.stack.
59
FPp.getValue = function getValue() {
60
var s = this.stack;
61
return s[s.length - 1];
62
};
63
64
FPp.getNode = function getNode() {
65
var s = this.stack;
66
67
for (var i = s.length - 1; i >= 0; i -= 2) {
68
var value = s[i];
69
if (n.Node.check(value)) {
70
return value;
71
}
72
}
73
74
return null;
75
};
76
77
FPp.getParentNode = function getParentNode() {
78
var s = this.stack;
79
var count = 0;
80
81
for (var i = s.length - 1; i >= 0; i -= 2) {
82
var value = s[i];
83
if (n.Node.check(value) && count++ > 0) {
84
return value;
85
}
86
}
87
88
return null;
89
};
90
91
// The length of the stack can be either even or odd, depending on whether
92
// or not we have a name for the root value. The difference between the
93
// index of the root value and the index of the final value is always
94
// even, though, which allows us to return the root value in constant time
95
// (i.e. without iterating backwards through the stack).
96
FPp.getRootValue = function getRootValue() {
97
var s = this.stack;
98
if (s.length % 2 === 0) {
99
return s[1];
100
}
101
return s[0];
102
};
103
104
// Temporarily push properties named by string arguments given after the
105
// callback function onto this.stack, then call the callback with a
106
// reference to this (modified) FastPath object. Note that the stack will
107
// be restored to its original state after the callback is finished, so it
108
// is probably a mistake to retain a reference to the path.
109
FPp.call = function call(callback/*, name1, name2, ... */) {
110
var s = this.stack;
111
var origLen = s.length;
112
var value = s[origLen - 1];
113
var argc = arguments.length;
114
for (var i = 1; i < argc; ++i) {
115
var name = arguments[i];
116
value = value[name];
117
s.push(name, value);
118
}
119
var result = callback(this);
120
s.length = origLen;
121
return result;
122
};
123
124
// Similar to FastPath.prototype.call, except that the value obtained by
125
// accessing this.getValue()[name1][name2]... should be array-like. The
126
// callback will be called with a reference to this path object for each
127
// element of the array.
128
FPp.each = function each(callback/*, name1, name2, ... */) {
129
var s = this.stack;
130
var origLen = s.length;
131
var value = s[origLen - 1];
132
var argc = arguments.length;
133
134
for (var i = 1; i < argc; ++i) {
135
var name = arguments[i];
136
value = value[name];
137
s.push(name, value);
138
}
139
140
for (var i = 0; i < value.length; ++i) {
141
if (i in value) {
142
s.push(i, value[i]);
143
// If the callback needs to know the value of i, call
144
// path.getName(), assuming path is the parameter name.
145
callback(this);
146
s.length -= 2;
147
}
148
}
149
150
s.length = origLen;
151
};
152
153
// Similar to FastPath.prototype.each, except that the results of the
154
// callback function invocations are stored in an array and returned at
155
// the end of the iteration.
156
FPp.map = function map(callback/*, name1, name2, ... */) {
157
var s = this.stack;
158
var origLen = s.length;
159
var value = s[origLen - 1];
160
var argc = arguments.length;
161
162
for (var i = 1; i < argc; ++i) {
163
var name = arguments[i];
164
value = value[name];
165
s.push(name, value);
166
}
167
168
var result = new Array(value.length);
169
170
for (var i = 0; i < value.length; ++i) {
171
if (i in value) {
172
s.push(i, value[i]);
173
result[i] = callback(this, i);
174
s.length -= 2;
175
}
176
}
177
178
s.length = origLen;
179
180
return result;
181
};
182
183
// Inspired by require("ast-types").NodePath.prototype.needsParens, but
184
// more efficient because we're iterating backwards through a stack.
185
FPp.needsParens = function(assumeExpressionContext) {
186
var parent = this.getParentNode();
187
if (!parent) {
188
return false;
189
}
190
191
var name = this.getName();
192
var node = this.getNode();
193
194
// Only expressions need parentheses.
195
if (!n.Expression.check(node)) {
196
return false;
197
}
198
199
// Identifiers never need parentheses.
200
if (node.type === "Identifier") {
201
return false;
202
}
203
204
switch (node.type) {
205
case "UnaryExpression":
206
case "SpreadElement":
207
case "SpreadProperty":
208
return parent.type === "MemberExpression"
209
&& name === "object"
210
&& parent.object === node;
211
212
case "BinaryExpression":
213
case "LogicalExpression":
214
switch (parent.type) {
215
case "CallExpression":
216
return name === "callee"
217
&& parent.callee === node;
218
219
case "UnaryExpression":
220
case "SpreadElement":
221
case "SpreadProperty":
222
return true;
223
224
case "MemberExpression":
225
return name === "object"
226
&& parent.object === node;
227
228
case "BinaryExpression":
229
case "LogicalExpression":
230
var po = parent.operator;
231
var pp = PRECEDENCE[po];
232
var no = node.operator;
233
var np = PRECEDENCE[no];
234
235
if (pp > np) {
236
return true;
237
}
238
239
if (pp === np && name === "right") {
240
assert.strictEqual(parent.right, node);
241
return true;
242
}
243
244
default:
245
return false;
246
}
247
248
case "SequenceExpression":
249
switch (parent.type) {
250
case "ForStatement":
251
// Although parentheses wouldn't hurt around sequence
252
// expressions in the head of for loops, traditional style
253
// dictates that e.g. i++, j++ should not be wrapped with
254
// parentheses.
255
return false;
256
257
case "ExpressionStatement":
258
return name !== "expression";
259
260
default:
261
// Otherwise err on the side of overparenthesization, adding
262
// explicit exceptions above if this proves overzealous.
263
return true;
264
}
265
266
case "YieldExpression":
267
switch (parent.type) {
268
case "BinaryExpression":
269
case "LogicalExpression":
270
case "UnaryExpression":
271
case "SpreadElement":
272
case "SpreadProperty":
273
case "CallExpression":
274
case "MemberExpression":
275
case "NewExpression":
276
case "ConditionalExpression":
277
case "YieldExpression":
278
return true;
279
280
default:
281
return false;
282
}
283
284
case "Literal":
285
return parent.type === "MemberExpression"
286
&& isNumber.check(node.value)
287
&& name === "object"
288
&& parent.object === node;
289
290
case "AssignmentExpression":
291
case "ConditionalExpression":
292
switch (parent.type) {
293
case "UnaryExpression":
294
case "SpreadElement":
295
case "SpreadProperty":
296
case "BinaryExpression":
297
case "LogicalExpression":
298
return true;
299
300
case "CallExpression":
301
return name === "callee"
302
&& parent.callee === node;
303
304
case "ConditionalExpression":
305
return name === "test"
306
&& parent.test === node;
307
308
case "MemberExpression":
309
return name === "object"
310
&& parent.object === node;
311
312
default:
313
return false;
314
}
315
316
default:
317
if (parent.type === "NewExpression" &&
318
name === "callee" &&
319
parent.callee === node) {
320
return containsCallExpression(node);
321
}
322
}
323
324
if (assumeExpressionContext !== true &&
325
!this.canBeFirstInStatement() &&
326
this.firstInStatement())
327
return true;
328
329
return false;
330
};
331
332
function isBinary(node) {
333
return n.BinaryExpression.check(node)
334
|| n.LogicalExpression.check(node);
335
}
336
337
function isUnaryLike(node) {
338
return n.UnaryExpression.check(node)
339
// I considered making SpreadElement and SpreadProperty subtypes
340
// of UnaryExpression, but they're not really Expression nodes.
341
|| (n.SpreadElement && n.SpreadElement.check(node))
342
|| (n.SpreadProperty && n.SpreadProperty.check(node));
343
}
344
345
var PRECEDENCE = {};
346
[["||"],
347
["&&"],
348
["|"],
349
["^"],
350
["&"],
351
["==", "===", "!=", "!=="],
352
["<", ">", "<=", ">=", "in", "instanceof"],
353
[">>", "<<", ">>>"],
354
["+", "-"],
355
["*", "/", "%"]
356
].forEach(function(tier, i) {
357
tier.forEach(function(op) {
358
PRECEDENCE[op] = i;
359
});
360
});
361
362
function containsCallExpression(node) {
363
if (n.CallExpression.check(node)) {
364
return true;
365
}
366
367
if (isArray.check(node)) {
368
return node.some(containsCallExpression);
369
}
370
371
if (n.Node.check(node)) {
372
return types.someField(node, function(name, child) {
373
return containsCallExpression(child);
374
});
375
}
376
377
return false;
378
}
379
380
FPp.canBeFirstInStatement = function() {
381
var node = this.getNode();
382
return !n.FunctionExpression.check(node)
383
&& !n.ObjectExpression.check(node);
384
};
385
386
FPp.firstInStatement = function() {
387
var s = this.stack;
388
var parentName, parent;
389
var childName, child;
390
391
for (var i = s.length - 1; i >= 0; i -= 2) {
392
if (n.Node.check(s[i])) {
393
childName = parentName;
394
child = parent;
395
parentName = s[i - 1];
396
parent = s[i];
397
}
398
399
if (!parent || !child) {
400
continue;
401
}
402
403
if (n.BlockStatement.check(parent) &&
404
parentName === "body" &&
405
childName === 0) {
406
assert.strictEqual(parent.body[0], child);
407
return true;
408
}
409
410
if (n.ExpressionStatement.check(parent) &&
411
childName === "expression") {
412
assert.strictEqual(parent.expression, child);
413
return true;
414
}
415
416
if (n.SequenceExpression.check(parent) &&
417
parentName === "expressions" &&
418
childName === 0) {
419
assert.strictEqual(parent.expressions[0], child);
420
continue;
421
}
422
423
if (n.CallExpression.check(parent) &&
424
childName === "callee") {
425
assert.strictEqual(parent.callee, child);
426
continue;
427
}
428
429
if (n.MemberExpression.check(parent) &&
430
childName === "object") {
431
assert.strictEqual(parent.object, child);
432
continue;
433
}
434
435
if (n.ConditionalExpression.check(parent) &&
436
childName === "test") {
437
assert.strictEqual(parent.test, child);
438
continue;
439
}
440
441
if (isBinary(parent) &&
442
childName === "left") {
443
assert.strictEqual(parent.left, child);
444
continue;
445
}
446
447
if (n.UnaryExpression.check(parent) &&
448
!parent.prefix &&
449
childName === "argument") {
450
assert.strictEqual(parent.argument, child);
451
continue;
452
}
453
454
return false;
455
}
456
457
return true;
458
};
459
460