Skip to content

Commit

Permalink
Fix: resolve-async recognizes .call/.apply (fixes #68)
Browse files Browse the repository at this point in the history
  • Loading branch information
platinumazure committed May 16, 2020
1 parent 654fbd7 commit c9d68bf
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 8 deletions.
27 changes: 19 additions & 8 deletions lib/rules/resolve-async.js
Expand Up @@ -37,12 +37,22 @@ module.exports = {
return utils.isAsyncCallExpression(callExpressionNode, assertContextVar);
}

function isAsyncCallbackVar(calleeNode) {
function getAsyncCallbackVarOrNull(calleeNode) {
const asyncState = asyncStateStack[asyncStateStack.length - 1];
let result = false;
let result = null;

if (asyncState && calleeNode.type === "Identifier") {
result = calleeNode.name in asyncState.asyncCallbackVars;
if (asyncState) {
if (calleeNode.type === "Identifier" && calleeNode.name in asyncState.asyncCallbackVars) {
result = calleeNode.name;
} else if (calleeNode.type === "MemberExpression") {
const isCallOrApply = calleeNode.property.type === "Identifier" &&
["call", "apply"].includes(calleeNode.property.name);
const isCallbackVar = calleeNode.object.name in asyncState.asyncCallbackVars;

if (isCallOrApply && isCallbackVar) {
result = calleeNode.object.name;
}
}
}

return result;
Expand All @@ -64,12 +74,12 @@ module.exports = {
}
}

function markAsyncCallbackVarCalled(calleeNode) {
function markAsyncCallbackVarCalled(name) {
const asyncState = asyncStateStack[asyncStateStack.length - 1];

/* istanbul ignore else: will correctly do nothing */
if (asyncState) {
asyncState.asyncCallbackVars[calleeNode.name] = true;
asyncState.asyncCallbackVars[name] = true;
}
}

Expand Down Expand Up @@ -110,6 +120,7 @@ module.exports = {

return {
"CallExpression": function (node) {
const callbackVar = getAsyncCallbackVarOrNull(node.callee);
let delta;

if (utils.isTest(node.callee)) {
Expand All @@ -119,8 +130,8 @@ module.exports = {
asyncCallbackVars: {},
assertContextVar: assertContextVar
});
} else if (isAsyncCallbackVar(node.callee)) {
markAsyncCallbackVarCalled(node.callee);
} else if (callbackVar) {
markAsyncCallbackVarCalled(callbackVar);
} else if (utils.isStop(node.callee)) {
delta = node.arguments.length ? +node.arguments[0] : 1;
incrementSemaphoreCount(delta);
Expand Down
14 changes: 14 additions & 0 deletions tests/lib/rules/resolve-async.js
Expand Up @@ -116,6 +116,10 @@ ruleTester.run("resolve-async", rule, {
"module('name', { afterEach: function () { var done = assert.async(); done(); } });",
"QUnit.module('name', { afterEach: function () { var done = assert.async(); done(); } });",

// assert.async callback can be invoked via .call/.apply
"QUnit.test('name', function (assert) { var done = assert.async(); done.call(); });",
"QUnit.test('name', function (assert) { var done = assert.async(); done.apply(); });",

// start/stop calls outside of test context should not affect count
"start(); test('name', function () { stop(); start(); });",
"stop(); test('name', function () { stop(); start(); });",
Expand Down Expand Up @@ -433,6 +437,16 @@ ruleTester.run("resolve-async", rule, {
]
},

// assert.async callback can be invoked via .call/.apply
{
code: "QUnit.test('name', function (assert) { var done1 = assert.async(), done2 = assert.async(); done1.call(); });",
errors: [createAsyncCallbackNotCalledMessage("CallExpression", "done2")]
},
{
code: "QUnit.test('name', function (assert) { var done1 = assert.async(), done2 = assert.async(); done1.apply(); });",
errors: [createAsyncCallbackNotCalledMessage("CallExpression", "done2")]
},

// start/stop calls outside of test context should not affect count
{
code: "start(); asyncTest('name', function () {});",
Expand Down

0 comments on commit c9d68bf

Please sign in to comment.