Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9faf9bf
feat(espower): spike implementation of per-argument value recorder
twada Feb 1, 2016
b2b4ae2
test(espower): case when assertions are not enclosed in functions
twada Feb 1, 2016
2c9c762
refactor(espower): introduce escope for more accurate scope detection
twada Feb 1, 2016
100201b
refactor(espower): refactor to findEspathOfAncestorNode function
twada Feb 1, 2016
5fa4e96
feat(espower): use generated identifier for per-argument value recorder
twada Feb 1, 2016
96b39a4
feat(espower): generate unique recorder variable name
twada Feb 1, 2016
c0e32ef
refactor(espower): prepare for embedding recorder class
twada Feb 1, 2016
d0fe351
feat(espower): embed recorder class at top level
twada Feb 1, 2016
fa304eb
chore(espower): unshift node insertion callback
twada Feb 1, 2016
c6497bf
refactor(espower): insert after 'use strict' directive
twada Feb 1, 2016
c701b01
refactor(espower): reorder methods by visibility
twada Feb 2, 2016
8ec2c2b
refactor(espower): remove callee guessing logic since callees are now…
twada Feb 2, 2016
7f107e6
refactor(espower): don't make functions within a loop
twada Feb 2, 2016
e037686
chore(espower): clone recorder AST to be embedded before updating loc…
twada Feb 2, 2016
1588071
test(espower): testing BinaryExpression
twada Feb 4, 2016
61abcec
test(espower): testing UnaryExpression
twada Feb 4, 2016
bb29f05
test(espower): testing MemberExpression
twada Feb 5, 2016
7865585
test(espower): testing CallExpression
twada Feb 5, 2016
a7ee2ff
test(espower): testing ArrayExpression, ObjectExpression and Property
twada Feb 5, 2016
d96651d
test(espower): testing LogicalExpression and ConditionalExpression
twada Feb 5, 2016
9708273
test(espower): testing AssignmentExpression and UpdateExpression
twada Feb 5, 2016
f897aa9
test(espower): testing FunctionExpression and ArrowFunctionExpression
twada Feb 5, 2016
c62b1c9
test(espower): testing ClassExpression and other non-target patterns
twada Feb 5, 2016
03741b4
test(espower): testing Literal and NewExpression
twada Feb 5, 2016
5befa5c
test(espower): testing TemplateLiteral and TaggedTemplateExpression
twada Feb 5, 2016
0f86f16
test(espower): testing SpreadElement
twada Feb 5, 2016
421cf79
test(espower): testing YieldExpression
twada Feb 5, 2016
197be55
test(espower): testing AwaitExpression
twada Feb 5, 2016
079bfc6
test(espower): back to all green
twada Feb 5, 2016
7efe94c
test(espower): fix callee
twada Feb 6, 2016
c38cee2
refactor(espower): automate recorder AST generation
twada Feb 8, 2016
9c495c1
test(espower): test with esprima too
twada Feb 10, 2016
90168b6
test(espower): add failing test for case without 'use strict' directi…
twada Feb 10, 2016
bc2d99c
fix(espower): insert declaration correctly if target body begins with…
twada Feb 10, 2016
5a55aa8
test(espower): fix failing tests
twada Feb 10, 2016
5a94f4e
chore(package): update espower-location-detector to 0.1.2
twada Feb 10, 2016
a7c27a5
chore(package): pin down blanket version for now
twada May 7, 2016
1066f6f
Merge branch 'master' into embedded-recorder
twada Jun 10, 2016
63b5177
Merge branch 'master' into embedded-recorder
twada Jun 22, 2016
8b8935a
test(espower): maint expected output for SequenceExpression
twada Jun 22, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
### [1.3.2](https://github.com/power-assert-js/espower/releases/tag/v1.3.2) (2016-06-22)


#### Bug Fixes

* fix breaking changes introduced in 1.3.0 and 1.3.1


### [1.3.1](https://github.com/power-assert-js/espower/releases/tag/v1.3.1) (2016-06-21)


#### Bug Fixes

