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.0 to 0.1.1

lib/util/identifier-to-literal.js

159

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

@@ -27,3 +28,3 @@ // Given a "template" expected AST that defines abstract identifier names

function _bind(actual, expected) {
var attr, comparator, areEquivalent;
var attr;

@@ -48,8 +49,4 @@ // Literal values

comparator = comparators[actual.type];
if (comparator) {
areEquivalent = comparator(actual, expected, options);
if (areEquivalent) {
return;
}
if (checkEquivalency(actual, expected, options)) {
return;
}

@@ -73,2 +70,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)) {
return true;
}
}
return false;
};
// A collection of comparator functions that recognize equivalent nodes that

@@ -82,51 +98,104 @@ // would otherwise be reported as unequal by simple object comparison. These

// - 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.
Identifier: function(actual, expected, options) {
var varPattern = options.varPattern;
var boundVars = options.boundVars;
var unboundName;
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;
if (!varPattern) {
return;
}
expected.name = boundVars[unboundName];
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;
// 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'];`).
MemberExpression: function(actual, expected) {
var props = {};
{
// 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;
if (actual.property.type === 'Literal') {
props.Literal = actual.property.value;
} else if (actual.property.type === 'Identifier') {
props.Identifier = actual.property.name;
// 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 (expected.property.type === 'Literal') {
props.Literal = expected.property.value;
} else if (expected.property.type === 'Identifier') {
props.Identifier = expected.property.name;
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;
}
return 'Identifier' in props && 'Literal' in props &&
props.Identifier === props.Literal;
}
};
];

@@ -133,0 +202,0 @@ compareAst.Error = Errors;

{
"name": "compare-ast",
"version": "0.1.0",
"version": "0.1.1",
"description": "Determine if two strings of JavaScript have equivalent abstract syntax trees.",

@@ -5,0 +5,0 @@ "main": "lib/compare.js",

@@ -32,2 +32,10 @@ # compareAst

// Allow for "fuzzy" string values by specifying the `stringPattern` option
// as a regular expression:
compareAst(
"var a = 'one', b = 'two', c = 'three';"
"var a = '__s1__', b = '__s2__', c = '__s3__';",
{ stringPattern: /__s\d+__/ }
);
## Tests

@@ -34,0 +42,0 @@

@@ -8,4 +8,10 @@ var compareAst = require('..');

test('dereferencing', function() {
compareAst('a.b;', 'a["b"];');
suite('dereferencing', function() {
test('identifier to literal', function() {
compareAst('a.b;', 'a["b"];');
});
test('literal to identifier', function() {
compareAst('a["b"];', 'a.b;');
});
});

@@ -31,2 +37,26 @@

test('string binding', function() {
compareAst(
'a["something"];"something2";"something";',
'a["__STR1__"]; "__STR2__"; "__STR1__";',
{ stringPattern: /__STR\d+__/ }
);
});
test('string binding with object dereferencing', function() {
compareAst(
'a.b;',
'a["_s1_"];',
{ stringPattern: /_s\d_/ }
);
});
test('variable binding and string binding', function() {
compareAst(
'a["b"];',
'_v1_["_s1_"];',
{ stringPattern: /_s\d_/, varPattern: /_v\d_/ }
);
});
suite('expected failures', function() {

@@ -71,3 +101,3 @@

test('double binding', function() {
test('double variable binding', function() {
noMatch([

@@ -82,2 +112,22 @@ '(function(a, b) { console.log(a); });',

test('double string binding', function() {
noMatch([
'var a = "one", b = "two", c = "three";',
'var a = "_s1_", b = "_s2_", c = "_s1_";',
{ stringPattern: /_s\d_/ },
],
2
);
});
test('double string binding (through object dereference)', function() {
noMatch([
'a.a; a.b; a.c;',
'a["_s1_"]; a["_s2_"]; a["_s1_"];',
{ stringPattern: /_s\d/ },
],
2
);
});
test('extra statements', function() {

@@ -87,3 +137,7 @@ noMatch(['a;', ''], 3);

test('unmatched member expression', function() {
noMatch(['a.b;', '3;'], 3);
});
});
});
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