UNPKG

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