Repository: eslint/eslint (js)

Issue:
Rule proposal: No useless template string, or prefer String()
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for new rule proposals. If you are proposing a new rule, please continue on. If you are here for another reason, please see below:

    1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
    2. To request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
    3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
    4. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

    Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**Please describe what the rule should do:**
Disallow useless template strings, such as `${x}`. The thing is this can be used as a way to cast a variable to string, which means the rule could instead be "prefer String()" or "prefer .toString()".

**What category of rule is this? (place an "X" next to just one item)**

[ ] Warns about a potential error (problem)
[X] Suggests an alternate way of doing something (suggestion)
[ ] Enforces code style (layout)
[ ] Other (please specify:)

**Provide 2-3 code examples that this rule will warn about:**

<!-- Put your code examples here -->
```js
const x = "soomething";
callAFunction(123, "mamamia", `${x}`);
```

```js
const x = 19
callAFunction(123, "mamamia", `${x}`);
```

**Why should this rule be included in ESLint (instead of a plugin)?**

Because eslint already had tons of `no-useless-something` rules, so this one would fit very well with the rest.

**Are you willing to submit a pull request to implement this rule?**
No unless you point me to the right resources and ask nicely and I have enough free time this week

Pull request description:
Update: Add disallowTemplateShorthand option in no-implicit-coercion
#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/master/CONTRIBUTING.md).

#### What is the purpose of this pull request? (put an "X" next to an item)

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[x] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

#### What changes did you make? (Give an overview)
This adds the `templateString` option to `no-implicit-coercion`. This makes the
rule report the following code:

```js
`${foo}`
```

For backwards compatibility, this was added as a separate option instead of as
default behaviour.

#### Is there anything you'd like reviewers to focus on?

Closes #12866

