no-unsafe-optional-chaining.js 6.9 KB

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