Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(lint/useShorthandFunctionType): add parens when needed #2601

Merged
merged 1 commit into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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


```
Loading