From efd6be15a71a39f82b610f0e24804214dc6630d2 Mon Sep 17 00:00:00 2001 From: Richard Xia Date: Sat, 1 Feb 2020 21:16:21 -0800 Subject: [PATCH] [Fix] `no-unused-modules`: handle `export { default } from` syntax Fixes #1631 --- CHANGELOG.md | 3 ++ src/rules/no-unused-modules.js | 53 ++++++++++++++++++++++++- tests/files/no-unused-modules/file-0.js | 1 + tests/files/no-unused-modules/file-s.js | 1 + tests/src/rules/no-unused-modules.js | 9 ++++- 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 tests/files/no-unused-modules/file-s.js diff --git a/CHANGELOG.md b/CHANGELOG.md index e8fbdb5f7..381a23ff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`order`]: fix `isExternalModule` detect on windows ([#1651], thanks [@fisker]) - [`order`]: recognize ".." as a "parent" path ([#1658], thanks [@golopot]) - [`no-duplicates`]: fix fixer on cases with default import ([#1666], thanks [@golopot]) +- [`no-unused-modules`]: Handle `export { default } from` syntax ([#1631], thanks [@richardxia]) ## [2.20.1] - 2020-02-01 ### Fixed @@ -660,6 +661,7 @@ for info on changes for earlier releases. [#1658]: https://github.com/benmosher/eslint-plugin-import/pull/1658 [#1651]: https://github.com/benmosher/eslint-plugin-import/pull/1651 [#1635]: https://github.com/benmosher/eslint-plugin-import/issues/1635 +[#1631]: https://github.com/benmosher/eslint-plugin-import/issues/1631 [#1625]: https://github.com/benmosher/eslint-plugin-import/pull/1625 [#1620]: https://github.com/benmosher/eslint-plugin-import/pull/1620 [#1619]: https://github.com/benmosher/eslint-plugin-import/pull/1619 @@ -1113,3 +1115,4 @@ for info on changes for earlier releases. [@IvanGoncharov]: https://github.com/IvanGoncharov [@wschurman]: https://github.com/wschurman [@fisker]: https://github.com/fisker +[@richardxia]: https://github.com/richardxia diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 44606dc85..9468dc87d 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -66,8 +66,54 @@ const CLASS_DECLARATION = 'ClassDeclaration' const DEFAULT = 'default' const TYPE_ALIAS = 'TypeAlias' +/** + * List of imports per file. + * + * Represented by a two-level Map to a Set of identifiers. The upper-level Map + * keys are the paths to the modules containing the imports, while the + * lower-level Map keys are the paths to the files which are being imported + * from. Lastly, the Set of identifiers contains either names being imported + * or a special AST node name listed above (e.g ImportDefaultSpecifier). + * + * For example, if we have a file named foo.js containing: + * + * import { o2 } from './bar.js'; + * + * Then we will have a structure that looks like: + * + * Map { 'foo.js' => Map { 'bar.js' => Set { 'o2' } } } + * + * @type {Map>>} + */ const importList = new Map() + +/** + * List of exports per file. + * + * Represented by a two-level Map to an object of metadata. The upper-level Map + * keys are the paths to the modules containing the exports, while the + * lower-level Map keys are the specific identifiers or special AST node names + * being exported. The leaf-level metadata object at the moment only contains a + * `whereUsed` propoerty, which contains a Set of paths to modules that import + * the name. + * + * For example, if we have a file named bar.js containing the following exports: + * + * const o2 = 'bar'; + * export { o2 }; + * + * And a file named foo.js containing the following import: + * + * import { o2 } from './bar.js'; + * + * Then we will have a structure that looks like: + * + * Map { 'bar.js' => Map { 'o2' => { whereUsed: Set { 'foo.js' } } } } + * + * @type {Map>} + */ const exportList = new Map() + const ignoredFiles = new Set() const filesOutsideSrc = new Set() @@ -453,9 +499,12 @@ module.exports = { } } - const exportStatement = exports.get(exportedValue) + // exportsList will always map any imported value of 'default' to 'ImportDefaultSpecifier' + const exportsKey = exportedValue === DEFAULT ? IMPORT_DEFAULT_SPECIFIER : exportedValue + + const exportStatement = exports.get(exportsKey) - const value = exportedValue === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportedValue + const value = exportsKey === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportsKey if (typeof exportStatement !== 'undefined'){ if (exportStatement.whereUsed.size < 1) { diff --git a/tests/files/no-unused-modules/file-0.js b/tests/files/no-unused-modules/file-0.js index a5319b5fc..6b5cc71bc 100644 --- a/tests/files/no-unused-modules/file-0.js +++ b/tests/files/no-unused-modules/file-0.js @@ -11,3 +11,4 @@ import {q} from './file-q' export * from './file-n' export { default, o0, o3 } from './file-o' export { p } from './file-p' +import s from './file-s' diff --git a/tests/files/no-unused-modules/file-s.js b/tests/files/no-unused-modules/file-s.js new file mode 100644 index 000000000..86587470b --- /dev/null +++ b/tests/files/no-unused-modules/file-s.js @@ -0,0 +1 @@ +export { default } from './file-o' diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index cb3d4c103..ac15fd915 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -110,7 +110,8 @@ ruleTester.run('no-unused-modules', rule, { import * as l from './file-l' export * from './file-n' export { default, o0, o3 } from './file-o' - export { p } from './file-p'`, + export { p } from './file-p' + import s from './file-s'`, filename: testFilePath('./no-unused-modules/file-0.js'), errors: [ error(`exported declaration 'default' not used within other modules`), @@ -165,7 +166,11 @@ ruleTester.run('no-unused-modules', rule, { // // test for export from ruleTester.run('no-unused-modules', rule, { - valid: [], + valid: [ + test({ options: unusedExportsOptions, + code: `export { default } from './file-o'`, + filename: testFilePath('./no-unused-modules/file-s.js')}), + ], invalid: [ test({ options: unusedExportsOptions, code: `export { k } from '${testFilePath('./no-unused-modules/file-k.js')}'`,