Skip to content

Commit 723798b

Browse files
ViniciusDev26claudeautofix-ci[bot]
authored
feat: apply fix to use consistent method signatures (#9544)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 956e367 commit 723798b

6 files changed

Lines changed: 247 additions & 37 deletions

File tree

.changeset/hip-things-hang.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Added an unsafe fix to [`useConsistentMethodSignatures`](https://biomejs.dev/linter/rules/use-consistent-method-signatures/) that automatically converts between method-style and property-style signatures.

crates/biome_js_analyze/src/lint/nursery/use_consistent_method_signatures.rs

Lines changed: 160 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
use crate::JsRuleAction;
12
use biome_analyze::{
2-
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
3+
Ast, FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
34
};
45
use biome_console::markup;
5-
use biome_js_syntax::{TsMethodSignatureTypeMember, TsPropertySignatureTypeMember};
6-
use biome_rowan::{AstNode, declare_node_union};
6+
use biome_js_factory::make;
7+
use biome_js_syntax::{
8+
AnyTsType, AnyTsTypeMember, T, TsMethodSignatureTypeMember, TsPropertySignatureTypeMember,
9+
TsTypeMemberList,
10+
};
11+
use biome_rowan::{AstNode, BatchMutationExt, TriviaPieceKind, declare_node_union};
712
use biome_rule_options::use_consistent_method_signatures::{
813
MethodSignatureStyle, UseConsistentMethodSignaturesOptions,
914
};
@@ -181,11 +186,8 @@ declare_lint_rule! {
181186
name: "useConsistentMethodSignatures",
182187
language: "ts",
183188
recommended: false,
184-
issue_number: Some("8780"),
185189
sources: &[RuleSource::EslintTypeScript("method-signature-style").same()],
186-
// TODO: Implement fix to convert between method/property
187-
// This will need to handle transforming overloads into intersections of function properties
188-
// fix_kind: FixKind::Unsafe,
190+
fix_kind: FixKind::Unsafe,
189191
}
190192
}
191193

@@ -247,6 +249,157 @@ impl Rule for UseConsistentMethodSignatures {
247249

248250
Some(diagnostic)
249251
}
252+
253+
fn action(
254+
ctx: &RuleContext<Self>,
255+
state: &Self::State,
256+
) -> Option<JsRuleAction> {
257+
let node = ctx.query();
258+
let mut mutation = ctx.root().begin();
259+
260+
let (prev, next) = match node {
261+
AnyTsMethodSignatureLike::TsMethodSignatureTypeMember(method) => {
262+
let new_node = method_to_property(method)?;
263+
(
264+
AnyTsTypeMember::from(method.clone()),
265+
AnyTsTypeMember::from(new_node),
266+
)
267+
}
268+
AnyTsMethodSignatureLike::TsPropertySignatureTypeMember(prop) => {
269+
let new_node = property_to_method(prop)?;
270+
(
271+
AnyTsTypeMember::from(prop.clone()),
272+
AnyTsTypeMember::from(new_node),
273+
)
274+
}
275+
};
276+
277+
mutation.replace_node(prev, next);
278+
279+
Some(JsRuleAction::new(
280+
ctx.metadata().action_category(ctx.category(), ctx.group()),
281+
ctx.metadata().applicability(),
282+
markup! { "Convert to "<Emphasis>{state.target_style}</Emphasis>"-style signature." }
283+
.to_owned(),
284+
mutation,
285+
))
286+
}
287+
}
288+
289+
/// Converts a method-style signature into a property-style one.
290+
///
291+
/// Returns `None` if:
292+
/// - The method has no explicit return type annotation (can't build a valid function type).
293+
/// - The method is one of several overloads (would need intersection types — handled separately).
294+
fn method_to_property(
295+
node: &TsMethodSignatureTypeMember,
296+
) -> Option<TsPropertySignatureTypeMember> {
297+
let return_type_annotation = node.return_type_annotation()?;
298+
299+
if has_method_overloads(node) {
300+
return None;
301+
}
302+
303+
let name = node.name().ok()?;
304+
let orig_params = node.parameters().ok()?;
305+
// Rebuild parameters with a `)` that has trailing whitespace so the output is
306+
// `(arg: string) => void` instead of `(arg: string)=> void`.
307+
let parameters = make::js_parameters(
308+
orig_params.l_paren_token().ok()?,
309+
orig_params.items(),
310+
make::token(T![')']).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]),
311+
);
312+
let return_type = return_type_annotation.ty().ok()?;
313+
314+
let function_type = make::ts_function_type(
315+
parameters,
316+
make::token(T![=>]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]),
317+
return_type,
318+
)
319+
.build()
320+
.with_type_parameters(node.type_parameters());
321+
322+
let type_annotation = make::ts_type_annotation(
323+
make::token(T![:]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]),
324+
AnyTsType::from(function_type),
325+
);
326+
327+
let mut builder = make::ts_property_signature_type_member(name)
328+
.with_type_annotation(type_annotation);
329+
330+
if let Some(opt) = node.optional_token() {
331+
builder = builder.with_optional_token(opt);
332+
}
333+
if let Some(sep) = node.separator_token() {
334+
builder = builder.with_separator_token_token(sep);
335+
}
336+
337+
Some(builder.build())
338+
}
339+
340+
/// Converts a property-style signature into a method-style one.
341+
///
342+
/// Returns `None` if the property has a `readonly` modifier (methods can't be readonly).
343+
fn property_to_method(
344+
node: &TsPropertySignatureTypeMember,
345+
) -> Option<TsMethodSignatureTypeMember> {
346+
if node.readonly_token().is_some() {
347+
return None;
348+
}
349+
350+
let name = node.name().ok()?;
351+
let type_annotation = node.type_annotation()?;
352+
let function_type = type_annotation.ty().ok()?.as_ts_function_type()?.clone();
353+
354+
let orig_params = function_type.parameters().ok()?;
355+
// Rebuild parameters with a clean `)` — the original `)` carries trailing whitespace
356+
// from the source (e.g. the space before `=>` in `(arg: string) => void`), which would
357+
// produce `method() : void` instead of the desired `method(): void`.
358+
let parameters = make::js_parameters(
359+
orig_params.l_paren_token().ok()?,
360+
orig_params.items(),
361+
make::token(T![')']),
362+
);
363+
let return_type = function_type.return_type().ok()?;
364+
365+
let return_type_annotation = make::ts_return_type_annotation(
366+
make::token(T![:]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]),
367+
return_type,
368+
);
369+
370+
let mut builder = make::ts_method_signature_type_member(name, parameters)
371+
.with_return_type_annotation(return_type_annotation);
372+
373+
if let Some(opt) = node.optional_token() {
374+
builder = builder.with_optional_token(opt);
375+
}
376+
if let Some(type_params) = function_type.type_parameters() {
377+
builder = builder.with_type_parameters(type_params);
378+
}
379+
if let Some(sep) = node.separator_token() {
380+
builder = builder.with_separator_token_token(sep);
381+
}
382+
383+
Some(builder.build())
384+
}
385+
386+
/// Returns `true` if `node` is one of multiple overloads — i.e. the parent type member list
387+
/// contains more than one method signature with the same name.
388+
fn has_method_overloads(node: &TsMethodSignatureTypeMember) -> bool {
389+
check_method_overloads(node).unwrap_or(false)
390+
}
391+
392+
fn check_method_overloads(node: &TsMethodSignatureTypeMember) -> Option<bool> {
393+
let name_text = node.name().ok()?.name()?;
394+
let member_list = node.parent::<TsTypeMemberList>()?;
395+
396+
let overload_count = member_list
397+
.into_iter()
398+
.filter_map(|m| m.as_ts_method_signature_type_member().cloned())
399+
.filter(|m| m.name().ok().and_then(|n| n.name()).is_some_and(|n| n == name_text))
400+
.count();
401+
402+
Some(overload_count > 1)
250403
}
251404

