Repository: eslint/eslint (js)

Issue:
no-implicit-coercion: <string expression> + "" triggers false positive
**Tell us about your environment**

Node version: v10.15.0
npm version: v6.4.1
Local ESLint version: v7.25.0 (Currently used)
Global ESLint version: Not found
Operating System: win32 10.0.19042

**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?**

`@typescript-eslint/parser`

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

```js
"use strict";
const MAX_LINES_PER_FUNCTION = 128;
const MAX_PARAMS = 8;

const MAX_CODE_DEPTH = 8;
module.exports = {
  env: {
    amd: true,
    browser: true,
    node: true,
  },
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2020,
    sourceType: "module",
  },
  plugins: ["prettier", "react-hooks", "prefer-arrow"],
  extends: ["eslint:recommended", "plugin:react/recommended", "prettier"],
  rules: {
    "accessor-pairs": ["error", { enforceForClassMembers: true, setWithoutGet: true }],
    "array-callback-return": ["error", { checkForEach: true }],
    "arrow-body-style": ["error", "as-needed"],
    "block-scoped-var": "error",
    "camelcase": "off",
    "class-methods-use-this": "error",
    "complexity": "error",
    "consistent-return": "error",
    "consistent-this": ["error", "self"],
    "curly": "error",
    "default-case": "error",
    "default-case-last": "error",
    "default-param-last": "error",
    "dot-notation": ["error", { allowKeywords: true }],
    "eqeqeq": ["error", "always"],
    "func-name-matching": "off",
    "func-names": "off",
    "func-style": ["error", "expression"],
    "grouped-accessor-pairs": "error",
    "guard-for-in": "error",
    "id-denylist": ["error", "data", "callback"],
    "id-length": "off",
    "id-match": "off",
    "init-declarations": "off",
    "line-comment-position": "off",comments
    "lines-between-class-members": ["error", "always", { exceptAfterSingleLine: true }],
    "max-classes-per-file": "error",
    "max-depth": ["error", MAX_CODE_DEPTH],
    "max-len": "off",
    "max-lines": "off",
    "max-lines-per-function": ["error", { max: MAX_LINES_PER_FUNCTION, skipBlankLines: true }],
    "max-nested-callbacks": "error",
    "max-params": ["error", MAX_PARAMS],
    "max-statements": "off",
    "max-statements-per-line": ["error", { max: 2 }],
    "multiline-comment-style": ["error", "starred-block"],
    "new-cap": "error",
    "no-alert": "error",
    "no-array-constructor": "error",
    "no-await-in-loop": "error",
    "no-bitwise": "error",
    "no-caller": "error",
    "no-confusing-arrow": "error",
    "no-console": ["error", { allow: ["warn", "error"] }],
    "no-constructor-return": "error",
    "no-continue": "off",
    "no-debugger": "error",
    "no-div-regex": "error",
    "no-dupe-class-members": "error",
    "no-duplicate-imports": "error",
    "no-else-return": "error",
    "no-empty": ["error", { allowEmptyCatch: true }],
    "no-empty-function": "error",
    "no-eq-null": "error",
    "no-eval": "error",
    "no-extend-native": "error",
    "no-extra-bind": "error",
    "no-extra-label": "error",
    "no-implicit-coercion": ["error", { allow: ["!!"] }],
    "no-implicit-globals": "error",
    "no-implied-eval": "error",
    "no-inline-comments": "off",
    "no-invalid-this": "error",
    "no-iterator": "error",
    "no-label-var": "error",
    "no-labels": ["error", { allowLoop: true }],
    "no-lone-blocks": "error",
    "no-lonely-if": "error",
    "no-loop-func": "error",
    "no-loss-of-precision": "error",
    "no-multi-assign": "off",
    "no-multi-str": "error",
    "no-negated-condition": "off",
    "no-nested-ternary": "off",
    "no-new": "error",
    "no-new-func": "error",
    "no-new-object": "error",
    "no-new-wrappers": "error",
    "no-nonoctal-decimal-escape": "error",
    "no-octal-escape": "error",
    "no-param-reassign": "error",
    "no-plusplus": "off",
    "no-promise-executor-return": "error",
    "no-proto": "error",
    "no-restricted-exports": "off",
    "no-restricted-globals": "off",
    "no-restricted-imports": "off",
    "no-restricted-properties": "off",
    "no-restricted-syntax": "off",
    "no-return-assign": "error",
    "no-return-await": "error",
    "no-script-url": "error",
    "no-self-compare": "error",
    "no-sequences": "error",
    "no-shadow": "off",
    "no-template-curly-in-string": "error",
    "no-ternary": "off",
    "no-throw-literal": "error",
    "no-trailing-spaces": "error",
    "no-undef-init": "off",
    "no-undefined": "off",
    "no-underscore-dangle": "error",
    "no-unmodified-loop-condition": "error",
    "no-unneeded-ternary": "error",
    "no-unreachable-loop": "error",
    "no-unsafe-optional-chaining": "error",
    "no-unused-expressions": "error",
    "no-use-before-define": ["error", { functions: false }],
    "no-useless-backreference": "error",
    "no-useless-call": "error",
    "no-useless-computed-key": "error",
    "no-useless-concat": "error",
    "no-useless-constructor": "error",
    "no-useless-rename": "error",
    "no-useless-return": "error",
    "no-var": "error",
    "no-void": ["error", { allowAsStatement: true }],
    "no-warning-comments": "off",
    "nonblock-statement-body-position": ["off"],
    "one-var": "off",
    "operator-assignment": ["error", "always"],
    "padding-line-between-statements": [
      "error",
      {
        blankLine: "always",
        next: "*",
        prev: ["block", "block-like", "cjs-export", "class", "export", "import"],
      },
      { blankLine: "any", next: ["export", "import"], prev: ["export", "import"] },
    ],
    "prefer-arrow-callback": "error",
    "prefer-exponentiation-operator": "error",
    "prefer-named-capture-group": "off",
    "prefer-numeric-literals": "error",
    "prefer-promise-reject-errors": "error",
    "prefer-regex-literals": "error",

    "prettier/prettier": ["error", { endOfLine: "auto" }],

    "radix": ["error", "as-needed"],

    "react-hooks/rules-of-hooks": "error",
    "react/display-name": "off",
    "react/no-unescaped-entities": "off",

    "require-atomic-updates": "error",
    "require-await": "error",
    "require-unicode-regexp": "off",
    "require-yield": "error",
    "rest-spread-spacing": ["error", "never"],
    "sort-imports": ["error", { ignoreCase: true, ignoreDeclarationSort: true }],
    "sort-keys": "off",
    "sort-vars": "off",
    "spaced-comment": ["error", "always"],
    "symbol-description": "error",
    "template-curly-spacing": "error",
    "valid-typeof": ["error", { requireStringLiterals: true }],
    "vars-on-top": "off",
    "yoda": "error",
  },
  overrides: [
    {
      env: {
        amd: true,
        browser: true,
        es2020: true,
        node: true,
      },
      files: ["*.ts", "*.tsx"],
      parserOptions: {
        project: "./tsconfig.json",
      },
      extends: [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/eslint-recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "prettier",
      ],
      plugins: ["@typescript-eslint", "prettier", "react-hooks"],
      rules: {
        "@typescript-eslint/explicit-function-return-type": "off",
        "@typescript-eslint/explicit-member-accessibility": "off",
        "@typescript-eslint/explicit-module-boundary-types": "off",
        "@typescript-eslint/member-delimiter-style": [
          "error",
          {
            multiline: {
              delimiter: "semi",
              requireLast: true,
            },
            singleline: {
              delimiter: "semi",
              requireLast: false,
            },
          },
        ],
        "@typescript-eslint/no-duplicate-imports": "error",
        "@typescript-eslint/no-explicit-any": "error",
        "@typescript-eslint/no-invalid-this": "error",
        "@typescript-eslint/no-magic-numbers": [
          "error",
          {
            ignore: [-1, 0, 0.001, 0.01, 0.5, 1, 2, 100, 1000],
            ignoreEnums: true,
            ignoreNumericLiteralTypes: true,
          },
        ],
        "@typescript-eslint/no-namespace": "error",
        "@typescript-eslint/no-non-null-assertion": "error",
        "@typescript-eslint/no-this-alias": "error",
        "@typescript-eslint/no-unused-vars": ["error", { ignoreRestSiblings: true }],
        "@typescript-eslint/no-use-before-define": ["off"],
        "@typescript-eslint/no-var-requires": "error",
        "@typescript-eslint/triple-slash-reference": [
          "error",
          {
            lib: "never",
            path: "never",
            types: "never",
          },
        ],
        "@typescript-eslint/unified-signatures": "error",
        "default-param-last": "off",
        "no-duplicate-imports": "off",
        "no-invalid-this": "off",
        "no-new-symbol": "error",
        "no-use-before-define": "off",
        "object-shorthand": [
          "error",
          "properties",
          {
            avoidQuotes: true,
          },
        ],
        "prefer-arrow/prefer-arrow-functions": [
          "error",
          {
            classPropertiesAllowed: false,
            disallowPrototype: true,
            singleReturnOnly: false,
          },
        ],
        "prefer-destructuring": "error",
        "prefer-object-spread": "error",
        "prefer-template": "error",

        "quotes": ["error", "double", { avoidEscape: true }],

        "react/prop-types": "off",
      },
    },
    {
      files: ["*.test.ts", "*.test.tsx"],
      rules: {
        "@typescript-eslint/no-magic-numbers": "off",
        "@typescript-eslint/no-unsafe-assignment": "off",
        "@typescript-eslint/no-unsafe-call": "off",
        "@typescript-eslint/no-unsafe-member-access": "off",
        "@typescript-eslint/no-var-requires": "off",
      },
    },
    {
      files: ["**/styleguide/**/*.tsx", "**/styleguide/**/*.ts"],
      rules: {
        "@typescript-eslint/no-magic-numbers": ["off"],
      },
    },
    {
      env: {
        amd: true,
        browser: true,
        jquery: true,
        node: true,
      },
      files: ["*.js", ".prettierrc.js"],
      globals: {
        $: "readonly",
      },
      parserOptions: {
        ecmaVersion: 5,
        sourceType: "script",
      },
      rules: {
        "block-scoped-var": "error",
        "consistent-this": ["error", "self", "ctl", "exports"],
        "func-style": ["off"],
        "max-lines-per-function": ["off"],
        "max-params": "off",
        "new-cap": [
          "error",
          {
            capIsNewExceptions: [
              "CssMediaWatcher",
              "Deferred",
              "GenerateUuid",
              "GetPseudoElementContent",
              "ParseBool",
              "SmoothScrollTo",
            ],
          },
        ],
        "no-invalid-this": "off",
        "no-magic-numbers": ["error", { ignore: [-1, 0, 0.001, 0.01, 0.5, 1, 2, 100, 1000] }],
        "no-var": "off",
        "prefer-arrow-callback": "off",
        "prefer-arrow/prefer-arrow-functions": "off",
        "prefer-exponentiation-operator": "off",
        "sort-keys": "off",
        "strict": ["error", "function"],
      },
    },
    {
      env: {
        amd: true,
        browser: true,
        jquery: true,
        mocha: true,
        node: true,
      },
      files: ["*.spec.js", "test-helper.js"],
      parserOptions: {
        ecmaVersion: 5,
        sourceType: "script",
      },
      plugins: ["chai-friendly"],
      rules: {
        "block-scoped-var": "error",
        "chai-friendly/no-unused-expressions": 2,
        "func-style": ["off"],
        "max-lines-per-function": ["off"],
        "max-params": "off",
        "new-cap": ["error"],
        "no-magic-numbers": ["off"],
        "no-unused-expressions": 0,
        "no-var": "off",
        "prefer-arrow-callback": "off",
        "prefer-arrow/prefer-arrow-functions": "off",
        "sort-keys": "off",
        "strict": ["error", "function"],
      },
    },
    {
      env: {
        amd: true,
        node: true,
      },
      files: ["gulpfile.js", ".eslintrc.js"],
      parserOptions: {
        ecmaVersion: 2020,
        sourceType: "script",
      },
      plugins: ["sort-keys-fix"],
      rules: {
        "arrow-body-style": ["error", "as-needed"],
        "block-scoped-var": "error",
        "no-magic-numbers": ["error", { ignore: [-1, 0, 0.001, 0.01, 0.5, 1, 2, 100, 1000] }],
        "no-var": "error",
        "prefer-arrow-callback": "error",
        "prefer-arrow/prefer-arrow-functions": [
          "error",
          {
            classPropertiesAllowed: false,
            disallowPrototype: true,
            singleReturnOnly: false,
          },
        ],
        "sort-keys": "error",
        "strict": ["error", "global"],
      },
    },
  ],
  settings: {
    react: {
      version: "detect",
    },
  },
};
```

