Skip to content

Commit 8a132ac

Browse files
authored
prefer-single-call: Check optional chaining (#2788)
1 parent 9aca7b9 commit 8a132ac

File tree

4 files changed

+340
-17
lines changed

4 files changed

+340
-17
lines changed

rules/prefer-single-call.js

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const messages = {
1414

1515
const isClassList = node => isMemberExpression(node, {
1616
property: 'classList',
17-
optional: false,
1817
computed: false,
1918
});
2019

@@ -24,7 +23,6 @@ const cases = [
2423
test: callExpression => isMethodCall(callExpression, {
2524
method: 'push',
2625
optionalCall: false,
27-
optionalMember: false,
2826
}),
2927
ignore: [
3028
'stream.push',
@@ -57,10 +55,7 @@ const cases = [
5755
},
5856
{
5957
description: 'importScripts()',
60-
test: callExpression => isCallExpression(callExpression, {
61-
name: 'importScripts',
62-
optional: false,
63-
}),
58+
test: callExpression => isCallExpression(callExpression, {name: 'importScripts'}),
6459
},
6560
];
6661

@@ -76,7 +71,7 @@ function create(context) {
7671
return {
7772
* CallExpression(secondCall) {
7873
for (const {description, test, ignore = []} of cases) {
79-
if (!test(secondCall) || secondCall.parent.type !== 'ExpressionStatement') {
74+
if (!test(secondCall)) {
8075
continue;
8176
}
8277

@@ -85,12 +80,26 @@ function create(context) {
8580
continue;
8681
}
8782

88-
const previousNode = getPreviousNode(secondCall.parent, context);
89-
if (previousNode?.type !== 'ExpressionStatement') {
83+
let secondExpressionStatement = secondCall.parent;
84+
if (secondExpressionStatement.type === 'ChainExpression' && secondCall === secondExpressionStatement.expression) {
85+
secondExpressionStatement = secondExpressionStatement.parent;
86+
}
87+
88+
if (secondExpressionStatement.type !== 'ExpressionStatement') {
9089
continue;
9190
}
9291

93-
const firstCall = previousNode.expression;
92+
const firstExpressionStatement = getPreviousNode(secondExpressionStatement, context);
93+
if (firstExpressionStatement?.type !== 'ExpressionStatement') {
94+
continue;
95+
}
96+
97+
let firstCall = firstExpressionStatement.expression;
98+
99+
if (firstCall.type === 'ChainExpression') {
100+
firstCall = firstCall.expression;
101+
}
102+
94103
if (!test(firstCall) || !isSameReference(firstCall.callee, secondCall.callee)) {
95104
continue;
96105
}
@@ -118,12 +127,10 @@ function create(context) {
118127
);
119128
}
120129

121-
const firstExpression = firstCall.parent;
122-
const secondExpression = secondCall.parent;
123-
const shouldKeepSemicolon = !isSemicolonToken(sourceCode.getLastToken(firstExpression))
124-
&& isSemicolonToken(sourceCode.getLastToken(secondExpression));
125-
const [, start] = sourceCode.getRange(firstExpression);
126-
const [, end] = sourceCode.getRange(secondExpression);
130+
const shouldKeepSemicolon = !isSemicolonToken(sourceCode.getLastToken(firstExpressionStatement))
131+
&& isSemicolonToken(sourceCode.getLastToken(secondExpressionStatement));
132+
const [, start] = sourceCode.getRange(firstExpressionStatement);
133+
const [, end] = sourceCode.getRange(secondExpressionStatement);
127134

128135
yield fixer.replaceTextRange([start, end], shouldKeepSemicolon ? ';' : '');
129136
};

test/prefer-single-call.js

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ test.snapshot({
9090
function bar() {}
9191
foo.push(bindEvents);
9292
`,
93+
outdent`
94+
foo.push?.(1);
95+
foo.push?.(2);
96+
`,
97+
// Does not allow optional call
98+
outdent`
99+
foo.push(1);
100+
foo.push?.(2);
101+
`,
102+
outdent`
103+
foo.push?.(1);
104+
foo.push(2);
105+
`,
93106
],
94107
invalid: [
95108
outdent`
@@ -208,6 +221,22 @@ test.snapshot({
208221
},
209222
],
210223
},
224+
outdent`
225+
foo.push(1);
226+
foo?.push(2);
227+
`,
228+
outdent`
229+
foo?.push(1);
230+
foo.push(2);
231+
`,
232+
outdent`
233+
foo?.push(1);
234+
foo?.push(2);
235+
`,
236+
outdent`
237+
foo?.bar.push(1);
238+
foo?.bar.push(2);
239+
`,
211240
],
212241
});
213242

@@ -255,7 +284,7 @@ test.snapshot({
255284
`,
256285
outdent`
257286
foo.classList.add("foo");
258-
foo?.classList.add("bar");
287+
foo.classList.add?.("bar");
259288
`,
260289
outdent`
261290
foo.notClassList.add("foo");
@@ -283,6 +312,39 @@ test.snapshot({
283312
foo().bar.classList.add("foo");
284313
foo().bar.classList.add("bar");
285314
`,
315+
// Optional
316+
outdent`
317+
foo.classList?.add("foo");
318+
foo.classList.add("bar");
319+
`,
320+
outdent`
321+
foo.classList.add("foo");
322+
foo.classList?.add("bar");
323+
`,
324+
outdent`
325+
foo.classList.add?.("foo");
326+
foo.classList.add("bar");
327+
`,
328+
outdent`
329+
foo.classList.add("foo");
330+
foo.classList.add?.("bar");
331+
`,
332+
outdent`
333+
foo.classList?.remove("foo");
334+
foo.classList.remove("bar");
335+
`,
336+
outdent`
337+
foo.classList.remove("foo");
338+
foo.classList?.remove("bar");
339+
`,
340+
outdent`
341+
foo.classList.remove?.("foo");
342+
foo.classList.remove("bar");
343+
`,
344+
outdent`
345+
foo.classList.remove("foo");
346+
foo.classList.remove?.("bar");
347+
`,
286348
],
287349
invalid: [
288350
outdent`
@@ -383,6 +445,18 @@ test.snapshot({
383445
foo.bar.classList.add(a);
384446
(foo)['bar'].classList.add(b);
385447
`,
448+
outdent`
449+
foo?.classList.add("foo");
450+
foo.classList.add("bar");
451+
`,
452+
outdent`
453+
foo.classList.add("foo");
454+
foo?.classList.add("bar");
455+
`,
456+
outdent`
457+
foo?.classList.add("foo");
458+
foo?.classList.add("bar");
459+
`,
386460
],
387461
});
388462

@@ -504,6 +578,18 @@ test.snapshot({
504578
importScripts(b)
505579
;[foo].forEach(bar)
506580
`,
581+
outdent`
582+
importScripts?.("foo.js");
583+
importScripts("bar.js");
584+
`,
585+
outdent`
586+
importScripts("foo.js");
587+
importScripts?.("bar.js");
588+
`,
589+
outdent`
590+
importScripts?.("foo.js");
591+
importScripts?.("bar.js");
592+
`,
507593
],
508594
});
509595

0 commit comments

Comments
 (0)