UNPKG

3.81 kBJavaScriptView Raw
1"use strict";
2
3// A simplified version of the AST traversal abstraction used by Recast:
4// https://github.com/benjamn/recast/blob/master/lib/fast-path.js
5
6const assert = require("assert");
7const utils = require("./utils.js");
8const brandKey = "reify-fast-path";
9const brand = typeof Symbol === "function"
10 ? Symbol.for(brandKey)
11 : brandKey;
12
13class FastPath {
14 constructor(ast) {
15 assert.notStrictEqual(ast, null);
16 assert.strictEqual(typeof ast, "object");
17 this.stack = [ast];
18 }
19
20 static isInstance(value) {
21 return value !== null &&
22 typeof value === "object" &&
23 value[brand] === true;
24 }
25
26 static from(value) {
27 return this.isInstance(value) ? value : new this(value);
28 }
29
30 // Temporarily push properties named by string arguments given after the
31 // callback function onto this.stack, then call the callback with a
32 // reference to this (modified) FastPath object. Note that the stack will
33 // be restored to its original state after the callback is finished, so it
34 // is probably a mistake to retain a reference to the path.
35 call(callback/*, name1, name2, ... */) {
36 const s = this.stack;
37 const origLen = s.length;
38 const argCount = arguments.length;
39 let value = s[origLen - 1];
40
41 for (let i = 1; i < argCount; ++i) {
42 const name = arguments[i];
43 value = value[name];
44 s.push(name, value);
45 }
46 const result = callback(this);
47 s.length = origLen;
48
49 return result;
50 }
51
52 // Similar to FastPath.prototype.call, except that the value obtained by
53 // accessing this.getValue()[name1][name2]... should be array-like. The
54 // callback will be called with a reference to this path object for each
55 // element of the array.
56 each(callback/*, name1, name2, ... */) {
57 const s = this.stack;
58 const origLen = s.length;
59 const argCount = arguments.length;
60 let value = s[origLen - 1];
61
62 for (let i = 1; i < argCount; ++i) {
63 const name = arguments[i];
64 value = value[name];
65 s.push(name, value);
66 }
67
68 for (let i = 0; i < value.length; ++i) {
69 s.push(i, value[i]);
70 // If the callback needs to know the value of i, call path.getName(),
71 // assuming path is the parameter name.
72 callback(this);
73 s.length -= 2;
74 }
75
76 s.length = origLen;
77 }
78
79 getContainer() {
80 return getStackAt(this, 3);
81 }
82
83 getName() {
84 // The name of the current property is always the penultimate element of
85 // this.stack, and always a String. Since the name is always a string,
86 // null is a safe sentinel value to return if we do not know the name of
87 // the (root) value.
88 return getStackAt(this, 2);
89 }
90
91 getNode() {
92 return getNodeAt(this, 0);
93 }
94
95 getParentNode() {
96 return getNodeAt(this, 1);
97 }
98
99 getValue() {
100 // The value of the current property is always the last element of
101 // this.stack.
102 return getStackAt(this, 1);
103 }
104
105 replace(newValue) {
106 const s = this.stack;
107 const len = s.length;
108 const oldValue = this.getValue();
109
110 if (len > 2) {
111 const parent = s[len - 3];
112 const name = this.getName();
113 parent[name] = s[len - 1] = newValue;
114 }
115 return oldValue;
116 }
117
118 valueIsNode() {
119 return utils.isNodeLike(this.getValue());
120 }
121};
122
123Object.setPrototypeOf(FastPath.prototype, null);
124
125Object.defineProperty(FastPath.prototype, brand, {
126 value: true,
127 enumerable: false,
128 writable: false,
129 configurable: false
130});
131
132function getNodeAt(path, pos) {
133 const s = path.stack;
134
135 for (let i = s.length - 1; i >= 0; i -= 2) {
136 const value = s[i];
137 if (utils.isNodeLike(value) && --pos < 0) {
138 return value;
139 }
140 }
141 return null;
142}
143
144function getStackAt(path, pos) {
145 const s = path.stack;
146 const len = s.length;
147 return len < pos ? null : s[len - pos];
148}
149
150module.exports = FastPath;