diff --git a/CHANGELOG.md b/CHANGELOG.md index ed35b29ad..a0a7223ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed - [`extensions`]: ignore unresolveable type-only imports ([#2270], [#2271], [@jablko]) - `importType`: fix `isExternalModule` calculation ([#2282], [@mx-bernhard]) +- [`no-import-module-exports`]: avoid false positives with a shadowed `module` or `exports` ([#2297], [@ljharb]) ### Changed - [Docs] [`order`]: add type to the default groups ([#2272], [@charpeni]) @@ -939,6 +940,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#2297]: https://github.com/import-js/eslint-plugin-import/pull/2297 [#2287]: https://github.com/import-js/eslint-plugin-import/pull/2287 [#2282]: https://github.com/import-js/eslint-plugin-import/pull/2282 [#2279]: https://github.com/import-js/eslint-plugin-import/pull/2279 diff --git a/src/rules/no-import-module-exports.js b/src/rules/no-import-module-exports.js index 45710929c..60140752c 100644 --- a/src/rules/no-import-module-exports.js +++ b/src/rules/no-import-module-exports.js @@ -13,6 +13,12 @@ function getEntryPoint(context) { } } +function findScope(context, identifier) { + const scopeManager = context.getSourceCode().scopeManager; + + return scopeManager.scopes.slice().reverse().find((scope) => scope.variables.some(variable => variable.identifiers.some((node) => node.name === identifier))); +} + module.exports = { meta: { type: 'problem', @@ -43,10 +49,11 @@ module.exports = { const isEntryPoint = entryPoint === fileName; const isIdentifier = node.object.type === 'Identifier'; const hasKeywords = (/^(module|exports)$/).test(node.object.name); - const isException = options.exceptions && - options.exceptions.some(glob => minimatch(fileName, glob)); + const objectScope = hasKeywords && findScope(context, node.object.name); + const hasCJSExportReference = hasKeywords && (!objectScope || objectScope.type === 'module'); + const isException = !!options.exceptions && options.exceptions.some(glob => minimatch(fileName, glob)); - if (isIdentifier && hasKeywords && !isEntryPoint && !isException) { + if (isIdentifier && hasCJSExportReference && !isEntryPoint && !isException) { importDeclarations.forEach(importDeclaration => { context.report({ node: importDeclaration, diff --git a/tests/src/rules/no-import-module-exports.js b/tests/src/rules/no-import-module-exports.js index 9ffbe6b56..0c3718cef 100644 --- a/tests/src/rules/no-import-module-exports.js +++ b/tests/src/rules/no-import-module-exports.js @@ -64,6 +64,56 @@ ruleTester.run('no-import-module-exports', rule, { `, filename: path.join(process.cwd(), 'tests/files/missing-entrypoint/cli.js'), }), + test({ + code: ` + import fs from 'fs/promises'; + + const subscriptions = new Map(); + + export default async (client) => { + /** + * loads all modules and their subscriptions + */ + const modules = await fs.readdir('./src/modules'); + + await Promise.all( + modules.map(async (moduleName) => { + // Loads the module + const module = await import(\`./modules/\${moduleName}/module.js\`); + // skips the module, in case it is disabled. + if (module.enabled) { + // Loads each of it's subscriptions into their according list. + module.subscriptions.forEach((fun, event) => { + if (!subscriptions.has(event)) { + subscriptions.set(event, []); + } + subscriptions.get(event).push(fun); + }); + } + }) + ); + + /** + * Setting up all events. + * binds all events inside the subscriptions map to call all functions provided + */ + subscriptions.forEach((funs, event) => { + client.on(event, (...args) => { + funs.forEach(async (fun) => { + try { + await fun(client, ...args); + } catch (e) { + client.emit('error', e); + } + }); + }); + }); + }; + `, + parserOptions: { + ecmaVersion: 2020, + }, + }), ], invalid: [ test({