From 3cc39fa008a1dd34b82a5e53623476a6e8dfce34 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 17 Aug 2021 21:44:33 -0700 Subject: [PATCH] =?UTF-8?q?[Fix]=20`named`,=20`namespace`:=20properly=20se?= =?UTF-8?q?t=20reexports=20on=20`export=20*=20as=20=E2=80=A6=20from`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1998. Fixes #2161. --- CHANGELOG.md | 3 ++ src/ExportMap.js | 66 +++++++++++++++------------ src/rules/namespace.js | 12 ++--- tests/files/export-star-2/middle.js | 1 + tests/files/export-star-2/upstream.js | 1 + tests/files/export-star/extfield.js | 1 + tests/files/export-star/extfield2.js | 1 + tests/files/export-star/models.js | 2 + tests/src/rules/named.js | 12 ++++- tests/src/rules/namespace.js | 14 +++++- 10 files changed, 75 insertions(+), 38 deletions(-) create mode 100644 tests/files/export-star-2/middle.js create mode 100644 tests/files/export-star-2/upstream.js create mode 100644 tests/files/export-star/extfield.js create mode 100644 tests/files/export-star/extfield2.js create mode 100644 tests/files/export-star/models.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 93ca6b6377..e59d246266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Fixed - `ExportMap`: Add default export when esModuleInterop is true and anything is exported ([#2184], thanks [@Maxim-Mazurok]) +- [`named`], [`namespace`]: properly set reexports on `export * as … from` ([#1998], [#2161], thanks [@ljharb]) ### Changed - [Docs] `max-dependencies`: 📖 Document `ignoreTypeImports` option ([#2196], thanks [@himynameisdave]) @@ -1145,10 +1146,12 @@ for info on changes for earlier releases. [#211]: https://github.com/import-js/eslint-plugin-import/pull/211 [#164]: https://github.com/import-js/eslint-plugin-import/pull/164 [#157]: https://github.com/import-js/eslint-plugin-import/pull/157 +[#2161]: https://github.com/import-js/eslint-plugin-import/issues/2161 [#2118]: https://github.com/import-js/eslint-plugin-import/issues/2118 [#2067]: https://github.com/import-js/eslint-plugin-import/issues/2067 [#2056]: https://github.com/import-js/eslint-plugin-import/issues/2056 [#2063]: https://github.com/import-js/eslint-plugin-import/issues/2063 +[#1998]: https://github.com/import-js/eslint-plugin-import/issues/1998 [#1965]: https://github.com/import-js/eslint-plugin-import/issues/1965 [#1924]: https://github.com/import-js/eslint-plugin-import/issues/1924 [#1854]: https://github.com/import-js/eslint-plugin-import/issues/1854 diff --git a/src/ExportMap.js b/src/ExportMap.js index 46d3cfe88c..f49841835b 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -414,6 +414,39 @@ ExportMap.parse = function (path, content, context) { return object; } + function processSpecifier(s, n, m) { + const nsource = n.source && n.source.value; + const exportMeta = {}; + let local; + + switch (s.type) { + case 'ExportDefaultSpecifier': + if (!n.source) return; + local = 'default'; + break; + case 'ExportNamespaceSpecifier': + m.namespace.set(s.exported.name, Object.defineProperty(exportMeta, 'namespace', { + get() { return resolveImport(nsource); }, + })); + return; + case 'ExportAllDeclaration': + local = s.exported ? s.exported.name : s.local.name; + break; + case 'ExportSpecifier': + if (!n.source) { + m.namespace.set(s.exported.name, addNamespace(exportMeta, s.local)); + return; + } + // else falls through + default: + local = s.local.name; + break; + } + + // todo: JSDoc + m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) }); + } + function captureDependency({ source }, isOnlyImportingTypes, importedSpecifiers = new Set()) { if (source == null) return null; @@ -489,6 +522,9 @@ ExportMap.parse = function (path, content, context) { if (n.type === 'ExportAllDeclaration') { const getter = captureDependency(n, n.exportKind === 'type'); if (getter) m.dependencies.add(getter); + if (n.exported) { + processSpecifier(n, n.exported, m); + } return; } @@ -546,35 +582,7 @@ ExportMap.parse = function (path, content, context) { } } - const nsource = n.source && n.source.value; - n.specifiers.forEach((s) => { - const exportMeta = {}; - let local; - - switch (s.type) { - case 'ExportDefaultSpecifier': - if (!n.source) return; - local = 'default'; - break; - case 'ExportNamespaceSpecifier': - m.namespace.set(s.exported.name, Object.defineProperty(exportMeta, 'namespace', { - get() { return resolveImport(nsource); }, - })); - return; - case 'ExportSpecifier': - if (!n.source) { - m.namespace.set(s.exported.name, addNamespace(exportMeta, s.local)); - return; - } - // else falls through - default: - local = s.local.name; - break; - } - - // todo: JSDoc - m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) }); - }); + n.specifiers.forEach((s) => processSpecifier(s, n, m)); } const exports = ['TSExportAssignment']; diff --git a/src/rules/namespace.js b/src/rules/namespace.js index a23cfeac88..74ee9dae3c 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -60,7 +60,7 @@ module.exports = { if (!imports.size) { context.report( specifier, - `No exported names found in module '${declaration.source.value}'.` + `No exported names found in module '${declaration.source.value}'.`, ); } namespaces.set(specifier.local.name, imports); @@ -69,7 +69,7 @@ module.exports = { case 'ImportSpecifier': { const meta = imports.get( // default to 'default' for default http://i.imgur.com/nj6qAWy.jpg - specifier.imported ? specifier.imported.name : 'default' + specifier.imported ? specifier.imported.name : 'default', ); if (!meta || !meta.namespace) { break; } namespaces.set(specifier.local.name, meta.namespace); @@ -96,7 +96,7 @@ module.exports = { if (!imports.size) { context.report( namespace, - `No exported names found in module '${declaration.source.value}'.` + `No exported names found in module '${declaration.source.value}'.`, ); } }, @@ -111,7 +111,7 @@ module.exports = { if (dereference.parent.type === 'AssignmentExpression' && dereference.parent.left === dereference) { context.report( dereference.parent, - `Assignment to member of namespace '${dereference.object.name}'.` + `Assignment to member of namespace '${dereference.object.name}'.`, ); } @@ -125,7 +125,7 @@ module.exports = { if (!allowComputed) { context.report( dereference.property, - `Unable to validate computed reference to imported namespace '${dereference.object.name}'.` + `Unable to validate computed reference to imported namespace '${dereference.object.name}'.`, ); } return; @@ -134,7 +134,7 @@ module.exports = { if (!namespace.has(dereference.property.name)) { context.report( dereference.property, - makeMessage(dereference.property, namepath) + makeMessage(dereference.property, namepath), ); break; } diff --git a/tests/files/export-star-2/middle.js b/tests/files/export-star-2/middle.js new file mode 100644 index 0000000000..2fc07cd9a3 --- /dev/null +++ b/tests/files/export-star-2/middle.js @@ -0,0 +1 @@ +export * as myName from './upstream'; diff --git a/tests/files/export-star-2/upstream.js b/tests/files/export-star-2/upstream.js new file mode 100644 index 0000000000..cc798ff50d --- /dev/null +++ b/tests/files/export-star-2/upstream.js @@ -0,0 +1 @@ +export const a = 1; diff --git a/tests/files/export-star/extfield.js b/tests/files/export-star/extfield.js new file mode 100644 index 0000000000..7a4e8a723a --- /dev/null +++ b/tests/files/export-star/extfield.js @@ -0,0 +1 @@ +export default 42; diff --git a/tests/files/export-star/extfield2.js b/tests/files/export-star/extfield2.js new file mode 100644 index 0000000000..5f2610afc7 --- /dev/null +++ b/tests/files/export-star/extfield2.js @@ -0,0 +1 @@ +export default NaN; diff --git a/tests/files/export-star/models.js b/tests/files/export-star/models.js new file mode 100644 index 0000000000..1091770f7f --- /dev/null +++ b/tests/files/export-star/models.js @@ -0,0 +1,2 @@ +export * as ExtfieldModel from './extfield'; +export * as Extfield2Model from './extfield2'; diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 57e40c91ac..dedd305b27 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -1,4 +1,4 @@ -import { test, SYNTAX_CASES, getTSParsers } from '../utils'; +import { test, SYNTAX_CASES, getTSParsers, testFilePath, testVersion } from '../utils'; import { RuleTester } from 'eslint'; import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'; @@ -182,10 +182,18 @@ ruleTester.run('named', rule, { }), ...SYNTAX_CASES, + + testVersion('>= 6', () => ({ + code: `import { ExtfieldModel, Extfield2Model } from './models';`, + filename: testFilePath('./export-star/downstream.js'), + parserOptions: { + sourceType: 'module', + ecmaVersion: 2020, + }, + })), ], invalid: [ - test({ code: 'import { somethingElse } from "./test-module"', errors: [ error('somethingElse', './test-module') ] }), diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index ce55bccc35..892e1e9609 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -1,4 +1,4 @@ -import { test, SYNTAX_CASES, getTSParsers } from '../utils'; +import { test, SYNTAX_CASES, getTSParsers, testVersion, testFilePath } from '../utils'; import { RuleTester } from 'eslint'; import flatMap from 'array.prototype.flatmap'; @@ -172,6 +172,18 @@ const valid = [ export const getExampleColor = () => color.example `, }), + + testVersion('>= 6', () => ({ + code: ` + import * as middle from './middle'; + + console.log(middle.myName); + `, + filename: testFilePath('export-star-2/downstream.js'), + parserOptions: { + ecmaVersion: 2020, + }, + })), ]; const invalid = [