Skip to content

Commit

Permalink
fix(lint/useShorthandFunctionType): add parens when needed
Browse files Browse the repository at this point in the history
  • Loading branch information
Conaclos committed Apr 25, 2024
1 parent 5bacbf3 commit 29d229a
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 47 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -163,12 +162,38 @@ impl Rule for UseShorthandFunctionType {

if let Some(ts_object_type) = ts_type_member_list.parent::<TsObjectType>() {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = { (arg: T): T };

Expand All @@ -35,4 +41,4 @@ let optionalCall: { (): number | undefined };
// Generic interface with a call signature
interface GenericInterface<T> {
(value: T): boolean;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = { (arg: T): T };

Expand All @@ -42,7 +48,6 @@ let optionalCall: { (): number | undefined };
interface GenericInterface<T> {
(value: T): boolean;
}

```

# Diagnostics
Expand Down Expand Up @@ -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.
Expand All @@ -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<T> = { (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<T>·=·{·(arg:·T):·T·};
30 │ + type·GenericCallSignature<T>·=·(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<T> {
> 37 │ (value: T): boolean;
35 │ // Generic object type with a call signature
> 36 │ type GenericCallSignature<T> = { (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<T>·=·{·(arg:·T):·T·};
36 │ + type·GenericCallSignature<T>·=·(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<T> {
> 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<T>·{
37 │ - → (value:·T):·boolean;
38 │ - }
36 │ + type·GenericInterface<T>·=·(value:·T)·=>·boolean
39 37 │
40 40 │
41 41 │ // Generic interface with a call signature
42 │ - interface·GenericInterface<T>·{
43 │ - → (value:·T):·boolean;
44 │ - }
42 │ + type·GenericInterface<T>·=·(value:·T)·=>·boolean
```

0 comments on commit 29d229a

Please sign in to comment.