Repository: eslint/eslint
Pull request #14658 (2021-06-02 11:15:51)

Upgrade: glob-parent version in package.json
<!--
    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
[ ] 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:

<!--
    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)


#### Is there anything you'd like reviewers to focus on?


mdjermanovic: Thanks for the PR! This version fixes a vulnerability.

Can you please sign our [CLA](https://cla.js.foundation/eslint/eslint?pullRequest=14658) so we could merge this? 

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/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)
+                )
             );
         }
 
diff --git a/package.json b/package.json
index 46c086010..cb95d5ed6 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
     "fast-deep-equal": "^3.1.3",
     "file-entry-cache": "^6.0.1",
     "functional-red-black-tree": "^1.0.1",
-    "glob-parent": "^5.0.0",
+    "glob-parent": "^5.1.2",
     "globals": "^13.6.0",
     "ignore": "^4.0.6",
     "import-fresh": "^3.0.0",


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: [
         {