no-unsafe-optional-chaining.js 7.0 KB

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