From 69d68e0de24590ffcde573d4b2964d411f132b1d Mon Sep 17 00:00:00 2001 From: morgan-coded <256248948+morgan-coded@users.noreply.github.com> Date: Wed, 3 Jun 2026 00:31:22 -0500 Subject: [PATCH 1/2] [Fix] `export`: do not flag `export type *` re-exports as duplicate of a value re-export --- CHANGELOG.md | 4 ++++ src/rules/export.js | 2 +- tests/files/export-type-star/m.ts | 3 +++ tests/src/rules/export.js | 16 +++++++++++++++- 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 tests/files/export-type-star/m.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e148eb2e4..6fc229baa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ 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]) - [`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]) @@ -1195,6 +1196,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 @@ -1212,6 +1214,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 @@ -2029,6 +2032,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 diff --git a/src/rules/export.js b/src/rules/export.js index fbbc39d75f..d44ca6b14e 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -194,7 +194,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'); } }); diff --git a/tests/files/export-type-star/m.ts b/tests/files/export-type-star/m.ts new file mode 100644 index 0000000000..4ef784995d --- /dev/null +++ b/tests/files/export-type-star/m.ts @@ -0,0 +1,3 @@ +export interface Foo {} + +export function f(foo: Foo): void {} diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 943bd72037..ac5a6caf03 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -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'; @@ -337,6 +337,20 @@ 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, + }) : [], + semver.satisfies(eslintPkg.version, '>= 6') ? [ test({ code: ` From e9093637d3b2773c2fce6a4bdd60a0e337578f06 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 4 Jun 2026 15:09:16 -0700 Subject: [PATCH 2/2] [Fix] `export`: do not flag type-only named re-exports as duplicate of a value re-export Handle `export type { x }` (declaration `exportKind`) and `export { type x }` (specifier `exportKind`) the same way `export type *` is, routing the name into the type namespace so it does not collide with a same-named value re-export. --- CHANGELOG.md | 1 + src/rules/export.js | 4 ++++ tests/src/rules/export.js | 21 +++++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fc229baa4..961a47b73c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### 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]) diff --git a/src/rules/export.js b/src/rules/export.js index d44ca6b14e..4a59272de7 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -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, ); }, diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index ac5a6caf03..fa22538f12 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -351,6 +351,27 @@ context('TypeScript', function () { ...parserConfig, }) : [], + // 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: `