Skip to content

Commit e2f0f0a

Browse files
authored
refactor(linter): improve docs and simplify code of eslint/no-duplicate-imports (#11320)
1 parent 9798ef1 commit e2f0f0a

File tree

1 file changed

+57
-83
lines changed

1 file changed

+57
-83
lines changed

crates/oxc_linter/src/rules/eslint/no_duplicate_imports.rs

Lines changed: 57 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
106115
enum ImportType {
@@ -119,10 +128,10 @@ enum ModuleType {
119128

120129
impl 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 {
302302
fn 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

Comments
 (0)