diff --git a/crates/oxc_linter/src/rules/eslint/no_duplicate_imports.rs b/crates/oxc_linter/src/rules/eslint/no_duplicate_imports.rs index ea76821507134..821d41859456d 100644 --- a/crates/oxc_linter/src/rules/eslint/no_duplicate_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_duplicate_imports.rs @@ -278,6 +278,16 @@ impl Rule for NoDuplicateImports { } for entry in &module_record.indirect_export_entries { + // Skip checking `export { ... };` without `from` clause, matching ESLint behavior. + // + // In an export list, `entry.statement_span` points to the **import** statement, + // while `entry.span` points to the exported item, so they won't overlap. + // This is unlike in aggregated export (`export { ... } from '...'`) in which + // `entry.statement_span` points to the **export** statement. + if !entry.statement_span.contains_inclusive(entry.span) { + continue; + } + let Some(module_request) = &entry.module_request else { continue; }; @@ -678,6 +688,16 @@ fn test() { export type { Something } from "os";"#, Some(serde_json::json!([{ "includeExports": true }])), ), + ( + r#"import { M, MC } from "./types.ts"; + export { M, MC };"#, + Some(serde_json::json!([{ "includeExports": true }])), + ), + ( + r#"import type { M, MC } from "./types.ts"; + export type { M, MC };"#, + Some(serde_json::json!([{ "includeExports": true }])), + ), ( r#"export type { Something } from "os"; export * from "os";"#,