1 | /**
|
---|
2 | * @fileoverview Rule to replace assignment expressions with logical operator assignment
|
---|
3 | * @author Daniel Martens
|
---|
4 | */
|
---|
5 | "use strict";
|
---|
6 |
|
---|
7 | //------------------------------------------------------------------------------
|
---|
8 | // Requirements
|
---|
9 | //------------------------------------------------------------------------------
|
---|
10 | const astUtils = require("./utils/ast-utils.js");
|
---|
11 |
|
---|
12 | //------------------------------------------------------------------------------
|
---|
13 | // Helpers
|
---|
14 | //------------------------------------------------------------------------------
|
---|
15 |
|
---|
16 | const baseTypes = new Set(["Identifier", "Super", "ThisExpression"]);
|
---|
17 |
|
---|
18 | /**
|
---|
19 | * Returns true iff either "undefined" or a void expression (eg. "void 0")
|
---|
20 | * @param {ASTNode} expression Expression to check
|
---|
21 | * @param {import('eslint-scope').Scope} scope Scope of the expression
|
---|
22 | * @returns {boolean} True iff "undefined" or "void ..."
|
---|
23 | */
|
---|
24 | function isUndefined(expression, scope) {
|
---|
25 | if (expression.type === "Identifier" && expression.name === "undefined") {
|
---|
26 | return astUtils.isReferenceToGlobalVariable(scope, expression);
|
---|
27 | }
|
---|
28 |
|
---|
29 | return expression.type === "UnaryExpression" &&
|
---|
30 | expression.operator === "void" &&
|
---|
31 | expression.argument.type === "Literal" &&
|
---|
32 | expression.argument.value === 0;
|
---|
33 | }
|
---|
34 |
|
---|
35 | /**
|
---|
36 | * Returns true iff the reference is either an identifier or member expression
|
---|
37 | * @param {ASTNode} expression Expression to check
|
---|
38 | * @returns {boolean} True for identifiers and member expressions
|
---|
39 | */
|
---|
40 | function isReference(expression) {
|
---|
41 | return (expression.type === "Identifier" && expression.name !== "undefined") ||
|
---|
42 | expression.type === "MemberExpression";
|
---|
43 | }
|
---|
44 |
|
---|
45 | /**
|
---|
46 | * Returns true iff the expression checks for nullish with loose equals.
|
---|
47 | * Examples: value == null, value == void 0
|
---|
48 | * @param {ASTNode} expression Test condition
|
---|
49 | * @param {import('eslint-scope').Scope} scope Scope of the expression
|
---|
50 | * @returns {boolean} True iff implicit nullish comparison
|
---|
51 | */
|
---|
52 | function isImplicitNullishComparison(expression, scope) {
|
---|
53 | if (expression.type !== "BinaryExpression" || expression.operator !== "==") {
|
---|
54 | return false;
|
---|
55 | }
|
---|
56 |
|
---|
57 | const reference = isReference(expression.left) ? "left" : "right";
|
---|
58 | const nullish = reference === "left" ? "right" : "left";
|
---|
59 |
|
---|
60 | return isReference(expression[reference]) &&
|
---|
61 | (astUtils.isNullLiteral(expression[nullish]) || isUndefined(expression[nullish], scope));
|
---|
62 | }
|
---|
63 |
|
---|
64 | /**
|
---|
65 | * Condition with two equal comparisons.
|
---|
66 | * @param {ASTNode} expression Condition
|
---|
67 | * @returns {boolean} True iff matches ? === ? || ? === ?
|
---|
68 | */
|
---|
69 | function isDoubleComparison(expression) {
|
---|
70 | return expression.type === "LogicalExpression" &&
|
---|
71 | expression.operator === "||" &&
|
---|
72 | expression.left.type === "BinaryExpression" &&
|
---|
73 | expression.left.operator === "===" &&
|
---|
74 | expression.right.type === "BinaryExpression" &&
|
---|
75 | expression.right.operator === "===";
|
---|
76 | }
|
---|
77 |
|
---|
78 | /**
|
---|
79 | * Returns true iff the expression checks for undefined and null.
|
---|
80 | * Example: value === null || value === undefined
|
---|
81 | * @param {ASTNode} expression Test condition
|
---|
82 | * @param {import('eslint-scope').Scope} scope Scope of the expression
|
---|
83 | * @returns {boolean} True iff explicit nullish comparison
|
---|
84 | */
|
---|
85 | function isExplicitNullishComparison(expression, scope) {
|
---|
86 | if (!isDoubleComparison(expression)) {
|
---|
87 | return false;
|
---|
88 | }
|
---|
89 | const leftReference = isReference(expression.left.left) ? "left" : "right";
|
---|
90 | const leftNullish = leftReference === "left" ? "right" : "left";
|
---|
91 | const rightReference = isReference(expression.right.left) ? "left" : "right";
|
---|
92 | const rightNullish = rightReference === "left" ? "right" : "left";
|
---|
93 |
|
---|
94 | return astUtils.isSameReference(expression.left[leftReference], expression.right[rightReference]) &&
|
---|
95 | ((astUtils.isNullLiteral(expression.left[leftNullish]) && isUndefined(expression.right[rightNullish], scope)) ||
|
---|
96 | (isUndefined(expression.left[leftNullish], scope) && astUtils.isNullLiteral(expression.right[rightNullish])));
|
---|
97 | }
|
---|
98 |
|
---|
99 | /**
|
---|
100 | * Returns true for Boolean(arg) calls
|
---|
101 | * @param {ASTNode} expression Test condition
|
---|
102 | * @param {import('eslint-scope').Scope} scope Scope of the expression
|
---|
103 | * @returns {boolean} Whether the expression is a boolean cast
|
---|
104 | */
|
---|
105 | function isBooleanCast(expression, scope) {
|
---|
106 | return expression.type === "CallExpression" &&
|
---|
107 | expression.callee.name === "Boolean" &&
|
---|
108 | expression.arguments.length === 1 &&
|
---|
109 | astUtils.isReferenceToGlobalVariable(scope, expression.callee);
|
---|
110 | }
|
---|
111 |
|
---|
112 | /**
|
---|
113 | * Returns true for:
|
---|
114 | * truthiness checks: value, Boolean(value), !!value
|
---|
115 | * falsiness checks: !value, !Boolean(value)
|
---|
116 | * nullish checks: value == null, value === undefined || value === null
|
---|
117 | * @param {ASTNode} expression Test condition
|
---|
118 | * @param {import('eslint-scope').Scope} scope Scope of the expression
|
---|
119 | * @returns {?{ reference: ASTNode, operator: '??'|'||'|'&&'}} Null if not a known existence
|
---|
120 | */
|
---|
121 | function getExistence(expression, scope) {
|
---|
122 | const isNegated = expression.type === "UnaryExpression" && expression.operator === "!";
|
---|
123 | const base = isNegated ? expression.argument : expression;
|
---|
124 |
|
---|
125 | switch (true) {
|
---|
126 | case isReference(base):
|
---|
127 | return { reference: base, operator: isNegated ? "||" : "&&" };
|
---|
128 | case base.type === "UnaryExpression" && base.operator === "!" && isReference(base.argument):
|
---|
129 | return { reference: base.argument, operator: "&&" };
|
---|
130 | case isBooleanCast(base, scope) && isReference(base.arguments[0]):
|
---|
131 | return { reference: base.arguments[0], operator: isNegated ? "||" : "&&" };
|
---|
132 | case isImplicitNullishComparison(expression, scope):
|
---|
133 | return { reference: isReference(expression.left) ? expression.left : expression.right, operator: "??" };
|
---|
134 | case isExplicitNullishComparison(expression, scope):
|
---|
135 | return { reference: isReference(expression.left.left) ? expression.left.left : expression.left.right, operator: "??" };
|
---|
136 | default: return null;
|
---|
137 | }
|
---|
138 | }
|
---|
139 |
|
---|
140 | /**
|
---|
141 | * Returns true iff the node is inside a with block
|
---|
142 | * @param {ASTNode} node Node to check
|
---|
143 | * @returns {boolean} True iff passed node is inside a with block
|
---|
144 | */
|
---|
145 | function isInsideWithBlock(node) {
|
---|
146 | if (node.type === "Program") {
|
---|
147 | return false;
|
---|
148 | }
|
---|
149 |
|
---|
150 | return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent);
|
---|
151 | }
|
---|
152 |
|
---|
153 | /**
|
---|
154 | * Gets the leftmost operand of a consecutive logical expression.
|
---|
155 | * @param {SourceCode} sourceCode The ESLint source code object
|
---|
156 | * @param {LogicalExpression} node LogicalExpression
|
---|
157 | * @returns {Expression} Leftmost operand
|
---|
158 | */
|
---|
159 | function getLeftmostOperand(sourceCode, node) {
|
---|
160 | let left = node.left;
|
---|
161 |
|
---|
162 | while (left.type === "LogicalExpression" && left.operator === node.operator) {
|
---|
163 |
|
---|
164 | if (astUtils.isParenthesised(sourceCode, left)) {
|
---|
165 |
|
---|
166 | /*
|
---|
167 | * It should have associativity,
|
---|
168 | * but ignore it if use parentheses to make the evaluation order clear.
|
---|
169 | */
|
---|
170 | return left;
|
---|
171 | }
|
---|
172 | left = left.left;
|
---|
173 | }
|
---|
174 | return left;
|
---|
175 |
|
---|
176 | }
|
---|
177 |
|
---|
178 | //------------------------------------------------------------------------------
|
---|
179 | // Rule Definition
|
---|
180 | //------------------------------------------------------------------------------
|
---|
181 | /** @type {import('../shared/types').Rule} */
|
---|
182 | module.exports = {
|
---|
183 | meta: {
|
---|
184 | type: "suggestion",
|
---|
185 |
|
---|
186 | docs: {
|
---|
187 | description: "Require or disallow logical assignment operator shorthand",
|
---|
188 | recommended: false,
|
---|
189 | url: "https://eslint.org/docs/latest/rules/logical-assignment-operators"
|
---|
190 | },
|
---|
191 |
|
---|
192 | schema: {
|
---|
193 | type: "array",
|
---|
194 | oneOf: [{
|
---|
195 | items: [
|
---|
196 | { const: "always" },
|
---|
197 | {
|
---|
198 | type: "object",
|
---|
199 | properties: {
|
---|
200 | enforceForIfStatements: {
|
---|
201 | type: "boolean"
|
---|
202 | }
|
---|
203 | },
|
---|
204 | additionalProperties: false
|
---|
205 | }
|
---|
206 | ],
|
---|
207 | minItems: 0, // 0 for allowing passing no options
|
---|
208 | maxItems: 2
|
---|
209 | }, {
|
---|
210 | items: [{ const: "never" }],
|
---|
211 | minItems: 1,
|
---|
212 | maxItems: 1
|
---|
213 | }]
|
---|
214 | },
|
---|
215 | fixable: "code",
|
---|
216 | hasSuggestions: true,
|
---|
217 | messages: {
|
---|
218 | assignment: "Assignment (=) can be replaced with operator assignment ({{operator}}).",
|
---|
219 | useLogicalOperator: "Convert this assignment to use the operator {{ operator }}.",
|
---|
220 | logical: "Logical expression can be replaced with an assignment ({{ operator }}).",
|
---|
221 | convertLogical: "Replace this logical expression with an assignment with the operator {{ operator }}.",
|
---|
222 | if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.",
|
---|
223 | convertIf: "Replace this 'if' statement with a logical assignment with operator {{ operator }}.",
|
---|
224 | unexpected: "Unexpected logical operator assignment ({{operator}}) shorthand.",
|
---|
225 | separate: "Separate the logical assignment into an assignment with a logical operator."
|
---|
226 | }
|
---|
227 | },
|
---|
228 |
|
---|
229 | create(context) {
|
---|
230 | const mode = context.options[0] === "never" ? "never" : "always";
|
---|
231 | const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements;
|
---|
232 | const sourceCode = context.sourceCode;
|
---|
233 | const isStrict = sourceCode.getScope(sourceCode.ast).isStrict;
|
---|
234 |
|
---|
235 | /**
|
---|
236 | * Returns false if the access could be a getter
|
---|
237 | * @param {ASTNode} node Assignment expression
|
---|
238 | * @returns {boolean} True iff the fix is safe
|
---|
239 | */
|
---|
240 | function cannotBeGetter(node) {
|
---|
241 | return node.type === "Identifier" &&
|
---|
242 | (isStrict || !isInsideWithBlock(node));
|
---|
243 | }
|
---|
244 |
|
---|
245 | /**
|
---|
246 | * Check whether only a single property is accessed
|
---|
247 | * @param {ASTNode} node reference
|
---|
248 | * @returns {boolean} True iff a single property is accessed
|
---|
249 | */
|
---|
250 | function accessesSingleProperty(node) {
|
---|
251 | if (!isStrict && isInsideWithBlock(node)) {
|
---|
252 | return node.type === "Identifier";
|
---|
253 | }
|
---|
254 |
|
---|
255 | return node.type === "MemberExpression" &&
|
---|
256 | baseTypes.has(node.object.type) &&
|
---|
257 | (!node.computed || (node.property.type !== "MemberExpression" && node.property.type !== "ChainExpression"));
|
---|
258 | }
|
---|
259 |
|
---|
260 | /**
|
---|
261 | * Adds a fixer or suggestion whether on the fix is safe.
|
---|
262 | * @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest
|
---|
263 | * @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion
|
---|
264 | * @param {boolean} shouldBeFixed Fix iff the condition is true
|
---|
265 | * @returns {Object} Descriptor with either an added fix or suggestion
|
---|
266 | */
|
---|
267 | function createConditionalFixer(descriptor, suggestion, shouldBeFixed) {
|
---|
268 | if (shouldBeFixed) {
|
---|
269 | return {
|
---|
270 | ...descriptor,
|
---|
271 | fix: suggestion.fix
|
---|
272 | };
|
---|
273 | }
|
---|
274 |
|
---|
275 | return {
|
---|
276 | ...descriptor,
|
---|
277 | suggest: [suggestion]
|
---|
278 | };
|
---|
279 | }
|
---|
280 |
|
---|
281 |
|
---|
282 | /**
|
---|
283 | * Returns the operator token for assignments and binary expressions
|
---|
284 | * @param {ASTNode} node AssignmentExpression or BinaryExpression
|
---|
285 | * @returns {import('eslint').AST.Token} Operator token between the left and right expression
|
---|
286 | */
|
---|
287 | function getOperatorToken(node) {
|
---|
288 | return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
|
---|
289 | }
|
---|
290 |
|
---|
291 | if (mode === "never") {
|
---|
292 | return {
|
---|
293 |
|
---|
294 | // foo ||= bar
|
---|
295 | "AssignmentExpression"(assignment) {
|
---|
296 | if (!astUtils.isLogicalAssignmentOperator(assignment.operator)) {
|
---|
297 | return;
|
---|
298 | }
|
---|
299 |
|
---|
300 | const descriptor = {
|
---|
301 | messageId: "unexpected",
|
---|
302 | node: assignment,
|
---|
303 | data: { operator: assignment.operator }
|
---|
304 | };
|
---|
305 | const suggestion = {
|
---|
306 | messageId: "separate",
|
---|
307 | *fix(ruleFixer) {
|
---|
308 | if (sourceCode.getCommentsInside(assignment).length > 0) {
|
---|
309 | return;
|
---|
310 | }
|
---|
311 |
|
---|
312 | const operatorToken = getOperatorToken(assignment);
|
---|
313 |
|
---|
314 | // -> foo = bar
|
---|
315 | yield ruleFixer.replaceText(operatorToken, "=");
|
---|
316 |
|
---|
317 | const assignmentText = sourceCode.getText(assignment.left);
|
---|
318 | const operator = assignment.operator.slice(0, -1);
|
---|
319 |
|
---|
320 | // -> foo = foo || bar
|
---|
321 | yield ruleFixer.insertTextAfter(operatorToken, ` ${assignmentText} ${operator}`);
|
---|
322 |
|
---|
323 | const precedence = astUtils.getPrecedence(assignment.right) <= astUtils.getPrecedence({ type: "LogicalExpression", operator });
|
---|
324 |
|
---|
325 | // ?? and || / && cannot be mixed but have same precedence
|
---|
326 | const mixed = assignment.operator === "??=" && astUtils.isLogicalExpression(assignment.right);
|
---|
327 |
|
---|
328 | if (!astUtils.isParenthesised(sourceCode, assignment.right) && (precedence || mixed)) {
|
---|
329 |
|
---|
330 | // -> foo = foo || (bar)
|
---|
331 | yield ruleFixer.insertTextBefore(assignment.right, "(");
|
---|
332 | yield ruleFixer.insertTextAfter(assignment.right, ")");
|
---|
333 | }
|
---|
334 | }
|
---|
335 | };
|
---|
336 |
|
---|
337 | context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
|
---|
338 | }
|
---|
339 | };
|
---|
340 | }
|
---|
341 |
|
---|
342 | return {
|
---|
343 |
|
---|
344 | // foo = foo || bar
|
---|
345 | "AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) {
|
---|
346 | const leftOperand = getLeftmostOperand(sourceCode, assignment.right);
|
---|
347 |
|
---|
348 | if (!astUtils.isSameReference(assignment.left, leftOperand)
|
---|
349 | ) {
|
---|
350 | return;
|
---|
351 | }
|
---|
352 |
|
---|
353 | const descriptor = {
|
---|
354 | messageId: "assignment",
|
---|
355 | node: assignment,
|
---|
356 | data: { operator: `${assignment.right.operator}=` }
|
---|
357 | };
|
---|
358 | const suggestion = {
|
---|
359 | messageId: "useLogicalOperator",
|
---|
360 | data: { operator: `${assignment.right.operator}=` },
|
---|
361 | *fix(ruleFixer) {
|
---|
362 | if (sourceCode.getCommentsInside(assignment).length > 0) {
|
---|
363 | return;
|
---|
364 | }
|
---|
365 |
|
---|
366 | // No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator
|
---|
367 | const assignmentOperatorToken = getOperatorToken(assignment);
|
---|
368 |
|
---|
369 | // -> foo ||= foo || bar
|
---|
370 | yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator);
|
---|
371 |
|
---|
372 | // -> foo ||= bar
|
---|
373 | const logicalOperatorToken = getOperatorToken(leftOperand.parent);
|
---|
374 | const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken);
|
---|
375 |
|
---|
376 | yield ruleFixer.removeRange([leftOperand.parent.range[0], firstRightOperandToken.range[0]]);
|
---|
377 | }
|
---|
378 | };
|
---|
379 |
|
---|
380 | context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
|
---|
381 | },
|
---|
382 |
|
---|
383 | // foo || (foo = bar)
|
---|
384 | 'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'(logical) {
|
---|
385 |
|
---|
386 | // Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal
|
---|
387 | if (isReference(logical.left) && astUtils.isSameReference(logical.left, logical.right.left)) {
|
---|
388 | const descriptor = {
|
---|
389 | messageId: "logical",
|
---|
390 | node: logical,
|
---|
391 | data: { operator: `${logical.operator}=` }
|
---|
392 | };
|
---|
393 | const suggestion = {
|
---|
394 | messageId: "convertLogical",
|
---|
395 | data: { operator: `${logical.operator}=` },
|
---|
396 | *fix(ruleFixer) {
|
---|
397 | if (sourceCode.getCommentsInside(logical).length > 0) {
|
---|
398 | return;
|
---|
399 | }
|
---|
400 |
|
---|
401 | const parentPrecedence = astUtils.getPrecedence(logical.parent);
|
---|
402 | const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && (
|
---|
403 | parentPrecedence === -1 ||
|
---|
404 | astUtils.getPrecedence({ type: "AssignmentExpression" }) < parentPrecedence
|
---|
405 | );
|
---|
406 |
|
---|
407 | if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) {
|
---|
408 | yield ruleFixer.insertTextBefore(logical, "(");
|
---|
409 | yield ruleFixer.insertTextAfter(logical, ")");
|
---|
410 | }
|
---|
411 |
|
---|
412 | // Also removes all opening parenthesis
|
---|
413 | yield ruleFixer.removeRange([logical.range[0], logical.right.range[0]]); // -> foo = bar)
|
---|
414 |
|
---|
415 | // Also removes all ending parenthesis
|
---|
416 | yield ruleFixer.removeRange([logical.right.range[1], logical.range[1]]); // -> foo = bar
|
---|
417 |
|
---|
418 | const operatorToken = getOperatorToken(logical.right);
|
---|
419 |
|
---|
420 | yield ruleFixer.insertTextBefore(operatorToken, logical.operator); // -> foo ||= bar
|
---|
421 | }
|
---|
422 | };
|
---|
423 | const fix = cannotBeGetter(logical.left) || accessesSingleProperty(logical.left);
|
---|
424 |
|
---|
425 | context.report(createConditionalFixer(descriptor, suggestion, fix));
|
---|
426 | }
|
---|
427 | },
|
---|
428 |
|
---|
429 | // if (foo) foo = bar
|
---|
430 | "IfStatement[alternate=null]"(ifNode) {
|
---|
431 | if (!checkIf) {
|
---|
432 | return;
|
---|
433 | }
|
---|
434 |
|
---|
435 | const hasBody = ifNode.consequent.type === "BlockStatement";
|
---|
436 |
|
---|
437 | if (hasBody && ifNode.consequent.body.length !== 1) {
|
---|
438 | return;
|
---|
439 | }
|
---|
440 |
|
---|
441 | const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent;
|
---|
442 | const scope = sourceCode.getScope(ifNode);
|
---|
443 | const existence = getExistence(ifNode.test, scope);
|
---|
444 |
|
---|
445 | if (
|
---|
446 | body.type === "ExpressionStatement" &&
|
---|
447 | body.expression.type === "AssignmentExpression" &&
|
---|
448 | body.expression.operator === "=" &&
|
---|
449 | existence !== null &&
|
---|
450 | astUtils.isSameReference(existence.reference, body.expression.left)
|
---|
451 | ) {
|
---|
452 | const descriptor = {
|
---|
453 | messageId: "if",
|
---|
454 | node: ifNode,
|
---|
455 | data: { operator: `${existence.operator}=` }
|
---|
456 | };
|
---|
457 | const suggestion = {
|
---|
458 | messageId: "convertIf",
|
---|
459 | data: { operator: `${existence.operator}=` },
|
---|
460 | *fix(ruleFixer) {
|
---|
461 | if (sourceCode.getCommentsInside(ifNode).length > 0) {
|
---|
462 | return;
|
---|
463 | }
|
---|
464 |
|
---|
465 | const firstBodyToken = sourceCode.getFirstToken(body);
|
---|
466 | const prevToken = sourceCode.getTokenBefore(ifNode);
|
---|
467 |
|
---|
468 | if (
|
---|
469 | prevToken !== null &&
|
---|
470 | prevToken.value !== ";" &&
|
---|
471 | prevToken.value !== "{" &&
|
---|
472 | firstBodyToken.type !== "Identifier" &&
|
---|
473 | firstBodyToken.type !== "Keyword"
|
---|
474 | ) {
|
---|
475 |
|
---|
476 | // Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b)
|
---|
477 | return;
|
---|
478 | }
|
---|
479 |
|
---|
480 |
|
---|
481 | const operatorToken = getOperatorToken(body.expression);
|
---|
482 |
|
---|
483 | yield ruleFixer.insertTextBefore(operatorToken, existence.operator); // -> if (foo) foo ||= bar
|
---|
484 |
|
---|
485 | yield ruleFixer.removeRange([ifNode.range[0], body.range[0]]); // -> foo ||= bar
|
---|
486 |
|
---|
487 | yield ruleFixer.removeRange([body.range[1], ifNode.range[1]]); // -> foo ||= bar, only present if "if" had a body
|
---|
488 |
|
---|
489 | const nextToken = sourceCode.getTokenAfter(body.expression);
|
---|
490 |
|
---|
491 | if (hasBody && (nextToken !== null && nextToken.value !== ";")) {
|
---|
492 | yield ruleFixer.insertTextAfter(ifNode, ";");
|
---|
493 | }
|
---|
494 | }
|
---|
495 | };
|
---|
496 | const shouldBeFixed = cannotBeGetter(existence.reference) ||
|
---|
497 | (ifNode.test.type !== "LogicalExpression" && accessesSingleProperty(existence.reference));
|
---|
498 |
|
---|
499 | context.report(createConditionalFixer(descriptor, suggestion, shouldBeFixed));
|
---|
500 | }
|
---|
501 | }
|
---|
502 | };
|
---|
503 | }
|
---|
504 | };
|
---|