Skip to content

Commit d1f637e

Browse files
authored
fix: parenthesize sequence expression operands in no-implicit-coercion (#21045)
* fix: parenthesize sequence expression operands in no-implicit-coercion * test: cover each coercion form with a sequence-expression operand Add cases for the unary +, -(-x), * 1, string concatenation and template shorthand recommendations so every getOperandText call site is exercised with a SequenceExpression operand.
1 parent 026e130 commit d1f637e

2 files changed

Lines changed: 136 additions & 8 deletions

File tree

lib/rules/no-implicit-coercion.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,21 @@ module.exports = {
244244
const [options] = context.options;
245245
const sourceCode = context.sourceCode;
246246

247+
/**
248+
* Gets the source text of a node to be used as the argument of a
249+
* `Boolean()`, `Number()`, or `String()` call in a recommendation. A
250+
* `SequenceExpression` operand must be parenthesized, otherwise its commas
251+
* would be parsed as argument separators, which changes the evaluated
252+
* operand (for example `!!(a, b)` becomes `Boolean(a, b)`).
253+
* @param {ASTNode} node The operand node.
254+
* @returns {string} The source text, parenthesized if needed.
255+
*/
256+
function getOperandText(node) {
257+
const text = sourceCode.getText(node);
258+
259+
return node.type === "SequenceExpression" ? `(${text})` : text;
260+
}
261+
247262
/**
248263
* Reports an error and autofixes the node
249264
* @param {ASTNode} node An ast node to report the error on.
@@ -309,7 +324,7 @@ module.exports = {
309324
options.boolean &&
310325
isDoubleLogicalNegating(node)
311326
) {
312-
const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`;
327+
const recommendation = `Boolean(${getOperandText(node.argument.argument)})`;
313328
const variable = astUtils.getVariableByName(
314329
sourceCode.getScope(node),
315330
"Boolean",
@@ -344,7 +359,7 @@ module.exports = {
344359
node.operator === "+" &&
345360
!isNumeric(node.argument)
346361
) {
347-
const recommendation = `Number(${sourceCode.getText(node.argument)})`;
362+
const recommendation = `Number(${getOperandText(node.argument)})`;
348363

349364
report(node, recommendation, true, false);
350365
}
@@ -359,7 +374,7 @@ module.exports = {
359374
node.argument.operator === "-" &&
360375
!isNumeric(node.argument.argument)
361376
) {
362-
const recommendation = `Number(${sourceCode.getText(node.argument.argument)})`;
377+
const recommendation = `Number(${getOperandText(node.argument.argument)})`;
363378

364379
report(node, recommendation, true, false);
365380
}
@@ -379,7 +394,7 @@ module.exports = {
379394
getNonNumericOperand(node);
380395

381396
if (nonNumericOperand) {
382-
const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`;
397+
const recommendation = `Number(${getOperandText(nonNumericOperand)})`;
383398

384399
report(node, recommendation, true, false);
385400
}
@@ -394,7 +409,7 @@ module.exports = {
394409
node.right.value === 0 &&
395410
!isNumeric(node.left)
396411
) {
397-
const recommendation = `Number(${sourceCode.getText(node.left)})`;
412+
const recommendation = `Number(${getOperandText(node.left)})`;
398413

399414
report(node, recommendation, true, false);
400415
}
@@ -406,7 +421,7 @@ module.exports = {
406421
options.string &&
407422
isConcatWithEmptyString(node)
408423
) {
409-
const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`;
424+
const recommendation = `String(${getOperandText(getNonEmptyOperand(node))})`;
410425

411426
report(node, recommendation, true, false);
412427
}
@@ -458,8 +473,7 @@ module.exports = {
458473
return;
459474
}
460475

461-
const code = sourceCode.getText(node.expressions[0]);
462-
const recommendation = `String(${code})`;
476+
const recommendation = `String(${getOperandText(node.expressions[0])})`;
463477

464478
report(node, recommendation, true, false);
465479
},

tests/lib/rules/no-implicit-coercion.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,120 @@ ruleTester.run("no-implicit-coercion", rule, {
174174
},
175175
],
176176
},
177+
{
178+
code: "!!(a, b)",
179+
output: "Boolean((a, b))",
180+
errors: [
181+
{
182+
messageId: "implicitCoercion",
183+
data: { recommendation: "Boolean((a, b))" },
184+
},
185+
],
186+
},
187+
{
188+
code: "(a, b) - 0",
189+
output: null,
190+
errors: [
191+
{
192+
messageId: "implicitCoercion",
193+
data: { recommendation: "Number((a, b))" },
194+
suggestions: [
195+
{
196+
messageId: "useRecommendation",
197+
data: { recommendation: "Number((a, b))" },
198+
output: "Number((a, b))",
199+
},
200+
],
201+
},
202+
],
203+
},
204+
{
205+
code: "+(a, b)",
206+
output: null,
207+
errors: [
208+
{
209+
messageId: "implicitCoercion",
210+
data: { recommendation: "Number((a, b))" },
211+
suggestions: [
212+
{
213+
messageId: "useRecommendation",
214+
data: { recommendation: "Number((a, b))" },
215+
output: "Number((a, b))",
216+
},
217+
],
218+
},
219+
],
220+
},
221+
{
222+
code: "-(-(a, b))",
223+
output: null,
224+
errors: [
225+
{
226+
messageId: "implicitCoercion",
227+
data: { recommendation: "Number((a, b))" },
228+
suggestions: [
229+
{
230+
messageId: "useRecommendation",
231+
data: { recommendation: "Number((a, b))" },
232+
output: "Number((a, b))",
233+
},
234+
],
235+
},
236+
],
237+
},
238+
{
239+
code: "(a, b) * 1",
240+
output: null,
241+
errors: [
242+
{
243+
messageId: "implicitCoercion",
244+
data: { recommendation: "Number((a, b))" },
245+
suggestions: [
246+
{
247+
messageId: "useRecommendation",
248+
data: { recommendation: "Number((a, b))" },
249+
output: "Number((a, b))",
250+
},
251+
],
252+
},
253+
],
254+
},
255+
{
256+
code: '(a, b) + ""',
257+
output: null,
258+
errors: [
259+
{
260+
messageId: "implicitCoercion",
261+
data: { recommendation: "String((a, b))" },
262+
suggestions: [
263+
{
264+
messageId: "useRecommendation",
265+
data: { recommendation: "String((a, b))" },
266+
output: "String((a, b))",
267+
},
268+
],
269+
},
270+
],
271+
},
272+
{
273+
code: "`${(a, b)}`",
274+
output: null,
275+
options: [{ disallowTemplateShorthand: true }],
276+
languageOptions: { ecmaVersion: 6 },
277+
errors: [
278+
{
279+
messageId: "implicitCoercion",
280+
data: { recommendation: "String((a, b))" },
281+
suggestions: [
282+
{
283+
messageId: "useRecommendation",
284+
data: { recommendation: "String((a, b))" },
285+
output: "String((a, b))",
286+
},
287+
],
288+
},
289+
],
290+
},
177291
{
178292
code: "!!(foo + bar)",
179293
output: "Boolean(foo + bar)",

0 commit comments

Comments
 (0)