prefer-promise-reject-errors.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. /**
  2. * @fileoverview restrict values that can be used as Promise rejection reasons
  3. * @author Teddy Katz
  4. */
  5. "use strict";
  6. const astUtils = require("./utils/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. /** @type {import('../shared/types').Rule} */
  11. module.exports = {
  12. meta: {
  13. type: "suggestion",
  14. defaultOptions: [{
  15. allowEmptyReject: false
  16. }],
  17. docs: {
  18. description: "Require using Error objects as Promise rejection reasons",
  19. recommended: false,
  20. url: "https://eslint.org/docs/latest/rules/prefer-promise-reject-errors"
  21. },
  22. fixable: null,
  23. schema: [
  24. {
  25. type: "object",
  26. properties: {
  27. allowEmptyReject: { type: "boolean" }
  28. },
  29. additionalProperties: false
  30. }
  31. ],
  32. messages: {
  33. rejectAnError: "Expected the Promise rejection reason to be an Error."
  34. }
  35. },
  36. create(context) {
  37. const [{ allowEmptyReject }] = context.options;
  38. const sourceCode = context.sourceCode;
  39. //----------------------------------------------------------------------
  40. // Helpers
  41. //----------------------------------------------------------------------
  42. /**
  43. * Checks the argument of a reject() or Promise.reject() CallExpression, and reports it if it can't be an Error
  44. * @param {ASTNode} callExpression A CallExpression node which is used to reject a Promise
  45. * @returns {void}
  46. */
  47. function checkRejectCall(callExpression) {
  48. if (!callExpression.arguments.length && allowEmptyReject) {
  49. return;
  50. }
  51. if (
  52. !callExpression.arguments.length ||
  53. !astUtils.couldBeError(callExpression.arguments[0]) ||
  54. callExpression.arguments[0].type === "Identifier" && callExpression.arguments[0].name === "undefined"
  55. ) {
  56. context.report({
  57. node: callExpression,
  58. messageId: "rejectAnError"
  59. });
  60. }
  61. }
  62. /**
  63. * Determines whether a function call is a Promise.reject() call
  64. * @param {ASTNode} node A CallExpression node
  65. * @returns {boolean} `true` if the call is a Promise.reject() call
  66. */
  67. function isPromiseRejectCall(node) {
  68. return astUtils.isSpecificMemberAccess(node.callee, "Promise", "reject");
  69. }
  70. //----------------------------------------------------------------------
  71. // Public
  72. //----------------------------------------------------------------------
  73. return {
  74. // Check `Promise.reject(value)` calls.
  75. CallExpression(node) {
  76. if (isPromiseRejectCall(node)) {
  77. checkRejectCall(node);
  78. }
  79. },
  80. /*
  81. * Check for `new Promise((resolve, reject) => {})`, and check for reject() calls.
  82. * This function is run on "NewExpression:exit" instead of "NewExpression" to ensure that
  83. * the nodes in the expression already have the `parent` property.
  84. */
  85. "NewExpression:exit"(node) {
  86. if (
  87. node.callee.type === "Identifier" && node.callee.name === "Promise" &&
  88. node.arguments.length && astUtils.isFunction(node.arguments[0]) &&
  89. node.arguments[0].params.length > 1 && node.arguments[0].params[1].type === "Identifier"
  90. ) {
  91. sourceCode.getDeclaredVariables(node.arguments[0])
  92. /*
  93. * Find the first variable that matches the second parameter's name.
  94. * If the first parameter has the same name as the second parameter, then the variable will actually
  95. * be "declared" when the first parameter is evaluated, but then it will be immediately overwritten
  96. * by the second parameter. It's not possible for an expression with the variable to be evaluated before
  97. * the variable is overwritten, because functions with duplicate parameters cannot have destructuring or
  98. * default assignments in their parameter lists. Therefore, it's not necessary to explicitly account for
  99. * this case.
  100. */
  101. .find(variable => variable.name === node.arguments[0].params[1].name)
  102. // Get the references to that variable.
  103. .references
  104. // Only check the references that read the parameter's value.
  105. .filter(ref => ref.isRead())
  106. // Only check the references that are used as the callee in a function call, e.g. `reject(foo)`.
  107. .filter(ref => ref.identifier.parent.type === "CallExpression" && ref.identifier === ref.identifier.parent.callee)
  108. // Check the argument of the function call to determine whether it's an Error.
  109. .forEach(ref => checkRejectCall(ref.identifier.parent));
  110. }
  111. }
  112. };
  113. }
  114. };