Skip to content

Commit

Permalink
[Fix] named, namespace: properly set reexports on `export * as … …
Browse files Browse the repository at this point in the history
…from`

Fixes #1998. Fixes #2161.
  • Loading branch information
ljharb committed Aug 18, 2021
1 parent b2bf591 commit 3ff4d77
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 38 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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])
Expand Down Expand Up @@ -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
Expand Down
66 changes: 37 additions & 29 deletions src/ExportMap.js
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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'];
Expand Down
12 changes: 6 additions & 6 deletions src/rules/namespace.js
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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}'.`,
);
}
},
Expand All @@ -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}'.`,
);
}

Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions tests/files/export-star-2/middle.js
@@ -0,0 +1 @@
export * as myName from './upstream';
1 change: 1 addition & 0 deletions tests/files/export-star-2/upstream.js
@@ -0,0 +1 @@
export const a = 1;
1 change: 1 addition & 0 deletions tests/files/export-star/extfield.js
@@ -0,0 +1 @@
export default 42;
1 change: 1 addition & 0 deletions tests/files/export-star/extfield2.js
@@ -0,0 +1 @@
export default NaN;
2 changes: 2 additions & 0 deletions tests/files/export-star/models.js
@@ -0,0 +1,2 @@
export * as ExtfieldModel from './extfield';
export * as Extfield2Model from './extfield2';
12 changes: 10 additions & 2 deletions 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';
Expand Down Expand Up @@ -182,10 +182,18 @@ ruleTester.run('named', rule, {
}),

...SYNTAX_CASES,

...[].concat(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') ] }),

Expand Down
14 changes: 13 additions & 1 deletion 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';

Expand Down Expand Up @@ -172,6 +172,18 @@ const valid = [
export const getExampleColor = () => color.example
`,
}),

...[].concat(testVersion('>= 6', () => ({
code: `
import * as middle from './middle';
console.log(middle.myName);
`,
filename: testFilePath('export-star-2/downstream.js'),
parserOptions: {
ecmaVersion: 2020,
},
})) || []),
];

const invalid = [
Expand Down

0 comments on commit 3ff4d77

Please sign in to comment.