</details>

**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.**

Here are some example statements which trigger it with our particular `eslintrc` setup:

```js
var foo = { x: "a" }.x + "";
```

> ESLint: use `String({ x: "a" }.x)` instead. (no-implicit-coercion)

```js
var foo = ["a"][0] + "";
```

> ESLint: use `String(["a"][0])` instead. (no-implicit-coercion)

```js
var foo = String("a") + "";
```

> ESLint: use `String(String("a"))` instead. (no-implicit-coercion)

Swapping the operands of `+` makes no difference.

Changing the empty string to any other value in all of these cases removes this error. I've tried `"a"`, the number 0, false, true, null, empty array `[]`, all cases for which I'd expect to find a message about implicit coercion — they all, counterintuitively, remove the error.

I am running it from MINGW64 (i.e., bash on Windows):

```bash
./node_modules/.bin/eslint "/C/Projects/Client Website/client-site/Client.Site/Client/Site/Client.Squad.Web.UI/Assets/Client/Site/js/ng/directives/domain/domain-feature.js"
```

**What did you expect to happen?**

No error should result from the cases above.

For example, for the very first example above I would expect the output:
```
  448:15  error  'foo' is assigned a value but never used  no-unused-vars

✖ 1 problem (1 error, 0 warnings)
```


**What actually happened? Please include the actual, raw output from ESLint.**

