diff --git a/CHANGELOG.md b/CHANGELOG.md index 88d7c411f7..894e6c18c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - `importType`: avoid crashing on a non-string' ([#2305], thanks [@ljharb]) - [`first`]: prevent crash when parsing angular templates ([#2210], thanks [@ljharb]) - `importType`: properly resolve `@/*`-aliased imports as internal ([#2334], thanks [@ombene]) +- `named`: avoid false positives when importing from a CommonJS module that uses `import()` ([#2341], thanks [@ludofischer]) ### Changed - [`no-default-import`]: report on the token "default" instead of the entire node ([#2299], thanks [@pmcelhaney]) diff --git a/src/ExportMap.js b/src/ExportMap.js index d818fa6ca8..9374c4f656 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -43,6 +43,13 @@ export default class ExportMap { */ this.imports = new Map(); this.errors = []; + /** + * true when we are still unsure if + * the module is ESM, happens when the module + * contains dynamic `import()` but no other + * ESM import/export + */ + this.maybeNotEsm = false; } get hasDefault() { return this.get('default') != null; } // stronger than this.has @@ -406,7 +413,8 @@ ExportMap.parse = function (path, content, context) { }, }); - if (!unambiguous.isModule(ast) && !hasDynamicImports) return null; + const maybeNotEsm = !unambiguous.isModule(ast); + if (maybeNotEsm && !hasDynamicImports) return null; const docstyle = (context.settings && context.settings['import/docstyle']) || ['jsdoc']; const docStyleParsers = {}; @@ -710,6 +718,7 @@ ExportMap.parse = function (path, content, context) { m.namespace.set('default', {}); // add default export } + m.maybeNotEsm = maybeNotEsm; return m; }; diff --git a/src/rules/named.js b/src/rules/named.js index 24e6bc0ac5..ffc787001e 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -40,7 +40,7 @@ module.exports = { } const imports = Exports.get(node.source.value, context); - if (imports == null) { + if (imports == null || imports.maybeNotEsm) { return; } @@ -97,6 +97,7 @@ module.exports = { // return if it's not a string source || source.type !== 'Literal' || variableExports == null + || variableExports.maybeNotEsm ) { return; } diff --git a/tests/files/dynamic-import-in-commonjs.js b/tests/files/dynamic-import-in-commonjs.js new file mode 100644 index 0000000000..259feb4cd9 --- /dev/null +++ b/tests/files/dynamic-import-in-commonjs.js @@ -0,0 +1,5 @@ +async function doSomething() { + await import('./bar.js'); +} + +exports.something = 'hello'; diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 992baa0fd0..0e87759af6 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -32,6 +32,9 @@ ruleTester.run('named', rule, { test({ code: 'import { jsxFoo } from "./jsx/AnotherComponent"', settings: { 'import/resolve': { 'extensions': ['.js', '.jsx'] } } }), + test({ code: 'import { something } from "./dynamic-import-in-commonjs"', + parserOptions: { ecmaVersion: 2021 } }), + // validate that eslint-disable-line silences this properly test({ code: 'import {a, b, d} from "./common"; ' + '// eslint-disable-line named' }), @@ -165,6 +168,11 @@ ruleTester.run('named', rule, { options: [{ commonjs: true }], }), + test({ code: 'const { something } = require("./dynamic-import-in-commonjs")', + parserOptions: { ecmaVersion: 2021 }, + options: [{ commonjs: true }], + }), + test({ code: 'const { baz } = require("./bar")', errors: [error('baz', './bar')],