securityos/node_modules/eslint-plugin-unicorn/rules/prefer-top-level-await.js

138 lines
3.4 KiB
JavaScript

'use strict';
const {findVariable, getFunctionHeadLocation} = require('@eslint-community/eslint-utils');
const {matches, not, memberExpressionSelector} = require('./selectors/index.js');
const ERROR_PROMISE = 'promise';
const ERROR_IIFE = 'iife';
const ERROR_IDENTIFIER = 'identifier';
const SUGGESTION_ADD_AWAIT = 'add-await';
const messages = {
[ERROR_PROMISE]: 'Prefer top-level await over using a promise chain.',
[ERROR_IIFE]: 'Prefer top-level await over an async IIFE.',
[ERROR_IDENTIFIER]: 'Prefer top-level await over an async function `{{name}}` call.',
[SUGGESTION_ADD_AWAIT]: 'Insert `await`.',
};
const promiseMethods = ['then', 'catch', 'finally'];
const topLevelCallExpression = [
'CallExpression',
not([':function *', 'ClassDeclaration *', 'ClassExpression *']),
].join('');
const iife = [
topLevelCallExpression,
matches([
'[callee.type="FunctionExpression"]',
'[callee.type="ArrowFunctionExpression"]',
]),
'[callee.async!=false]',
'[callee.generator!=true]',
].join('');
const promise = [
topLevelCallExpression,
memberExpressionSelector({
path: 'callee',
properties: promiseMethods,
includeOptional: true,
}),
].join('');
const identifier = [
topLevelCallExpression,
'[callee.type="Identifier"]',
].join('');
const isPromiseMethodCalleeObject = node =>
node.parent.type === 'MemberExpression'
&& node.parent.object === node
&& !node.parent.computed
&& node.parent.property.type === 'Identifier'
&& promiseMethods.includes(node.parent.property.name)
&& node.parent.parent.type === 'CallExpression'
&& node.parent.parent.callee === node.parent;
const isAwaitArgument = node => {
if (node.parent.type === 'ChainExpression') {
node = node.parent;
}
return node.parent.type === 'AwaitExpression' && node.parent.argument === node;
};
/** @param {import('eslint').Rule.RuleContext} context */
function create(context) {
return {
[promise](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
}
return {
node: node.callee.property,
messageId: ERROR_PROMISE,
};
},
[iife](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
}
return {
node,
loc: getFunctionHeadLocation(node.callee, context.getSourceCode()),
messageId: ERROR_IIFE,
};
},
[identifier](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
}
const variable = findVariable(context.getScope(), node.callee);
if (!variable || variable.defs.length !== 1) {
return;
}
const [definition] = variable.defs;
const value = definition.type === 'Variable' && definition.kind === 'const'
? definition.node.init
: definition.node;
if (
!value
|| !(
(
value.type === 'ArrowFunctionExpression'
|| value.type === 'FunctionExpression'
|| value.type === 'FunctionDeclaration'
) && !value.generator && value.async
)
) {
return;
}
return {
node,
messageId: ERROR_IDENTIFIER,
data: {name: node.callee.name},
suggest: [
{
messageId: SUGGESTION_ADD_AWAIT,
fix: fixer => fixer.insertTextBefore(node, 'await '),
},
],
};
},
};
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer top-level await over top-level promises and async function calls.',
},
hasSuggestions: true,
messages,
},
};