1
- use oxc_ast:: AstKind ;
1
+ use oxc_ast:: { AstKind , ast :: ImportDeclarationSpecifier } ;
2
2
use oxc_diagnostics:: OxcDiagnostic ;
3
3
use oxc_macros:: declare_oxc_lint;
4
4
use oxc_span:: Span ;
5
5
6
6
use crate :: { AstNode , context:: LintContext , rule:: Rule } ;
7
7
8
8
fn no_empty_named_blocks_diagnostic ( span : Span ) -> OxcDiagnostic {
9
- // See <https://oxc.rs/docs/contribute/linter/adding-rules.html#diagnostics> for details
10
9
OxcDiagnostic :: warn ( "Unexpected empty named import block." ) . with_label ( span)
11
10
}
12
11
@@ -16,12 +15,12 @@ pub struct NoEmptyNamedBlocks;
16
15
declare_oxc_lint ! (
17
16
/// ### What it does
18
17
///
19
- /// Enforces that named import blocks are not empty
18
+ /// Enforces that named import blocks are not empty.
20
19
///
21
20
/// ### Why is this bad?
22
21
///
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.
25
24
///
26
25
/// ### Examples
27
26
///
@@ -48,40 +47,41 @@ impl Rule for NoEmptyNamedBlocks {
48
47
let AstKind :: ImportDeclaration ( import_decl) = node. kind ( ) else {
49
48
return ;
50
49
} ;
51
- // if "import 'foo'"
50
+
52
51
let Some ( specifiers) = import_decl. specifiers . as_ref ( ) else {
53
52
return ;
54
53
} ;
54
+
55
55
if specifiers. is_empty ( ) {
56
- // for " import {} from 'mod'"
56
+ // import {} from 'mod'
57
57
ctx. diagnostic_with_fix ( no_empty_named_blocks_diagnostic ( import_decl. span ) , |fixer| {
58
58
fixer. delete_range ( import_decl. span )
59
59
} ) ;
60
+ return ;
60
61
}
61
62
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)
83
83
}
84
- }
84
+ } ) ;
85
85
}
86
86
}
87
87
}
@@ -108,20 +108,22 @@ fn test() {
108
108
"import type {}from'mod'" ,
109
109
"import type {} from 'mod'" ,
110
110
"import type{}from 'mod'" ,
111
+ "import{}from ''" ,
111
112
] ;
112
113
113
114
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 ''" , "" ) ,
125
127
] ;
126
128
127
129
Tester :: new ( NoEmptyNamedBlocks :: NAME , NoEmptyNamedBlocks :: PLUGIN , pass, fail)
0 commit comments