You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
4.1 KiB
JavaScript
179 lines
4.1 KiB
JavaScript
7 years ago
|
var assert = require("assert");
|
||
|
var types = require("../main");
|
||
|
var getFieldNames = types.getFieldNames;
|
||
|
var getFieldValue = types.getFieldValue;
|
||
|
var isArray = types.builtInTypes.array;
|
||
|
var isObject = types.builtInTypes.object;
|
||
|
var isDate = types.builtInTypes.Date;
|
||
|
var isRegExp = types.builtInTypes.RegExp;
|
||
|
var hasOwn = Object.prototype.hasOwnProperty;
|
||
|
|
||
|
function astNodesAreEquivalent(a, b, problemPath) {
|
||
|
if (isArray.check(problemPath)) {
|
||
|
problemPath.length = 0;
|
||
|
} else {
|
||
|
problemPath = null;
|
||
|
}
|
||
|
|
||
|
return areEquivalent(a, b, problemPath);
|
||
|
}
|
||
|
|
||
|
astNodesAreEquivalent.assert = function(a, b) {
|
||
|
var problemPath = [];
|
||
|
if (!astNodesAreEquivalent(a, b, problemPath)) {
|
||
|
if (problemPath.length === 0) {
|
||
|
assert.strictEqual(a, b);
|
||
|
} else {
|
||
|
assert.ok(
|
||
|
false,
|
||
|
"Nodes differ in the following path: " +
|
||
|
problemPath.map(subscriptForProperty).join("")
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function subscriptForProperty(property) {
|
||
|
if (/[_$a-z][_$a-z0-9]*/i.test(property)) {
|
||
|
return "." + property;
|
||
|
}
|
||
|
return "[" + JSON.stringify(property) + "]";
|
||
|
}
|
||
|
|
||
|
function areEquivalent(a, b, problemPath) {
|
||
|
if (a === b) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (isArray.check(a)) {
|
||
|
return arraysAreEquivalent(a, b, problemPath);
|
||
|
}
|
||
|
|
||
|
if (isObject.check(a)) {
|
||
|
return objectsAreEquivalent(a, b, problemPath);
|
||
|
}
|
||
|
|
||
|
if (isDate.check(a)) {
|
||
|
return isDate.check(b) && (+a === +b);
|
||
|
}
|
||
|
|
||
|
if (isRegExp.check(a)) {
|
||
|
return isRegExp.check(b) && (
|
||
|
a.source === b.source &&
|
||
|
a.global === b.global &&
|
||
|
a.multiline === b.multiline &&
|
||
|
a.ignoreCase === b.ignoreCase
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return a == b;
|
||
|
}
|
||
|
|
||
|
function arraysAreEquivalent(a, b, problemPath) {
|
||
|
isArray.assert(a);
|
||
|
var aLength = a.length;
|
||
|
|
||
|
if (!isArray.check(b) || b.length !== aLength) {
|
||
|
if (problemPath) {
|
||
|
problemPath.push("length");
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < aLength; ++i) {
|
||
|
if (problemPath) {
|
||
|
problemPath.push(i);
|
||
|
}
|
||
|
|
||
|
if (i in a !== i in b) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!areEquivalent(a[i], b[i], problemPath)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (problemPath) {
|
||
|
assert.strictEqual(problemPath.pop(), i);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function objectsAreEquivalent(a, b, problemPath) {
|
||
|
isObject.assert(a);
|
||
|
if (!isObject.check(b)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Fast path for a common property of AST nodes.
|
||
|
if (a.type !== b.type) {
|
||
|
if (problemPath) {
|
||
|
problemPath.push("type");
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var aNames = getFieldNames(a);
|
||
|
var aNameCount = aNames.length;
|
||
|
|
||
|
var bNames = getFieldNames(b);
|
||
|
var bNameCount = bNames.length;
|
||
|
|
||
|
if (aNameCount === bNameCount) {
|
||
|
for (var i = 0; i < aNameCount; ++i) {
|
||
|
var name = aNames[i];
|
||
|
var aChild = getFieldValue(a, name);
|
||
|
var bChild = getFieldValue(b, name);
|
||
|
|
||
|
if (problemPath) {
|
||
|
problemPath.push(name);
|
||
|
}
|
||
|
|
||
|
if (!areEquivalent(aChild, bChild, problemPath)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (problemPath) {
|
||
|
assert.strictEqual(problemPath.pop(), name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (!problemPath) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Since aNameCount !== bNameCount, we need to find some name that's
|
||
|
// missing in aNames but present in bNames, or vice-versa.
|
||
|
|
||
|
var seenNames = Object.create(null);
|
||
|
|
||
|
for (i = 0; i < aNameCount; ++i) {
|
||
|
seenNames[aNames[i]] = true;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < bNameCount; ++i) {
|
||
|
name = bNames[i];
|
||
|
|
||
|
if (!hasOwn.call(seenNames, name)) {
|
||
|
problemPath.push(name);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
delete seenNames[name];
|
||
|
}
|
||
|
|
||
|
for (name in seenNames) {
|
||
|
problemPath.push(name);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
module.exports = astNodesAreEquivalent;
|