* stop capturing SequenceExpression itself since SequenceExpressions are not enclosed in parentheses in some cases ([e8acbc61](https://github.com/power-assert-js/espower/commit/e8acbc61810454da05098baf6624b57d68deb3f9))


## [1.3.0](https://github.com/power-assert-js/espower/releases/tag/v1.3.0) (2016-06-21)


#### Features

* [Support SequenceExpression (i.e., comma operator)](https://github.com/power-assert-js/espower/pull/27)


### [1.2.1](https://github.com/power-assert-js/espower/releases/tag/v1.2.1) (2015-11-06)


Expand Down
16 changes: 15 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ var mochaPhantomJS = require('gulp-mocha-phantomjs');
var webserver = require('gulp-webserver');
var del = require('del');
var path = require('path');
var fs = require('fs');
var source = require('vinyl-source-stream');
var through = require('through2');
var browserify = require('browserify');
var licensify = require('licensify');
var packageJsonVersionify = require('package-json-versionify');
var dereserve = require('gulp-dereserve');
var derequire = require('gulp-derequire');
var acorn = require('acorn');
var espurify = require('espurify');
var config = {
jshint: {
src: './lib/**/*.js'
Expand Down Expand Up @@ -146,6 +150,7 @@ gulp.task('clean_bundle', function () {
gulp.task('bundle', ['clean_bundle'], function() {
var b = browserify({entries: config.bundle.srcFile, standalone: config.bundle.standalone});
b.plugin(licensify);
b.transform(packageJsonVersionify, {global: true});
var bundleStream = b.bundle();
return bundleStream
.pipe(source(config.bundle.destName))
Expand All @@ -160,6 +165,7 @@ LOCAL_BUILDS.forEach(function (name) {
});
gulp.task(name + '_bundle', ['clean_' + name + '_bundle'], function() {
var b = browserify({standalone: config[name + '_bundle'].standalone});
b.transform(packageJsonVersionify, {global: true});
if (config[name + '_bundle'].srcFile) {
b.add(config[name + '_bundle'].srcFile);
}
Expand All @@ -175,7 +181,15 @@ LOCAL_BUILDS.forEach(function (name) {
gulp.task('clean_deps', LOCAL_BUILDS.map(function (name) { return 'clean_' + name + '_bundle'; }));
gulp.task('build_deps', LOCAL_BUILDS.map(function (name) { return name + '_bundle'; }));

gulp.task('unit', function () {
gulp.task('generate_recorder_json', function (done) {
var filepath = path.join(__dirname, 'power-assert-recorder.js');
var ast = acorn.parse(fs.readFileSync(filepath), { ecmaVersion: 6, locations: true });
var callexp = espurify(ast).body[0].expression;
fs.writeFileSync(path.join(__dirname, 'lib', 'power-assert-recorder.json'), JSON.stringify(callexp, null, 2));
done();
});

gulp.task('unit', ['generate_recorder_json'], function () {
return runMochaSimply();
});

Expand Down
143 changes: 121 additions & 22 deletions lib/assertion-visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ var escodegen = require('escodegen');
var espurify = require('espurify');
var espurifyWithRaw = espurify.customize({extra: 'raw'});
var isArray = require('isarray');
var deepEqual = require('deep-equal');
var syntax = estraverse.Syntax;
var EspowerLocationDetector = require('espower-location-detector');
var EspowerError = require('./espower-error');
Expand All @@ -20,10 +19,12 @@ var canonicalCodeOptions = {
},
verbatim: 'x-verbatim-espower'
};
var recorderClassAst = require('./power-assert-recorder.json');

function AssertionVisitor (matcher, options) {
this.matcher = matcher;
this.options = options || {};
this.options = options;
this.valueRecorder = null;
this.locationDetector = new EspowerLocationDetector(this.options);
this.currentArgumentPath = null;
this.argumentModified = false;
Expand All @@ -33,7 +34,6 @@ AssertionVisitor.prototype.enter = function (controller) {
this.assertionPath = [].concat(controller.path());
var currentNode = controller.current();
this.canonicalCode = this.generateCanonicalCode(currentNode);
this.powerAssertCalleeObject = this.guessPowerAssertCalleeObjectFor(currentNode.callee);
this.location = this.locationDetector.locationFor(currentNode);
var enclosingFunc = findEnclosingFunction(controller.parents());
this.withinGenerator = enclosingFunc && enclosingFunc.generator;
Expand All @@ -52,6 +52,8 @@ AssertionVisitor.prototype.enterArgument = function (controller) {
return undefined;
}
this.verifyNotInstrumented(currentNode);
// create recorder per argument
this.valueRecorder = this.createNewRecorder(controller);
// entering target argument
this.currentArgumentPath = [].concat(controller.path());
return undefined;
Expand All @@ -67,6 +69,7 @@ AssertionVisitor.prototype.leaveArgument = function (resultTree) {
} finally {
this.currentArgumentPath = null;
this.argumentModified = false;
this.valueRecorder = null;
}
};

Expand All @@ -76,13 +79,12 @@ AssertionVisitor.prototype.captureNode = function (controller) {
var path = controller.path();
var n = newNodeWithLocationCopyOf(currentNode);
var relativeEsPath = path.slice(this.assertionPath.length);
var newCalleeObject = updateLocRecursively(espurify(this.powerAssertCalleeObject), n, this.options.visitorKeys);
return n({
type: syntax.CallExpression,
callee: n({
type: syntax.MemberExpression,
computed: false,
object: newCalleeObject,
object: this.valueRecorder,
property: n({
type: syntax.Identifier,
name: '_capt'
Expand Down Expand Up @@ -152,7 +154,6 @@ AssertionVisitor.prototype.generateCanonicalCode = function (node) {
AssertionVisitor.prototype.captureArgument = function (node) {
var n = newNodeWithLocationCopyOf(node);
var props = [];
var newCalleeObject = updateLocRecursively(espurify(this.powerAssertCalleeObject), n, this.options.visitorKeys);
addLiteralTo(props, n, 'content', this.canonicalCode);
addLiteralTo(props, n, 'filepath', this.location.source);
addLiteralTo(props, n, 'line', this.location.line);
Expand All @@ -167,7 +168,7 @@ AssertionVisitor.prototype.captureArgument = function (node) {
callee: n({
type: syntax.MemberExpression,
computed: false,
object: newCalleeObject,
object: this.valueRecorder,
property: n({
type: syntax.Identifier,
name: '_expr'
Expand All @@ -189,24 +190,90 @@ AssertionVisitor.prototype.verifyNotInstrumented = function (currentNode) {
}
var prop = currentNode.callee.property;
if (prop.type === syntax.Identifier && prop.name === '_expr') {
if (astEqual(currentNode.callee.object, this.powerAssertCalleeObject)) {
var errorMessage = 'Attempted to transform AST twice.';
if (this.options.path) {
errorMessage += ' path: ' + this.options.path;
}
throw new EspowerError(errorMessage, this.verifyNotInstrumented);
var errorMessage = 'Attempted to transform AST twice.';
if (this.options.path) {
errorMessage += ' path: ' + this.options.path;
}
throw new EspowerError(errorMessage, this.verifyNotInstrumented);
}
};

AssertionVisitor.prototype.guessPowerAssertCalleeObjectFor = function (node) {
switch(node.type) {
case syntax.Identifier:
return node;
case syntax.MemberExpression:
return node.object; // Returns browser.assert when browser.assert.element(selector)
AssertionVisitor.prototype.createNewRecorder = function (controller) {
var currentScope = this.options.currentScope;
var scopeBlockEspath = findEspathOfAncestorNode(currentScope.block, controller);
var recorderConstructorName = this.getRecorderConstructorName(controller);
var recorderVariableName = this.options.transformation.generateUniqueName('rec');

var currentNode = controller.current();
var createNode = newNodeWithLocationCopyOf(currentNode);
var ident = createNode({
type: syntax.Identifier,
name: recorderVariableName
});
var init = this.createNewExpression(createNode, recorderConstructorName);
var decl = this.createVariableDeclaration(createNode, ident, init);
this.options.transformation.register(scopeBlockEspath, function (matchNode) {
var body;
if (/Function/.test(matchNode.type)) {
var blockStatement = matchNode.body;
body = blockStatement.body;
} else {
body = matchNode.body;
}
insertAfterUseStrictDirective(decl, body);
});
return ident;
};

AssertionVisitor.prototype.getRecorderConstructorName = function (controller) {
var ctorName = this.options.storage.powerAssertRecorderConstructorName;
if (!ctorName) {
ctorName = this.createRecorderClass(controller);
}
return null;
return ctorName;
};

AssertionVisitor.prototype.createRecorderClass = function (controller) {
var globalScope = this.options.globalScope;
var globalScopeBlockEspath = findEspathOfAncestorNode(globalScope.block, controller);
var createNode = newNodeWithLocationCopyOf(globalScope.block);
var ctorName = this.options.transformation.generateUniqueName('PowerAssertRecorder');
var ident = createNode({
type: syntax.Identifier,
name: ctorName
});
var classDef = updateLocRecursively(espurify(recorderClassAst), createNode, this.options.visitorKeys);
var decl = this.createVariableDeclaration(createNode, ident, classDef);
this.options.transformation.register(globalScopeBlockEspath, function (matchNode) {
insertAfterUseStrictDirective(decl, matchNode.body);
});
this.options.storage.powerAssertRecorderConstructorName = ctorName;
return ctorName;
};

AssertionVisitor.prototype.createVariableDeclaration = function (createNode, ident, init) {
return createNode({
type: syntax.VariableDeclaration,
declarations: [
createNode({
type: syntax.VariableDeclarator,
id: ident,
init: init
})
],
kind: 'var'
});
};

AssertionVisitor.prototype.createNewExpression = function (createNode, constructorName) {
return createNode({
type: syntax.NewExpression,
callee: createNode({
type: syntax.Identifier,
name: constructorName
}),
arguments: []
});
};

function addLiteralTo (props, createNode, name, value) {
Expand Down Expand Up @@ -278,8 +345,40 @@ function newNodeWithLocationCopyOf (original) {
};
}

function astEqual (ast1, ast2) {
return deepEqual(espurify(ast1), espurify(ast2));
function findEspathOfAncestorNode (targetNode, controller) {
// iterate child to root
var child, parent;
var path = controller.path();
var parents = controller.parents();
var popUntilParent = function (key) {
if (parent[key] !== undefined) {
return;
}
popUntilParent(path.pop());
};
for (var i = parents.length - 1; i >= 0; i--) {
parent = parents[i];
if (child) {
popUntilParent(path.pop());
}
if (parent === targetNode) {
return path.join('/');
}
child = parent;
}
return null;
}

function insertAfterUseStrictDirective (decl, body) {
var firstBody = body[0];
if (firstBody.type === syntax.ExpressionStatement) {
var expression = firstBody.expression;
if (expression.type === syntax.Literal && expression.value === 'use strict') {
body.splice(1,0, decl);
return;
}
}
body.unshift(decl);
}

function isFunction (node) {
Expand Down
Loading