Skip to content

Commit

Permalink
tools: add linting rule for async IIFEs
Browse files Browse the repository at this point in the history
The result of an async IIFE should always be handled in our tests,
typically by adding `.then(common.mustCall())` to verify that the
async function actually finishes executing at some point.

PR-URL: #34363
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
  • Loading branch information
addaleax committed Jul 20, 2020
1 parent 2c4ebe0 commit 77b68f9
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 0 deletions.
1 change: 1 addition & 0 deletions test/.eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ rules:
node-core/prefer-common-mustnotcall: error
node-core/crypto-check: error
node-core/eslint-check: error
node-core/async-iife-no-unused-result: error
node-core/inspector-check: error
## common module is mandatory in tests
node-core/required-modules:
Expand Down
49 changes: 49 additions & 0 deletions test/parallel/test-eslint-async-iife-no-unused-result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
common.skipIfEslintMissing();

const RuleTester = require('../../tools/node_modules/eslint').RuleTester;
const rule = require('../../tools/eslint-rules/async-iife-no-unused-result');

const message = 'The result of an immediately-invoked async function needs ' +
'to be used (e.g. with `.then(common.mustCall())`)';

const tester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
tester.run('async-iife-no-unused-result', rule, {
valid: [
'(() => {})()',
'(async () => {})',
'(async () => {})().then()',
'(async () => {})().catch()',
'(function () {})()',
'(async function () {})',
'(async function () {})().then()',
'(async function () {})().catch()',
],
invalid: [
{
code: '(async () => {})()',
errors: [{ message }],
output: '(async () => {})()',
},
{
code: '(async function() {})()',
errors: [{ message }],
output: '(async function() {})()',
},
{
code: "const common = require('../common');(async () => {})()",
errors: [{ message }],
output: "const common = require('../common');(async () => {})()" +
'.then(common.mustCall())',
},
{
code: "const common = require('../common');(async function() {})()",
errors: [{ message }],
output: "const common = require('../common');(async function() {})()" +
'.then(common.mustCall())',
},
]
});
37 changes: 37 additions & 0 deletions tools/eslint-rules/async-iife-no-unused-result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';
const { isCommonModule } = require('./rules-utils.js');

function isAsyncIIFE(node) {
const { callee: { type, async } } = node;
const types = ['FunctionExpression', 'ArrowFunctionExpression'];
return types.includes(type) && async;
}

const message =
'The result of an immediately-invoked async function needs to be used ' +
'(e.g. with `.then(common.mustCall())`)';

module.exports = {
create: function(context) {
let hasCommonModule = false;
return {
CallExpression: function(node) {
if (isCommonModule(node) && node.parent.type === 'VariableDeclarator') {
hasCommonModule = true;
}

if (!isAsyncIIFE(node)) return;
if (node.parent && node.parent.type === 'ExpressionStatement') {
context.report({
node,
message,
fix: (fixer) => {
if (hasCommonModule)
return fixer.insertTextAfter(node, '.then(common.mustCall())');
}
});
}
}
};
}
};

0 comments on commit 77b68f9

Please sign in to comment.