252405
declare_node_union! {

crates/biome_js_analyze/tests/specs/nursery/useConsistentMethodSignatures/invalid-methods.ts.snap

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ interface OverloadedProps {
3232

3333
# Diagnostics
3434
```
35-
invalid-methods.ts:4:3 lint/nursery/useConsistentMethodSignatures ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
35+
invalid-methods.ts:4:3 lint/nursery/useConsistentMethodSignatures FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━
3636
3737
i Prefer using method-style over property-style method signatures.
3838
@@ -46,15 +46,22 @@ invalid-methods.ts:4:3 lint/nursery/useConsistentMethodSignatures ━━━━
4646
4747
i If this isn't what you want, consider changing the style option in the rule's settings.
4848
49-
i This rule is still being actively worked on, so it may be missing features or have rough edges. Visit https://github.com/biomejs/biome/issues/8780 for more information or to report possible bugs.
50-
5149
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
5250
51+
i Unsafe fix: Convert to method-style signature.
52+
53+
2 2 │
54+
3 3 │ interface PropIface {
55+
4- ··propFunc:·(argstring=>·number;
56+
4+ ··propFunc(argstring):·number;
57+
5 5}
58+
6 6 │
59+
5360
5461
```
5562

5663
```
57-
invalid-methods.ts:8:3 lint/nursery/useConsistentMethodSignatures ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
64+
invalid-methods.ts:8:3 lint/nursery/useConsistentMethodSignatures FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━
5865
5966
i Prefer using method-style over property-style method signatures.
6067
@@ -68,15 +75,22 @@ invalid-methods.ts:8:3 lint/nursery/useConsistentMethodSignatures ━━━━
6875
6976
i If this isn't what you want, consider changing the style option in the rule's settings.
7077
71-
i This rule is still being actively worked on, so it may be missing features or have rough edges. Visit https://github.com/biomejs/biome/issues/8780 for more information or to report possible bugs.
72-
7378
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
7479
80+
i Unsafe fix: Convert to method-style signature.
81+
82+
6 6 │
83+
7 7 │ type GenericProp<T, U> = {
84+
8- ··p:·(argT=>·U;
85+
8+ ··p(argT):·U;
86+
9 9};
87+
10 10 │
88+
7589
7690
```
7791

7892
```
79-
invalid-methods.ts:13:7 lint/nursery/useConsistentMethodSignatures ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
93+
invalid-methods.ts:13:7 lint/nursery/useConsistentMethodSignatures FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━
8094
8195
i Prefer using method-style over property-style method signatures.
8296
@@ -91,15 +105,22 @@ invalid-methods.ts:13:7 lint/nursery/useConsistentMethodSignatures ━━━━
91105
92106
i If this isn't what you want, consider changing the style option in the rule's settings.
93107
94-
i This rule is still being actively worked on, so it may be missing features or have rough edges. Visit https://github.com/biomejs/biome/issues/8780 for more information or to report possible bugs.
95-
96108
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
97109
110+
i Unsafe fix: Convert to method-style signature.
111+
112+
11 11 │ type PropUnion =
113+
12 12 │ | {
114+
13- ······foo:·(barnumber=>·number;
115+
13+ ······foo(barnumber):·number;
116+
14 14}
117+
15 15 │ | 4;
118+
98119
99120
```
100121

101122
```
102-
invalid-methods.ts:18:3 lint/nursery/useConsistentMethodSignatures ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
123+
invalid-methods.ts:18:3 lint/nursery/useConsistentMethodSignatures FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━
103124
104125
i Prefer using method-style over property-style method signatures.
105126
@@ -113,9 +134,16 @@ invalid-methods.ts:18:3 lint/nursery/useConsistentMethodSignatures ━━━━
113134
114135
i If this isn't what you want, consider changing the style option in the rule's settings.
115136
116-
i This rule is still being actively worked on, so it may be missing features or have rough edges. Visit https://github.com/biomejs/biome/issues/8780 for more information or to report possible bugs.
117-
118137
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
119138
139+
i Unsafe fix: Convert to method-style signature.
140+
141+
16 16 │
142+
17 17 │ type bad = {
143+
18- ··qux:·(quuxnumber=>·"quuux";
144+
18+ ··qux(quuxnumber):·"quuux";
145+
19 19} & { foo: string };
146+
20 20 │
147+
120148
121149
```

0 commit comments

Comments
 (0)