diff --git a/crates/biome_js_analyze/src/lint/nursery/use_explicit_function_return_type.rs b/crates/biome_js_analyze/src/lint/nursery/use_explicit_function_return_type.rs index f1cc1979efcd..8dc6f6add2f9 100644 --- a/crates/biome_js_analyze/src/lint/nursery/use_explicit_function_return_type.rs +++ b/crates/biome_js_analyze/src/lint/nursery/use_explicit_function_return_type.rs @@ -3,12 +3,12 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_semantic::HasClosureAstNode; -use biome_js_syntax::{AnyJsBinding, AnyJsExpression, AnyJsFunctionBody, AnyTsType}; +use biome_js_syntax::{AnyJsBinding, AnyJsExpression, AnyJsFunctionBody, AnyTsType, JsSyntaxKind}; use biome_js_syntax::{ AnyJsFunction, JsGetterClassMember, JsGetterObjectMember, JsMethodClassMember, JsMethodObjectMember, }; -use biome_rowan::{declare_node_union, AstNode, TextRange}; +use biome_rowan::{declare_node_union, AstNode, SyntaxNodeOptionExt, TextRange}; declare_lint_rule! { /// Require explicit return types on functions and class methods. @@ -99,6 +99,15 @@ declare_lint_rule! { /// const func = (value: number) => ({ foo: 'bar', value }) as const; /// ``` /// + /// ```ts + /// // Callbacks without return types + /// setTimeout(function() { console.log("Hello!"); }, 1000); + /// ``` + /// ```ts + /// // IIFE + /// (() => {})(); + /// ``` + /// pub UseExplicitFunctionReturnType { version: "next", name: "useExplicitFunctionReturnType", @@ -130,6 +139,10 @@ impl Rule for UseExplicitFunctionReturnType { return None; } + if is_function_used_in_argument_or_expression_list(func) { + return None; + } + let func_range = func.syntax().text_range(); if let Ok(Some(AnyJsBinding::JsIdentifierBinding(id))) = func.id() { return Some(TextRange::new( @@ -190,11 +203,14 @@ impl Rule for UseExplicitFunctionReturnType { } } -/** - * Checks if an arrow function immediately returns a `as const` value. - * const func = (value: number) => ({ foo: 'bar', value }) as const; - * const func = () => x as const; - */ +/// Checks if an arrow function immediately returns an `as const` value. +/// +/// # Examples +/// +/// ```typescript +/// const func = (value: number) => ({ foo: 'bar', value }) as const; +/// const func = () => x as const; +/// ``` fn is_direct_const_assertion_in_arrow_functions(func: &AnyJsFunction) -> bool { let AnyJsFunction::JsArrowFunctionExpression(arrow_func) = func else { return false; @@ -214,3 +230,32 @@ fn is_direct_const_assertion_in_arrow_functions(func: &AnyJsFunction) -> bool { ts_ref.text() == "const" } + +/// Checks if a function is allowed within specific expression contexts. +/// These include function calls, array elements, and parenthesized expressions. +/// +/// # Examples +/// +/// JS_CALL_ARGUMENT_LIST: +/// - `window.addEventListener('click', () => {});` +/// - `const foo = arr.map(i => i * i);` +/// - `setTimeout(function() { console.log("Hello!"); }, 1000);` +/// +/// JS_ARRAY_ELEMENT_LIST: +/// - `[function () {}, () => {}];` +/// +/// JS_PARENTHESIZED_EXPRESSION: +/// - `(function () {});` +/// - `(() => {})();` +fn is_function_used_in_argument_or_expression_list(func: &AnyJsFunction) -> bool { + matches!( + func.syntax().parent().kind(), + Some( + JsSyntaxKind::JS_CALL_ARGUMENT_LIST + | JsSyntaxKind::JS_ARRAY_ELEMENT_LIST + // We include JS_PARENTHESIZED_EXPRESSION for IIFE (Immediately Invoked Function Expressions). + // We also assume that the parent of the parent is a call expression. + | JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION + ) + ) +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/invalid.ts b/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/invalid.ts index b04c51575623..290a11b5e955 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/invalid.ts +++ b/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/invalid.ts @@ -40,4 +40,7 @@ const obj = { }; const func = (value: number) => ({ type: 'X', value }) as any; -const func = (value: number) => ({ type: 'X', value }) as Action; \ No newline at end of file +const func = (value: number) => ({ type: 'X', value }) as Action; + +export default () => {}; +export default function () {} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/invalid.ts.snap index d89074f3d0a5..3d5794922220 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/invalid.ts.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/invalid.ts.snap @@ -47,6 +47,9 @@ const obj = { const func = (value: number) => ({ type: 'X', value }) as any; const func = (value: number) => ({ type: 'X', value }) as Action; + +export default () => {}; +export default function () {} ``` # Diagnostics @@ -267,6 +270,7 @@ invalid.ts:42:14 lint/nursery/useExplicitFunctionReturnType ━━━━━━ > 42 │ const func = (value: number) => ({ type: 'X', value }) as any; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 43 │ const func = (value: number) => ({ type: 'X', value }) as Action; + 44 │ i Declaring the return type makes the code self-documenting and can speed up TypeScript type checking. @@ -283,6 +287,42 @@ invalid.ts:43:14 lint/nursery/useExplicitFunctionReturnType ━━━━━━ 42 │ const func = (value: number) => ({ type: 'X', value }) as any; > 43 │ const func = (value: number) => ({ type: 'X', value }) as Action; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 44 │ + 45 │ export default () => {}; + + i Declaring the return type makes the code self-documenting and can speed up TypeScript type checking. + + i Add a return type annotation. + + +``` + +``` +invalid.ts:45:16 lint/nursery/useExplicitFunctionReturnType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Missing return type on function. + + 43 │ const func = (value: number) => ({ type: 'X', value }) as Action; + 44 │ + > 45 │ export default () => {}; + │ ^^^^^^^^ + 46 │ export default function () {} + + i Declaring the return type makes the code self-documenting and can speed up TypeScript type checking. + + i Add a return type annotation. + + +``` + +``` +invalid.ts:46:16 lint/nursery/useExplicitFunctionReturnType ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Missing return type on function. + + 45 │ export default () => {}; + > 46 │ export default function () {} + │ ^^^^^^^^^^^^^^ i Declaring the return type makes the code self-documenting and can speed up TypeScript type checking. diff --git a/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/valid.ts b/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/valid.ts index 01bd485ec710..aa6e78b37dc6 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/valid.ts +++ b/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/valid.ts @@ -33,5 +33,25 @@ const obj = { }, }; +export default (): void => {}; +export default function (): void {} + +// check direct const assertions const func = (value: number) => ({ foo: 'bar', value }) as const; -const func = () => x as const; \ No newline at end of file +const func = () => x as const; + + +// check allow expressions +node.addEventListener('click', () => {}); +node.addEventListener('click', function () {}); +const foo = arr.map(i => i * i); +fn(() => {}); +fn(function () {}); +[function () {}, () => {}]; +(function () { + console.log("This is an IIFE"); +})(); +(() => { + console.log("This is an IIFE"); +})(); +setTimeout(function() { console.log("Hello!"); }, 1000); \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/valid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/valid.ts.snap index dc5823e095c5..23bdedafbf97 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/valid.ts.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/useExplicitFunctionReturnType/valid.ts.snap @@ -39,6 +39,26 @@ const obj = { }, }; +export default (): void => {}; +export default function (): void {} + +// check direct const assertions const func = (value: number) => ({ foo: 'bar', value }) as const; const func = () => x as const; + + +// check allow expressions +node.addEventListener('click', () => {}); +node.addEventListener('click', function () {}); +const foo = arr.map(i => i * i); +fn(() => {}); +fn(function () {}); +[function () {}, () => {}]; +(function () { + console.log("This is an IIFE"); +})(); +(() => { + console.log("This is an IIFE"); +})(); +setTimeout(function() { console.log("Hello!"); }, 1000); ```