Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
- [`no-deprecated`]: detect `@deprecated` on default-exported identifier declarations ([#3247], thanks [@mixelburg] [@etyrrell22])

### Fixed
- [`export`]: Fixed false positives for duplicate exports when a value export and `export type *` expose the same name. ([#3260], [#3136], thanks [@mykola-mokhnach] [@morgan-coded])
- [`export`]: Fixed false positives for duplicate exports when a value export and a type-only named re-export (`export type { x }` or `export { type x }`) expose the same name. ([#3136], thanks [@mykola-mokhnach] [@ljharb])
- [`no-duplicates`]: fix `prefer-inline` autofix producing invalid syntax when an identifier named `from` is imported ([#3236], thanks [@DukeDeSouth] [@Quantaly])
- [`no-duplicates`]: avoid false positives for TypeScript namespace and default type imports that cannot be merged ([#3195], thanks [@sjh9714] [@robyoder])
- ExportMap: resolve export * as ns re-exports under modern parsers ([#3250], thanks [@rasmi] [@JounQin] [@butterybread])
Expand Down Expand Up @@ -1195,6 +1197,7 @@ for info on changes for earlier releases.

[`memo-parser`]: ./memo-parser/README.md

[#3260]: https://github.com/import-js/eslint-plugin-import/pull/3260
[#3250]: https://github.com/import-js/eslint-plugin-import/pull/3250
[#3247]: https://github.com/import-js/eslint-plugin-import/pull/3247
[#3246]: https://github.com/import-js/eslint-plugin-import/pull/3246
Expand All @@ -1212,6 +1215,7 @@ for info on changes for earlier releases.
[#3152]: https://github.com/import-js/eslint-plugin-import/pull/3152
[#3151]: https://github.com/import-js/eslint-plugin-import/pull/3151
[#3138]: https://github.com/import-js/eslint-plugin-import/pull/3138
[#3136]: https://github.com/import-js/eslint-plugin-import/issues/3136
[#3129]: https://github.com/import-js/eslint-plugin-import/pull/3129
[#3128]: https://github.com/import-js/eslint-plugin-import/pull/3128
[#3127]: https://github.com/import-js/eslint-plugin-import/pull/3127
Expand Down Expand Up @@ -2029,6 +2033,7 @@ for info on changes for earlier releases.
[@msvab]: https://github.com/msvab
[@mulztob]: https://github.com/mulztob
[@mx-bernhard]: https://github.com/mx-bernhard
[@mykola-mokhnach]: https://github.com/mykola-mokhnach
[@Nfinished]: https://github.com/Nfinished
[@nickofthyme]: https://github.com/nickofthyme
[@nicolashenry]: https://github.com/nicolashenry
Expand Down
6 changes: 5 additions & 1 deletion src/rules/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,14 @@ module.exports = {
},

ExportSpecifier(node) {
// `export type { x }` sets exportKind on the declaration; `export { type x }` sets it on the specifier.
// Either way the name belongs to the type namespace and must not clash with a value re-export.
const isType = node.exportKind === 'type' || node.parent.exportKind === 'type';
addNamed(
node.exported.name || node.exported.value,
node.exported,
getParent(node.parent),
isType,
);
},

Expand Down Expand Up @@ -194,7 +198,7 @@ module.exports = {
remoteExports.forEach((v, name) => {
if (name !== 'default') {
any = true; // poor man's filter
addNamed(name, node, parent);
addNamed(name, node, parent, node.exportKind === 'type');
}
});

Expand Down
3 changes: 3 additions & 0 deletions tests/files/export-type-star/m.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface Foo {}

export function f(foo: Foo): void {}
37 changes: 36 additions & 1 deletion tests/src/rules/export.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, testFilePath, SYNTAX_CASES, getTSParsers, testVersion } from '../utils';
import { test, testFilePath, SYNTAX_CASES, getTSParsers, testVersion, typescriptEslintParserSatisfies } from '../utils';

import { RuleTester } from '../rule-tester';
import eslintPkg from 'eslint/package.json';
Expand Down Expand Up @@ -337,6 +337,41 @@ context('TypeScript', function () {
...parserConfig,
}),

// https://github.com/import-js/eslint-plugin-import/issues/3136
// `export type *` re-exports names into the type namespace, so they must
// not be reported as duplicates of a same-named value re-export.
// `export type *` is TypeScript 5.0 syntax, first parsed by `@typescript-eslint/parser` 5.54,
// so this is gated on the parser version (below), not on the TypeScript version.
typescriptEslintParserSatisfies('>= 5.54') ? test({
code: `
export { f } from './m';
export type * from './m';
`,
filename: testFilePath('export-type-star/index.ts'),
...parserConfig,
}) : [],
Comment thread
ljharb marked this conversation as resolved.

// https://github.com/import-js/eslint-plugin-import/issues/3136
// Named type-only re-exports (`export type { x }`) and inline type specifiers
// (`export { type x }`) also land in the type namespace, so they must not be
// reported as duplicates of a same-named value re-export.
typescriptEslintParserSatisfies('>= 5.54') ? test({
code: `
export { f } from './m';
export type { f } from './m';
`,
filename: testFilePath('export-type-star/index.ts'),
...parserConfig,
}) : [],
typescriptEslintParserSatisfies('>= 5.54') ? test({
code: `
export { f } from './m';
export { type f } from './m';
`,
filename: testFilePath('export-type-star/index.ts'),
...parserConfig,
}) : [],

semver.satisfies(eslintPkg.version, '>= 6') ? [
test({
code: `
Expand Down
Loading