```
  448:15  error  'foo' is assigned a value but never used  no-unused-vars
  448:21  error  use `String({ x: "a" }.x)` instead        no-implicit-coercion

✖ 2 problems (2 errors, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.
```

See the ESLint messages above.

Pull request:
Fix: no-implicit-coercion false positive with `String()` (fixes #14623)
<!--
    Thank you for contributing!

    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

#### 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)

<!--
    The following template is intentionally not a markdown checkbox list for the reasons
    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
-->

[ ] Documentation update
[x] 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))
[ ] 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:

fixes #14623 (the accepted part)

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

#### What changes did you make? (Give an overview)

Fixed the `no-implicit-coercion` rule to treat `String(...)` as a string value and thus not report string coercions on it.

Also added this check for the `disallowTemplateShorthand` option.

#### Is there anything you'd like reviewers to focus on?


Patch:
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index 59e275de5..9dadae00e 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -139,6 +139,7 @@ Additionally, the `context` object has the following methods:
     * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned.
     * Otherwise, if the node does not declare any variables, an empty array is returned.
 * `getFilename()` - returns the filename associated with the source.
+* `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `<text>` if not specified.
 * `getScope()` - returns the [scope](./scope-manager-interface.md#scope-interface) of the currently-traversed node. This information can be used to track references to variables.
 * `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint.
 * `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`.
diff --git a/docs/rules/no-duplicate-imports.md b/docs/rules/no-duplicate-imports.md
index 683f31584..437d2c3da 100644
--- a/docs/rules/no-duplicate-imports.md
+++ b/docs/rules/no-duplicate-imports.md
@@ -12,7 +12,7 @@ import { find } from 'module';
 
 ## Rule Details
 
-This rule requires that all imports from a single module exists in a single `import` statement.
+This rule requires that all imports from a single module that can be merged exist in a single `import` statement.
 
 Example of **incorrect** code for this rule:
 
