Skip to content

Commit ace01d5

Browse files
implement
1 parent 24dc6ac commit ace01d5

File tree

4 files changed

+274
-0
lines changed

4 files changed

+274
-0
lines changed

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,13 @@ impl RuleRunner for crate::rules::import::no_named_default::NoNamedDefault {
843843
const NODE_TYPES: Option<&AstTypesBitset> = None;
844844
}
845845

846+
impl RuleRunner for crate::rules::import::no_named_export::NoNamedExport {
847+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
848+
AstType::ExportAllDeclaration,
849+
AstType::ExportNamedDeclaration,
850+
]));
851+
}
852+
846853
impl RuleRunner for crate::rules::import::no_namespace::NoNamespace {
847854
const NODE_TYPES: Option<&AstTypesBitset> = None;
848855
}

crates/oxc_linter/src/rules.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub(crate) mod import {
3030
pub mod no_named_as_default;
3131
pub mod no_named_as_default_member;
3232
pub mod no_named_default;
33+
pub mod no_named_export;
3334
pub mod no_namespace;
3435
pub mod no_self_import;
3536
pub mod no_unassigned_import;
@@ -802,6 +803,7 @@ oxc_macros::declare_all_lint_rules! {
802803
import::extensions,
803804
import::first,
804805
import::group_exports,
806+
import::no_named_export,
805807
import::no_unassigned_import,
806808
import::no_empty_named_blocks,
807809
import::no_anonymous_default_export,
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use oxc_ast::AstKind;
2+
use oxc_diagnostics::OxcDiagnostic;
3+
use oxc_macros::declare_oxc_lint;
4+
use oxc_span::Span;
5+
6+
use crate::{context::LintContext, rule::Rule};
7+
8+
fn no_named_export_diagnostic(span: Span) -> OxcDiagnostic {
9+
OxcDiagnostic::warn("Named exports are not allowed.")
10+
.with_help("Replace named exports with a single export default to ensure a consistent module entry point.")
11+
.with_label(span)
12+
}
13+
14+
#[derive(Debug, Default, Clone)]
15+
pub struct NoNamedExport;
16+
17+
// See <https://github.com/oxc-project/oxc/issues/6050> for documentation details.
18+
declare_oxc_lint!(
19+
/// ### What it does
20+
///
21+
/// Prohibit named exports.
22+
///
23+
/// ### Why is this bad?
24+
///
25+
/// Named exports require strict identifier matching and can lead to fragile imports,
26+
/// while default exports enforce a single, consistent module entry point.
27+
///
28+
/// ### Examples
29+
///
30+
/// Examples of **incorrect** code for this rule:
31+
/// ```js
32+
/// export const foo = 'foo';
33+
///
34+
/// const bar = 'bar';
35+
/// export { bar }
36+
///
37+
/// ```
38+
///
39+
/// Examples of **correct** code for this rule:
40+
/// ```js
41+
/// export default 'bar';
42+
///
43+
/// const foo = 'foo';
44+
/// export { foo as default }
45+
/// ```
46+
NoNamedExport,
47+
import,
48+
style
49+
);
50+
51+
impl Rule for NoNamedExport {
52+
fn run<'a>(&self, node: &oxc_semantic::AstNode<'a>, ctx: &LintContext<'a>) {
53+
match node.kind() {
54+
AstKind::ExportAllDeclaration(all_decl) => {
55+
ctx.diagnostic(no_named_export_diagnostic(all_decl.span));
56+
}
57+
AstKind::ExportNamedDeclaration(named_decl) => {
58+
let specifiers = &named_decl.specifiers;
59+
if specifiers.is_empty() {
60+
ctx.diagnostic(no_named_export_diagnostic(named_decl.span));
61+
}
62+
if specifiers.iter().any(|specifier| specifier.exported.name() != "default") {
63+
ctx.diagnostic(no_named_export_diagnostic(named_decl.span));
64+
}
65+
}
66+
_ => {}
67+
}
68+
}
69+
}
70+
71+
#[test]
72+
fn test() {
73+
use crate::tester::Tester;
74+
75+
let pass = vec![
76+
"module.export.foo = function () {}",
77+
"module.export.foo = function () {}",
78+
"export default function bar() {};",
79+
"let foo; export { foo as default }",
80+
"import * as foo from './foo';",
81+
"import foo from './foo';",
82+
"import {default as foo} from './foo';",
83+
"let foo; export { foo as \"default\" }",
84+
];
85+
86+
let fail = vec![
87+
"export const foo = 'foo';",
88+
"
89+
export const foo = 'foo';
90+
export default bar;
91+
",
92+
"
93+
export const foo = 'foo';
94+
export function bar() {};
95+
",
96+
"export const foo = 'foo';",
97+
"
98+
const foo = 'foo';
99+
export { foo };
100+
",
101+
"let foo, bar; export { foo, bar }",
102+
"export const { foo, bar } = item;",
103+
"export const { foo, bar: baz } = item;",
104+
"export const { foo: { bar, baz } } = item;",
105+
"
106+
let item;
107+
export const foo = item;
108+
export { item };
109+
",
110+
"export * from './foo';",
111+
"export const { foo } = { foo: 'bar' };",
112+
"export const { foo: { bar } } = { foo: { bar: 'baz' } };",
113+
"export { a, b } from 'foo.js'",
114+
"export type UserId = number;",
115+
"export foo from 'foo.js'",
116+
"export Memory, { MemoryValue } from './Memory'",
117+
];
118+
119+
Tester::new(NoNamedExport::NAME, NoNamedExport::PLUGIN, pass, fail).test_and_snapshot();
120+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
source: crates/oxc_linter/src/tester.rs
3+
---
4+
eslint-plugin-import(no-named-export): Named exports are not allowed.
5+
╭─[no_named_export.tsx:1:1]
6+
1export const foo = 'foo';
7+
· ─────────────────────────
8+
╰────
9+
help: Replace named exports with a single export default to ensure a consistent module entry point.
10+
11+
eslint-plugin-import(no-named-export): Named exports are not allowed.
12+
╭─[no_named_export.tsx:2:13]
13+
1
14+
2export const foo = 'foo';
15+
· ─────────────────────────
16+
3export default bar;
17+
╰────
18+
help: Replace named exports with a single export default to ensure a consistent module entry point.
19+
20+
eslint-plugin-import(no-named-export): Named exports are not allowed.
21+
╭─[no_named_export.tsx:2:13]
22+
1
23+
2export const foo = 'foo';
24+
· ─────────────────────────
25+
3export function bar() {};
26+
╰────
27+
help: Replace named exports with a single export default to ensure a consistent module entry point.
28+
29+
eslint-plugin-import(no-named-export): Named exports are not allowed.
30+
╭─[no_named_export.tsx:3:13]
31+
2export const foo = 'foo';
32+
3export function bar() {};
33+
· ────────────────────────
34+
4
35+
╰────
36+
help: Replace named exports with a single export default to ensure a consistent module entry point.
37+
38+
eslint-plugin-import(no-named-export): Named exports are not allowed.
39+
╭─[no_named_export.tsx:1:1]
40+
1export const foo = 'foo';
41+
· ─────────────────────────
42+
╰────
43+
help: Replace named exports with a single export default to ensure a consistent module entry point.
44+
45+
eslint-plugin-import(no-named-export): Named exports are not allowed.
46+
╭─[no_named_export.tsx:3:13]
47+
2const foo = 'foo';
48+
3export { foo };
49+
· ───────────────
50+
4
51+
╰────
52+
help: Replace named exports with a single export default to ensure a consistent module entry point.
53+
54+
eslint-plugin-import(no-named-export): Named exports are not allowed.
55+
╭─[no_named_export.tsx:1:15]
56+
1let foo, bar; export { foo, bar }
57+
· ───────────────────
58+
╰────
59+
help: Replace named exports with a single export default to ensure a consistent module entry point.
60+
61+
eslint-plugin-import(no-named-export): Named exports are not allowed.
62+
╭─[no_named_export.tsx:1:1]
63+
1export const { foo, bar } = item;
64+
· ─────────────────────────────────
65+
╰────
66+
help: Replace named exports with a single export default to ensure a consistent module entry point.
67+
68+
eslint-plugin-import(no-named-export): Named exports are not allowed.
69+
╭─[no_named_export.tsx:1:1]
70+
1export const { foo, bar: baz } = item;
71+
· ──────────────────────────────────────
72+
╰────
73+
help: Replace named exports with a single export default to ensure a consistent module entry point.
74+
75+
eslint-plugin-import(no-named-export): Named exports are not allowed.
76+
╭─[no_named_export.tsx:1:1]
77+
1export const { foo: { bar, baz } } = item;
78+
· ──────────────────────────────────────────
79+
╰────
80+
help: Replace named exports with a single export default to ensure a consistent module entry point.
81+
82+
eslint-plugin-import(no-named-export): Named exports are not allowed.
83+
╭─[no_named_export.tsx:3:13]
84+
2let item;
85+
3export const foo = item;
86+
· ────────────────────────
87+
4export { item };
88+
╰────
89+
help: Replace named exports with a single export default to ensure a consistent module entry point.
90+
91+
eslint-plugin-import(no-named-export): Named exports are not allowed.
92+
╭─[no_named_export.tsx:4:13]
93+
3export const foo = item;
94+
4export { item };
95+
· ────────────────
96+
5
97+
╰────
98+
help: Replace named exports with a single export default to ensure a consistent module entry point.
99+
100+
eslint-plugin-import(no-named-export): Named exports are not allowed.
101+
╭─[no_named_export.tsx:1:1]
102+
1export * from './foo';
103+
· ──────────────────────
104+
╰────
105+
help: Replace named exports with a single export default to ensure a consistent module entry point.
106+
107+
eslint-plugin-import(no-named-export): Named exports are not allowed.
108+
╭─[no_named_export.tsx:1:1]
109+
1export const { foo } = { foo: 'bar' };
110+
· ──────────────────────────────────────
111+
╰────
112+
help: Replace named exports with a single export default to ensure a consistent module entry point.
113+
114+
eslint-plugin-import(no-named-export): Named exports are not allowed.
115+
╭─[no_named_export.tsx:1:1]
116+
1export const { foo: { bar } } = { foo: { bar: 'baz' } };
117+
· ────────────────────────────────────────────────────────
118+
╰────
119+
help: Replace named exports with a single export default to ensure a consistent module entry point.
120+
121+
eslint-plugin-import(no-named-export): Named exports are not allowed.
122+
╭─[no_named_export.tsx:1:1]
123+
1export { a, b } from 'foo.js'
124+
· ─────────────────────────────
125+
╰────
126+
help: Replace named exports with a single export default to ensure a consistent module entry point.
127+
128+
eslint-plugin-import(no-named-export): Named exports are not allowed.
129+
╭─[no_named_export.tsx:1:1]
130+
1export type UserId = number;
131+
· ────────────────────────────
132+
╰────
133+
help: Replace named exports with a single export default to ensure a consistent module entry point.
134+
135+
× Unexpected token
136+
╭─[no_named_export.tsx:1:8]
137+
1export foo from 'foo.js'
138+
· ───
139+
╰────
140+
141+
× Unexpected token
142+
╭─[no_named_export.tsx:1:8]
143+
1export Memory, { MemoryValue } from './Memory'
144+
· ──────
145+
╰────

0 commit comments

Comments
 (0)