Skip to content

Commit

Permalink
feat: remove runtime parser dependency by removing escallmatch
Browse files Browse the repository at this point in the history
  • Loading branch information
twada committed Feb 15, 2019
1 parent 3972082 commit fac74e4
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 61 deletions.
9 changes: 9 additions & 0 deletions README.md
Expand Up @@ -154,6 +154,15 @@ A raw (either as a string which can be JSON.parse'd, or an object) [SourceMap](h
VisitorKeys for AST traversal. See [estraverse.VisitorKeys](https://github.com/estools/estraverse/blob/4.0.0/estraverse.js#L217-L288) and [babel.types.VISITOR_KEYS](https://github.com/babel/babel/blob/v5.1.13/src/babel/types/visitor-keys.json).


#### (optional) options.parse

| type | default value |
|:--------------------|:--------------|
| `function` | N/A |

A function to parse pattern string specified by `options.patterns`. This property is optional.


### const options = espower.defaultOptions();

Returns default options object for `espower` function. In other words, returns
Expand Down
2 changes: 1 addition & 1 deletion esp.js
Expand Up @@ -5,6 +5,6 @@ var escodegen = require('escodegen');
var jsCode = process.argv[2];
var parserOptions = {ecmaVersion: 6, locations: true};
var jsAst = acorn.parse(jsCode, parserOptions);
var modifiedAst = espower(jsAst);
var modifiedAst = espower(jsAst, { parse: acorn.parse });

console.log(escodegen.generate(modifiedAst));
30 changes: 30 additions & 0 deletions lib/create-pattern-matchers.js
@@ -0,0 +1,30 @@
'use strict';

const CallMatcher = require('call-matcher');

const createPatternMatchers = (options) => {
return options.patterns.map((p, index) => {
const pattern = typeof p === 'string' ? p : p.pattern;
const signatureAst = options.parse(pattern);
const expression = signatureAst.body[0].expression;
const matcher = new CallMatcher(expression, options);
const args = matcher.argumentSignatures().map((sig, idx) => {
if (typeof p === 'object' && p !== null && Array.isArray(p.args)) {
return Object.assign({}, sig, p.args[idx]);
}
return sig;
});
const lastParam = args[args.length - 1];
if (lastParam.name === 'message' && lastParam.kind === 'optional') {
lastParam.message = true;
}
return {
matcher,
index,
pattern,
args
};
});
};

module.exports = createPatternMatchers;
20 changes: 14 additions & 6 deletions lib/instrumentor.js
Expand Up @@ -3,16 +3,24 @@
const estraverse = require('estraverse');
const Syntax = estraverse.Syntax;
const escope = require('escope');
const escallmatch = require('escallmatch');
const createPatternMatchers = require('../lib/create-pattern-matchers');
const AssertionVisitor = require('./assertion-visitor');
const Transformation = require('./transformation');
const isSpreadElement = (node) => node.type === 'SpreadElement';
const withoutMatcherAndIndex = (p) => {
const res = Object.assign({}, p);
delete res.matcher;
delete res.index;
return res;
};

class Instrumentor {
constructor (options) {
verifyOptionPrerequisites(options);
this.options = options;
this.matchers = options.patterns.map((pattern) => escallmatch(pattern, options));
this.patternMatchers = createPatternMatchers(options);
this.options = Object.assign({}, options, {
patterns: this.patternMatchers.map(withoutMatcherAndIndex)
});
}

instrument (ast) {
Expand Down Expand Up @@ -54,15 +62,15 @@ class Instrumentor {
return assertionVisitor.enterArgument(controller);
}
} else if (currentNode.type === Syntax.CallExpression) {
const matcher = that.matchers.find((matcher) => matcher.test(currentNode));
if (matcher) {
const patternMatcher = that.patternMatchers.find((pm) => pm.matcher.test(currentNode));
if (patternMatcher) {
// skip modifying argument if SpreadElement appears immediately beneath assert
if (currentNode.arguments.some(isSpreadElement)) {
skipping = true;
return controller.skip();
}
// entering target assertion
assertionVisitor = new AssertionVisitor(matcher, Object.assign({
assertionVisitor = new AssertionVisitor(patternMatcher.matcher, Object.assign({
storage: storage,
transformation: transformation,
globalScope: globalScope,
Expand Down
40 changes: 4 additions & 36 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -17,7 +17,7 @@
}
],
"dependencies": {
"escallmatch": "^1.5.0",
"call-matcher": "^2.0.0",
"escodegen": "^1.7.0",
"escope": "^3.3.0",
"espower-location-detector": "^2.0.0",
Expand Down
21 changes: 12 additions & 9 deletions test/espower_option_test.js
Expand Up @@ -11,7 +11,7 @@ var join = require('path').join;

function instrument (jsCode, options) {
var jsAST = acorn.parse(jsCode, {ecmaVersion: 7, locations: true, plugins: {asyncawait: true}});
var espoweredAST = espower(jsAST, options);
var espoweredAST = espower(jsAST, Object.assign({parse: acorn.parse}, options));
var instrumentedCode = escodegen.generate(espoweredAST, {format: {compact: true}});
return instrumentedCode;
}
Expand Down Expand Up @@ -92,11 +92,11 @@ describe('instrumentation tests for options', function () {
}

describe('deprecated: destructive option -> treated as destructive:true every time', function () {
function destructiveOptionTest (testName, option, callback) {
function destructiveOptionTest (testName, options, callback) {
it(testName, function () {
var tree = acorn.parse('assert(falsyStr);', {ecmaVersion: 6, locations: true, ranges: true});
var saved = deepCopy(tree);
var result = espower(tree, option);
var result = espower(tree, Object.assign({parse: acorn.parse}, options));
callback(assert, saved, tree, result);
});
}
Expand All @@ -108,7 +108,7 @@ describe('instrumentation tests for options', function () {
it('options.destructive is deprecate and always treated as destructive:true', function () {
var tree = acorn.parse('assert(falsyStr);', {ecmaVersion: 6, locations: true, ranges: true});
assert.throws(function () {
espower(tree, {destructive: false});
espower(tree, {parse: acorn.parse, destructive: false});
}, Error);
});
destructiveOptionTest('destructive: true', {destructive: true}, function (assert, before, tree, after) {
Expand Down Expand Up @@ -174,7 +174,7 @@ describe('option prerequisites', function () {
function optionPrerequisitesTest (name, options, expected) {
it(name, function () {
try {
espower(this.tree, options);
espower(this.tree, Object.assign({parse: acorn.parse}, options));
assert.ok(false, 'Error should be thrown');
} catch (e) {
assert.equal(e.message, expected);
Expand All @@ -198,7 +198,7 @@ describe('AST prerequisites. Error should be thrown if location is missing.', fu
});
it('error message when path option is not specified', function () {
try {
espower(this.tree);
espower(this.tree, {parse: acorn.parse});
assert.ok(false, 'Error should be thrown');
} catch (e) {
assert.equal(e.name, 'Error');
Expand All @@ -209,7 +209,7 @@ describe('AST prerequisites. Error should be thrown if location is missing.', fu
});
it('error message when path option is specified', function () {
try {
espower(this.tree, {path: '/path/to/baz_test.js'});
espower(this.tree, {parse: acorn.parse, path: '/path/to/baz_test.js'});
assert.ok(false, 'Error should be thrown');
} catch (e) {
assert.equal(e.name, 'Error');
Expand All @@ -227,7 +227,7 @@ describe('AST prerequisites. Error should be thrown if AST is already instrument
var alreadyEspoweredCode = "assert(_rec1._expr(_rec1._capt(falsyStr,'arguments/0'),{content:'assert(falsyStr)',filepath:'/path/to/some_test.js',line:1}));";
var ast = acorn.parse(alreadyEspoweredCode, {ecmaVersion: 6, locations: true});
try {
espower(ast, {path: '/path/to/baz_test.js'});
espower(ast, {parse: acorn.parse, path: '/path/to/baz_test.js'});
assert.ok(false, 'Error should be thrown');
} catch (e) {
assert.equal(e.name, 'Error');
Expand All @@ -242,6 +242,7 @@ describe('AST prerequisites. Error should be thrown if AST is already instrument
var ast = acorn.parse(alreadyEspoweredCode, {ecmaVersion: 6, locations: true});
try {
espower(ast, {
parse: acorn.parse,
path: '/path/to/foo_test.js',
patterns: [
'browser.assert.element(selection, [message])'
Expand All @@ -263,7 +264,7 @@ describe('location information', function () {
it('preserve location of instrumented nodes.', function () {
var jsCode = 'assert((three * (seven * ten)) === three);';
var tree = acorn.parse(jsCode, {ecmaVersion: 6, locations: true, ranges: true});
var result = espower(tree, {path: '/path/to/baz_test.js'});
var result = espower(tree, {parse: acorn.parse, path: '/path/to/baz_test.js'});
estraverse.traverse(result, function (node) {
if (typeof node.type === 'undefined') return;
assert.ok(typeof node.loc !== 'undefined', 'type: ' + node.type);
Expand Down Expand Up @@ -327,6 +328,7 @@ describe('incoming SourceMap support', function () {

var intermediateFilepath = '/path/to/absolute/intermediate/transformed_test.js';
var espoweredAST = espower(acorn.parse(compactCode, {ecmaVersion: 6, locations: true, sourceFile: intermediateFilepath}), {
parse: acorn.parse,
patterns: [
'assert.equal(actual, expected, [message])'
],
Expand Down Expand Up @@ -433,6 +435,7 @@ describe('sourceRoot option', function () {
var jsCode = 'assert(falsyStr);';
var jsAST = acorn.parse(jsCode, {ecmaVersion: 6, locations: true, sourceFile: config.incomingFilepath});
var espoweredAST = espower(jsAST, {
parse: acorn.parse,
path: config.incomingFilepath,
sourceRoot: config.espowerSourceRoot
});
Expand Down
12 changes: 6 additions & 6 deletions test/fixture_test.js
Expand Up @@ -17,8 +17,8 @@ function testWithParser (fixtureName, parse, manipulate) {
var expectedFilepath = path.resolve(__dirname, 'fixtures', fixtureName, 'expected.js');
var actualFilepath = path.resolve(__dirname, 'fixtures', fixtureName, 'actual.js');

var jsAST = parse(fixtureFilepath);
var espoweredAST = manipulate(jsAST, {ecmaVersion: 2018, path: 'path/to/some_test.js'});
var jsAST = parse(fs.readFileSync(fixtureFilepath, 'utf8'));
var espoweredAST = manipulate(jsAST, {ecmaVersion: 2018, path: 'path/to/some_test.js', parse: parse});
var output = escodegen.generate(espoweredAST);

var actual = output + '\n';
Expand All @@ -31,13 +31,13 @@ function testWithParser (fixtureName, parse, manipulate) {
}

function testTransform (fixtureName, extraOptions) {
function by_acorn (filepath) {
function by_acorn (code) {
var parserOptions = {ecmaVersion: 2018, locations: true, plugins: {asyncawait: true}};
return acorn.parse(fs.readFileSync(filepath, 'utf8'), parserOptions);
return acorn.parse(code, parserOptions);
}
function by_esprima (filepath) {
function by_esprima (code) {
var parserOptions = {tolerant: true, loc: true};
return esprima.parse(fs.readFileSync(filepath, 'utf8'), parserOptions);
return esprima.parse(code, parserOptions);
}
function by_espower (ast, options) {
return espower(ast, options);
Expand Down
2 changes: 1 addition & 1 deletion test/instrumentation_test.js
Expand Up @@ -22,7 +22,7 @@ describe('instrumentation spec', function () {
function testWithParserOptions (jsCode, expected, options) {
it(jsCode, function () {
var jsAST = acorn.parse(jsCode, options);
var espoweredAST = espower(jsAST, {path: 'path/to/some_test.js'});
var espoweredAST = espower(jsAST, { path: 'path/to/some_test.js', parse: acorn.parse });
var instrumentedCode = escodegen.generate(espoweredAST, {format: {compact: true}});
assert.equal(instrumentedCode, expected);
assert(acorn.parse(instrumentedCode, options));
Expand Down
3 changes: 2 additions & 1 deletion test/visitor_keys_test.js
@@ -1,6 +1,7 @@
'use strict';

var espower = require('..');
var acorn = require('acorn');
var assert = require('assert');

//// original code of jsx_test.json:
Expand All @@ -27,6 +28,6 @@ var ast = require('./jsx_test.json');
it('custom visitorKeys', function () {
var visitorKeys = require('./visitor-keys.json');
assert.doesNotThrow(function () {
espower(ast, { visitorKeys: visitorKeys });
espower(ast, { visitorKeys: visitorKeys, parse: acorn.parse });
});
});

0 comments on commit fac74e4

Please sign in to comment.