@@ -33,6 +33,16 @@ import { merge, find } from 'module';
 import something from 'another-module';
 ```
 
+Example of **correct** code for this rule:
+
+```js
+/*eslint no-duplicate-imports: "error"*/
+
+// not mergeable
+import { merge } from 'module';
+import * as something from 'module';
+```
+
 ## Options
 
 This rule takes one optional argument, an object with a single key, `includeExports` which is a `boolean`. It defaults to `false`.
@@ -58,3 +68,17 @@ import { merge, find } from 'module';
 
 export { find };
 ```
+
+Example of **correct** code for this rule with the `{ "includeExports": true }` option:
+
+```js
+/*eslint no-duplicate-imports: ["error", { "includeExports": true }]*/
+
+import { merge, find } from 'module';
+
+// cannot be merged with the above import
+export * as something from 'module';
+
+// cannot be written differently
+export * from 'module';
+```
diff --git a/docs/rules/no-sequences.md b/docs/rules/no-sequences.md
index 87ba3c4b9..f12c46573 100644
--- a/docs/rules/no-sequences.md
+++ b/docs/rules/no-sequences.md
@@ -73,7 +73,7 @@ Examples of **incorrect** code for arrow functions:
 /*eslint no-sequences: "error"*/
 const foo = (val) => (console.log('bar'), val);
 
