From 9fa8d2037f042efd8e9bd6df03c70d899f213977 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 14 Jul 2020 17:46:02 +0200 Subject: [PATCH] tools: add linting rule for async IIFEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: https://github.com/nodejs/node/pull/34363 Reviewed-By: Colin Ihrig Reviewed-By: Rich Trott Reviewed-By: Tobias Nießen Reviewed-By: Richard Lau Reviewed-By: James M Snell Reviewed-By: Luigi Pinca --- test/.eslintrc.yaml | 1 + ...test-eslint-async-iife-no-unused-result.js | 49 +++++++++++++++++++ .../async-iife-no-unused-result.js | 40 +++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 test/parallel/test-eslint-async-iife-no-unused-result.js create mode 100644 tools/eslint-rules/async-iife-no-unused-result.js diff --git a/test/.eslintrc.yaml b/test/.eslintrc.yaml index 38c1d294edc95f..6a0104f7ce17ca 100644 --- a/test/.eslintrc.yaml +++ b/test/.eslintrc.yaml @@ -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: diff --git a/test/parallel/test-eslint-async-iife-no-unused-result.js b/test/parallel/test-eslint-async-iife-no-unused-result.js new file mode 100644 index 00000000000000..6e7f60c183b830 --- /dev/null +++ b/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())', + }, + ] +}); diff --git a/tools/eslint-rules/async-iife-no-unused-result.js b/tools/eslint-rules/async-iife-no-unused-result.js new file mode 100644 index 00000000000000..bb089de8832d88 --- /dev/null +++ b/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())'); + } + }); + } + } + }; + } +};