diff --git a/crates/biome_js_analyze/src/analyzers/style/use_shorthand_function_type.rs b/crates/biome_js_analyze/src/analyzers/style/use_shorthand_function_type.rs index 6112f90efeb7..a1f180a40022 100644 --- a/crates/biome_js_analyze/src/analyzers/style/use_shorthand_function_type.rs +++ b/crates/biome_js_analyze/src/analyzers/style/use_shorthand_function_type.rs @@ -145,7 +145,8 @@ impl Rule for UseShorthandFunctionType { node, )?), ) - .build(); + .build() + .with_type_parameters(interface_decl.type_parameters()); mutation.replace_node( AnyJsDeclarationClause::from(interface_decl), diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_jsx_key_in_iterable.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_jsx_key_in_iterable.rs index a4b83e9c6a70..3824d8f370df 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_jsx_key_in_iterable.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/use_jsx_key_in_iterable.rs @@ -206,7 +206,14 @@ fn handle_iterators( let body = callback.body().ok()?; match body { AnyJsFunctionBody::AnyJsExpression(expr) => { - handle_potential_react_component(&expr, model, is_inside_jsx) + // unwrap parenthesized expression + let mut inner_expr = expr; + while let AnyJsExpression::JsParenthesizedExpression(parenthesized_expr) = + inner_expr + { + inner_expr = parenthesized_expr.expression().ok()?; + } + handle_potential_react_component(&inner_expr, model, is_inside_jsx) .map(|state| vec![state]) } AnyJsFunctionBody::JsFunctionBody(body) => { diff --git a/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/invalid.jsx index cf84322cb0c6..6a26c119f7ab 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/invalid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/invalid.jsx @@ -35,3 +35,5 @@ React.Children.map(c => React.cloneElement(c)); (

{data.map(c =>

)}) (

{data.map(c => xyz)}

) + +(

{data.map(c => (

))}) \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/invalid.jsx.snap index f6930686c0a6..2f1129760362 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/invalid.jsx.snap @@ -42,6 +42,7 @@ React.Children.map(c => React.cloneElement(c)); (

{data.map(c => xyz)}

) +(

{data.map(c => (

))}) ``` # Diagnostics @@ -586,6 +587,7 @@ invalid.jsx:37:21 lint/nursery/useJsxKeyInIterable ━━━━━━━━━ > 37 │ (

{data.map(c => xyz)}

) │ ^^^ 38 │ + 39 │ (

{data.map(c => (

))}) i Either return a JSX expression, or suppress this instance if you determine it is safe. @@ -593,3 +595,20 @@ invalid.jsx:37:21 lint/nursery/useJsxKeyInIterable ━━━━━━━━━ ``` + +``` +invalid.jsx:39:22 lint/nursery/useJsxKeyInIterable ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Missing key property for this element in iterable. + + 37 │ (

{data.map(c => xyz)}

) + 38 │ + > 39 │ (

{data.map(c => (

))}) + │ ^^^^ + + i The order of the items may change, and having a key can help React identify which item was moved. + + i Check the React documentation. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/valid.jsx index d9088a1e5006..c657aca06d9c 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/valid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/valid.jsx @@ -42,3 +42,5 @@ React.Children.map(c => React.cloneElement(c, {key: c})); (

{[

,

,

]}) (

{data.map(c =>

)}) + +(

{data.map(c => (

))}) \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/valid.jsx.snap index a5a0830aadc6..6cbcb18b389a 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/valid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/useJsxKeyInIterable/valid.jsx.snap @@ -49,4 +49,5 @@ React.Children.map(c => React.cloneElement(c, {key: c})); (

{data.map(c =>

)}) +(

{data.map(c => (

))}) ``` 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 92a27cb65df2..e8f9fcd63508 100644 --- a/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts +++ b/crates/biome_js_analyze/tests/specs/style/useShorthandFunctionType/invalid.ts @@ -31,3 +31,8 @@ type GenericCallSignature = { (arg: T): T }; // Object type with optional call signature let optionalCall: { (): number | undefined }; + +// Generic interface with a call signature +interface GenericInterface { + (value: T): boolean; +} 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 9fec550677d4..84f235718988 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 @@ -38,6 +38,11 @@ type GenericCallSignature = { (arg: T): T }; // Object type with optional call signature let optionalCall: { (): number | undefined }; +// Generic interface with a call signature +interface GenericInterface { + (value: T): boolean; +} + ``` # Diagnostics @@ -229,6 +234,7 @@ invalid.ts:33:21 lint/style/useShorthandFunctionType FIXABLE ━━━━━ > 33 │ let optionalCall: { (): number | undefined }; │ ^^^^^^^^^^^^^^^^^^^^^^ 34 │ + 35 │ // Generic interface with a call signature i Types containing only a call signature can be shortened to a function type. @@ -239,8 +245,34 @@ invalid.ts:33:21 lint/style/useShorthandFunctionType FIXABLE ━━━━━ 33 │ - let·optionalCall:·{·():·number·|·undefined·}; 33 │ + let·optionalCall:·()·=>·number·|·undefined; 34 34 │ + 35 35 │ // Generic interface with a call signature ``` +``` +invalid.ts:37:2 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; + │ ^^^^^^^^^^^^^^^^^^^^ + 38 │ } + 39 │ + + 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 │ + +``` diff --git a/website/src/assets/vscode-extension.png b/website/src/assets/vscode-extension.png index 2ef4ba3dd6fe..06f6e7deb39a 100644 Binary files a/website/src/assets/vscode-extension.png and b/website/src/assets/vscode-extension.png differ