Skip to content

Commit 6c4b533

Browse files
committedMar 23, 2025·
fix(linter): false positive in import/no-empty-named-blocks (#9974)
closes #9914
1 parent 402d8b7 commit 6c4b533

File tree

2 files changed

+46
-67
lines changed

2 files changed

+46
-67
lines changed
 

‎crates/oxc_linter/src/rules/import/no_empty_named_blocks.rs

+42-40
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
use oxc_ast::AstKind;
1+
use oxc_ast::{AstKind, ast::ImportDeclarationSpecifier};
22
use oxc_diagnostics::OxcDiagnostic;
33
use oxc_macros::declare_oxc_lint;
44
use oxc_span::Span;
55

66
use crate::{AstNode, context::LintContext, rule::Rule};
77

88
fn no_empty_named_blocks_diagnostic(span: Span) -> OxcDiagnostic {
9-
// See <https://oxc.rs/docs/contribute/linter/adding-rules.html#diagnostics> for details
109
OxcDiagnostic::warn("Unexpected empty named import block.").with_label(span)
1110
}
1211

@@ -16,12 +15,12 @@ pub struct NoEmptyNamedBlocks;
1615
declare_oxc_lint!(
1716
/// ### What it does
1817
///
19-
/// Enforces that named import blocks are not empty
18+
/// Enforces that named import blocks are not empty.
2019
///
2120
/// ### Why is this bad?
2221
///
23-
/// Empty named imports serve no practical purpose and
24-
/// often result from accidental deletions or tool-generated code.
22+
/// Empty named imports serve no practical purpose and often
23+
/// result from accidental deletions or tool-generated code.
2524
///
2625
/// ### Examples
2726
///
@@ -48,40 +47,41 @@ impl Rule for NoEmptyNamedBlocks {
4847
let AstKind::ImportDeclaration(import_decl) = node.kind() else {
4948
return;
5049
};
51-
// if "import 'foo'"
50+
5251
let Some(specifiers) = import_decl.specifiers.as_ref() else {
5352
return;
5453
};
54+
5555
if specifiers.is_empty() {
56-
// for "import {} from 'mod'"
56+
// import {} from 'mod'
5757
ctx.diagnostic_with_fix(no_empty_named_blocks_diagnostic(import_decl.span), |fixer| {
5858
fixer.delete_range(import_decl.span)
5959
});
60+
return;
6061
}
6162

62-
let source_token_str = Span::new(import_decl.span.start, import_decl.source.span.start - 1)
63-
.source_text(ctx.source_text());
64-
// find is there anything between '{' and '}'
65-
if let Some(start) = source_token_str.find('{') {
66-
if let Some(end) = source_token_str[start..].find('}') {
67-
let between_braces = &source_token_str[start + 1..start + end];
68-
if between_braces.trim().is_empty() {
69-
ctx.diagnostic_with_fix(
70-
no_empty_named_blocks_diagnostic(import_decl.span),
71-
|fixer| {
72-
// "import a, {} from 'mod" => "import a from 'mod'"
73-
// we just remove the space between ',' and '}'
74-
if let Some(comma_idx) = source_token_str[..start].rfind(',') {
75-
let remove_start = comma_idx as u32;
76-
let remove_end = (start + end + 1) as u32;
77-
fixer.delete_range(Span::new(remove_start, remove_end))
78-
} else {
79-
fixer.noop()
80-
}
81-
},
82-
);
63+
let [ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier)] = specifiers.as_slice()
64+
else {
65+
return;
66+
};
67+
68+
let span = Span::new(specifier.span.end, import_decl.source.span.start);
69+
let source_token_str = ctx.source_range(span);
70+
71+
// import Default, {} from 'mod'
72+
if let Some(start) = source_token_str.find(',') {
73+
let Some(end) = source_token_str[start..].find("from") else { return };
74+
75+
let start = span.start + start as u32;
76+
let span = Span::new(start, start + end as u32);
77+
78+
ctx.diagnostic_with_fix(no_empty_named_blocks_diagnostic(import_decl.span), |fixer| {
79+
if start == specifier.span.end {
80+
fixer.replace(span, " ")
81+
} else {
82+
fixer.delete_range(span)
8383
}
84-
}
84+
});
8585
}
8686
}
8787
}
@@ -108,20 +108,22 @@ fn test() {
108108
"import type {}from'mod'",
109109
"import type {} from 'mod'",
110110
"import type{}from 'mod'",
111+
"import{}from ''",
111112
];
112113

113114
let fix = vec![
114-
("import Default, {} from 'mod'", "import Default from 'mod'", None),
115-
("import { } from 'mod'", "", None),
116-
("import a, {} from 'mod'", "import a from 'mod'", None),
117-
("import a, { } from 'mod'", "import a from 'mod'", None),
118-
("import a, { } from 'mod'", "import a from 'mod'", None),
119-
("import a, { } from 'mod'", "import a from 'mod'", None),
120-
("import a, { } from'mod'", "import a from'mod'", None),
121-
("import type a, { } from'mod'", "import type a from'mod'", None),
122-
("import a,{} from 'mod'", "import a from 'mod'", None),
123-
("import type a,{} from 'foo'", "import type a from 'foo'", None),
124-
("import type {} from 'foo'", "", None),
115+
("import Default, {} from 'mod'", "import Default from 'mod'"),
116+
("import { } from 'mod'", ""),
117+
("import a, {} from 'mod'", "import a from 'mod'"),
118+
("import a, { } from 'mod'", "import a from 'mod'"),
119+
("import a, { } from 'mod'", "import a from 'mod'"),
120+
("import a, { } from 'mod'", "import a from 'mod'"),
121+
("import a, { } from'mod'", "import a from'mod'"),
122+
("import type a, { } from'mod'", "import type a from'mod'"),
123+
("import a,{} from 'mod'", "import a from 'mod'"),
124+
("import type a,{} from 'foo'", "import type a from 'foo'"),
125+
("import type {} from 'foo'", ""),
126+
("import{}from ''", ""),
125127
];
126128

127129
Tester::new(NoEmptyNamedBlocks::NAME, NoEmptyNamedBlocks::PLUGIN, pass, fail)

‎crates/oxc_linter/src/snapshots/import_no_empty_named_blocks.snap

+4-27
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,12 @@ source: crates/oxc_linter/src/tester.rs
88
╰────
99
help: Delete this code.
1010

11-
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
12-
╭─[no_empty_named_blocks.tsx:1:1]
13-
1import {} from 'mod'
14-
· ────────────────────
15-
╰────
16-
1711
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
1812
╭─[no_empty_named_blocks.tsx:1:1]
1913
1import Default, {} from 'mod'
2014
· ─────────────────────────────
2115
╰────
22-
help: Delete this code.
16+
help: Replace `, {} ` with ` `.
2317

2418
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
2519
╭─[no_empty_named_blocks.tsx:1:1]
@@ -28,38 +22,20 @@ source: crates/oxc_linter/src/tester.rs
2822
╰────
2923
help: Delete this code.
3024

31-
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
32-
╭─[no_empty_named_blocks.tsx:1:1]
33-
1import{}from'mod'
34-
· ─────────────────
35-
╰────
36-
3725
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
3826
╭─[no_empty_named_blocks.tsx:1:1]
3927
1import type {}from'mod'
4028
· ───────────────────────
4129
╰────
4230
help: Delete this code.
4331

44-
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
45-
╭─[no_empty_named_blocks.tsx:1:1]
46-
1import type {}from'mod'
47-
· ───────────────────────
48-
╰────
49-
5032
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
5133
╭─[no_empty_named_blocks.tsx:1:1]
5234
1import type {} from 'mod'
5335
· ─────────────────────────
5436
╰────
5537
help: Delete this code.
5638

57-
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
58-
╭─[no_empty_named_blocks.tsx:1:1]
59-
1import type {} from 'mod'
60-
· ─────────────────────────
61-
╰────
62-
6339
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
6440
╭─[no_empty_named_blocks.tsx:1:1]
6541
1import type{}from 'mod'
@@ -69,6 +45,7 @@ source: crates/oxc_linter/src/tester.rs
6945

7046
eslint-plugin-import(no-empty-named-blocks): Unexpected empty named import block.
7147
╭─[no_empty_named_blocks.tsx:1:1]
72-
1import type{}from 'mod'
73-
· ───────────────────────
48+
1import{}from ''
49+
· ───────────────
7450
╰────
51+
help: Delete this code.

0 commit comments

Comments
 (0)
Please sign in to comment.