UNPKG

4.39 kBJavaScriptView Raw
1var types = require("../main");
2var getFieldNames = types.getFieldNames;
3var getFieldValue = types.getFieldValue;
4var isArray = types.builtInTypes.array;
5var isObject = types.builtInTypes.object;
6var isDate = types.builtInTypes.Date;
7var isRegExp = types.builtInTypes.RegExp;
8var hasOwn = Object.prototype.hasOwnProperty;
9
10function astNodesAreEquivalent(a, b, problemPath) {
11 if (isArray.check(problemPath)) {
12 problemPath.length = 0;
13 } else {
14 problemPath = null;
15 }
16
17 return areEquivalent(a, b, problemPath);
18}
19
20astNodesAreEquivalent.assert = function(a, b) {
21 var problemPath = [];
22 if (!astNodesAreEquivalent(a, b, problemPath)) {
23 if (problemPath.length === 0) {
24 if (a !== b) {
25 throw new Error("Nodes must be equal");
26 }
27 } else {
28 throw new Error(
29 "Nodes differ in the following path: " +
30 problemPath.map(subscriptForProperty).join("")
31 );
32 }
33 }
34};
35
36function subscriptForProperty(property) {
37 if (/[_$a-z][_$a-z0-9]*/i.test(property)) {
38 return "." + property;
39 }
40 return "[" + JSON.stringify(property) + "]";
41}
42
43function areEquivalent(a, b, problemPath) {
44 if (a === b) {
45 return true;
46 }
47
48 if (isArray.check(a)) {
49 return arraysAreEquivalent(a, b, problemPath);
50 }
51
52 if (isObject.check(a)) {
53 return objectsAreEquivalent(a, b, problemPath);
54 }
55
56 if (isDate.check(a)) {
57 return isDate.check(b) && (+a === +b);
58 }
59
60 if (isRegExp.check(a)) {
61 return isRegExp.check(b) && (
62 a.source === b.source &&
63 a.global === b.global &&
64 a.multiline === b.multiline &&
65 a.ignoreCase === b.ignoreCase
66 );
67 }
68
69 return a == b;
70}
71
72function arraysAreEquivalent(a, b, problemPath) {
73 isArray.assert(a);
74 var aLength = a.length;
75
76 if (!isArray.check(b) || b.length !== aLength) {
77 if (problemPath) {
78 problemPath.push("length");
79 }
80 return false;
81 }
82
83 for (var i = 0; i < aLength; ++i) {
84 if (problemPath) {
85 problemPath.push(i);
86 }
87
88 if (i in a !== i in b) {
89 return false;
90 }
91
92 if (!areEquivalent(a[i], b[i], problemPath)) {
93 return false;
94 }
95
96 if (problemPath) {
97 var problemPathTail = problemPath.pop();
98 if (problemPathTail !== i) {
99 throw new Error("" + problemPathTail);
100 }
101 }
102 }
103
104 return true;
105}
106
107function objectsAreEquivalent(a, b, problemPath) {
108 isObject.assert(a);
109 if (!isObject.check(b)) {
110 return false;
111 }
112
113 // Fast path for a common property of AST nodes.
114 if (a.type !== b.type) {
115 if (problemPath) {
116 problemPath.push("type");
117 }
118 return false;
119 }
120
121 var aNames = getFieldNames(a);
122 var aNameCount = aNames.length;
123
124 var bNames = getFieldNames(b);
125 var bNameCount = bNames.length;
126
127 if (aNameCount === bNameCount) {
128 for (var i = 0; i < aNameCount; ++i) {
129 var name = aNames[i];
130 var aChild = getFieldValue(a, name);
131 var bChild = getFieldValue(b, name);
132
133 if (problemPath) {
134 problemPath.push(name);
135 }
136
137 if (!areEquivalent(aChild, bChild, problemPath)) {
138 return false;
139 }
140
141 if (problemPath) {
142 var problemPathTail = problemPath.pop();
143 if (problemPathTail !== name) {
144 throw new Error("" + problemPathTail);
145 }
146 }
147 }
148
149 return true;
150 }
151
152 if (!problemPath) {
153 return false;
154 }
155
156 // Since aNameCount !== bNameCount, we need to find some name that's
157 // missing in aNames but present in bNames, or vice-versa.
158
159 var seenNames = Object.create(null);
160
161 for (i = 0; i < aNameCount; ++i) {
162 seenNames[aNames[i]] = true;
163 }
164
165 for (i = 0; i < bNameCount; ++i) {
166 name = bNames[i];
167
168 if (!hasOwn.call(seenNames, name)) {
169 problemPath.push(name);
170 return false;
171 }
172
173 delete seenNames[name];
174 }
175
176 for (name in seenNames) {
177 problemPath.push(name);
178 break;
179 }
180
181 return false;
182}
183
184module.exports = astNodesAreEquivalent;