Skip to content

Commit

Permalink
feat(espower): disambiguate between function calls and async/yield ex…
Browse files Browse the repository at this point in the history
…pressions

In order to fully disambiguate the contextual keywords `async`
and `yield`, we need to pass along some addtional context to
power-assert-formatter. Specifically whether the enclosing function
is async or a generator.

This adds two new properties to the context passed to `assert._expr()`

 - `async: true` if the function enclosing the assertion is an async function
 - `generator: true` if the function enclosing the assertion is a generator

In both cases, we do not set a property if the enclosing function is not
a generator/async (i.e. we never pass `async:false`). This is primarily
to avoid having to rewrite a bunch of existing unit tests.
  • Loading branch information
jamestalmage committed Nov 6, 2015
1 parent cccfa90 commit 398585f
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 7 deletions.
10 changes: 9 additions & 1 deletion lib/assertion-visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function astEqual (ast1, ast2) {
return deepEqual(espurify(ast1), espurify(ast2));
}

function AssertionVisitor (matcher, assertionPath, options) {
function AssertionVisitor (matcher, assertionPath, enclosingFunc, options) {
this.matcher = matcher;
this.assertionPath = [].concat(assertionPath);
this.options = options || {};
Expand All @@ -37,6 +37,8 @@ function AssertionVisitor (matcher, assertionPath, options) {
}
this.currentArgumentPath = null;
this.argumentModified = false;
this.withinGenerator = enclosingFunc && enclosingFunc.generator;
this.withinAsync = enclosingFunc && enclosingFunc.async;
}

AssertionVisitor.prototype.enter = function (currentNode, parentNode) {
Expand Down Expand Up @@ -148,8 +150,14 @@ AssertionVisitor.prototype.captureArgument = function (node) {
var n = newNodeWithLocationCopyOf(node);
var props = [];
var newCalleeObject = updateLocRecursively(espurify(this.powerAssertCalleeObject), n, this.options.visitorKeys);
if (this.withinAsync) {
addLiteralTo(props, n, 'async', true);
}
addLiteralTo(props, n, 'content', this.canonicalCode);
addLiteralTo(props, n, 'filepath', this.filepath);
if (this.withinGenerator) {
addLiteralTo(props, n, 'generator', true);
}
addLiteralTo(props, n, 'line', this.lineNum);
return n({
type: syntax.CallExpression,
Expand Down
20 changes: 19 additions & 1 deletion lib/instrumentor.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ Instrumentor.prototype.instrument = function (ast) {
var candidates = that.matchers.filter(function (matcher) { return matcher.test(currentNode); });
if (candidates.length === 1) {
// entering target assertion
assertionVisitor = new AssertionVisitor(candidates[0], path, that.options);
var enclosingFunc = findEnclosingFunction(controller.parents());
assertionVisitor = new AssertionVisitor(candidates[0], path, enclosingFunc, that.options);
assertionVisitor.enter(currentNode, parentNode);
return undefined;
}
Expand Down Expand Up @@ -83,6 +84,23 @@ function isCalleeOfParentCallExpression (parentNode, currentKey) {
return parentNode.type === syntax.CallExpression && currentKey === 'callee';
}

function isFunction(node) {
return [
syntax.FunctionDeclaration,
syntax.FunctionExpression,
syntax.ArrowFunctionExpression
].indexOf(node.type) !== -1;
}

function findEnclosingFunction(parents) {
for (var i = parents.length - 1; i >= 0; i--) {
if (isFunction(parents[i])) {
return parents[i];
}
}
return null;
}

function verifyAstPrerequisites (ast, options) {
var errorMessage;
if (typeof ast.loc === 'undefined') {
Expand Down
44 changes: 39 additions & 5 deletions test/instrumentation_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ describe('instrumentation spec', function () {

function inst (jsCode, expected) {
describe('with loc, range', function () {
var options = {ecmaVersion: 7, locations: true, ranges: true, plugins: {asyncawait: {awaitAnywhere: true}}};
var options = {ecmaVersion: 7, locations: true, ranges: true, plugins: {asyncawait: true}};
testWithParserOptions(jsCode, expected, options);
});
describe('with loc', function () {
var options = {ecmaVersion: 7, locations: true, plugins: {asyncawait: {awaitAnywhere: true}}};
var options = {ecmaVersion: 7, locations: true, plugins: {asyncawait: true}};
testWithParserOptions(jsCode, expected, options);
});
}
Expand Down Expand Up @@ -400,12 +400,46 @@ describe('instrumentation spec', function () {

describe('YieldExpression', function () {
inst("function *gen() {assert((yield bigOrSmall(size)) === 'big')}",
"function*gen(){assert(assert._expr(assert._capt(assert._capt(yield bigOrSmall(assert._capt(size,'arguments/0/left/argument/arguments/0')),'arguments/0/left')==='big','arguments/0'),{content:'assert((yield bigOrSmall(size)) === \\'big\\')',filepath:'path/to/some_test.js',line:1}));}");
"function*gen(){assert(assert._expr(assert._capt(assert._capt(yield bigOrSmall(assert._capt(size,'arguments/0/left/argument/arguments/0')),'arguments/0/left')==='big','arguments/0'),{content:'assert((yield bigOrSmall(size)) === \\'big\\')',filepath:'path/to/some_test.js',generator:true,line:1}));}");
});

describe('YieldExpression vs FunctionCall disambiguation', function () {
inst("function baz() {assert((yield (foo)) === bar)}",
"function baz(){assert(assert._expr(assert._capt(assert._capt(yield(assert._capt(foo,'arguments/0/left/arguments/0')),'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{content:'assert(yield(foo) === bar)',filepath:'path/to/some_test.js',line:1}));}");

inst("function *baz() {assert((yield (foo)) === bar)}",
"function*baz(){assert(assert._expr(assert._capt(assert._capt(yield foo,'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{content:'assert((yield foo) === bar)',filepath:'path/to/some_test.js',generator:true,line:1}));}");

inst("var baz = function () {assert((yield (foo)) === bar)}",
"var baz=function(){assert(assert._expr(assert._capt(assert._capt(yield(assert._capt(foo,'arguments/0/left/arguments/0')),'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{content:'assert(yield(foo) === bar)',filepath:'path/to/some_test.js',line:1}));};");

inst("var baz = function *() {assert((yield (foo)) === bar)}",
"var baz=function*(){assert(assert._expr(assert._capt(assert._capt(yield foo,'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{content:'assert((yield foo) === bar)',filepath:'path/to/some_test.js',generator:true,line:1}));};");
});

describe('AwaitExpression', function () {
inst("function *gen() {assert((await bigOrSmall(size)) === 'big')}",
"function*gen(){assert(assert._expr(assert._capt(assert._capt(await bigOrSmall(assert._capt(size,'arguments/0/left/argument/arguments/0')),'arguments/0/left')==='big','arguments/0'),{content:'assert((await bigOrSmall(size)) === \\'big\\')',filepath:'path/to/some_test.js',line:1}));}");
inst("async function gen() {assert((await bigOrSmall(size)) === 'big')}",
"async function gen(){assert(assert._expr(assert._capt(assert._capt(await bigOrSmall(assert._capt(size,'arguments/0/left/argument/arguments/0')),'arguments/0/left')==='big','arguments/0'),{async:true,content:'assert((await bigOrSmall(size)) === \\'big\\')',filepath:'path/to/some_test.js',line:1}));}");
});

describe('AwaitExpression vs FunctionCall disambiguation', function () {
inst("function baz() {assert((await (foo)) === bar)}",
"function baz(){assert(assert._expr(assert._capt(assert._capt(await(assert._capt(foo,'arguments/0/left/arguments/0')),'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{content:'assert(await(foo) === bar)',filepath:'path/to/some_test.js',line:1}));}");

inst("async function baz() {assert((await (foo)) === bar)}",
"async function baz(){assert(assert._expr(assert._capt(assert._capt(await foo,'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{async:true,content:'assert((await foo) === bar)',filepath:'path/to/some_test.js',line:1}));}");

inst("var baz = function () {assert((await (foo)) === bar)}",
"var baz=function(){assert(assert._expr(assert._capt(assert._capt(await(assert._capt(foo,'arguments/0/left/arguments/0')),'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{content:'assert(await(foo) === bar)',filepath:'path/to/some_test.js',line:1}));};");

inst("var baz = async function () {assert((await (foo)) === bar)}",
"var baz=async function(){assert(assert._expr(assert._capt(assert._capt(await foo,'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{async:true,content:'assert((await foo) === bar)',filepath:'path/to/some_test.js',line:1}));};");

inst("var baz = () => {assert((await (foo)) === bar)};",
"var baz=()=>{assert(assert._expr(assert._capt(assert._capt(await(assert._capt(foo,'arguments/0/left/arguments/0')),'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{content:'assert(await(foo) === bar)',filepath:'path/to/some_test.js',line:1}));};");

inst("var baz = async () => {assert((await (foo)) === bar)}",
"var baz=async()=>{assert(assert._expr(assert._capt(assert._capt(await foo,'arguments/0/left')===assert._capt(bar,'arguments/0/right'),'arguments/0'),{async:true,content:'assert((await foo) === bar)',filepath:'path/to/some_test.js',line:1}));};");
});

describe('Enhanced Object Literals', function () {
Expand Down

0 comments on commit 398585f

Please sign in to comment.