no-unsafe-optional-chaining.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /**
  2. * @fileoverview Rule to disallow unsafe optional chaining
  3. * @author Yeon JuAn
  4. */
  5. "use strict";
  6. const UNSAFE_ARITHMETIC_OPERATORS = new Set(["+", "-", "/", "*", "%", "**"]);
  7. const UNSAFE_ASSIGNMENT_OPERATORS = new Set([
  8. "+=",
  9. "-=",
  10. "/=",
  11. "*=",
  12. "%=",
  13. "**=",
  14. ]);
  15. const UNSAFE_RELATIONAL_OPERATORS = new Set(["in", "instanceof"]);
  16. /**
  17. * Checks whether a node is a destructuring pattern or not
  18. * @param {ASTNode} node node to check
  19. * @returns {boolean} `true` if a node is a destructuring pattern, otherwise `false`
  20. */
  21. function isDestructuringPattern(node) {
  22. return node.type === "ObjectPattern" || node.type === "ArrayPattern";
  23. }
  24. /** @type {import('../types').Rule.RuleModule} */
  25. module.exports = {
  26. meta: {
  27. type: "problem",
  28. defaultOptions: [
  29. {
  30. disallowArithmeticOperators: false,
  31. },
  32. ],
  33. docs: {
  34. description:
  35. "Disallow use of optional chaining in contexts where the `undefined` value is not allowed",
  36. recommended: true,
  37. url: "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining",
  38. },
  39. schema: [
  40. {
  41. type: "object",
  42. properties: {
  43. disallowArithmeticOperators: {
  44. type: "boolean",
  45. },
  46. },
  47. additionalProperties: false,
  48. },
  49. ],
  50. fixable: null,
  51. messages: {
  52. unsafeOptionalChain:
  53. "Unsafe usage of optional chaining. If it short-circuits with 'undefined' the evaluation will throw TypeError.",
  54. unsafeArithmetic:
  55. "Unsafe arithmetic operation on optional chaining. It can result in NaN.",
  56. },
  57. },
  58. create(context) {
  59. const [{ disallowArithmeticOperators }] = context.options;
  60. /**
  61. * Reports unsafe usage of optional chaining
  62. * @param {ASTNode} node node to report
  63. * @returns {void}
  64. */
  65. function reportUnsafeUsage(node) {
  66. context.report({
  67. messageId: "unsafeOptionalChain",
  68. node,
  69. });
  70. }
  71. /**
  72. * Reports unsafe arithmetic operation on optional chaining
  73. * @param {ASTNode} node node to report
  74. * @returns {void}
  75. */
  76. function reportUnsafeArithmetic(node) {
  77. context.report({
  78. messageId: "unsafeArithmetic",
  79. node,
  80. });
  81. }
  82. /**
  83. * Checks and reports if a node can short-circuit with `undefined` by optional chaining.
  84. * @param {ASTNode} [node] node to check
  85. * @param {Function} reportFunc report function
  86. * @returns {void}
  87. */
  88. function checkUndefinedShortCircuit(node, reportFunc) {
  89. if (!node) {
  90. return;
  91. }
  92. switch (node.type) {
  93. case "LogicalExpression":
  94. if (node.operator === "||" || node.operator === "??") {
  95. checkUndefinedShortCircuit(node.right, reportFunc);
  96. } else if (node.operator === "&&") {
  97. checkUndefinedShortCircuit(node.left, reportFunc);
  98. checkUndefinedShortCircuit(node.right, reportFunc);
  99. }
  100. break;
  101. case "SequenceExpression":
  102. checkUndefinedShortCircuit(
  103. node.expressions.at(-1),
  104. reportFunc,
  105. );
  106. break;
  107. case "ConditionalExpression":
  108. checkUndefinedShortCircuit(node.consequent, reportFunc);
  109. checkUndefinedShortCircuit(node.alternate, reportFunc);
  110. break;
  111. case "AwaitExpression":
  112. checkUndefinedShortCircuit(node.argument, reportFunc);
  113. break;
  114. case "ChainExpression":
  115. reportFunc(node);
  116. break;
  117. default:
  118. break;
  119. }
  120. }
  121. /**
  122. * Checks unsafe usage of optional chaining
  123. * @param {ASTNode} node node to check
  124. * @returns {void}
  125. */
  126. function checkUnsafeUsage(node) {
  127. checkUndefinedShortCircuit(node, reportUnsafeUsage);
  128. }
  129. /**
  130. * Checks unsafe arithmetic operations on optional chaining
  131. * @param {ASTNode} node node to check
  132. * @returns {void}
  133. */
  134. function checkUnsafeArithmetic(node) {
  135. checkUndefinedShortCircuit(node, reportUnsafeArithmetic);
  136. }
  137. return {
  138. "AssignmentExpression, AssignmentPattern"(node) {
  139. if (isDestructuringPattern(node.left)) {
  140. checkUnsafeUsage(node.right);
  141. }
  142. },
  143. "ClassDeclaration, ClassExpression"(node) {
  144. checkUnsafeUsage(node.superClass);
  145. },
  146. CallExpression(node) {
  147. if (!node.optional) {
  148. checkUnsafeUsage(node.callee);
  149. }
  150. },
  151. NewExpression(node) {
  152. checkUnsafeUsage(node.callee);
  153. },
  154. VariableDeclarator(node) {
  155. if (isDestructuringPattern(node.id)) {
  156. checkUnsafeUsage(node.init);
  157. }
  158. },
  159. MemberExpression(node) {
  160. if (!node.optional) {
  161. checkUnsafeUsage(node.object);
  162. }
  163. },
  164. TaggedTemplateExpression(node) {
  165. checkUnsafeUsage(node.tag);
  166. },
  167. ForOfStatement(node) {
  168. checkUnsafeUsage(node.right);
  169. },
  170. SpreadElement(node) {
  171. if (node.parent && node.parent.type !== "ObjectExpression") {
  172. checkUnsafeUsage(node.argument);
  173. }
  174. },
  175. BinaryExpression(node) {
  176. if (UNSAFE_RELATIONAL_OPERATORS.has(node.operator)) {
  177. checkUnsafeUsage(node.right);
  178. }
  179. if (
  180. disallowArithmeticOperators &&
  181. UNSAFE_ARITHMETIC_OPERATORS.has(node.operator)
  182. ) {
  183. checkUnsafeArithmetic(node.right);
  184. checkUnsafeArithmetic(node.left);
  185. }
  186. },
  187. WithStatement(node) {
  188. checkUnsafeUsage(node.object);
  189. },
  190. UnaryExpression(node) {
  191. if (
  192. disallowArithmeticOperators &&
  193. UNSAFE_ARITHMETIC_OPERATORS.has(node.operator)
  194. ) {
  195. checkUnsafeArithmetic(node.argument);
  196. }
  197. },
  198. AssignmentExpression(node) {
  199. if (
  200. disallowArithmeticOperators &&
  201. UNSAFE_ASSIGNMENT_OPERATORS.has(node.operator)
  202. ) {
  203. checkUnsafeArithmetic(node.right);
  204. }
  205. },
  206. };
  207. },
  208. };