Skip to content

Commit

Permalink
Fix: no-implicit-coercion string concat false positive (fixes #7057)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaicataldo committed Sep 5, 2016
1 parent 3960617 commit 8f479a6
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 9 deletions.
30 changes: 22 additions & 8 deletions lib/rules/no-implicit-coercion.js
Expand Up @@ -5,6 +5,8 @@

"use strict";

const astUtils = require("../ast-utils");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -105,15 +107,25 @@ function getNonNumericOperand(node) {
return null;
}

/**
* Checks whether a node is an empty string literal or not.
* @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the passed in node is an
* empty string literal or not.
*/
function isEmptyString(node) {
return astUtils.isStringLiteral(node) && (node.value === "" || (node.type === "TemplateLiteral" && node.quasis.length === 1 && node.quasis[0].value.cooked === ""));
}

/**
* Checks whether or not a node is a concatenating with an empty string.
* @param {ASTNode} node - A BinaryExpression node to check.
* @returns {boolean} Whether or not the node is a concatenating with an empty string.
*/
function isConcatWithEmptyString(node) {
return node.operator === "+" && (
(node.left.type === "Literal" && node.left.value === "") ||
(node.right.type === "Literal" && node.right.value === "")
(isEmptyString(node.left) && !astUtils.isStringLiteral(node.right)) ||
(isEmptyString(node.right) && !astUtils.isStringLiteral(node.left))
);
}

Expand All @@ -123,17 +135,19 @@ function isConcatWithEmptyString(node) {
* @returns {boolean} Whether or not the node is appended with an empty string.
*/
function isAppendEmptyString(node) {
return node.operator === "+=" && node.right.type === "Literal" && node.right.value === "";
return node.operator === "+=" && isEmptyString(node.right);
}

/**
* Gets a node that is the left or right operand of a node, is not the specified literal.
* @param {ASTNode} node - A BinaryExpression node to get.
* @param {any} value - A literal value to check.
* @returns {ASTNode} A node that is the left or right operand of the node, is not the specified literal.
*/
function getOtherOperand(node, value) {
if (node.left.type === "Literal" && node.left.value === value) {
function getEmptyStringOperand(node) {
if (
(node.left.type === "Literal" && node.left.value === "") ||
(node.left.type === "TemplateLiteral" && node.left.quasis.length === 1 && node.left.quasis[0].value.cooked === "")
) {
return node.right;
}
return node.left;
Expand Down Expand Up @@ -236,7 +250,7 @@ module.exports = {
context.report(
node,
"use `String({{code}})` instead.", {
code: sourceCode.getText(getOtherOperand(node, ""))
code: sourceCode.getText(getEmptyStringOperand(node))
});
}
},
Expand All @@ -250,7 +264,7 @@ module.exports = {
context.report(
node,
"use `{{code}} = String({{code}})` instead.", {
code: sourceCode.getText(getOtherOperand(node, ""))
code: sourceCode.getText(getEmptyStringOperand(node))
});
}
}
Expand Down
17 changes: 16 additions & 1 deletion tests/lib/rules/no-implicit-coercion.js
Expand Up @@ -64,6 +64,16 @@ ruleTester.run("no-implicit-coercion", rule, {
{code: "0 + foo"},
{code: "~foo.bar()"},

// https://github.com/eslint/eslint/issues/7057
{code: "'' + 'foo'"},
{code: "'' + `${foo}`", parserOptions: { ecmaVersion: 6 } },
{code: "`` + 'foo'", parserOptions: { ecmaVersion: 6 } },
{code: "'foo' + ''"},
{code: "`${foo}` + ''", parserOptions: { ecmaVersion: 6 } },
{code: "'foo' + ``", parserOptions: { ecmaVersion: 6 } },
{code: "foo += 'bar'"},
{code: "+42"},

{code: "!!foo", options: [{boolean: false}]},
{code: "~foo.indexOf(1)", options: [{boolean: false}]},
{code: "+foo", options: [{number: false}]},
Expand Down Expand Up @@ -95,6 +105,11 @@ ruleTester.run("no-implicit-coercion", rule, {
{code: "var a = ~foo.indexOf(1)", options: [{boolean: true, allow: ["!!"]}], errors: [{message: "use `foo.indexOf(1) !== -1` instead.", type: "UnaryExpression"}]},
{code: "var a = 1 * foo", options: [{boolean: true, allow: ["+"]}], errors: [{message: "use `Number(foo)` instead.", type: "BinaryExpression"}]},
{code: "var a = +foo", options: [{boolean: true, allow: ["*"]}], errors: [{message: "use `Number(foo)` instead.", type: "UnaryExpression"}]},
{code: "var a = \"\" + foo", options: [{boolean: true, allow: ["*"]}], errors: [{message: "use `String(foo)` instead.", type: "BinaryExpression"}]}
{code: "var a = \"\" + foo", options: [{boolean: true, allow: ["*"]}], errors: [{message: "use `String(foo)` instead.", type: "BinaryExpression"}]},

// https://github.com/eslint/eslint/issues/7057
{code: "'' + foo", errors: [{message: "use `String(foo)` instead.", type: "BinaryExpression"}]},
{code: "`` + foo", parserOptions: { ecmaVersion: 6 }, errors: [{message: "use `String(foo)` instead.", type: "BinaryExpression"}]},
{code: "foo += ``", parserOptions: { ecmaVersion: 6 }, errors: [{message: "use `foo = String(foo)` instead.", type: "AssignmentExpression"}]},
]
});

0 comments on commit 8f479a6

Please sign in to comment.