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 authored and targos committed Jun 11, 2021
1 parent 89ee6ab commit 9fa8d20
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 0 deletions.
1 change: 1 addition & 0 deletions test/.eslintrc.yaml
Expand Up @@ -53,6 +53,7 @@ rules:
node-core/prefer-common-mustsucceed: 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
@@ -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())',
},
]
});
40 changes: 40 additions & 0 deletions tools/eslint-rules/async-iife-no-unused-result.js
@@ -0,0 +1,40 @@
'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 = {
meta: {
fixable: 'code'
},
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 9fa8d20

Please sign in to comment.