Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

compare-ast

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

compare-ast - npm Package Compare versions

Comparing version 0.1.2 to 0.2.0

.travis.yml

167

lib/compare.js
var parse = require('esprima').parse;
var Errors = require('./match-error');
var toLiteral = require('./util/identifier-to-literal');
var normalizeMemberExpr = require('./util/normalize-member-expr');

@@ -10,7 +10,24 @@ // Given a "template" expected AST that defines abstract identifier names

var actualAst, expectedAst;
options = options || {};
// Lookup table of bound variable names
options.boundVars = {};
if (!options.comparators) {
options.comparators = [];
}
/*
* A collection of comparator functions that recognize equivalent nodes
* that would otherwise be reported as unequal by simple object comparison.
* These may:
*
* - return `true` if the given nodes can be verified as equivalent with no
* further processing
* - return an instance of MatchError if the nodes are verified as
* uniquivalent
* - return any other value if equivalency cannot be determined
*/
Array.prototype.push.apply(options.comparators, [
require('./comparators/fuzzy-identifiers')(options),
require('./comparators/fuzzy-strings')(options)
]);
try {

@@ -52,3 +69,6 @@ actualAst = parse(actualSrc).body;

if (checkEquivalency(actual, expected, options)) {
normalizeMemberExpr(actual);
normalizeMemberExpr(expected);
if (checkEquivalency(options.comparators, actual, expected)) {
return;

@@ -73,134 +93,21 @@ }

var checkEquivalency = function(actual, expected, options) {
var len = comparators.length;
var comparator, idx, types;
for (idx = 0; idx < len; ++idx) {
comparator = comparators[idx];
types = comparator.types;
if (!(actual.type === types[0] && expected.type === types[1]) &&
!(actual.type === types[1] && expected.type === types[0])) {
continue;
}
if (comparator.handler(actual, expected, options)) {
var checkEquivalency = function(comparators, actual, expected) {
var result = comparators.map(function(comparator) {
return comparator(actual, expected);
}).reduce(function(prev, current) {
if (current === true) {
return true;
}
return prev || current;
}, null);
if (result instanceof Errors) {
throw result;
} else if (result === true) {
return true;
}
return false;
};
// A collection of comparator functions that recognize equivalent nodes that
// would otherwise be reported as unequal by simple object comparison. These
// may:
//
// - return `true` if the given nodes can be verified as equivalent with no
// further processing
// - throw an error if the nodes are verified as uniquivalent
// - return any other value if equivalency cannot be determined
var comparators = [
{
// Update unbound variable names in the expected AST, using the
// previously-bound value when available.
types: ['Identifier', 'Identifier'],
handler: function(actual, expected, options) {
var varPattern = options.varPattern;
var boundVars = options.boundVars;
var unboundName;
if (!varPattern) {
return;
}
if (varPattern.test(expected.name)) {
unboundName = expected.name;
if (!(unboundName in boundVars)) {
boundVars[unboundName] = actual.name;
}
expected.name = boundVars[unboundName];
// This inequality would be caught by the next recursion, but it is
// asserted here to provide a more specific error--the ASTs do not
// match because a variable was re-bound.
if (expected.name !== actual.name) {
throw new Errors.BindingError(actual.name, expected.name);
} else {
return true;
}
}
}
},
{
// Recognize simple string deferencing as equivalent to "dot notation"
// with a matching identifier (i.e. `a.b;` is equivalent to `a['b'];`).
types: ['MemberExpression', 'MemberExpression'],
handler: function(actual, expected, options) {
var literal, props;
// When the actual property is an Identifier, compare its literal
// representation against the expected property.
if (actual.property.type === 'Identifier') {
literal = toLiteral(actual.property);
if (checkEquivalency(literal, expected.property, options)) {
actual.property = literal;
return true;
}
}
props = {};
if (actual.property.type === 'Literal') {
props.Literal = actual.property.value;
} else if (actual.property.type === 'Identifier') {
props.Identifier = actual.property.name;
}
if (expected.property.type === 'Literal') {
props.Literal = expected.property.value;
} else if (expected.property.type === 'Identifier') {
props.Identifier = expected.property.name;
}
return 'Identifier' in props && 'Literal' in props &&
props.Identifier === props.Literal;
}
},
{
// Fuzzy string matching
types: ['Literal', 'Literal'],
handler: function(actual, expected, options) {
var stringPattern = options.stringPattern;
var boundStrings;
if (!options.boundStrings) {
options.boundStrings = {};
}
boundStrings = options.boundStrings;
if (!stringPattern) {
return false;
}
if (!stringPattern.test(expected.value)) {
return false;
}
var expectedValue = boundStrings[expected.value];
// This string value has previously been bound
if (expectedValue) {
if (expectedValue !== actual.value) {
throw new Errors.BindingError(actual.value, expected.value);
}
} else {
boundStrings[expected.value] = actual.value;
}
expected.value = actual.value;
return true;
}
}
];
compareAst.Error = Errors;
module.exports = compareAst;
{
"name": "compare-ast",
"version": "0.1.2",
"version": "0.2.0",
"description": "Determine if two strings of JavaScript have equivalent abstract syntax trees.",

@@ -15,3 +15,4 @@ "main": "lib/compare.js",

"test": "mocha --ui tdd",
"jshint": "jshint lib/ test/"
"jshint": "jshint lib/ test/",
"ci": "npm run jshint && npm test"
},

@@ -18,0 +19,0 @@ "author": "Mike Pennisi",

@@ -6,2 +6,5 @@ # compareAst

[![Build
Status](https://travis-ci.org/jugglinmike/compare-ast.png)](https://travis-ci.org/jugglinmike/compare-ast)
## API

@@ -13,2 +16,33 @@

### Built-in Relaxing Options
compareAst supports configuration of the following relaxing options:
- `varPattern` - a regular expression describing identifier names that, when
encountered in the "expected" AST, will be "bound" to the corresponding
identifier in the "actual" AST. All further occurences of that identifier
must match the original bound value.
- `stringPattern` - a regular expression describing string values that, when
encountered in the "expected" AST, will be "bound" to the corresponding
string value in the "actual" AST. All further occurences of that string value
must match the original bound value.
See the "Examples" section below for more information on defining these
relaxing options.
### Custom Comparators
The `options` object may specify an array of `comparators`. These functions can
be used to further relax the criteria for equivalency. Each will be invoked for
every node under comparison. These nodes are generated by
[esprima](http://esprima.org/); see the [esprima
documentation](http://esprima.org/doc/index.html) for a description of their
structure.
compareAst recognizes the following comparator return types:
- Instance of `compareAst.Errors` - the two nodes are not equivalent
- `true` - the two nodes are equivalent
- `undefined` - equivalency cannot be determined by this comparator
## Examples

@@ -15,0 +49,0 @@

@@ -60,2 +60,21 @@ var compareAst = require('..');

test('custom comparator', function() {
var vals = [3, 4];
var threeOrFour = function(actual, expected) {
if (actual.type !== 'Literal' || expected.type !== 'Literal') {
return;
}
if (vals.indexOf(actual.value) > -1 &&
vals.indexOf(expected.value) > -1) {
return true;
}
};
compareAst(
'a.b + 3',
'a["b"] + 4',
{ comparators: [threeOrFour] }
);
});
suite('expected failures', function() {

@@ -62,0 +81,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc