From 0e348084afe8c77e0fea04a912c8b83e1e458af3 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 21 Mar 2024 18:41:54 +0800 Subject: [PATCH] feat(linter/import) check type import in no_duplicates --- .../src/rules/import/no_duplicates.rs | 416 +++++++++++------- .../src/snapshots/no_duplicates.snap | 214 ++++++--- 2 files changed, 411 insertions(+), 219 deletions(-) diff --git a/crates/oxc_linter/src/rules/import/no_duplicates.rs b/crates/oxc_linter/src/rules/import/no_duplicates.rs index 241a06f32402a..c89f1f7897414 100644 --- a/crates/oxc_linter/src/rules/import/no_duplicates.rs +++ b/crates/oxc_linter/src/rules/import/no_duplicates.rs @@ -1,12 +1,16 @@ use itertools::Itertools; use oxc_diagnostics::miette::{miette, LabeledSpan, Severity}; use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use oxc_syntax::module_record::ImportImportName; use crate::{context::LintContext, rule::Rule}; /// #[derive(Debug, Default, Clone)] -pub struct NoDuplicates; +pub struct NoDuplicates { + prefer_inline: bool, +} declare_oxc_lint!( /// ### What it does @@ -17,6 +21,15 @@ declare_oxc_lint!( ); impl Rule for NoDuplicates { + fn from_configuration(value: serde_json::Value) -> Self { + Self { + prefer_inline: value + .get("preferInline") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false), + } + } + fn run_once(&self, ctx: &LintContext<'_>) { let module_record = ctx.semantic().module_record(); @@ -32,20 +45,49 @@ impl Rule for NoDuplicates { }) .group_by(|r| r.0.clone()); - for (_path, group) in &groups { - let labels = group - .into_iter() - .flat_map(|(_path, spans)| spans) - .map(|span| LabeledSpan::underline(*span)) - .collect::>(); - - if labels.len() > 1 { - ctx.diagnostic(miette!( - severity = Severity::Warning, - labels = labels, - "eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places" - )); + let check_duplicates = |spans: Option<&Vec<&Span>>| { + if let Some(spans) = spans { + if spans.len() > 1 { + let labels = + spans.iter().map(|span| LabeledSpan::underline(**span)).collect::>(); + ctx.diagnostic(miette!( + severity = Severity::Warning, + labels = labels, + "eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places" + )); + } } + }; + + for (_path, group) in &groups { + let has_type_import = module_record.import_entries.iter().any(|entry| entry.is_type); + // When prefer_inline is false, 0 is value, 1 is type named, 2 is type default or type namespace + // When prefer_inline is true, 0 is value and type named, 2 is type default or type namespace + let import_entries_maps = + group.into_iter().flat_map(|(_path, spans)| spans).into_group_map_by(|span| { + // We should early return if there is no type import + if !has_type_import { + return 0; + }; + for entry in &module_record.import_entries { + if entry.module_request.span() != **span { + continue; + } + + if entry.is_type { + return match entry.import_name { + ImportImportName::Name(_) => i8::from(!self.prefer_inline), + ImportImportName::NamespaceObject + | ImportImportName::Default(_) => 2, + }; + } + } + 0 + }); + + check_duplicates(import_entries_maps.get(&0)); + check_duplicates(import_entries_maps.get(&1)); + check_duplicates(import_entries_maps.get(&2)); } } } @@ -53,158 +95,234 @@ impl Rule for NoDuplicates { #[test] fn test() { use crate::tester::Tester; + use serde_json::json; let pass = vec![ - r#"import "./malformed.js""#, - r"import { x } from './foo'; import { y } from './bar'", - r#"import foo from "234artaf"; import { shoop } from "234q25ad""#, + (r#"import "./malformed.js""#, None), + (r"import { x } from './foo'; import { y } from './bar'", None), + (r#"import foo from "234artaf"; import { shoop } from "234q25ad""#, None), // r#"import { x } from './foo'; import type { y } from './foo'"#, // TODO: considerQueryString // r#"import x from './bar?optionX'; import y from './bar?optionY';"#, - r"import x from './foo'; import y from './bar';", + (r"import x from './foo'; import y from './bar';", None), // TODO: separate namespace // r#"import * as ns from './foo'; import {y} from './foo'"#, // r#"import {y} from './foo'; import * as ns from './foo'"#, // TypeScript - // TODO: distinguish type imports in module record - // r#"import type { x } from './foo'; import y from './foo'"#, - // r#"import type x from './foo'; import type y from './bar'"#, - // r#"import type {x} from './foo'; import type {y} from './bar'"#, - // r#"import type x from './foo'; import type {y} from './foo'"#, - // r#"import type {} from './module'; - // import {} from './module2';"#, - // r#"import type { Identifier } from 'module'; - - // declare module 'module2' { - // import type { Identifier } from 'module'; - // } - - // declare module 'module3' { - // import type { Identifier } from 'module'; - // }"#, - // r#"import { type x } from './foo'; import y from './foo'"#, - // r#"import { type x } from './foo'; import { y } from './foo'"#, - // r#"import { type x } from './foo'; import type y from 'foo'"#, + (r"import type { x } from './foo'; import y from './foo'", None), + (r"import type x from './foo'; import type y from './bar'", None), + (r"import type {x} from './foo'; import type {y} from './bar'", None), + (r"import type x from './foo'; import type {y} from './foo'", None), + ( + r"import type {} from './module'; + import {} from './module2';", + None, + ), + ( + r"import type { Identifier } from 'module'; + + declare module 'module2' { + import type { Identifier } from 'module'; + } + + declare module 'module3' { + import type { Identifier } from 'module'; + }", + None, + ), + (r"import { type x } from './foo'; import y from './foo'", None), + (r"import { type x } from './foo'; import { y } from './foo'", None), + (r"import { type x } from './foo'; import type y from 'foo'", None), ]; let fail = vec![ - r"import { x } from './foo'; import { y } from './foo'", - r"import {x} from './foo'; import {y} from './foo'; import { z } from './foo'", + (r"import { x } from './foo'; import { y } from './foo'", None), + (r"import {x} from './foo'; import {y} from './foo'; import { z } from './foo'", None), // TODO: settings: { 'import/resolve': { paths: [path.join(process.cwd(), 'tests', 'files')], }, }, // r#"import { x } from './bar'; import { y } from 'bar';"#, - r"import x from './bar.js?optionX'; import y from './bar?optionX';", - r"import x from './bar?optionX'; import y from './bar?optionY';", - r"import x from './bar?optionX'; import y from './bar.js?optionX';", - // we can't figure out non-existent files - // r#"import foo from 'non-existent'; import bar from 'non-existent';"#, - // r#"import type { x } from './foo'; import type { y } from './foo'"#, - r"import './foo'; import './foo'", - r"import { x, /* x */ } from './foo'; import {//y -y//y2 -} from './foo'", - r"import {x} from './foo'; import {} from './foo'", - r"import {a} from './foo'; import { a } from './foo'", - r"import {a,b} from './foo'; import { b, c } from './foo'; import {b,c,d} from './foo'", - r"import {a} from './foo'; import { a/*,b*/ } from './foo'", - r"import {a} from './foo'; import { a } from './foo'", - r"import {a,b} from './foo'; import { b, c } from './foo'; import {b,c,d} from './foo'", - r"import {a} from './foo'; import { a/*,b*/ } from './foo'", - r"import {x} from './foo'; import {} from './foo'; import {/*c*/} from './foo'; import {y} from './foo'", - r"import { } from './foo'; import {x} from './foo'", - r"import './foo'; import {x} from './foo'", - r"import'./foo'; import {x} from './foo'", - r"import './foo'; import { /*x*/} from './foo'; import {//y -} from './foo'; import {z} from './foo'", - r"import './foo'; import def, {x} from './foo'", - r"import './foo'; import def from './foo'", - r"import def from './foo'; import {x} from './foo'", - r"import {x} from './foo'; import def from './foo'", - r"import{x} from './foo'; import def from './foo'", - r"import {x} from './foo'; import def, {y} from './foo'", - r"import * as ns1 from './foo'; import * as ns2 from './foo'", - r"import * as ns from './foo'; import {x} from './foo'; import {y} from './foo'", - r"import {x} from './foo'; import * as ns from './foo'; import {y} from './foo'; import './foo'", - r"// some-tool-disable-next-line - import {x} from './foo' - import {//y -y} from './foo'", - r"import {x} from './foo' - // some-tool-disable-next-line - import {y} from './foo'", - r"import {x} from './foo' // some-tool-disable-line - import {y} from './foo'", - r"import {x} from './foo' - import {y} from './foo' // some-tool-disable-line", - r"import {x} from './foo' - /* comment */ import {y} from './foo'", - r"import {x} from './foo' - import {y} from './foo' /* comment - multiline */", - r"import {x} from './foo' -import {y} from './foo' -// some-tool-disable-next-line", - r"import {x} from './foo' -// comment - -import {y} from './foo'", - r"import {x} from './foo' - import/* comment */{y} from './foo'", - r"import {x} from './foo' - import/* comment */'./foo'", - r"import {x} from './foo' - import{y}/* comment */from './foo'", - r"import {x} from './foo' - import{y}from/* comment */'./foo'", - r"import {x} from - // some-tool-disable-next-line - './foo' + (r"import x from './bar.js?optionX'; import y from './bar?optionX';", None), + (r"import x from './bar?optionX'; import y from './bar?optionY';", None), + (r"import x from './bar?optionX'; import y from './bar.js?optionX';", None), + (r"import foo from 'non-existent'; import bar from 'non-existent';", None), + (r"import type { x } from './foo'; import type { y } from './foo'", None), + (r"import './foo'; import './foo'", None), + ( + r"import { x, /* x */ } from './foo'; import {//y + y//y2 + } from './foo'", + None, + ), + (r"import {x} from './foo'; import {} from './foo'", None), + (r"import {a} from './foo'; import { a } from './foo'", None), + ( + r"import {a,b} from './foo'; import { b, c } from './foo'; import {b,c,d} from './foo'", + None, + ), + (r"import {a} from './foo'; import { a/*,b*/ } from './foo'", None), + (r"import {a} from './foo'; import { a } from './foo'", None), + ( + r"import {a,b} from './foo'; import { b, c } from './foo'; import {b,c,d} from './foo'", + None, + ), + (r"import {a} from './foo'; import { a/*,b*/ } from './foo'", None), + ( + r"import {x} from './foo'; import {} from './foo'; import {/*c*/} from './foo'; import {y} from './foo'", + None, + ), + (r"import { } from './foo'; import {x} from './foo'", None), + (r"import './foo'; import {x} from './foo'", None), + (r"import'./foo'; import {x} from './foo'", None), + ( + r"import './foo'; import { /*x*/} from './foo'; import {//y + } from './foo'; import {z} from './foo'", + None, + ), + (r"import './foo'; import def, {x} from './foo'", None), + (r"import './foo'; import def from './foo'", None), + (r"import def from './foo'; import {x} from './foo'", None), + (r"import {x} from './foo'; import def from './foo'", None), + (r"import{x} from './foo'; import def from './foo'", None), + (r"import {x} from './foo'; import def, {y} from './foo'", None), + (r"import * as ns1 from './foo'; import * as ns2 from './foo'", None), + (r"import * as ns from './foo'; import {x} from './foo'; import {y} from './foo'", None), + ( + r"import {x} from './foo'; import * as ns from './foo'; import {y} from './foo'; import './foo'", + None, + ), + ( + r"// some-tool-disable-next-line + import {x} from './foo' + import {//y + y} from './foo'", + None, + ), + ( + r"import {x} from './foo' + // some-tool-disable-next-line + import {y} from './foo'", + None, + ), + ( + r"import {x} from './foo' // some-tool-disable-line + import {y} from './foo'", + None, + ), + ( + r"import {x} from './foo' + import {y} from './foo' // some-tool-disable-line", + None, + ), + ( + r"import {x} from './foo' + /* comment */ import {y} from './foo'", + None, + ), + ( + r"import {x} from './foo' + import {y} from './foo' /* comment + multiline */", + None, + ), + ( + r"import {x} from './foo' + import {y} from './foo' + // some-tool-disable-next-line", + None, + ), + ( + r"import {x} from './foo' + // comment + import {y} from './foo'", - r"import { Foo } from './foo'; -import { Bar } from './foo'; -export const value = {}", - r"import { Foo } from './foo'; -import Bar from './foo'; -export const value = {}", - r"import { - DEFAULT_FILTER_KEYS, - BULK_DISABLED, - } from '../constants'; - import React from 'react'; - import { - BULK_ACTIONS_ENABLED - } from '../constants'; - - const TestComponent = () => { - return
-
; - } + None, + ), + ( + r"import {x} from './foo' + import/* comment */{y} from './foo'", + None, + ), + ( + r"import {x} from './foo' + import/* comment */'./foo'", + None, + ), + ( + r"import {x} from './foo' + import{y}/* comment */from './foo'", + None, + ), + ( + r"import {x} from './foo' + import{y}from/* comment */'./foo'", + None, + ), + ( + r"import {x} from + // some-tool-disable-next-line + './foo' + import {y} from './foo'", + None, + ), + ( + r"import { Foo } from './foo'; + import { Bar } from './foo'; + export const value = {}", + None, + ), + ( + r"import { Foo } from './foo'; + import Bar from './foo'; + export const value = {}", + None, + ), + ( + r"import { + DEFAULT_FILTER_KEYS, + BULK_DISABLED, + } from '../constants'; + import React from 'react'; + import { + BULK_ACTIONS_ENABLED + } from '../constants'; + + const TestComponent = () => { + return
+
; + } + + export default TestComponent;", + None, + ), + ( + r"import {A1,} from 'foo'; + import {B1,} from 'foo'; + import {C1,} from 'foo'; - export default TestComponent;", - r"import {A1,} from 'foo'; - import {B1,} from 'foo'; - import {C1,} from 'foo'; - - import { - A2, - } from 'bar'; - import { - B2, - } from 'bar'; - import { - C2, - } from 'bar';", + import { + A2, + } from 'bar'; + import { + B2, + } from 'bar'; + import { + C2, + } from 'bar';", + None, + ), // TypeScript - // TODO: distinguish type imports in module record - // r#"import type x from './foo'; import type y from './foo'"#, - // r#"import type x from './foo'; import type x from './foo'"#, - // r#"import type {x} from './foo'; import type {y} from './foo'"#, - // r#"import {type x} from './foo'; import type {y} from './foo'"#, - // r#"import {type x} from 'foo'; import type {y} from 'foo'"#, - // r#"import {type x} from 'foo'; import type {y} from 'foo'"#, - // r#"import {type x} from './foo'; import {type y} from './foo'"#, - // r#"import {type x} from './foo'; import {type y} from './foo'"#, - // r#"import {AValue, type x, BValue} from './foo'; import {type y} from './foo'"#, - // r#"import {AValue} from './foo'; import type {AType} from './foo'"#, + (r"import type x from './foo'; import type y from './foo'", None), + (r"import type x from './foo'; import type x from './foo'", None), + (r"import type {x} from './foo'; import type {y} from './foo'", None), + (r"import {type x} from './foo'; import type {y} from './foo'", None), + (r"import {type x} from 'foo'; import type {y} from 'foo'", None), + (r"import {type x} from 'foo'; import type {y} from 'foo'", None), + (r"import {type x} from './foo'; import {type y} from './foo'", None), + (r"import {type x} from './foo'; import {type y} from './foo'", None), + (r"import {AValue, type x, BValue} from './foo'; import {type y} from './foo'", None), + ( + r"import {AValue} from './foo'; import type {AType} from './foo'", + Some(json!({ "preferInline": true })), + ), ]; Tester::new(NoDuplicates::NAME, pass, fail) diff --git a/crates/oxc_linter/src/snapshots/no_duplicates.snap b/crates/oxc_linter/src/snapshots/no_duplicates.snap index 9ed54b173e001..0354b5309c76b 100644 --- a/crates/oxc_linter/src/snapshots/no_duplicates.snap +++ b/crates/oxc_linter/src/snapshots/no_duplicates.snap @@ -32,6 +32,18 @@ expression: no_duplicates · ─────────────── ────────────────── ╰──── + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:17] + 1 │ import foo from 'non-existent'; import bar from 'non-existent'; + · ────────────── ────────────── + ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:24] + 1 │ import type { x } from './foo'; import type { y } from './foo' + · ─────── ─────── + ╰──── + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:8] 1 │ import './foo'; import './foo' @@ -42,9 +54,9 @@ expression: no_duplicates ╭─[index.ts:1:28] 1 │ import { x, /* x */ } from './foo'; import {//y · ─────── - 2 │ y//y2 - 3 │ } from './foo' - · ─────── + 2 │ y//y2 + 3 │ } from './foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places @@ -161,8 +173,8 @@ expression: no_duplicates ╭─[index.ts:1:8] 1 │ import './foo'; import { /*x*/} from './foo'; import {//y · ─────── ─────── - 2 │ } from './foo'; import {z} from './foo' - · ─────── ─────── + 2 │ } from './foo'; import {z} from './foo' + · ─────── ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places @@ -220,165 +232,227 @@ expression: no_duplicates ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places - ╭─[index.ts:2:25] + ╭─[index.ts:2:29] 1 │ // some-tool-disable-next-line - 2 │ import {x} from './foo' - · ─────── - 3 │ import {//y - 4 │ y} from './foo' - · ─────── + 2 │ import {x} from './foo' + · ─────── + 3 │ import {//y + 4 │ y} from './foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ // some-tool-disable-next-line - 3 │ import {y} from './foo' - · ─────── + 2 │ // some-tool-disable-next-line + 3 │ import {y} from './foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' // some-tool-disable-line · ─────── - 2 │ import {y} from './foo' - · ─────── + 2 │ import {y} from './foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ import {y} from './foo' // some-tool-disable-line - · ─────── + 2 │ import {y} from './foo' // some-tool-disable-line + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ /* comment */ import {y} from './foo' - · ─────── + 2 │ /* comment */ import {y} from './foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ import {y} from './foo' /* comment - · ─────── - 3 │ multiline */ + 2 │ import {y} from './foo' /* comment + · ─────── + 3 │ multiline */ ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ import {y} from './foo' - · ─────── - 3 │ // some-tool-disable-next-line + 2 │ import {y} from './foo' + · ─────── + 3 │ // some-tool-disable-next-line ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ // comment + 2 │ // comment 3 │ - 4 │ import {y} from './foo' - · ─────── + 4 │ import {y} from './foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ import/* comment */{y} from './foo' - · ─────── + 2 │ import/* comment */{y} from './foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ import/* comment */'./foo' - · ─────── + 2 │ import/* comment */'./foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ import{y}/* comment */from './foo' - · ─────── + 2 │ import{y}/* comment */from './foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:17] 1 │ import {x} from './foo' · ─────── - 2 │ import{y}from/* comment */'./foo' - · ─────── + 2 │ import{y}from/* comment */'./foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places - ╭─[index.ts:3:9] - 2 │ // some-tool-disable-next-line - 3 │ './foo' - · ─────── - 4 │ import {y} from './foo' - · ─────── + ╭─[index.ts:3:13] + 2 │ // some-tool-disable-next-line + 3 │ './foo' + · ─────── + 4 │ import {y} from './foo' + · ─────── ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:21] 1 │ import { Foo } from './foo'; · ─────── - 2 │ import { Bar } from './foo'; - · ─────── - 3 │ export const value = {} + 2 │ import { Bar } from './foo'; + · ─────── + 3 │ export const value = {} ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:21] 1 │ import { Foo } from './foo'; · ─────── - 2 │ import Bar from './foo'; - · ─────── - 3 │ export const value = {} + 2 │ import Bar from './foo'; + · ─────── + 3 │ export const value = {} ╰──── × Unexpected token - ╭─[index.ts:12:12] - 11 │ return
- 12 │
; - · ─ - 13 │ } + ╭─[index.ts:12:16] + 11 │ return
+ 12 │
; + · ─ + 13 │ } ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places ╭─[index.ts:1:19] 1 │ import {A1,} from 'foo'; · ───── - 2 │ import {B1,} from 'foo'; - · ───── - 3 │ import {C1,} from 'foo'; - · ───── + 2 │ import {B1,} from 'foo'; + · ───── + 3 │ import {C1,} from 'foo'; + · ───── 4 │ ╰──── ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places - ╭─[index.ts:7:16] - 6 │ A2, - 7 │ } from 'bar'; - · ───── - 8 │ import { - 9 │ B2, - 10 │ } from 'bar'; - · ───── - 11 │ import { - 12 │ C2, - 13 │ } from 'bar'; - · ───── + ╭─[index.ts:7:20] + 6 │ A2, + 7 │ } from 'bar'; + · ───── + 8 │ import { + 9 │ B2, + 10 │ } from 'bar'; + · ───── + 11 │ import { + 12 │ C2, + 13 │ } from 'bar'; + · ───── ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:20] + 1 │ import type x from './foo'; import type y from './foo' + · ─────── ─────── + ╰──── + + × Identifier `x` has already been declared + ╭─[index.ts:1:13] + 1 │ import type x from './foo'; import type x from './foo' + · ┬ ┬ + · │ ╰── It can not be redeclared here + · ╰── `x` has already been declared here + ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:22] + 1 │ import type {x} from './foo'; import type {y} from './foo' + · ─────── ─────── + ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:22] + 1 │ import {type x} from './foo'; import type {y} from './foo' + · ─────── ─────── + ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:22] + 1 │ import {type x} from 'foo'; import type {y} from 'foo' + · ───── ───── + ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:22] + 1 │ import {type x} from 'foo'; import type {y} from 'foo' + · ───── ───── + ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:22] + 1 │ import {type x} from './foo'; import {type y} from './foo' + · ─────── ─────── + ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:22] + 1 │ import {type x} from './foo'; import {type y} from './foo' + · ─────── ─────── + ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:38] + 1 │ import {AValue, type x, BValue} from './foo'; import {type y} from './foo' + · ─────── ─────── + ╰──── + + ⚠ eslint-plugin-import(no-duplicates): Forbid repeated import of the same module in multiple places + ╭─[index.ts:1:22] + 1 │ import {AValue} from './foo'; import type {AType} from './foo' + · ─────── ─────── + ╰────