Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add two more cases to no-implicit-coercion #17832

Merged
merged 18 commits into from
Dec 27, 2023
8 changes: 7 additions & 1 deletion docs/src/rules/no-implicit-coercion.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Such as:
var b = !!foo;
var b = ~foo.indexOf(".");
var n = +foo;
var n = -(-foo);
var n = foo - 0;
mdjermanovic marked this conversation as resolved.
Show resolved Hide resolved
var n = 1 * foo;
var s = "" + foo;
foo += ``;
Expand All @@ -26,6 +28,8 @@ var b = Boolean(foo);
var b = foo.indexOf(".") !== -1;
var n = Number(foo);
var n = Number(foo);
var n = Number(foo);
var n = Number(foo);
var s = String(foo);
foo = String(foo);
```
Expand All @@ -42,7 +46,7 @@ This rule has three main options and one override option to allow some coercions
* `"number"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `number` type.
* `"string"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `string` type.
* `"disallowTemplateShorthand"` (`false` by default) - When this is `true`, this rule warns `string` type conversions using `${expression}` form.
* `"allow"` (`empty` by default) - Each entry in this array can be one of `~`, `!!`, `+` or `*` that are to be allowed.
* `"allow"` (`empty` by default) - Each entry in this array can be one of `~`, `!!`, `+`, `- -`, `-`, or `*` that are to be allowed.

Note that operator `+` in `allow` list would allow `+foo` (number coercion) as well as `"" + foo` (string coercion).

Expand Down Expand Up @@ -87,6 +91,8 @@ Examples of **incorrect** code for the default `{ "number": true }` option:
/*eslint no-implicit-coercion: "error"*/

var n = +foo;
var n = -(-foo);
var n = foo - 0;
var n = 1 * foo;
```

Expand Down
18 changes: 17 additions & 1 deletion lib/rules/no-implicit-coercion.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------

const INDEX_OF_PATTERN = /^(?:i|lastI)ndexOf$/u;
const ALLOWABLE_OPERATORS = ["~", "!!", "+", "*"];
const ALLOWABLE_OPERATORS = ["~", "!!", "+", "- -", "-", "*"];

/**
* Parses and normalizes an option object.
Expand Down Expand Up @@ -300,6 +300,14 @@ module.exports = {

report(node, recommendation, true);
}

// -(-foo)
operatorAllowed = options.allow.includes("- -");
if (!operatorAllowed && options.number && node.operator === "-" && node.argument.type === "UnaryExpression" && node.argument.operator === "-" && !isNumeric(node.argument.argument)) {
const recommendation = `Number(${sourceCode.getText(node.argument.argument)})`;

report(node, recommendation, false);
}
},

// Use `:exit` to prevent double reporting
Expand All @@ -317,6 +325,14 @@ module.exports = {
report(node, recommendation, true);
}

// foo - 0
operatorAllowed = options.allow.includes("-");
if (!operatorAllowed && options.number && node.operator === "-" && node.right.type === "Literal" && node.right.value === 0 && !isNumeric(node.left)) {
const recommendation = `Number(${sourceCode.getText(node.left)})`;

report(node, recommendation, true);
}

// "" + foo
operatorAllowed = options.allow.includes("+");
if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) {
Expand Down
28 changes: 28 additions & 0 deletions tests/lib/rules/no-implicit-coercion.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,23 @@ ruleTester.run("no-implicit-coercion", rule, {
"-foo",
"+1234",
"-1234",
"- -1234",
"+Number(lol)",
"-parseFloat(lol)",
"2 * foo",
"1 * 1234",
"123 - 0",
"1 * Number(foo)",
"1 * parseInt(foo)",
"1 * parseFloat(foo)",
"Number(foo) * 1",
"Number(foo) - 0",
"parseInt(foo) * 1",
"parseFloat(foo) * 1",
"- -Number(foo)",
"1 * 1234 * 678 * Number(foo)",
"1 * 1234 * 678 * parseInt(foo)",
"(1 - 0) * parseInt(foo)",
"1234 * 1 * 678 * Number(foo)",
"1234 * 1 * Number(foo) * Number(bar)",
"1234 * 1 * Number(foo) * parseInt(bar)",
Expand All @@ -54,6 +59,7 @@ ruleTester.run("no-implicit-coercion", rule, {
"1234 * parseInt(foo) * 1 * Number(bar)",
"1234 * parseFloat(foo) * 1 * parseInt(bar)",
"1234 * parseFloat(foo) * 1 * Number(bar)",
"(- -1234) * (parseFloat(foo) - 0) * (Number(bar) - 0)",
"1234*foo*1",
"1234*1*foo",
"1234*bar*1*foo",
Expand All @@ -69,13 +75,17 @@ ruleTester.run("no-implicit-coercion", rule, {
{ code: "!!foo", options: [{ boolean: false }] },
{ code: "~foo.indexOf(1)", options: [{ boolean: false }] },
{ code: "+foo", options: [{ number: false }] },
{ code: "-(-foo)", options: [{ number: false }] },
{ code: "foo - 0", options: [{ number: false }] },
{ code: "1*foo", options: [{ number: false }] },
{ code: "\"\"+foo", options: [{ string: false }] },
{ code: "foo += \"\"", options: [{ string: false }] },
{ code: "var a = !!foo", options: [{ boolean: true, allow: ["!!"] }] },
{ code: "var a = ~foo.indexOf(1)", options: [{ boolean: true, allow: ["~"] }] },
{ code: "var a = ~foo", options: [{ boolean: true }] },
{ code: "var a = 1 * foo", options: [{ boolean: true, allow: ["*"] }] },
{ code: "- -foo", options: [{ number: true, allow: ["- -"] }] },
{ code: "foo - 0", options: [{ number: true, allow: ["-"] }] },
{ code: "var a = +foo", options: [{ boolean: true, allow: ["+"] }] },
{ code: "var a = \"\" + foo", options: [{ boolean: true, string: true, allow: ["+"] }] },

Expand Down Expand Up @@ -157,6 +167,15 @@ ruleTester.run("no-implicit-coercion", rule, {
type: "UnaryExpression"
}]
},
{
code: "-(-foo)",
output: null,
errors: [{
messageId: "useRecommendation",
data: { recommendation: "Number(foo)" },
type: "UnaryExpression"
}]
},
{
code: "+foo.bar",
output: "Number(foo.bar)",
Expand Down Expand Up @@ -193,6 +212,15 @@ ruleTester.run("no-implicit-coercion", rule, {
type: "BinaryExpression"
}]
},
{
code: "foo.bar-0",
output: "Number(foo.bar)",
errors: [{
messageId: "useRecommendation",
data: { recommendation: "Number(foo.bar)" },
type: "BinaryExpression"
}]
},
{
code: "\"\"+foo",
output: "String(foo)",
Expand Down