123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- 'use strict';
- const {matches, methodCallSelector} = require('./selectors/index.js');
- const {getParenthesizedRange} = require('./utils/parentheses.js');
- const MESSAGE_ID_RESOLVE = 'resolve';
- const MESSAGE_ID_REJECT = 'reject';
- const messages = {
- [MESSAGE_ID_RESOLVE]: 'Prefer `{{type}} value` over `{{type}} Promise.resolve(value)`.',
- [MESSAGE_ID_REJECT]: 'Prefer `throw error` over `{{type}} Promise.reject(error)`.',
- };
- const selector = [
- methodCallSelector({
- object: 'Promise',
- methods: ['resolve', 'reject'],
- }),
- matches([
- 'ArrowFunctionExpression > .body',
- 'ReturnStatement > .argument',
- 'YieldExpression[delegate!=true] > .argument',
- ]),
- ].join('');
- const functionTypes = new Set([
- 'ArrowFunctionExpression',
- 'FunctionDeclaration',
- 'FunctionExpression',
- ]);
- function getFunctionNode(node) {
- let isInTryStatement = false;
- let functionNode;
- for (; node; node = node.parent) {
- if (functionTypes.has(node.type)) {
- functionNode = node;
- break;
- }
- if (node.type === 'TryStatement') {
- isInTryStatement = true;
- }
- }
- return {
- functionNode,
- isInTryStatement,
- };
- }
- function isPromiseCallback(node) {
- if (
- node.parent.type === 'CallExpression'
- && node.parent.callee.type === 'MemberExpression'
- && !node.parent.callee.computed
- && node.parent.callee.property.type === 'Identifier'
- ) {
- const {callee: {property}, arguments: arguments_} = node.parent;
- if (
- arguments_.length === 1
- && (
- property.name === 'then'
- || property.name === 'catch'
- || property.name === 'finally'
- )
- && arguments_[0] === node
- ) {
- return true;
- }
- if (
- arguments_.length === 2
- && property.name === 'then'
- && (
- arguments_[0] === node
- || (arguments_[0].type !== 'SpreadElement' && arguments_[1] === node)
- )
- ) {
- return true;
- }
- }
- return false;
- }
- function createProblem(callExpression, fix) {
- const {callee, parent} = callExpression;
- const method = callee.property.name;
- const type = parent.type === 'YieldExpression' ? 'yield' : 'return';
- return {
- node: callee,
- messageId: method,
- data: {type},
- fix,
- };
- }
- function fix(callExpression, isInTryStatement, sourceCode) {
- if (callExpression.arguments.length > 1) {
- return;
- }
- const {callee, parent, arguments: [errorOrValue]} = callExpression;
- if (errorOrValue?.type === 'SpreadElement') {
- return;
- }
- const isReject = callee.property.name === 'reject';
- const isYieldExpression = parent.type === 'YieldExpression';
- if (
- isReject
- && (
- isInTryStatement
- || (isYieldExpression && parent.parent.type !== 'ExpressionStatement')
- )
- ) {
- return;
- }
- return function (fixer) {
- const isArrowFunctionBody = parent.type === 'ArrowFunctionExpression';
- let text = errorOrValue ? sourceCode.getText(errorOrValue) : '';
- if (errorOrValue?.type === 'SequenceExpression') {
- text = `(${text})`;
- }
- if (isReject) {
- // `return Promise.reject()` -> `throw undefined`
- text = text || 'undefined';
- text = `throw ${text}`;
- if (isYieldExpression) {
- return fixer.replaceTextRange(
- getParenthesizedRange(parent, sourceCode),
- text,
- );
- }
- text += ';';
- // `=> Promise.reject(error)` -> `=> { throw error; }`
- if (isArrowFunctionBody) {
- text = `{ ${text} }`;
- return fixer.replaceTextRange(
- getParenthesizedRange(callExpression, sourceCode),
- text,
- );
- }
- } else {
- // eslint-disable-next-line no-lonely-if
- if (isYieldExpression) {
- text = `yield${text ? ' ' : ''}${text}`;
- } else if (parent.type === 'ReturnStatement') {
- text = `return${text ? ' ' : ''}${text};`;
- } else {
- if (errorOrValue?.type === 'ObjectExpression') {
- text = `(${text})`;
- }
- // `=> Promise.resolve()` -> `=> {}`
- text = text || '{}';
- }
- }
- return fixer.replaceText(
- isArrowFunctionBody ? callExpression : parent,
- text,
- );
- };
- }
- /** @param {import('eslint').Rule.RuleContext} context */
- const create = context => {
- const sourceCode = context.getSourceCode();
- return {
- [selector](callExpression) {
- const {functionNode, isInTryStatement} = getFunctionNode(callExpression);
- if (!functionNode || !(functionNode.async || isPromiseCallback(functionNode))) {
- return;
- }
- return createProblem(
- callExpression,
- fix(callExpression, isInTryStatement, sourceCode),
- );
- },
- };
- };
- /** @type {import('eslint').Rule.RuleModule} */
- module.exports = {
- create,
- meta: {
- type: 'suggestion',
- docs: {
- description: 'Disallow returning/yielding `Promise.resolve/reject()` in async functions or promise callbacks',
- },
- fixable: 'code',
- messages,
- },
- };
|