Permalink
Cannot retrieve contributors at this time
| /** | |
| * @fileoverview restrict values that can be used as Promise rejection reasons | |
| * @author Teddy Katz | |
| */ | |
| "use strict"; | |
| const astUtils = require("./utils/ast-utils"); | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
| module.exports = { | |
| meta: { | |
| type: "suggestion", | |
| docs: { | |
| description: "require using Error objects as Promise rejection reasons", | |
| category: "Best Practices", | |
| recommended: false, | |
| url: "https://eslint.org/docs/rules/prefer-promise-reject-errors" | |
| }, | |
| fixable: null, | |
| schema: [ | |
| { | |
| type: "object", | |
| properties: { | |
| allowEmptyReject: { type: "boolean", default: false } | |
| }, | |
| additionalProperties: false | |
| } | |
| ] | |
| }, | |
| create(context) { | |
| const ALLOW_EMPTY_REJECT = context.options.length && context.options[0].allowEmptyReject; | |
| //---------------------------------------------------------------------- | |
| // Helpers | |
| //---------------------------------------------------------------------- | |
| /** | |
| * Checks the argument of a reject() or Promise.reject() CallExpression, and reports it if it can't be an Error | |
| * @param {ASTNode} callExpression A CallExpression node which is used to reject a Promise | |
| * @returns {void} | |
| */ | |
| function checkRejectCall(callExpression) { | |
| if (!callExpression.arguments.length && ALLOW_EMPTY_REJECT) { | |
| return; | |
| } | |
| if ( | |
| !callExpression.arguments.length || | |
| !astUtils.couldBeError(callExpression.arguments[0]) || | |
| callExpression.arguments[0].type === "Identifier" && callExpression.arguments[0].name === "undefined" | |
| ) { | |
| context.report({ | |
| node: callExpression, | |
| message: "Expected the Promise rejection reason to be an Error." | |
| }); | |
| } | |
| } | |
| /** | |
| * Determines whether a function call is a Promise.reject() call | |
| * @param {ASTNode} node A CallExpression node | |
| * @returns {boolean} `true` if the call is a Promise.reject() call | |
| */ | |
| function isPromiseRejectCall(node) { | |
| return node.callee.type === "MemberExpression" && | |
| node.callee.object.type === "Identifier" && node.callee.object.name === "Promise" && | |
| node.callee.property.type === "Identifier" && node.callee.property.name === "reject"; | |
| } | |
| //---------------------------------------------------------------------- | |
| // Public | |
| //---------------------------------------------------------------------- | |
| return { | |
| // Check `Promise.reject(value)` calls. | |
| CallExpression(node) { | |
| if (isPromiseRejectCall(node)) { | |
| checkRejectCall(node); | |
| } | |
| }, | |
| /* | |
| * Check for `new Promise((resolve, reject) => {})`, and check for reject() calls. | |
| * This function is run on "NewExpression:exit" instead of "NewExpression" to ensure that | |
| * the nodes in the expression already have the `parent` property. | |
| */ | |
| "NewExpression:exit"(node) { | |
| if ( | |
| node.callee.type === "Identifier" && node.callee.name === "Promise" && | |
| node.arguments.length && astUtils.isFunction(node.arguments[0]) && | |
| node.arguments[0].params.length > 1 && node.arguments[0].params[1].type === "Identifier" | |
| ) { | |
| context.getDeclaredVariables(node.arguments[0]) | |
| /* | |
| * Find the first variable that matches the second parameter's name. | |
| * If the first parameter has the same name as the second parameter, then the variable will actually | |
| * be "declared" when the first parameter is evaluated, but then it will be immediately overwritten | |
| * by the second parameter. It's not possible for an expression with the variable to be evaluated before | |
| * the variable is overwritten, because functions with duplicate parameters cannot have destructuring or | |
| * default assignments in their parameter lists. Therefore, it's not necessary to explicitly account for | |
| * this case. | |
| */ | |
| .find(variable => variable.name === node.arguments[0].params[1].name) | |
| // Get the references to that variable. | |
| .references | |
| // Only check the references that read the parameter's value. | |
| .filter(ref => ref.isRead()) | |
| // Only check the references that are used as the callee in a function call, e.g. `reject(foo)`. | |
| .filter(ref => ref.identifier.parent.type === "CallExpression" && ref.identifier === ref.identifier.parent.callee) | |
| // Check the argument of the function call to determine whether it's an Error. | |
| .forEach(ref => checkRejectCall(ref.identifier.parent)); | |
| } | |
| } | |
| }; | |
| } | |
| }; |