|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- /**
- * @fileoverview Rule to disallow unsafe optional chaining
- * @author Yeon JuAn
- */
-
- "use strict";
-
- const UNSAFE_ARITHMETIC_OPERATORS = new Set(["+", "-", "/", "*", "%", "**"]);
- const UNSAFE_ASSIGNMENT_OPERATORS = new Set(["+=", "-=", "/=", "*=", "%=", "**="]);
- const UNSAFE_RELATIONAL_OPERATORS = new Set(["in", "instanceof"]);
-
- /**
- * Checks whether a node is a destructuring pattern or not
- * @param {ASTNode} node node to check
- * @returns {boolean} `true` if a node is a destructuring pattern, otherwise `false`
- */
- function isDestructuringPattern(node) {
- return node.type === "ObjectPattern" || node.type === "ArrayPattern";
- }
-
- module.exports = {
- meta: {
- type: "problem",
-
- docs: {
- description: "disallow use of optional chaining in contexts where the `undefined` value is not allowed",
- category: "Possible Errors",
- recommended: false,
- url: "https://eslint.org/docs/rules/no-unsafe-optional-chaining"
- },
- schema: [{
- type: "object",
- properties: {
- disallowArithmeticOperators: {
- type: "boolean",
- default: false
- }
- },
- additionalProperties: false
- }],
- fixable: null,
- messages: {
- unsafeOptionalChain: "Unsafe usage of optional chaining. If it short-circuits with 'undefined' the evaluation will throw TypeError.",
- unsafeArithmetic: "Unsafe arithmetic operation on optional chaining. It can result in NaN."
- }
- },
-
- create(context) {
- const options = context.options[0] || {};
- const disallowArithmeticOperators = (options.disallowArithmeticOperators) || false;
-
- /**
- * Reports unsafe usage of optional chaining
- * @param {ASTNode} node node to report
- * @returns {void}
- */
- function reportUnsafeUsage(node) {
- context.report({
- messageId: "unsafeOptionalChain",
- node
- });
- }
-
- /**
- * Reports unsafe arithmetic operation on optional chaining
- * @param {ASTNode} node node to report
- * @returns {void}
- */
- function reportUnsafeArithmetic(node) {
- context.report({
- messageId: "unsafeArithmetic",
- node
- });
- }
-
- /**
- * Checks and reports if a node can short-circuit with `undefined` by optional chaining.
- * @param {ASTNode} [node] node to check
- * @param {Function} reportFunc report function
- * @returns {void}
- */
- function checkUndefinedShortCircuit(node, reportFunc) {
- if (!node) {
- return;
- }
- switch (node.type) {
- case "LogicalExpression":
- if (node.operator === "||" || node.operator === "??") {
- checkUndefinedShortCircuit(node.right, reportFunc);
- } else if (node.operator === "&&") {
- checkUndefinedShortCircuit(node.left, reportFunc);
- checkUndefinedShortCircuit(node.right, reportFunc);
- }
- break;
- case "SequenceExpression":
- checkUndefinedShortCircuit(
- node.expressions[node.expressions.length - 1],
- reportFunc
- );
- break;
- case "ConditionalExpression":
- checkUndefinedShortCircuit(node.consequent, reportFunc);
- checkUndefinedShortCircuit(node.alternate, reportFunc);
- break;
- case "AwaitExpression":
- checkUndefinedShortCircuit(node.argument, reportFunc);
- break;
- case "ChainExpression":
- reportFunc(node);
- break;
- default:
- break;
- }
- }
-
- /**
- * Checks unsafe usage of optional chaining
- * @param {ASTNode} node node to check
- * @returns {void}
- */
- function checkUnsafeUsage(node) {
- checkUndefinedShortCircuit(node, reportUnsafeUsage);
- }
-
- /**
- * Checks unsafe arithmetic operations on optional chaining
- * @param {ASTNode} node node to check
- * @returns {void}
- */
- function checkUnsafeArithmetic(node) {
- checkUndefinedShortCircuit(node, reportUnsafeArithmetic);
- }
-
- return {
- "AssignmentExpression, AssignmentPattern"(node) {
- if (isDestructuringPattern(node.left)) {
- checkUnsafeUsage(node.right);
- }
- },
- "ClassDeclaration, ClassExpression"(node) {
- checkUnsafeUsage(node.superClass);
- },
- CallExpression(node) {
- if (!node.optional) {
- checkUnsafeUsage(node.callee);
- }
- },
- NewExpression(node) {
- checkUnsafeUsage(node.callee);
- },
- VariableDeclarator(node) {
- if (isDestructuringPattern(node.id)) {
- checkUnsafeUsage(node.init);
- }
- },
- MemberExpression(node) {
- if (!node.optional) {
- checkUnsafeUsage(node.object);
- }
- },
- TaggedTemplateExpression(node) {
- checkUnsafeUsage(node.tag);
- },
- ForOfStatement(node) {
- checkUnsafeUsage(node.right);
- },
- SpreadElement(node) {
- if (node.parent && node.parent.type !== "ObjectExpression") {
- checkUnsafeUsage(node.argument);
- }
- },
- BinaryExpression(node) {
- if (UNSAFE_RELATIONAL_OPERATORS.has(node.operator)) {
- checkUnsafeUsage(node.right);
- }
- if (
- disallowArithmeticOperators &&
- UNSAFE_ARITHMETIC_OPERATORS.has(node.operator)
- ) {
- checkUnsafeArithmetic(node.right);
- checkUnsafeArithmetic(node.left);
- }
- },
- WithStatement(node) {
- checkUnsafeUsage(node.object);
- },
- UnaryExpression(node) {
- if (
- disallowArithmeticOperators &&
- UNSAFE_ARITHMETIC_OPERATORS.has(node.operator)
- ) {
- checkUnsafeArithmetic(node.argument);
- }
- },
- AssignmentExpression(node) {
- if (
- disallowArithmeticOperators &&
- UNSAFE_ASSIGNMENT_OPERATORS.has(node.operator)
- ) {
- checkUnsafeArithmetic(node.right);
- }
- }
- };
- }
- };
|