Skip to content
This repository has been archived by the owner on Aug 4, 2020. It is now read-only.

Commit

Permalink
Add no-await-in-loop rule.
Browse files Browse the repository at this point in the history
Closes #20
  • Loading branch information
nmote committed Oct 19, 2015
1 parent 1197501 commit ecae7d2
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 0 deletions.
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ module.exports = {
'object-curly-spacing': require('./rules/object-curly-spacing'),
'object-shorthand': require('./rules/object-shorthand'),
'arrow-parens': require('./rules/arrow-parens'),
'no-await-in-loop': require('./rules/no-await-in-loop'),
},
rulesConfig: {
'generator-star-spacing': 0,
'new-cap': 0,
'object-curly-spacing': 0,
'object-shorthand': 0,
'arrow-parens': 0,
'no-await-in-loop': 0,
}
};
65 changes: 65 additions & 0 deletions rules/no-await-in-loop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* @fileoverview Rule to disallow uses of await inside of loops.
* @author Nat Mote
*/
"use strict";

// Node types which are considered loops.
var loopTypes = new Set([
'ForStatement',
'ForOfStatement',
'ForInStatement',
'WhileStatement',
'DoWhileStatement',
]);

// Node types at which we should stop looking for loops. For example, it is fine to declare an async
// function within a loop, and use await inside of that.
var boundaryTypes = new Set([
'FunctionDeclaration',
'FunctionExpression',
'ArrowFunctionExpression',
]);

module.exports = function(context) {
return {
// babel-eslint transpiles AwaitExpressions to YieldExpressions, but the actual node kind is
// still available in _babelType.
YieldExpression: function(node) {
if (node._babelType === 'AwaitExpression') {
var ancestors = context.getAncestors();
// Reverse so that we can traverse from the deepest node upwards.
ancestors.reverse();
// Create a set of all the ancestors plus this node so that we can check
// if this use of await appears in the body of the loop as opposed to
// the right-hand side of a for...of, for example.
var ancestorSet = new Set(ancestors).add(node);
for (var i = 0; i < ancestors.length; i++) {
var ancestor = ancestors[i];
if (boundaryTypes.has(ancestor.type)) {
// Short-circuit out if we encounter a boundary type. Loops above
// this do not matter.
return;
}
if (loopTypes.has(ancestor.type)) {
// Only report if we are actually in the body or another part that gets executed on
// every iteration.
if (
ancestorSet.has(ancestor.body) ||
ancestorSet.has(ancestor.test) ||
ancestorSet.has(ancestor.update)
) {
context.report(
node,
'Avoid using await inside a loop. Consider refactoring to use Promise.all. If ' +
'you are sure you want to do this, add `// eslint-disable-line ' +
context.id + '` at the end of this line.'
);
return;
}
}
}
}
},
};
}
106 changes: 106 additions & 0 deletions tests/no-await-in-loop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* @fileoverview Tests for no-await-in-loop.
* @author Nat Mote
*/

"use strict";

var rule = require("../rules/no-await-in-loop"),
RuleTester = require('eslint').RuleTester;

var features = {
};

function test(code, errors){
var result = {
code: code,
parser: 'babel-eslint',
ecmaFeatures: features,
};
if (errors != null) {
result.errors = errors;
}
return result;
}

var ruleName = 'babel/no-await-in-loop';

var message = 'Avoid using await inside a loop. Consider refactoring to use Promise.all. If ' +
'you are sure you want to do this, add `// eslint-disable-line ' +
ruleName + '` at the end of this line.'

function ok(code) {
return test(code);
}

function err(code) {
return test(code, [message]);
}

// Construct an async function with the given body
function fun(body) {
return "async function foo() { " + body + " }";
}

// Construct a loop
function loop(kind, condition, body) {
return kind + " (" + condition + ") { " + body + " }";
}

// Construct a class with the given body
function cls(body) {
return "class Foo { " + body + " }";
}

var cases = [
ok(fun("await bar;")),

// While loops
ok(fun(loop("while", "true", fun("await bar;")))), // Blocked by a function declaration
err(fun(loop("while", "baz", "await bar;"))),
err(fun(loop("while", "await foo()", ""))),

// For of loops
err(fun(loop("for", "var bar of baz", "await bar;"))),

// For in loops
err(fun(loop("for", "var bar in baz", "await bar;"))),

// For loops
ok(fun(loop("for", "var i = await bar; i < n; i++", ""))),
err(fun(loop("for", "var i; i < n; i++", "await bar;"))),
err(fun(loop("for", "var i; await foo(i); i++", ""))),
err(fun(loop("for", "var i; i < n; i = await bar", ""))),

// Do while loops
ok(fun("do { } while (bar);")),
err(fun("do { await bar; } while (baz);")),
err(fun("do { } while (await bar);")),

// Blocked by a function expression
ok(fun(loop("while", "true", "var y = async function() { await bar; }"))),
// Blocked by an arrow function
ok(fun(loop("while", "true", "var y = async () => await foo;"))),
ok(fun(loop("while", "true", "var y = async () => { await foo; }"))),
// Blocked by a class method,
ok(fun(loop("while", "true", cls("async foo() { await bar; }")))),

// Deep in a loop body
err(fun(loop("while", "true", "if (bar) { foo(await bar); }"))),
// Deep in a loop condition
err(fun(loop("while", "xyz || 5 > await x", ""))),
];

function hasError(testCase) {
return testCase.errors != null && testCase.errors.length > 0;
}

function hasNoError(testCase) {
return !hasError(testCase);
}

var ruleTester = new RuleTester();
ruleTester.run(ruleName, rule, {
valid: cases.filter(hasNoError),
invalid: cases.filter(hasError),
});

0 comments on commit ecae7d2

Please sign in to comment.