-const foo = () => ((bar = 123), 10));
+const foo = () => ((bar = 123), 10);
 
 const foo = () => { return (bar = 123), 10 }
 ```
diff --git a/lib/linter/linter.js b/lib/linter/linter.js
index bdc6c1b1d..44e88f845 100644
--- a/lib/linter/linter.js
+++ b/lib/linter/linter.js
@@ -828,9 +828,10 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
  * @param {string} filename The reported filename of the code
  * @param {boolean} disableFixes If true, it doesn't make `fix` properties.
  * @param {string | undefined} cwd cwd of the cli
+ * @param {string} physicalFilename The full path of the file on disk without any code block information
  * @returns {Problem[]} An array of reported problems
  */
-function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd) {
+function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd, physicalFilename) {
     const emitter = createEmitter();
     const nodeQueue = [];
     let currentNode = sourceCode.ast;
@@ -859,6 +860,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
                 getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
                 getCwd: () => cwd,
                 getFilename: () => filename,
+                getPhysicalFilename: () => physicalFilename || filename,
                 getScope: () => getScope(sourceCode.scopeManager, currentNode),
                 getSourceCode: () => sourceCode,
                 markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, parserOptions, name),
@@ -1181,7 +1183,8 @@ class Linter {
                 settings,
                 options.filename,
                 options.disableFixes,
-                slots.cwd
+                slots.cwd,
+                providedOptions.physicalFilename
             );
         } catch (err) {
             err.message += `\nOccurred while linting ${options.filename}`;
@@ -1284,6 +1287,7 @@ class Linter {
     _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
         const filename = options.filename || "<input>";
         const filenameToExpose = normalizeFilename(filename);
+        const physicalFilename = options.physicalFilename || filenameToExpose;
         const text = ensureText(textOrSourceCode);
         const preprocess = options.preprocess || (rawText => [rawText]);
 
@@ -1316,7 +1320,7 @@ class Linter {
                 return this._verifyWithConfigArray(
                     blockText,
                     configForRecursive,
-                    { ...options, filename: blockName }
+                    { ...options, filename: blockName, physicalFilename }
                 );
             }
 
@@ -1324,7 +1328,7 @@ class Linter {
             return this._verifyWithoutProcessors(
                 blockText,
                 config,
-                { ...options, filename: blockName }
+                { ...options, filename: blockName, physicalFilename }
             );
         });
 
diff --git a/lib/rules/no-duplicate-imports.js b/lib/rules/no-duplicate-imports.js
index 7218dc64a..cc3da1d5a 100644
--- a/lib/rules/no-duplicate-imports.js
+++ b/lib/rules/no-duplicate-imports.js
@@ -4,92 +4,225 @@
  */
 "use strict";
 
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const NAMED_TYPES = ["ImportSpecifier", "ExportSpecifier"];
+const NAMESPACE_TYPES = [
+    "ImportNamespaceSpecifier",
+    "ExportNamespaceSpecifier"
+];
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
 
 /**
- * Returns the name of the module imported or re-exported.
+ * Check if an import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier).
+ * @param {string} importExportType An import/export type to check.
+ * @param {string} type Can be "named" or "namespace"
+ * @returns {boolean} True if import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier) and false if it doesn't.
+ */
+function isImportExportSpecifier(importExportType, type) {
+    const arrayToCheck = type === "named" ? NAMED_TYPES : NAMESPACE_TYPES;
+
+    return arrayToCheck.includes(importExportType);
+}
+
+/**
+ * Return the type of (import|export).
  * @param {ASTNode} node A node to get.
- * @returns {string} the name of the module, or empty string if no name.
+ * @returns {string} The type of the (import|export).
  */
-function getValue(node) {
-    if (node && node.source && node.source.value) {
-        return node.source.value.trim();
+function getImportExportType(node) {
+    if (node.specifiers && node.specifiers.length > 0) {
+        const nodeSpecifiers = node.specifiers;
+        const index = nodeSpecifiers.findIndex(
+            ({ type }) =>
+                isImportExportSpecifier(type, "named") ||
+                isImportExportSpecifier(type, "namespace")
+        );
+        const i = index > -1 ? index : 0;
+
+        return nodeSpecifiers[i].type;
     }
+    if (node.type === "ExportAllDeclaration") {
+        if (node.exported) {
+            return "ExportNamespaceSpecifier";
+        }
+        return "ExportAll";
+    }
+    return "SideEffectImport";
+}
 
-    return "";
+/**
+ * Returns a boolean indicates if two (import|export) can be merged
+ * @param {ASTNode} node1 A node to check.
+ * @param {ASTNode} node2 A node to check.
+ * @returns {boolean} True if two (import|export) can be merged, false if they can't.
+ */
+function isImportExportCanBeMerged(node1, node2) {
+    const importExportType1 = getImportExportType(node1);
+    const importExportType2 = getImportExportType(node2);
+
+    if (
+        (importExportType1 === "ExportAll" &&
+            importExportType2 !== "ExportAll" &&
+            importExportType2 !== "SideEffectImport") ||
+        (importExportType1 !== "ExportAll" &&
+            importExportType1 !== "SideEffectImport" &&
+            importExportType2 === "ExportAll")
+    ) {
+        return false;
+    }
+    if (
+        (isImportExportSpecifier(importExportType1, "namespace") &&
+            isImportExportSpecifier(importExportType2, "named")) ||
+        (isImportExportSpecifier(importExportType2, "namespace") &&
+            isImportExportSpecifier(importExportType1, "named"))
+    ) {
+        return false;
+    }
+    return true;
 }
 
 /**
- * Checks if the name of the import or export exists in the given array, and reports if so.
- * @param {RuleContext} context The ESLint rule context object.
- * @param {ASTNode} node A node to get.
- * @param {string} value The name of the imported or exported module.
- * @param {string[]} array The array containing other imports or exports in the file.
- * @param {string} messageId A messageId to be reported after the name of the module
- *
- * @returns {void} No return value
+ * Returns a boolean if we should report (import|export).
+ * @param {ASTNode} node A node to be reported or not.
+ * @param {[ASTNode]} previousNodes An array contains previous nodes of the module imported or exported.
+ * @returns {boolean} True if the (import|export) should be reported.
  */
-function checkAndReport(context, node, value, array, messageId) {
-    if (array.indexOf(value) !== -1) {
-        context.report({
-            node,
-            messageId,
-            data: {
-                module: value
-            }
-        });
+function shouldReportImportExport(node, previousNodes) {
+    let i = 0;
+
+    while (i < previousNodes.length) {
+        if (isImportExportCanBeMerged(node, previousNodes[i])) {
+            return true;
+        }
+        i++;
     }
+    return false;
 }
 
 /**
- * @callback nodeCallback
- * @param {ASTNode} node A node to handle.
+ * Returns array contains only nodes with declarations types equal to type.
+ * @param {[{node: ASTNode, declarationType: string}]} nodes An array contains objects, each object contains a node and a declaration type.
+ * @param {string} type Declaration type.
+ * @returns {[ASTNode]} An array contains only nodes with declarations types equal to type.
+ */
+function getNodesByDeclarationType(nodes, type) {
+    return nodes
+        .filter(({ declarationType }) => declarationType === type)
+        .map(({ node }) => node);
+}
+
+/**
+ * Returns the name of the module imported or re-exported.
+ * @param {ASTNode} node A node to get.
+ * @returns {string} The name of the module, or empty string if no name.
  */
+function getModule(node) {
+    if (node && node.source && node.source.value) {
+        return node.source.value.trim();
+    }
+    return "";
+}
 
 /**
- * Returns a function handling the imports of a given file
+ * Checks if the (import|export) can be merged with at least one import or one export, and reports if so.
  * @param {RuleContext} context The ESLint rule context object.
+ * @param {ASTNode} node A node to get.
+ * @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
+ * @param {string} declarationType A declaration type can be an import or export.
  * @param {boolean} includeExports Whether or not to check for exports in addition to imports.
- * @param {string[]} importsInFile The array containing other imports in the file.
- * @param {string[]} exportsInFile The array containing other exports in the file.
- *
- * @returns {nodeCallback} A function passed to ESLint to handle the statement.
+ * @returns {void} No return value.
  */
-function handleImports(context, includeExports, importsInFile, exportsInFile) {
-    return function(node) {
-        const value = getValue(node);
+function checkAndReport(
+    context,
+    node,
+    modules,
+    declarationType,
+    includeExports
+) {
+    const module = getModule(node);
 
-        if (value) {
-            checkAndReport(context, node, value, importsInFile, "import");
+    if (modules.has(module)) {
+        const previousNodes = modules.get(module);
+        const messagesIds = [];
+        const importNodes = getNodesByDeclarationType(previousNodes, "import");
+        let exportNodes;
 
+        if (includeExports) {
+            exportNodes = getNodesByDeclarationType(previousNodes, "export");
+        }
+        if (declarationType === "import") {
+            if (shouldReportImportExport(node, importNodes)) {
+                messagesIds.push("import");
+            }
             if (includeExports) {
-                checkAndReport(context, node, value, exportsInFile, "importAs");
+                if (shouldReportImportExport(node, exportNodes)) {
+                    messagesIds.push("importAs");
+                }
+            }
+        } else if (declarationType === "export") {
+            if (shouldReportImportExport(node, exportNodes)) {
+                messagesIds.push("export");
+            }
+            if (shouldReportImportExport(node, importNodes)) {
+                messagesIds.push("exportAs");
             }
-
-            importsInFile.push(value);
         }
-    };
+        messagesIds.forEach(messageId =>
+            context.report({
+                node,
+                messageId,
+                data: {
+                    module
+                }
+            }));
+    }
 }
 
 /**
- * Returns a function handling the exports of a given file
+ * @callback nodeCallback
+ * @param {ASTNode} node A node to handle.
+ */
+
+/**
+ * Returns a function handling the (imports|exports) of a given file
  * @param {RuleContext} context The ESLint rule context object.
- * @param {string[]} importsInFile The array containing other imports in the file.
- * @param {string[]} exportsInFile The array containing other exports in the file.
- *
+ * @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
+ * @param {string} declarationType A declaration type can be an import or export.
+ * @param {boolean} includeExports Whether or not to check for exports in addition to imports.
  * @returns {nodeCallback} A function passed to ESLint to handle the statement.
  */
-function handleExports(context, importsInFile, exportsInFile) {
+function handleImportsExports(
+    context,
+    modules,
+    declarationType,
+    includeExports
+) {
     return function(node) {
-        const value = getValue(node);
+        const module = getModule(node);
+
+        if (module) {
+            checkAndReport(
+                context,
+                node,
+                modules,
+                declarationType,
+                includeExports
+            );
+            const currentNode = { node, declarationType };
+            let nodes = [currentNode];
 
-        if (value) {
-            checkAndReport(context, node, value, exportsInFile, "export");
-            checkAndReport(context, node, value, importsInFile, "exportAs");
+            if (modules.has(module)) {
+                const previousNodes = modules.get(module);
 
-            exportsInFile.push(value);
+                nodes = [...previousNodes, currentNode];
+            }
+            modules.set(module, nodes);
         }
     };
 }
@@ -105,16 +238,19 @@ module.exports = {
             url: "https://eslint.org/docs/rules/no-duplicate-imports"
         },
 
-        schema: [{
-            type: "object",
-            properties: {
-                includeExports: {
-                    type: "boolean",
-                    default: false
-                }
-            },
-            additionalProperties: false
-        }],
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    includeExports: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
         messages: {
             import: "'{{module}}' import is duplicated.",
             importAs: "'{{module}}' import is duplicated as export.",
@@ -125,18 +261,30 @@ module.exports = {
 
     create(context) {
         const includeExports = (context.options[0] || {}).includeExports,
-            importsInFile = [],
-            exportsInFile = [];
-
+            modules = new Map();
         const handlers = {
-            ImportDeclaration: handleImports(context, includeExports, importsInFile, exportsInFile)
+            ImportDeclaration: handleImportsExports(
+                context,
+                modules,
+                "import",
+                includeExports
+            )
         };
 
         if (includeExports) {
-            handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile);
-            handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile);
+            handlers.ExportNamedDeclaration = handleImportsExports(
+                context,
+                modules,
+                "export",
+                includeExports
+            );
+            handlers.ExportAllDeclaration = handleImportsExports(
+                context,
+                modules,
+                "export",
+                includeExports
+            );
         }
-
         return handlers;
     }
 };
diff --git a/lib/rules/no-implicit-coercion.js b/lib/rules/no-implicit-coercion.js
index b9bb45489..993b8d1f1 100644
--- a/lib/rules/no-implicit-coercion.js
+++ b/lib/rules/no-implicit-coercion.js
@@ -109,6 +109,20 @@ function getNonNumericOperand(node) {
     return null;
 }
 
+/**
+ * Checks whether an expression evaluates to a string.
+ * @param {ASTNode} node node that represents the expression to check.
+ * @returns {boolean} Whether or not the expression evaluates to a string.
+ */
+function isStringType(node) {
+    return astUtils.isStringLiteral(node) ||
+        (
+            node.type === "CallExpression" &&
+            node.callee.type === "Identifier" &&
+            node.callee.name === "String"
+        );
+}
+
 /**
  * Checks whether a node is an empty string literal or not.
  * @param {ASTNode} node The node to check.
@@ -126,8 +140,8 @@ function isEmptyString(node) {
  */
 function isConcatWithEmptyString(node) {
     return node.operator === "+" && (
-        (isEmptyString(node.left) && !astUtils.isStringLiteral(node.right)) ||
-        (isEmptyString(node.right) && !astUtils.isStringLiteral(node.left))
+        (isEmptyString(node.left) && !isStringType(node.right)) ||
+        (isEmptyString(node.right) && !isStringType(node.left))
     );
 }
 
@@ -332,6 +346,11 @@ module.exports = {
                     return;
                 }
 
+                // if the expression is already a string, then this isn't a coercion
+                if (isStringType(node.expressions[0])) {
+                    return;
+                }
+
                 const code = sourceCode.getText(node.expressions[0]);
                 const recommendation = `String(${code})`;
 
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index adf465905..f04818f8e 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -449,18 +449,24 @@ module.exports = {
             return ref.isRead() && (
 
                 // self update. e.g. `a += 1`, `a++`
-                (// in RHS of an assignment for itself. e.g. `a = a + 1`
-                    ((
+                (
+                    (
                         parent.type === "AssignmentExpression" &&
-                    isUnusedExpression(parent) &&
-                    parent.left === id
+                        parent.left === id &&
+                        isUnusedExpression(parent)
                     ) ||
+                    (
+                        parent.type === "UpdateExpression" &&
+                        isUnusedExpression(parent)
+                    )
+                ) ||
+
+                // in RHS of an assignment for itself. e.g. `a = a + 1`
                 (
-                    parent.type === "UpdateExpression" &&
-                    isUnusedExpression(parent)
-                ) || rhsNode &&
-                isInside(id, rhsNode) &&
-                !isInsideOfStorableFunction(id, rhsNode)))
+                    rhsNode &&
+                    isInside(id, rhsNode) &&
+                    !isInsideOfStorableFunction(id, rhsNode)
+                )
             );
         }
 


Test patch:
diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js
index 976bd7657..bcf3edfb9 100644
--- a/tests/lib/linter/linter.js
+++ b/tests/lib/linter/linter.js
@@ -1559,6 +1559,22 @@ describe("Linter", () => {
             assert.strictEqual(messages[0].message, filename);
         });
 
+        it("has access to the physicalFilename", () => {
+            linter.defineRule(code, context => ({
+                Literal(node) {
+                    context.report(node, context.getPhysicalFilename());
+                }
+            }));
+
+            const config = { rules: {} };
+
+            config.rules[code] = 1;
+
+            const messages = linter.verify("0", config, filename);
+
+            assert.strictEqual(messages[0].message, filename);
+        });
+
         it("defaults filename to '<input>'", () => {
             linter.defineRule(code, context => ({
                 Literal(node) {
@@ -3408,6 +3424,41 @@ var a = "test2";
             });
         });
 
+        describe("physicalFilenames", () => {
+            it("should be same as `filename` passed on options object, if no processors are used", () => {
+                const physicalFilenameChecker = sinon.spy(context => {
+                    assert.strictEqual(context.getPhysicalFilename(), "foo.js");
+                    return {};
+                });
+
+                linter.defineRule("checker", physicalFilenameChecker);
+                linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" });
+                assert(physicalFilenameChecker.calledOnce);
+            });
+
+            it("should default physicalFilename to <input> when options object doesn't have filename", () => {
+                const physicalFilenameChecker = sinon.spy(context => {
+                    assert.strictEqual(context.getPhysicalFilename(), "<input>");
+                    return {};
+                });
+
+                linter.defineRule("checker", physicalFilenameChecker);
+                linter.verify("foo;", { rules: { checker: "error" } }, {});
+                assert(physicalFilenameChecker.calledOnce);
+            });
+
+            it("should default physicalFilename to <input> when only two arguments are passed", () => {
+                const physicalFilenameChecker = sinon.spy(context => {
+                    assert.strictEqual(context.getPhysicalFilename(), "<input>");
+                    return {};
+                });
+
+                linter.defineRule("checker", physicalFilenameChecker);
+                linter.verify("foo;", { rules: { checker: "error" } });
+                assert(physicalFilenameChecker.calledOnce);
+            });
+        });
+
         it("should report warnings in order by line and column when called", () => {
 
             const code = "foo()\n    alert('test')";
@@ -4783,14 +4834,17 @@ var a = "test2";
 
     describe("processors", () => {
         let receivedFilenames = [];
+        let receivedPhysicalFilenames = [];
 
         beforeEach(() => {
             receivedFilenames = [];
+            receivedPhysicalFilenames = [];
 
             // A rule that always reports the AST with a message equal to the source text
             linter.defineRule("report-original-text", context => ({
                 Program(ast) {
                     receivedFilenames.push(context.getFilename());
+                    receivedPhysicalFilenames.push(context.getPhysicalFilename());
                     context.report({ node: ast, message: context.getSourceCode().text });
                 }
             }));
@@ -4845,10 +4899,16 @@ var a = "test2";
 
                 assert.strictEqual(problems.length, 3);
                 assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
+
+                // filename
                 assert.strictEqual(receivedFilenames.length, 3);
                 assert(/^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0]));
                 assert(/^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1]));
                 assert(/^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2]));
+
+                // physical filename
+                assert.strictEqual(receivedPhysicalFilenames.length, 3);
+                assert.strictEqual(receivedPhysicalFilenames.every(name => name === filename), true);
             });
 
             it("should receive text even if a SourceCode object was given.", () => {
diff --git a/tests/lib/rules/no-duplicate-imports.js b/tests/lib/rules/no-duplicate-imports.js
index 42d35c857..e200bbc70 100644
--- a/tests/lib/rules/no-duplicate-imports.js
+++ b/tests/lib/rules/no-duplicate-imports.js
@@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/no-duplicate-imports"),
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, sourceType: "module" } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 12, sourceType: "module" } });
 
 ruleTester.run("no-duplicate-imports", rule, {
     valid: [
@@ -26,6 +26,9 @@ ruleTester.run("no-duplicate-imports", rule, {
         "import * as Foobar from \"async\";",
         "import \"foo\"",
         "import os from \"os\";\nexport { something } from \"os\";",
+        "import * as bar from \"os\";\nimport { baz } from \"os\";",
+        "import foo, * as bar from \"os\";\nimport { baz } from \"os\";",
+        "import foo, { bar } from \"os\";\nimport * as baz from \"os\";",
         {
             code: "import os from \"os\";\nexport { hello } from \"hello\";",
             options: [{ includeExports: true }]
@@ -45,6 +48,26 @@ ruleTester.run("no-duplicate-imports", rule, {
         {
             code: "import { merge } from \"lodash-es\";\nexport { merge as lodashMerge }",
             options: [{ includeExports: true }]
+        },
+        {
+            code: "export { something } from \"os\";\nexport * as os from \"os\";",
+            options: [{ includeExports: true }]
+        },
+        {
+            code: "import { something } from \"os\";\nexport * as os from \"os\";",
+            options: [{ includeExports: true }]
+        },
+        {
+            code: "import * as os from \"os\";\nexport { something } from \"os\";",
+            options: [{ includeExports: true }]
+        },
+        {
+            code: "import os from \"os\";\nexport * from \"os\";",
+            options: [{ includeExports: true }]
+        },
+        {
+            code: "export { something } from \"os\";\nexport * from \"os\";",
+            options: [{ includeExports: true }]
         }
     ],
     invalid: [
@@ -53,11 +76,22 @@ ruleTester.run("no-duplicate-imports", rule, {
             errors: [{ messageId: "import", data: { module: "fs" }, type: "ImportDeclaration" }]
         },
         {
-            code: "import { merge } from \"lodash-es\";import { find } from \"lodash-es\";",
+            code: "import { merge } from \"lodash-es\";\nimport { find } from \"lodash-es\";",
             errors: [{ messageId: "import", data: { module: "lodash-es" }, type: "ImportDeclaration" }]
         },
         {
-            code: "import { merge } from \"lodash-es\";import _ from \"lodash-es\";",
+            code: "import { merge } from \"lodash-es\";\nimport _ from \"lodash-es\";",
+            errors: [{ messageId: "import", data: { module: "lodash-es" }, type: "ImportDeclaration" }]
+        },
+        {
+            code: "import os from \"os\";\nimport { something } from \"os\";\nimport * as foobar from \"os\";",
+            errors: [
+                { messageId: "import", data: { module: "os" }, type: "ImportDeclaration" },
+                { messageId: "import", data: { module: "os" }, type: "ImportDeclaration" }
+            ]
+        },
+        {
+            code: "import * as modns from \"lodash-es\";\nimport { merge } from \"lodash-es\";\nimport { baz } from \"lodash-es\";",
             errors: [{ messageId: "import", data: { module: "lodash-es" }, type: "ImportDeclaration" }]
         },
         {
@@ -66,7 +100,7 @@ ruleTester.run("no-duplicate-imports", rule, {
             errors: [{ messageId: "export", data: { module: "os" }, type: "ExportNamedDeclaration" }]
         },
         {
-            code: "import os from \"os\"; export { os as foobar } from \"os\";\nexport { something } from \"os\";",
+            code: "import os from \"os\";\nexport { os as foobar } from \"os\";\nexport { something } from \"os\";",
             options: [{ includeExports: true }],
             errors: [
                 { messageId: "exportAs", data: { module: "os" }, type: "ExportNamedDeclaration" },
@@ -80,7 +114,27 @@ ruleTester.run("no-duplicate-imports", rule, {
             errors: [{ messageId: "exportAs", data: { module: "os" }, type: "ExportNamedDeclaration" }]
         },
         {
-            code: "import os from \"os\";\nexport * from \"os\";",
+            code: "import os from \"os\";\nexport * as os from \"os\";",
+            options: [{ includeExports: true }],
+            errors: [{ messageId: "exportAs", data: { module: "os" }, type: "ExportAllDeclaration" }]
+        },
+        {
+            code: "export * as os from \"os\";\nimport os from \"os\";",
+            options: [{ includeExports: true }],
+            errors: [{ messageId: "importAs", data: { module: "os" }, type: "ImportDeclaration" }]
+        },
+        {
+            code: "import * as modns from \"mod\";\nexport * as  modns from \"mod\";",
+            options: [{ includeExports: true }],
+            errors: [{ messageId: "exportAs", data: { module: "mod" }, type: "ExportAllDeclaration" }]
+        },
+        {
+            code: "export * from \"os\";\nexport * from \"os\";",
+            options: [{ includeExports: true }],
+            errors: [{ messageId: "export", data: { module: "os" }, type: "ExportAllDeclaration" }]
+        },
+        {
+            code: "import \"os\";\nexport * from \"os\";",
             options: [{ includeExports: true }],
             errors: [{ messageId: "exportAs", data: { module: "os" }, type: "ExportAllDeclaration" }]
         }
diff --git a/tests/lib/rules/no-implicit-coercion.js b/tests/lib/rules/no-implicit-coercion.js
index fa0a63824..f7ca9dcff 100644
--- a/tests/lib/rules/no-implicit-coercion.js
+++ b/tests/lib/rules/no-implicit-coercion.js
@@ -95,7 +95,16 @@ ruleTester.run("no-implicit-coercion", rule, {
         { code: "`${foo}`", parserOptions: { ecmaVersion: 6 } },
         { code: "`${foo}`", options: [{ }], parserOptions: { ecmaVersion: 6 } },
         { code: "`${foo}`", options: [{ disallowTemplateShorthand: false }], parserOptions: { ecmaVersion: 6 } },
-        "+42"
+        "+42",
+
+        // https://github.com/eslint/eslint/issues/14623
+        "'' + String(foo)",
+        "String(foo) + ''",
+        { code: "`` + String(foo)", parserOptions: { ecmaVersion: 6 } },
+        { code: "String(foo) + ``", parserOptions: { ecmaVersion: 6 } },
+        { code: "`${'foo'}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${`foo`}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${String(foo)}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } }
     ],
     invalid: [
         {