Skip to content

Commit

Permalink
New: require-await rule (fixes #6820) (#7435)
Browse files Browse the repository at this point in the history
* New: `require-await` rule (fixes #6820)

* Fixes fileoverview tag.

* extracts `isEmptyFunction` to ast-utils.
  • Loading branch information
mysticatea authored and ilyavolodin committed Nov 24, 2016
1 parent b7432bd commit 89bd8de
Show file tree
Hide file tree
Showing 7 changed files with 692 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/eslint.json
Expand Up @@ -210,6 +210,7 @@
"quote-props": "off",
"quotes": "off",
"radix": "off",
"require-await": "off",
"require-jsdoc": "off",
"require-yield": "error",
"rest-spread-spacing": "off",
Expand Down
54 changes: 54 additions & 0 deletions docs/rules/require-await.md
@@ -0,0 +1,54 @@
# Disallow async functions which have no `await` expression (require-await)

Async functions which have no `await` expression may be the unintentional result of refactoring.

## Rule Details

This rule warns async functions which have no `await` expression.

Examples of **incorrect** code for this rule:

```js
/*eslint require-await: "error"*/

async function foo() {
doSomething();
}

bar(async () => {
doSomething();
});
```

Examples of **correct** code for this rule:

```js
/*eslint require-await: "error"*/

async function foo() {
await doSomething();
}

bar(async () => {
await doSomething();
});

function foo() {
doSomething();
}

bar(() => {
doSomething();
});

// Allow empty functions.
async function noop() {}
```

## When Not To Use It

If you don't want to notify async functions which have no `await` expression, then it's safe to disable this rule.

## Related Rules

* [require-yield](require-yield.md)
8 changes: 8 additions & 0 deletions docs/rules/require-yield.md
Expand Up @@ -35,3 +35,11 @@ function foo() {
// This rule does not warn on empty generator functions.
function* foo() { }
```

## When Not To Use It

If you don't want to notify generator functions that have `yield` expression, then it's safe to disable this rule.

This comment has been minimized.

Copy link
@vsemozhetbyt

vsemozhetbyt Nov 24, 2016

Contributor

that have => that do not have ?


## Related Rules

* [require-await](require-await.md)
262 changes: 262 additions & 0 deletions lib/ast-utils.js
Expand Up @@ -195,6 +195,40 @@ function isParenthesised(sourceCode, node) {
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}

/**
* Gets the `=>` token of the given arrow function node.
*
* @param {ASTNode} node - The arrow function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {Token} `=>` token.
*/
function getArrowToken(node, sourceCode) {
let token = sourceCode.getTokenBefore(node.body);

while (token.value !== "=>") {
token = sourceCode.getTokenBefore(token);
}

return token;
}

/**
* Gets the `(` token of the given function node.
*
* @param {ASTNode} node - The function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {Token} `(` token.
*/
function getOpeningParenOfParams(node, sourceCode) {
let token = node.id ? sourceCode.getTokenAfter(node.id) : sourceCode.getFirstToken(node);

while (token.value !== "(") {
token = sourceCode.getTokenAfter(token);
}

return token;
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -612,6 +646,26 @@ module.exports = {
return Boolean(node && anyFunctionPattern.test(node.type));
},

/**
* Checks whether the given node is an empty block node or not.
*
* @param {ASTNode|null} node - The node to check.
* @returns {boolean} `true` if the node is an empty block.
*/
isEmptyBlock(node) {
return Boolean(node && node.type === "BlockStatement" && node.body.length === 0);
},

/**
* Checks whether the given node is an empty function node or not.
*
* @param {ASTNode|null} node - The node to check.
* @returns {boolean} `true` if the node is an empty function.
*/
isEmptyFunction(node) {
return module.exports.isFunction(node) && module.exports.isEmptyBlock(node.body);
},

/**
* Gets the property name of a given node.
* The node can be a MemberExpression, a Property, or a MethodDefinition.
Expand Down Expand Up @@ -736,5 +790,213 @@ module.exports = {
*/
isDecimalInteger(node) {
return node.type === "Literal" && typeof node.value === "number" && /^(0|[1-9]\d*)$/.test(node.raw);
},

/**
* Gets the name and kind of the given function node.
*
* - `function foo() {}` .................... `function 'foo'`
* - `(function foo() {})` .................. `function 'foo'`
* - `(function() {})` ...................... `function`
* - `function* foo() {}` ................... `generator function 'foo'`
* - `(function* foo() {})` ................. `generator function 'foo'`
* - `(function*() {})` ..................... `generator function`
* - `() => {}` ............................. `arrow function`
* - `async () => {}` ....................... `async arrow function`
* - `({ foo: function foo() {} })` ......... `method 'foo'`
* - `({ foo: function() {} })` ............. `method 'foo'`
* - `({ ['foo']: function() {} })` ......... `method 'foo'`
* - `({ [foo]: function() {} })` ........... `method`
* - `({ foo() {} })` ....................... `method 'foo'`
* - `({ foo: function* foo() {} })` ........ `generator method 'foo'`
* - `({ foo: function*() {} })` ............ `generator method 'foo'`
* - `({ ['foo']: function*() {} })` ........ `generator method 'foo'`
* - `({ [foo]: function*() {} })` .......... `generator method`
* - `({ *foo() {} })` ...................... `generator method 'foo'`
* - `({ foo: async function foo() {} })` ... `async method 'foo'`
* - `({ foo: async function() {} })` ....... `async method 'foo'`
* - `({ ['foo']: async function() {} })` ... `async method 'foo'`
* - `({ [foo]: async function() {} })` ..... `async method`
* - `({ async foo() {} })` ................. `async method 'foo'`
* - `({ get foo() {} })` ................... `getter 'foo'`
* - `({ set foo(a) {} })` .................. `setter 'foo'`
* - `class A { constructor() {} }` ......... `constructor`
* - `class A { foo() {} }` ................. `method 'foo'`
* - `class A { *foo() {} }` ................ `generator method 'foo'`
* - `class A { async foo() {} }` ........... `async method 'foo'`
* - `class A { ['foo']() {} }` ............. `method 'foo'`
* - `class A { *['foo']() {} }` ............ `generator method 'foo'`
* - `class A { async ['foo']() {} }` ....... `async method 'foo'`
* - `class A { [foo]() {} }` ............... `method`
* - `class A { *[foo]() {} }` .............. `generator method`
* - `class A { async [foo]() {} }` ......... `async method`
* - `class A { get foo() {} }` ............. `getter 'foo'`
* - `class A { set foo(a) {} }` ............ `setter 'foo'`
* - `class A { static foo() {} }` .......... `static method 'foo'`
* - `class A { static *foo() {} }` ......... `static generator method 'foo'`
* - `class A { static async foo() {} }` .... `static async method 'foo'`
* - `class A { static get foo() {} }` ...... `static getter 'foo'`
* - `class A { static set foo(a) {} }` ..... `static setter 'foo'`
*
* @param {ASTNode} node - The function node to get.
* @returns {string} The name and kind of the function node.
*/
getFunctionNameWithKind(node) {
const parent = node.parent;
const tokens = [];

if (parent.type === "MethodDefinition" && parent.static) {
tokens.push("static");
}
if (node.async) {
tokens.push("async");
}
if (node.generator) {
tokens.push("generator");
}

if (node.type === "ArrowFunctionExpression") {
tokens.push("arrow", "function");
} else if (parent.type === "Property" || parent.type === "MethodDefinition") {
if (parent.kind === "constructor") {
return "constructor";
} else if (parent.kind === "get") {
tokens.push("getter");
} else if (parent.kind === "set") {
tokens.push("setter");
} else {
tokens.push("method");
}
} else {
tokens.push("function");
}

if (node.id) {
tokens.push(`'${node.id.name}'`);
} else {
const name = module.exports.getStaticPropertyName(parent);

if (name) {
tokens.push(`'${name}'`);
}
}

return tokens.join(" ");
},

/**
* Gets the location of the given function node for reporting.
*
* - `function foo() {}`
* ^^^^^^^^^^^^
* - `(function foo() {})`
* ^^^^^^^^^^^^
* - `(function() {})`
* ^^^^^^^^
* - `function* foo() {}`
* ^^^^^^^^^^^^^
* - `(function* foo() {})`
* ^^^^^^^^^^^^^
* - `(function*() {})`
* ^^^^^^^^^
* - `() => {}`
* ^^
* - `async () => {}`
* ^^
* - `({ foo: function foo() {} })`
* ^^^^^^^^^^^^^^^^^
* - `({ foo: function() {} })`
* ^^^^^^^^^^^^^
* - `({ ['foo']: function() {} })`
* ^^^^^^^^^^^^^^^^^
* - `({ [foo]: function() {} })`
* ^^^^^^^^^^^^^^^
* - `({ foo() {} })`
* ^^^
* - `({ foo: function* foo() {} })`
* ^^^^^^^^^^^^^^^^^^
* - `({ foo: function*() {} })`
* ^^^^^^^^^^^^^^
* - `({ ['foo']: function*() {} })`
* ^^^^^^^^^^^^^^^^^^
* - `({ [foo]: function*() {} })`
* ^^^^^^^^^^^^^^^^
* - `({ *foo() {} })`
* ^^^^
* - `({ foo: async function foo() {} })`
* ^^^^^^^^^^^^^^^^^^^^^^^
* - `({ foo: async function() {} })`
* ^^^^^^^^^^^^^^^^^^^
* - `({ ['foo']: async function() {} })`
* ^^^^^^^^^^^^^^^^^^^^^^^
* - `({ [foo]: async function() {} })`
* ^^^^^^^^^^^^^^^^^^^^^
* - `({ async foo() {} })`
* ^^^^^^^^^
* - `({ get foo() {} })`
* ^^^^^^^
* - `({ set foo(a) {} })`
* ^^^^^^^
* - `class A { constructor() {} }`
* ^^^^^^^^^^^
* - `class A { foo() {} }`
* ^^^
* - `class A { *foo() {} }`
* ^^^^
* - `class A { async foo() {} }`
* ^^^^^^^^^
* - `class A { ['foo']() {} }`
* ^^^^^^^
* - `class A { *['foo']() {} }`
* ^^^^^^^^
* - `class A { async ['foo']() {} }`
* ^^^^^^^^^^^^^
* - `class A { [foo]() {} }`
* ^^^^^
* - `class A { *[foo]() {} }`
* ^^^^^^
* - `class A { async [foo]() {} }`
* ^^^^^^^^^^^
* - `class A { get foo() {} }`
* ^^^^^^^
* - `class A { set foo(a) {} }`
* ^^^^^^^
* - `class A { static foo() {} }`
* ^^^^^^^^^^
* - `class A { static *foo() {} }`
* ^^^^^^^^^^^
* - `class A { static async foo() {} }`
* ^^^^^^^^^^^^^^^^
* - `class A { static get foo() {} }`
* ^^^^^^^^^^^^^^
* - `class A { static set foo(a) {} }`
* ^^^^^^^^^^^^^^
*
* @param {ASTNode} node - The function node to get.
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @returns {string} The location of the function node for reporting.
*/
getFunctionHeadLoc(node, sourceCode) {
const parent = node.parent;
let start = null;
let end = null;

if (node.type === "ArrowFunctionExpression") {
const arrowToken = getArrowToken(node, sourceCode);

start = arrowToken.loc.start;
end = arrowToken.loc.end;
} else if (parent.type === "Property" || parent.type === "MethodDefinition") {
start = parent.loc.start;
end = getOpeningParenOfParams(node, sourceCode).loc.start;
} else {
start = node.loc.start;
end = getOpeningParenOfParams(node, sourceCode).loc.start;
}

return {
start: Object.assign({}, start),
end: Object.assign({}, end),
};
}
};

0 comments on commit 89bd8de

Please sign in to comment.