222 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
/**
 | 
						|
 * @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";
 | 
						|
}
 | 
						|
 | 
						|
/** @type {import('../types').Rule.RuleModule} */
 | 
						|
module.exports = {
 | 
						|
	meta: {
 | 
						|
		type: "problem",
 | 
						|
 | 
						|
		defaultOptions: [
 | 
						|
			{
 | 
						|
				disallowArithmeticOperators: false,
 | 
						|
			},
 | 
						|
		],
 | 
						|
 | 
						|
		docs: {
 | 
						|
			description:
 | 
						|
				"Disallow use of optional chaining in contexts where the `undefined` value is not allowed",
 | 
						|
			recommended: true,
 | 
						|
			url: "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining",
 | 
						|
		},
 | 
						|
		schema: [
 | 
						|
			{
 | 
						|
				type: "object",
 | 
						|
				properties: {
 | 
						|
					disallowArithmeticOperators: {
 | 
						|
						type: "boolean",
 | 
						|
					},
 | 
						|
				},
 | 
						|
				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 [{ disallowArithmeticOperators }] = context.options;
 | 
						|
 | 
						|
		/**
 | 
						|
		 * 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.at(-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);
 | 
						|
				}
 | 
						|
			},
 | 
						|
		};
 | 
						|
	},
 | 
						|
};
 |