From 29d229a1b1ad1ae359be38adc11b8dd3937b504e Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Thu, 25 Apr 2024 17:16:32 +0200 Subject: [PATCH] fix(lint/useShorthandFunctionType): add parens when needed --- CHANGELOG.md | 14 +- .../lint/style/use_shorthand_function_type.rs | 45 +++++-- .../style/useShorthandFunctionType/invalid.ts | 8 +- .../useShorthandFunctionType/invalid.ts.snap | 123 +++++++++++++----- 4 files changed, 143 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9858bcbab20a..f2dc2d7fb7b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,9 +79,21 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b Contributed by @Conaclos +- Fix [useShorthandFunctionType](https://biomejs.dev/linter/rules/use-shorthand-function-type/) that suggested invalid code fixes when parentheses are required ([#2595](https://github.com/biomejs/biome/issues/2595)). + + Previously, the rule didn't add parentheses when they were needed. + It now adds parentheses when the function signature is inside an array, a union, or an intersection. + + ```diff + - type Union = { (): number } | string; + + type Union = (() => number) | string; + ``` + + Contributed by @Conaclos + - Fix [useTemplate](https://biomejs.dev/linter/rules/use-template/) that wrongly escaped strings in some edge cases ([#2580](https://github.com/biomejs/biome/issues/2580)). - Previously, the rule didn't correctly escaped characters preceded by an escaped character. + Previously, the rule didn't correctly escape characters preceded by an escaped character. Contributed by @Conaclos diff --git a/crates/biome_js_analyze/src/lint/style/use_shorthand_function_type.rs b/crates/biome_js_analyze/src/lint/style/use_shorthand_function_type.rs index 1837ae2cebe2..17ded5e4ec88 100644 --- a/crates/biome_js_analyze/src/lint/style/use_shorthand_function_type.rs +++ b/crates/biome_js_analyze/src/lint/style/use_shorthand_function_type.rs @@ -9,10 +9,10 @@ use biome_js_factory::make; use biome_js_factory::make::ts_type_alias_declaration; use biome_js_syntax::AnyTsType::TsThisType; use biome_js_syntax::{ - AnyJsDeclarationClause, AnyTsReturnType, AnyTsType, TsCallSignatureTypeMember, TsFunctionType, - TsInterfaceDeclaration, TsObjectType, TsTypeMemberList, T, + AnyJsDeclarationClause, AnyTsReturnType, AnyTsType, JsSyntaxKind, TsCallSignatureTypeMember, + TsFunctionType, TsInterfaceDeclaration, TsObjectType, TsTypeMemberList, T, }; -use biome_rowan::{AstNode, AstNodeList, BatchMutationExt, TriviaPieceKind}; +use biome_rowan::{AstNode, AstNodeList, BatchMutationExt, SyntaxNodeOptionExt, TriviaPieceKind}; declare_rule! { /// Enforce using function types instead of object type with call signatures. @@ -152,7 +152,6 @@ impl Rule for UseShorthandFunctionType { AnyJsDeclarationClause::from(interface_decl), AnyJsDeclarationClause::from(type_alias_declaration), ); - return Some(JsRuleAction { category: ActionCategory::QuickFix, applicability: Applicability::Always, @@ -163,12 +162,38 @@ impl Rule for UseShorthandFunctionType { if let Some(ts_object_type) = ts_type_member_list.parent::() { let new_function_type = convert_ts_call_signature_type_member_to_function_type(node)?; - - mutation.replace_node( - AnyTsType::from(ts_object_type), - AnyTsType::from(new_function_type), - ); - + // This is a simplification of the `needs_parentheses` + // available in biome_js_formatter/src/ts/types/function_type.rs + let needs_parens = matches!( + ts_object_type.syntax().parent().kind(), + Some( + JsSyntaxKind::TS_RETURN_TYPE_ANNOTATION + | JsSyntaxKind::TS_CONDITIONAL_TYPE + | JsSyntaxKind::TS_ARRAY_TYPE + | JsSyntaxKind::TS_TYPE_OPERATOR_TYPE + | JsSyntaxKind::TS_REST_TUPLE_TYPE_ELEMENT + | JsSyntaxKind::TS_OPTIONAL_TUPLE_TYPE_ELEMENT + | JsSyntaxKind::TS_UNION_TYPE_VARIANT_LIST + | JsSyntaxKind::TS_INTERSECTION_TYPE_ELEMENT_LIST + ) + ) || matches!(new_function_type.return_type().map(|return_type| { + let AnyTsReturnType::AnyTsType(any_ts_type) = return_type else { + return None; + }; + Some(any_ts_type) + }), Ok(Some(AnyTsType::TsInferType(infer_type))) if infer_type.constraint().is_some()); + let new_function_type: AnyTsType = if needs_parens { + make::ts_parenthesized_type( + make::token(T!['(']), + new_function_type.trim_trivia()?.into(), + make::token(T![')']), + ) + .into() + } else { + new_function_type.into() + }; + + mutation.replace_node(AnyTsType::from(ts_object_type), new_function_type); return Some(JsRuleAction { category: ActionCategory::QuickFix, applicability: Applicability::Always, diff --git a/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts b/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts index e8f9fcd63508..f4f9ae1e5b85 100644 --- a/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts +++ b/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts @@ -26,6 +26,12 @@ let nestedObj: { inner: { (): boolean } }; // Object type with call signature as a type union member type UnionWithCallSignature = { (): string } | string; +// Object type with call signature as a type intersection member +export type IntersectionCallSignature = { (): string } & string; + +// Object type with call signature as a type array +export type ArrayCallSignature = readonly { (): string }[]; + // Generic object type with a call signature type GenericCallSignature = { (arg: T): T }; @@ -35,4 +41,4 @@ let optionalCall: { (): number | undefined }; // Generic interface with a call signature interface GenericInterface { (value: T): boolean; -} +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts.snap index 84f235718988..88e381774595 100644 --- a/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts.snap +++ b/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts.snap @@ -32,6 +32,12 @@ let nestedObj: { inner: { (): boolean } }; // Object type with call signature as a type union member type UnionWithCallSignature = { (): string } | string; +// Object type with call signature as a type intersection member +export type IntersectionCallSignature = { (): string } & string; + +// Object type with call signature as a type array +export type ArrayCallSignature = readonly { (): string }[]; + // Generic object type with a call signature type GenericCallSignature = { (arg: T): T }; @@ -42,7 +48,6 @@ let optionalCall: { (): number | undefined }; interface GenericInterface { (value: T): boolean; } - ``` # Diagnostics @@ -184,7 +189,7 @@ invalid.ts:27:33 lint/style/useShorthandFunctionType FIXABLE ━━━━━ > 27 │ type UnionWithCallSignature = { (): string } | string; │ ^^^^^^^^^^ 28 │ - 29 │ // Generic object type with a call signature + 29 │ // Object type with call signature as a type intersection member i Types containing only a call signature can be shortened to a function type. @@ -193,86 +198,134 @@ invalid.ts:27:33 lint/style/useShorthandFunctionType FIXABLE ━━━━━ 25 25 │ 26 26 │ // Object type with call signature as a type union member 27 │ - type·UnionWithCallSignature·=·{·():·string·}·|·string; - 27 │ + type·UnionWithCallSignature·=·()·=>·string·|·string; + 27 │ + type·UnionWithCallSignature·=·(()·=>·string)·|·string; 28 28 │ - 29 29 │ // Generic object type with a call signature + 29 29 │ // Object type with call signature as a type intersection member ``` ``` -invalid.ts:30:34 lint/style/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.ts:30:43 lint/style/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Use a function type instead of a call signature. - 29 │ // Generic object type with a call signature - > 30 │ type GenericCallSignature = { (arg: T): T }; - │ ^^^^^^^^^^^ + 29 │ // Object type with call signature as a type intersection member + > 30 │ export type IntersectionCallSignature = { (): string } & string; + │ ^^^^^^^^^^ 31 │ - 32 │ // Object type with optional call signature + 32 │ // Object type with call signature as a type array i Types containing only a call signature can be shortened to a function type. i Safe fix: Use a function type instead of an object type with a call signature. 28 28 │ - 29 29 │ // Generic object type with a call signature - 30 │ - type·GenericCallSignature·=·{·(arg:·T):·T·}; - 30 │ + type·GenericCallSignature·=·(arg:·T)·=>·T; + 29 29 │ // Object type with call signature as a type intersection member + 30 │ - export·type·IntersectionCallSignature·=·{·():·string·}·&·string; + 30 │ + export·type·IntersectionCallSignature·=·(()·=>·string)·&·string; 31 31 │ - 32 32 │ // Object type with optional call signature + 32 32 │ // Object type with call signature as a type array ``` ``` -invalid.ts:33:21 lint/style/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.ts:33:45 lint/style/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Use a function type instead of a call signature. - 32 │ // Object type with optional call signature - > 33 │ let optionalCall: { (): number | undefined }; - │ ^^^^^^^^^^^^^^^^^^^^^^ + 32 │ // Object type with call signature as a type array + > 33 │ export type ArrayCallSignature = readonly { (): string }[]; + │ ^^^^^^^^^^ 34 │ - 35 │ // Generic interface with a call signature + 35 │ // Generic object type with a call signature i Types containing only a call signature can be shortened to a function type. i Safe fix: Use a function type instead of an object type with a call signature. 31 31 │ - 32 32 │ // Object type with optional call signature - 33 │ - let·optionalCall:·{·():·number·|·undefined·}; - 33 │ + let·optionalCall:·()·=>·number·|·undefined; + 32 32 │ // Object type with call signature as a type array + 33 │ - export·type·ArrayCallSignature·=·readonly·{·():·string·}[]; + 33 │ + export·type·ArrayCallSignature·=·readonly·(()·=>·string)[]; 34 34 │ - 35 35 │ // Generic interface with a call signature + 35 35 │ // Generic object type with a call signature ``` ``` -invalid.ts:37:2 lint/style/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.ts:36:34 lint/style/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Use a function type instead of a call signature. - 35 │ // Generic interface with a call signature - 36 │ interface GenericInterface { - > 37 │ (value: T): boolean; + 35 │ // Generic object type with a call signature + > 36 │ type GenericCallSignature = { (arg: T): T }; + │ ^^^^^^^^^^^ + 37 │ + 38 │ // Object type with optional call signature + + i Types containing only a call signature can be shortened to a function type. + + i Safe fix: Use a function type instead of an object type with a call signature. + + 34 34 │ + 35 35 │ // Generic object type with a call signature + 36 │ - type·GenericCallSignature·=·{·(arg:·T):·T·}; + 36 │ + type·GenericCallSignature·=·(arg:·T)·=>·T; + 37 37 │ + 38 38 │ // Object type with optional call signature + + +``` + +``` +invalid.ts:39:21 lint/style/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Use a function type instead of a call signature. + + 38 │ // Object type with optional call signature + > 39 │ let optionalCall: { (): number | undefined }; + │ ^^^^^^^^^^^^^^^^^^^^^^ + 40 │ + 41 │ // Generic interface with a call signature + + i Types containing only a call signature can be shortened to a function type. + + i Safe fix: Use a function type instead of an object type with a call signature. + + 37 37 │ + 38 38 │ // Object type with optional call signature + 39 │ - let·optionalCall:·{·():·number·|·undefined·}; + 39 │ + let·optionalCall:·()·=>·number·|·undefined; + 40 40 │ + 41 41 │ // Generic interface with a call signature + + +``` + +``` +invalid.ts:43:2 lint/style/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Use a function type instead of a call signature. + + 41 │ // Generic interface with a call signature + 42 │ interface GenericInterface { + > 43 │ (value: T): boolean; │ ^^^^^^^^^^^^^^^^^^^^ - 38 │ } - 39 │ + 44 │ } i Types containing only a call signature can be shortened to a function type. i Safe fix: Alias a function type instead of using an interface with a call signature. - 34 34 │ - 35 35 │ // Generic interface with a call signature - 36 │ - interface·GenericInterface·{ - 37 │ - → (value:·T):·boolean; - 38 │ - } - 36 │ + type·GenericInterface·=·(value:·T)·=>·boolean - 39 37 │ + 40 40 │ + 41 41 │ // Generic interface with a call signature + 42 │ - interface·GenericInterface·{ + 43 │ - → (value:·T):·boolean; + 44 │ - } + 42 │ + type·GenericInterface·=·(value:·T)·=>·boolean ```