diff --git a/.changeset/chatty-ravens-agree.md b/.changeset/chatty-ravens-agree.md new file mode 100644 index 000000000000..b004619ac94d --- /dev/null +++ b/.changeset/chatty-ravens-agree.md @@ -0,0 +1,13 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#7289](https://github.com/biomejs/biome/issues/7289). The rule [`useImportType`](https://biomejs.dev/linter/rules/use-import-type/) now inlines `import type` into `import { type }` when the `style` option is set to `inlineType`. + +Example: + +```ts +import type { T } from "mod"; +// becomes +import { type T } from "mod"; +``` diff --git a/crates/biome_js_analyze/src/lint/style/use_import_type.rs b/crates/biome_js_analyze/src/lint/style/use_import_type.rs index 7ad0fbd2df94..d1a09db83920 100644 --- a/crates/biome_js_analyze/src/lint/style/use_import_type.rs +++ b/crates/biome_js_analyze/src/lint/style/use_import_type.rs @@ -175,11 +175,12 @@ impl Rule for UseImportType { } let import = ctx.query(); let import_clause = import.import_clause().ok()?; - let extension = ctx.file_path().extension()?; - let extension = extension.as_bytes(); // Import attributes and type-only imports are not compatible in ESM. if import_clause.attribute().is_some() - && extension != b"cts" + && ctx + .file_path() + .extension() + .is_none_or(|extension| extension != "cts") && !matches!(ctx.root(), AnyJsRoot::JsScript(_)) { return None; @@ -286,10 +287,28 @@ impl Rule for UseImportType { is_only_used_as_type(model, default_binding).then_some(ImportTypeFix::UseImportType) } AnyJsImportClause::JsImportNamedClause(clause) => { + let type_token = clause.type_token(); + if style == Style::InlineType && type_token.is_some() { + // Inline `import type` into `import { type }` + let specifiers = clause + .named_specifiers() + .ok()? + .specifiers() + .iter() + .collect::, _>>() + .ok()?; + return if specifiers.is_empty() { + None + } else { + Some(ImportTypeFix::AddTypeQualifiers( + specifiers.into_boxed_slice(), + )) + }; + } match named_import_type_fix( model, &clause.named_specifiers().ok()?, - clause.type_token().is_some(), + type_token.is_some(), )? { NamedImportTypeFix::UseImportType(specifiers) => { if style == Style::InlineType { @@ -394,16 +413,26 @@ impl Rule for UseImportType { } } ImportTypeFix::AddTypeQualifiers(named_specifiers) => { - let mut diagnostic = RuleDiagnostic::new( - rule_category!(), - import_clause.range(), - "Some named imports are only used as types.", - ); - for specifier in named_specifiers { - diagnostic = - diagnostic.detail(specifier.range(), "This import is only used as a type.") + if import_clause.type_token().is_some() { + RuleDiagnostic::new( + rule_category!(), + import_clause.range(), + markup! { + "Use ""import { type }"" instead of ""import type""." + }, + ) + } else { + let mut diagnostic = RuleDiagnostic::new( + rule_category!(), + import_clause.range(), + "Some named imports are only used as types.", + ); + for specifier in named_specifiers { + diagnostic = diagnostic + .detail(specifier.range(), "This import is only used as a type.") + } + diagnostic } - diagnostic } ImportTypeFix::RemoveTypeQualifiers(type_tokens) => { let mut diagnostic = RuleDiagnostic::new( @@ -727,6 +756,10 @@ impl Rule for UseImportType { } } ImportTypeFix::AddTypeQualifiers(specifiers) => { + if let Some(type_token) = import_clause.type_token() { + // Inline `import type` into `import { type }` + mutation.remove_token(type_token); + } for specifier in specifiers { let new_specifier = specifier .clone() diff --git a/crates/biome_js_analyze/tests/specs/style/useImportType/invalid-inline-type.ts b/crates/biome_js_analyze/tests/specs/style/useImportType/invalid-inline-type.ts index f52ced19e2fe..eafc8de568db 100644 --- a/crates/biome_js_analyze/tests/specs/style/useImportType/invalid-inline-type.ts +++ b/crates/biome_js_analyze/tests/specs/style/useImportType/invalid-inline-type.ts @@ -16,3 +16,6 @@ export { V3, V4 }; import V5, { T9 } from "mod"; export type { T9 }; export { V5 }; + +import type { T10 } from "mod"; +export type { T10 }; diff --git a/crates/biome_js_analyze/tests/specs/style/useImportType/invalid-inline-type.ts.snap b/crates/biome_js_analyze/tests/specs/style/useImportType/invalid-inline-type.ts.snap index 3eed873a4449..dd54cbd01b54 100644 --- a/crates/biome_js_analyze/tests/specs/style/useImportType/invalid-inline-type.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useImportType/invalid-inline-type.ts.snap @@ -23,6 +23,9 @@ import V5, { T9 } from "mod"; export type { T9 }; export { V5 }; +import type { T10 } from "mod"; +export type { T10 }; + ``` # Diagnostics @@ -177,3 +180,29 @@ invalid-inline-type.ts:16:8 lint/style/useImportType FIXABLE ━━━━━ │ +++++ ``` + +``` +invalid-inline-type.ts:20:8 lint/style/useImportType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Use import { type } instead of import type. + + 18 │ export { V5 }; + 19 │ + > 20 │ import type { T10 } from "mod"; + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 21 │ export type { T10 }; + 22 │ + + i Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules. + + i Safe fix: Add inline type keywords. + + 18 18 │ export { V5 }; + 19 19 │ + 20 │ - import·type·{·T10·}·from·"mod"; + 20 │ + import·{·type·T10·}·from·"mod"; + 21 21 │ export type { T10 }; + 22 22 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useImportType/valid-inline-type.ts b/crates/biome_js_analyze/tests/specs/style/useImportType/valid-inline-type.ts index ce2cfa714369..bd48357a4896 100644 --- a/crates/biome_js_analyze/tests/specs/style/useImportType/valid-inline-type.ts +++ b/crates/biome_js_analyze/tests/specs/style/useImportType/valid-inline-type.ts @@ -5,3 +5,7 @@ export type { T1, T2 }; import V1, { type T3 } from "mod"; export type { T3 }; export { V1 }; + +// Edge case +import type {} from "mod"; + diff --git a/crates/biome_js_analyze/tests/specs/style/useImportType/valid-inline-type.ts.snap b/crates/biome_js_analyze/tests/specs/style/useImportType/valid-inline-type.ts.snap index 1c4f86bf19e2..99cd5a15abd5 100644 --- a/crates/biome_js_analyze/tests/specs/style/useImportType/valid-inline-type.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useImportType/valid-inline-type.ts.snap @@ -12,4 +12,8 @@ import V1, { type T3 } from "mod"; export type { T3 }; export { V1 }; +// Edge case +import type {} from "mod"; + + ```