@@ -10,21 +10,29 @@ use crate::{
1010 rule:: Rule ,
1111} ;
1212
13- fn no_duplicate_imports_diagnostic ( module_name : & str , span : Span , span2 : Span ) -> OxcDiagnostic {
13+ fn no_duplicate_imports_diagnostic (
14+ module_name : & str ,
15+ span : Span ,
16+ previous_span : Span ,
17+ ) -> OxcDiagnostic {
1418 OxcDiagnostic :: warn ( format ! ( "'{module_name}' import is duplicated" ) )
1519 . with_help ( "Merge the duplicated import into a single import statement" )
1620 . with_labels ( [
1721 span. label ( "This import is duplicated" ) ,
18- span2 . label ( "Can be merged with this import" ) ,
22+ previous_span . label ( "Can be merged with this import" ) ,
1923 ] )
2024}
2125
22- fn no_duplicate_exports_diagnostic ( module_name : & str , span : Span , span2 : Span ) -> OxcDiagnostic {
26+ fn no_duplicate_exports_diagnostic (
27+ module_name : & str ,
28+ span : Span ,
29+ previous_span : Span ,
30+ ) -> OxcDiagnostic {
2331 OxcDiagnostic :: warn ( format ! ( "'{module_name}' export is duplicated" ) )
2432 . with_help ( "Merge the duplicated exports into a single export statement" )
2533 . with_labels ( [
2634 span. label ( "This export is duplicated" ) ,
27- span2 . label ( "Can be merged with this" ) ,
35+ previous_span . label ( "Can be merged with this" ) ,
2836 ] )
2937}
3038
@@ -65,21 +73,21 @@ declare_oxc_lint!(
6573 ///
6674 /// #### includeExports
6775 ///
68- /// `{ "includeExports" : boolean }`
76+ /// `{ type : boolean, default: false }`
6977 ///
7078 /// When `true` this rule will also look at exports to see if there is both a re-export of a
7179 /// module as in `export ... from 'module'` and also a standard import statement for the same
7280 /// module. This would count as a rule violation because there are in a sense two statements
7381 /// importing from the same module.
7482 ///
75- /// Examples of **incorrect** when this rule is set to `true`
83+ /// Examples of **incorrect** code when `includeExports` is set to `true`:
7684 /// ```js
7785 /// import { merge } from 'module';
7886 ///
7987 /// export { find } from 'module'; // re-export which is an import and an export.
8088 /// ```
8189 ///
82- /// Examples of **correct** when this rule is set to `true`
90+ /// Examples of **correct** code when `includeExports` is set to `true`:
8391 ///
8492 /// If re-exporting from an imported module, you should add the imports to the
8593 /// `import` statement, and export that directly, not use `export ... from`.
@@ -100,7 +108,8 @@ declare_oxc_lint!(
100108 NoDuplicateImports ,
101109 eslint,
102110 style,
103- pending) ;
111+ pending
112+ ) ;
104113
105114#[ derive( Debug , Clone , PartialEq ) ]
106115enum ImportType {
@@ -119,10 +128,10 @@ enum ModuleType {
119128
120129impl Rule for NoDuplicateImports {
121130 fn from_configuration ( value : serde_json:: Value ) -> Self {
122- let Some ( value) = value. get ( 0 ) else { return Self { include_exports : false } } ;
131+ let value = value. get ( 0 ) ;
123132 Self {
124133 include_exports : value
125- . get ( "includeExports" )
134+ . and_then ( |v| v . get ( "includeExports" ) )
126135 . and_then ( serde_json:: Value :: as_bool)
127136 . unwrap_or ( false ) ,
128137 }
@@ -132,62 +141,54 @@ impl Rule for NoDuplicateImports {
132141 let module_record = ctx. module_record ( ) ;
133142 let mut import_map: FxHashMap < & CompactStr , Vec < ( ImportType , Span , ModuleType ) > > =
134143 FxHashMap :: default ( ) ;
135- let mut current_span : Option < Span > = None ;
144+ let mut previous_span : Option < Span > = None ;
136145 let mut side_effect_import_map: FxHashMap < & CompactStr , Vec < Span > > = FxHashMap :: default ( ) ;
137146
138147 for entry in & module_record. import_entries {
139148 let source = & entry. module_request . name ;
140149 let span = entry. module_request . span ;
141150
142- let same_statement = if let Some ( curr_span) = current_span {
143- curr_span == span
144- } else {
145- current_span = Some ( span) ;
146- true
147- } ;
148-
149151 let import_type = match & entry. import_name {
150152 ImportImportName :: Name ( _) => ImportType :: Named ,
151153 ImportImportName :: NamespaceObject => ImportType :: Namespace ,
152154 ImportImportName :: Default ( _) => ImportType :: Default ,
153155 } ;
154156
155- if let Some ( existing) = import_map. get ( source) {
156- let can_merge = can_merge_imports ( & import_type, existing, same_statement) ;
157- if can_merge {
158- ctx. diagnostic ( no_duplicate_imports_diagnostic (
159- source,
160- span,
161- existing. first ( ) . unwrap ( ) . 1 ,
162- ) ) ;
163- continue ;
157+ if previous_span != Some ( span) {
158+ previous_span = Some ( span) ;
159+
160+ if let Some ( existing) = import_map. get ( source) {
161+ if can_merge_imports ( & import_type, existing) {
162+ ctx. diagnostic ( no_duplicate_imports_diagnostic (
163+ source,
164+ span,
165+ existing. first ( ) . unwrap ( ) . 1 ,
166+ ) ) ;
167+ continue ;
168+ }
164169 }
165170 }
166171
167172 import_map. entry ( source) . or_default ( ) . push ( ( import_type, span, ModuleType :: Import ) ) ;
168-
169- if !same_statement {
170- current_span = Some ( span) ;
171- }
172173 }
173174
174- for ( source, requests) in & module_record. requested_modules {
175- for request in requests {
176- if request. is_import && module_record. import_entries . is_empty ( ) {
177- side_effect_import_map. entry ( source) . or_default ( ) . push ( request. span ) ;
175+ if module_record. import_entries . is_empty ( ) {
176+ for ( source, requests) in & module_record. requested_modules {
177+ for request in requests {
178+ if request. is_import {
179+ side_effect_import_map. entry ( source) . or_default ( ) . push ( request. span ) ;
180+ }
178181 }
179182 }
180- }
181183
182- for ( source, spans) in & side_effect_import_map {
183- if spans. len ( ) > 1 {
184- for span in spans {
185- let i = spans. iter ( ) . position ( |s| s == span) . unwrap ( ) ;
186- if i > 0 {
184+ for ( source, spans) in & side_effect_import_map {
185+ let mut spans_iter = spans. iter ( ) ;
186+ if let Some ( first_span) = spans_iter. next ( ) {
187+ for following_span in spans_iter {
187188 ctx. diagnostic ( no_duplicate_imports_diagnostic (
188189 source,
189- * span ,
190- * spans . first ( ) . unwrap ( ) ,
190+ * following_span ,
191+ * first_span ,
191192 ) ) ;
192193 }
193194 }
@@ -266,7 +267,6 @@ impl Rule for NoDuplicateImports {
266267 span,
267268 existing. first ( ) . unwrap ( ) . 1 ,
268269 ) ) ;
269- continue ;
270270 }
271271
272272 continue ;
@@ -302,12 +302,7 @@ impl Rule for NoDuplicateImports {
302302fn can_merge_imports (
303303 current_type : & ImportType ,
304304 existing : & [ ( ImportType , Span , ModuleType ) ] ,
305- same_statement : bool ,
306305) -> bool {
307- if same_statement {
308- return false ;
309- }
310-
311306 let namespace = existing. iter ( ) . find ( |( t, _, _) | matches ! ( t, ImportType :: Namespace ) ) ;
312307 let named = existing. iter ( ) . find ( |( t, _, _) | matches ! ( t, ImportType :: Named ) ) ;
313308 let default = existing. iter ( ) . find ( |( t, _, _) | matches ! ( t, ImportType :: Default ) ) ;
@@ -316,46 +311,25 @@ fn can_merge_imports(
316311 let has_named = named. is_some ( ) ;
317312 let has_default = default. is_some ( ) ;
318313
319- if matches ! ( current_type, ImportType :: Named ) && has_named {
320- return true ;
321- }
322-
323- if matches ! ( current_type, ImportType :: Namespace ) {
324- if has_named && has_default {
325- if let Some ( ( _, named_span, _) ) = named {
326- if let Some ( ( _, default_span, _) ) = default {
327- if named_span == default_span {
328- return false ;
314+ match current_type {
315+ ImportType :: Named => has_named,
316+ ImportType :: Namespace => {
317+ if has_named && has_default {
318+ if let Some ( ( _, named_span, _) ) = named {
319+ if let Some ( ( _, default_span, _) ) = default {
320+ if named_span == default_span {
321+ return false ;
322+ }
329323 }
330324 }
331325 }
332- }
333326
334- if has_namespace {
335- return true ;
336- }
337- if has_default && !same_statement {
338- return true ;
327+ has_namespace || has_default
339328 }
329+ ImportType :: Default => has_default || has_namespace || has_named,
330+ ImportType :: SideEffect => !existing. is_empty ( ) ,
331+ ImportType :: AllButDefault => false ,
340332 }
341-
342- if matches ! ( current_type, ImportType :: Default ) {
343- if has_default {
344- return true ;
345- }
346- if has_named && !same_statement {
347- return true ;
348- }
349- if has_namespace {
350- return true ;
351- }
352- }
353-
354- if matches ! ( current_type, ImportType :: SideEffect ) && !existing. is_empty ( ) {
355- return true ;
356- }
357-
358- false
359333}
360334
361335#[ test]
0 commit comments