Patch:
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index ce7271357..f1e21584e 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -21,15 +21,15 @@ While ESLint is designed to be run on the command line, it's possible to use ESL
     * [EditInfo type][editinfo]
     * [Formatter type][formatter]
 * [SourceCode](#sourcecode)
-    * [splitLines()](#sourcecode-splitlines)
+    * [splitLines()](#sourcecodesplitlines)
 * [Linter](#linter)
-    * [verify()](#linter-verify)
-    * [verifyAndFix()](#linter-verifyandfix)
-    * [defineRule()](#linter-definerule)
-    * [defineRules()](#linter-definerules)
-    * [getRules()](#linter-getrules)
-    * [defineParser()](#linter-defineparser)
-    * [version](#linter-version)
+    * [verify()](#linterverify)
+    * [verifyAndFix()](#linterverifyandfix)
+    * [defineRule()](#linterdefinerule)
+    * [defineRules()](#linterdefinerules)
+    * [getRules()](#lintergetrules)
+    * [defineParser()](#linterdefineparser)
+    * [version](#linterversionlinterversion)
 * [linter (deprecated)](#linter-1)
 * [CLIEngine (deprecated)](#cliengine)
 * [RuleTester](#ruletester)
diff --git a/docs/rules/arrow-parens.md b/docs/rules/arrow-parens.md
index e4fc5845a..144ebdf07 100644
--- a/docs/rules/arrow-parens.md
+++ b/docs/rules/arrow-parens.md
@@ -52,7 +52,7 @@ This rule has a string option and an object one.
 String options are:
 
 * `"always"` (default) requires parens around arguments in all cases.
-* `"as-needed"` enforces no braces where they can be omitted.
+* `"as-needed"` enforces no parens where they can be omitted.
 
 Object properties for variants of the `"as-needed"` option:
 
diff --git a/docs/rules/no-implicit-coercion.md b/docs/rules/no-implicit-coercion.md
index f78e40c66..8ccee339f 100644
--- a/docs/rules/no-implicit-coercion.md
+++ b/docs/rules/no-implicit-coercion.md
@@ -33,10 +33,11 @@ This rule is aimed to flag shorter notations for the type conversion, then sugge
 
 This rule has three main options and one override option to allow some coercions as required.
 
-* `"boolean"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `boolean` type.
-* `"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.
-* `"allow"` (`empty` by default) - Each entry in this array can be one of `~`, `!!`, `+` or `*` that are to be allowed.
+-   `"boolean"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `boolean` type.
+-   `"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.
 
 Note that operator `+` in `allow` list would allow `+foo` (number coercion) as well as `"" + foo` (string coercion).
 
@@ -106,6 +107,42 @@ var s = String(foo);
 foo = String(foo);
 ```
 
+### disallowTemplateShorthand
+
+This option is **not** affected by the `string` option.
+
+Examples of **incorrect** code for the `{ "disallowTemplateShorthand": true }` option:
+
+```js
+/*eslint no-implicit-coercion: ["error", { "disallowTemplateShorthand": true }]*/
+
+var s = `${foo}`;
+```
+
+Examples of **correct** code for the `{ "disallowTemplateShorthand": true }` option:
+
+```js
+/*eslint no-implicit-coercion: ["error", { "disallowTemplateShorthand": true }]*/
+
+var s = String(foo);
+
+var s = `a${foo}`;
+
+var s = `${foo}b`;
+
+var s = `${foo}${bar}`;
+
+var s = tag`${foo}`;
+```
+
+Examples of **correct** code for the default `{ "disallowTemplateShorthand": false }` option:
+
+```js
+/*eslint no-implicit-coercion: ["error", { "disallowTemplateShorthand": false }]*/
+
+var s = `${foo}`;
+```
+
 ### allow
 
 Using `allow` list, we can override and allow specific operators.
diff --git a/lib/rules/no-implicit-coercion.js b/lib/rules/no-implicit-coercion.js
index a639711ec..b9bb45489 100644
--- a/lib/rules/no-implicit-coercion.js
+++ b/lib/rules/no-implicit-coercion.js
@@ -24,6 +24,7 @@ function parseOptions(options) {
         boolean: "boolean" in options ? options.boolean : true,
         number: "number" in options ? options.number : true,
         string: "string" in options ? options.string : true,
+        disallowTemplateShorthand: "disallowTemplateShorthand" in options ? options.disallowTemplateShorthand : false,
         allow: options.allow || []
     };
 }
@@ -180,6 +181,10 @@ module.exports = {
                     type: "boolean",
                     default: true
                 },
+                disallowTemplateShorthand: {
+                    type: "boolean",
+                    default: false
+                },
                 allow: {
                     type: "array",
                     items: {
@@ -299,6 +304,38 @@ module.exports = {
 
                     report(node, recommendation, true);
                 }
+            },
+
+            TemplateLiteral(node) {
+                if (!options.disallowTemplateShorthand) {
+                    return;
+                }
+
+                // tag`${foo}`
+                if (node.parent.type === "TaggedTemplateExpression") {
+                    return;
+                }
+
+                // `` or `${foo}${bar}`
+                if (node.expressions.length !== 1) {
+                    return;
+                }
+
+
+                //  `prefix${foo}`
+                if (node.quasis[0].value.cooked !== "") {
+                    return;
+                }
+
+                //  `${foo}postfix`
+                if (node.quasis[1].value.cooked !== "") {
+                    return;
+                }
+
+                const code = sourceCode.getText(node.expressions[0]);
+                const recommendation = `String(${code})`;
+
+                report(node, recommendation, true);
             }
         };
     }


Test patch:
diff --git a/tests/lib/rules/no-implicit-coercion.js b/tests/lib/rules/no-implicit-coercion.js
index fa2b68b49..fa0a63824 100644
--- a/tests/lib/rules/no-implicit-coercion.js
+++ b/tests/lib/rules/no-implicit-coercion.js
@@ -88,6 +88,13 @@ ruleTester.run("no-implicit-coercion", rule, {
         { code: "`${foo}` + ''", parserOptions: { ecmaVersion: 6 } },
         "foo += 'bar'",
         { code: "foo += `${bar}`", parserOptions: { ecmaVersion: 6 } },
+        { code: "`a${foo}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}b`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}${bar}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "tag`${foo}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}`", parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}`", options: [{ }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}`", options: [{ disallowTemplateShorthand: false }], parserOptions: { ecmaVersion: 6 } },
         "+42"
     ],
     invalid: [
@@ -248,6 +255,39 @@ ruleTester.run("no-implicit-coercion", rule, {
                 type: "BinaryExpression"
             }]
         },
+        {
+            code: "`${foo}`",
+            output: "String(foo)",
+            options: [{ disallowTemplateShorthand: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "useRecommendation",
+                data: { recommendation: "String(foo)" },
+                type: "TemplateLiteral"
+            }]
+        },
+        {
+            code: "`\\\n${foo}`",
+            output: "String(foo)",
+            options: [{ disallowTemplateShorthand: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "useRecommendation",
+                data: { recommendation: "String(foo)" },
+                type: "TemplateLiteral"
+            }]
+        },
+        {
+            code: "`${foo}\\\n`",
+            output: "String(foo)",
+            options: [{ disallowTemplateShorthand: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "useRecommendation",
+                data: { recommendation: "String(foo)" },
+                type: "TemplateLiteral"
+            }]
+        },
         {
             code: "foo += \"\"",
             output: "foo = String(foo)",