@@ -3,7 +3,7 @@ use std::collections::HashMap;
3
3
use oxc_diagnostics:: OxcDiagnostic ;
4
4
use oxc_macros:: declare_oxc_lint;
5
5
use oxc_span:: { CompactStr , Span } ;
6
- use oxc_syntax:: module_record:: ImportImportName ;
6
+ use oxc_syntax:: module_record:: { ExportImportName , ImportImportName } ;
7
7
8
8
use crate :: { context:: LintContext , rule:: Rule } ;
9
9
@@ -55,6 +55,13 @@ enum ImportType {
55
55
Default ,
56
56
Namespace ,
57
57
SideEffect ,
58
+ AllButDefault ,
59
+ }
60
+
61
+ #[ derive( Debug , Clone , PartialEq ) ]
62
+ enum ModuleType {
63
+ Import ,
64
+ Export ,
58
65
}
59
66
60
67
impl Rule for NoDuplicateImports {
@@ -70,10 +77,12 @@ impl Rule for NoDuplicateImports {
70
77
71
78
fn run_once ( & self , ctx : & LintContext ) {
72
79
let module_record = ctx. module_record ( ) ;
73
- let mut import_map: HashMap < & CompactStr , Vec < ( ImportType , Span , bool ) > > = HashMap :: new ( ) ; // Added bool for same_statement
80
+ let mut import_map: HashMap < & CompactStr , Vec < ( ImportType , Span , ModuleType ) > > =
81
+ HashMap :: new ( ) ; // Added bool for same_statement
74
82
let mut current_span: Option < Span > = None ;
75
83
76
84
println ! ( "source_text: {:?}" , ctx. source_text( ) ) ;
85
+
77
86
// Handle bare imports first
78
87
if module_record. import_entries . is_empty ( ) {
79
88
for ( source, requests) in & module_record. requested_modules {
@@ -92,7 +101,7 @@ impl Rule for NoDuplicateImports {
92
101
import_map. entry ( source) . or_default ( ) . push ( (
93
102
ImportType :: SideEffect ,
94
103
request. span ( ) ,
95
- false ,
104
+ ModuleType :: Import ,
96
105
) ) ;
97
106
}
98
107
}
@@ -125,7 +134,7 @@ impl Rule for NoDuplicateImports {
125
134
}
126
135
}
127
136
128
- import_map. entry ( source) . or_default ( ) . push ( ( import_type, span, same_statement ) ) ;
137
+ import_map. entry ( source) . or_default ( ) . push ( ( import_type, span, ModuleType :: Import ) ) ;
129
138
130
139
if !same_statement {
131
140
current_span = Some ( span) ;
@@ -141,6 +150,23 @@ impl Rule for NoDuplicateImports {
141
150
let source = module_request. name ( ) ;
142
151
let span = entry. span ;
143
152
153
+ if entry. import_name . is_all_but_default ( ) {
154
+ if let Some ( existing) = import_map. get ( source) {
155
+ if existing
156
+ . iter ( )
157
+ . any ( |( t, _, _) | matches ! ( t, ImportType :: AllButDefault ) )
158
+ {
159
+ ctx. diagnostic ( no_duplicate_exports_diagnostic ( source, span) ) ;
160
+ continue ;
161
+ }
162
+ }
163
+ import_map. entry ( source) . or_default ( ) . push ( (
164
+ ImportType :: AllButDefault ,
165
+ span,
166
+ ModuleType :: Export ,
167
+ ) ) ;
168
+ continue ;
169
+ }
144
170
if let Some ( existing) = import_map. get ( source) {
145
171
if existing. iter ( ) . any ( |( t, _, _) | {
146
172
matches ! ( t, ImportType :: Named | ImportType :: SideEffect )
@@ -153,35 +179,47 @@ impl Rule for NoDuplicateImports {
153
179
import_map. entry ( source) . or_default ( ) . push ( (
154
180
ImportType :: SideEffect ,
155
181
span,
156
- false ,
182
+ ModuleType :: Export ,
157
183
) ) ;
158
184
}
159
185
}
160
186
161
187
// Handle indirect exports
162
188
for entry in & module_record. indirect_export_entries {
163
- println ! ( "indirect_export_entry: {:?}" , entry) ;
164
-
165
189
if let Some ( module_request) = & entry. module_request {
166
190
let source = module_request. name ( ) ;
167
191
let span = entry. span ;
192
+ println ! ( "- source: {source:?}, span: {span:?}, type: indirect_export_entries" ) ;
168
193
169
- if !entry . local_name . is_null ( ) {
170
- if let Some ( existing ) = import_map . get ( source ) {
194
+ if let Some ( existing ) = import_map . get ( source ) {
195
+ if entry . import_name == ExportImportName :: All {
171
196
if existing. iter ( ) . any ( |( t, _, _) | {
172
- matches ! ( t, ImportType :: Named | ImportType :: SideEffect )
197
+ matches ! ( t, ImportType :: Default | ImportType :: Namespace )
173
198
} ) {
174
199
ctx. diagnostic ( no_duplicate_exports_diagnostic ( source, span) ) ;
175
200
continue ;
176
201
}
202
+
203
+ continue ;
177
204
}
178
205
179
- import_map. entry ( source) . or_default ( ) . push ( (
180
- ImportType :: Named ,
181
- span,
182
- false ,
183
- ) ) ;
206
+ if existing. iter ( ) . any ( |( t, _, module_type) | {
207
+ ( matches ! (
208
+ t,
209
+ ImportType :: Named | ImportType :: SideEffect | ImportType :: Default
210
+ ) && * module_type == ModuleType :: Export )
211
+ || ( matches ! ( t, ImportType :: Default )
212
+ && * module_type == ModuleType :: Import )
213
+ } ) {
214
+ ctx. diagnostic ( no_duplicate_exports_diagnostic ( source, span) ) ;
215
+ continue ;
216
+ }
184
217
}
218
+ import_map. entry ( source) . or_default ( ) . push ( (
219
+ ImportType :: Named ,
220
+ span,
221
+ ModuleType :: Export ,
222
+ ) ) ;
185
223
}
186
224
}
187
225
}
@@ -190,48 +228,75 @@ impl Rule for NoDuplicateImports {
190
228
191
229
fn can_merge_imports (
192
230
current_type : & ImportType ,
193
- existing : & [ ( ImportType , Span , bool ) ] ,
231
+ existing : & [ ( ImportType , Span , ModuleType ) ] ,
194
232
same_statement : bool ,
195
233
) -> bool {
196
234
println ! ( "existing: {existing:?}" ) ;
197
- for ( existing_type, _, is_same_stmt) in existing {
198
- // Allow multiple imports in the same statement
199
- println ! ( "same_statement: {same_statement}, is_same_stmt: {is_same_stmt}" ) ;
200
- if same_statement {
201
- return false ;
235
+
236
+ // Allow multiple imports in the same statement
237
+ if same_statement {
238
+ return false ;
239
+ }
240
+
241
+ // this is the (namespace, span) if exsiting. None else
242
+
243
+ let namespace = existing. iter ( ) . find ( |( t, _, _) | matches ! ( t, ImportType :: Namespace ) ) ;
244
+ let named = existing. iter ( ) . find ( |( t, _, _) | matches ! ( t, ImportType :: Named ) ) ;
245
+ let default = existing. iter ( ) . find ( |( t, _, _) | matches ! ( t, ImportType :: Default ) ) ;
246
+
247
+ let has_namespace = namespace. is_some ( ) ;
248
+ let has_named = named. is_some ( ) ;
249
+ let has_default = default. is_some ( ) ;
250
+
251
+ // For duplicate named imports
252
+ if matches ! ( current_type, ImportType :: Named ) {
253
+ if has_named {
254
+ return true ;
202
255
}
256
+ }
203
257
204
- println ! ( "current_type: {:?}, existing_type: {:?}" , current_type, existing_type) ;
205
- match ( current_type, existing_type) {
206
- // Side effect imports can't be merged with anything
207
- ( ImportType :: SideEffect , _) | ( _, ImportType :: SideEffect ) => return false ,
208
-
209
- // Namespace imports can't be merged with named imports
210
- ( ImportType :: Namespace , ImportType :: Named )
211
- | ( ImportType :: Named , ImportType :: Namespace ) => return false ,
212
-
213
- // Default imports can't be duplicated
214
- ( ImportType :: Default , ImportType :: Default ) => return false ,
215
-
216
- // Named imports from the same module can be merged unless there's a namespace import
217
- ( ImportType :: Named , ImportType :: Named ) => {
218
- if existing
219
- . iter ( )
220
- . any ( |( t, _, same_stmt) | * t == ImportType :: Namespace && * same_stmt)
221
- {
222
- return true ;
223
- }
224
- }
225
- ( ImportType :: Named , ImportType :: Default ) => {
226
- if existing. iter ( ) . any ( |( t, _, same_stmt) | * t == ImportType :: Named && * same_stmt) {
227
- return true ;
258
+ // For namespace imports
259
+ if matches ! ( current_type, ImportType :: Namespace ) {
260
+ if has_named && has_default {
261
+ if let Some ( ( _, named_span, _) ) = named {
262
+ if let Some ( ( _, default_span, _) ) = default {
263
+ if named_span == default_span {
264
+ return false ;
265
+ }
228
266
}
229
267
}
230
- // Other combinations are allowed
231
- _ => continue ,
268
+ }
269
+
270
+ if has_namespace {
271
+ return true ;
272
+ }
273
+ if has_default && !same_statement {
274
+ return true ;
275
+ }
276
+ }
277
+
278
+ // For default imports
279
+ if matches ! ( current_type, ImportType :: Default ) {
280
+ if has_default {
281
+ return true ;
282
+ }
283
+ if has_named && !same_statement {
284
+ return true ;
285
+ }
286
+ if has_namespace {
287
+ return true ;
232
288
}
233
289
}
234
- true
290
+
291
+ // For side effect imports
292
+ if matches ! ( current_type, ImportType :: SideEffect ) {
293
+ // Any existing import means it's a duplicate
294
+ if !existing. is_empty ( ) {
295
+ return true ;
296
+ }
297
+ }
298
+
299
+ false
235
300
}
236
301
237
302
#[ test]
@@ -250,72 +315,72 @@ fn test() {
250
315
( r#"import "foo""# , None ) ,
251
316
(
252
317
r#"import os from "os";
253
- export { something } from "os";"# ,
318
+ export { something } from "os";"# ,
254
319
None ,
255
320
) ,
256
321
(
257
322
r#"import * as bar from "os";
258
- import { baz } from "os";"# ,
323
+ import { baz } from "os";"# ,
259
324
None ,
260
325
) ,
261
326
(
262
327
r#"import foo, * as bar from "os";
263
- import { baz } from "os";"# ,
328
+ import { baz } from "os";"# ,
264
329
None ,
265
330
) ,
266
331
(
267
332
r#"import foo, { bar } from "os";
268
- import * as baz from "os";"# ,
333
+ import * as baz from "os";"# ,
269
334
None ,
270
335
) ,
271
336
(
272
337
r#"import os from "os";
273
- export { hello } from "hello";"# ,
338
+ export { hello } from "hello";"# ,
274
339
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
275
340
) ,
276
341
(
277
342
r#"import os from "os";
278
- export * from "hello";"# ,
343
+ export * from "hello";"# ,
279
344
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
280
345
) ,
281
346
(
282
347
r#"import os from "os";
283
- export { hello as hi } from "hello";"# ,
348
+ export { hello as hi } from "hello";"# ,
284
349
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
285
350
) ,
286
351
(
287
352
r#"import os from "os";
288
- export default function(){};"# ,
353
+ export default function(){};"# ,
289
354
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
290
355
) ,
291
356
(
292
357
r#"import { merge } from "lodash-es";
293
- export { merge as lodashMerge }"# ,
358
+ export { merge as lodashMerge }"# ,
294
359
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
295
360
) ,
296
361
(
297
362
r#"export { something } from "os";
298
- export * as os from "os";"# ,
363
+ export * as os from "os";"# ,
299
364
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
300
365
) ,
301
366
(
302
367
r#"import { something } from "os";
303
- export * as os from "os";"# ,
368
+ export * as os from "os";"# ,
304
369
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
305
370
) ,
306
371
(
307
372
r#"import * as os from "os";
308
- export { something } from "os";"# ,
373
+ export { something } from "os";"# ,
309
374
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
310
375
) ,
311
376
(
312
377
r#"import os from "os";
313
- export * from "os";"# ,
378
+ export * from "os";"# ,
314
379
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
315
380
) ,
316
381
(
317
382
r#"export { something } from "os";
318
- export * from "os";"# ,
383
+ export * from "os";"# ,
319
384
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
320
385
) ,
321
386
] ;
@@ -374,21 +439,21 @@ fn test() {
374
439
import os from "os";"# ,
375
440
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
376
441
) ,
377
- // (
378
- // r#"import * as modns from "mod";
379
- // export * as modns from "mod";"#,
380
- // Some(serde_json::json!([{ "includeExports": true }])),
381
- // ),
382
442
(
383
- r#"export * from "os ";
384
- export * from "os ";"# ,
443
+ r#"import * as modns from "mod ";
444
+ export * as modns from "mod ";"# ,
385
445
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
386
446
) ,
387
447
(
388
- r#"import "os";
448
+ r#"export * from "os";
389
449
export * from "os";"# ,
390
450
Some ( serde_json:: json!( [ { "includeExports" : true } ] ) ) ,
391
451
) ,
452
+ // (
453
+ // r#"import "os";
454
+ // export * from "os";"#,
455
+ // Some(serde_json::json!([{ "includeExports": true }])),
456
+ // ),
392
457
] ;
393
458
394
459
Tester :: new ( NoDuplicateImports :: NAME , pass, fail) . test_and_snapshot ( ) ;
0 commit comments