@@ -6,9 +6,9 @@ use biome_js_factory::make;
6
6
use biome_js_semantic:: SemanticModel ;
7
7
use biome_js_syntax:: {
8
8
binding_ext:: AnyJsBindingDeclaration , AnyJsExpression , AnyJsName , AnyTsName , AnyTsReturnType ,
9
- AnyTsType , JsArrowFunctionExpression , JsCallExpression , JsExpressionStatement ,
10
- JsFunctionDeclaration , JsMethodClassMember , JsMethodObjectMember , JsStaticMemberExpression ,
11
- JsSyntaxKind , TsReturnTypeAnnotation ,
9
+ AnyTsType , AnyTsVariableAnnotation , JsArrowFunctionExpression , JsCallExpression ,
10
+ JsExpressionStatement , JsFunctionDeclaration , JsMethodClassMember , JsMethodObjectMember ,
11
+ JsStaticMemberExpression , JsSyntaxKind , JsVariableDeclarator , TsReturnTypeAnnotation ,
12
12
} ;
13
13
use biome_rowan:: { AstNode , AstSeparatedList , BatchMutationExt , SyntaxNodeCast , TriviaPieceKind } ;
14
14
@@ -45,6 +45,15 @@ declare_lint_rule! {
45
45
/// returnsPromise().then(() => {});
46
46
/// ```
47
47
///
48
+ /// ```ts,expect_diagnostic
49
+ /// const returnsPromise = async (): Promise<string> => {
50
+ /// return 'value';
51
+ /// }
52
+ /// async function returnsPromiseInAsyncFunction() {
53
+ /// returnsPromise().then(() => {});
54
+ /// }
55
+ /// ```
56
+ ///
48
57
/// ### Valid
49
58
///
50
59
/// ```ts
@@ -195,24 +204,27 @@ fn is_callee_a_promise(callee: &AnyJsExpression, model: &SemanticModel) -> bool
195
204
let Some ( any_js_binding_decl) = binding. tree ( ) . declaration ( ) else {
196
205
return false ;
197
206
} ;
198
-
199
- let AnyJsBindingDeclaration :: JsFunctionDeclaration ( func_decl) = any_js_binding_decl
200
- else {
201
- return false ;
202
- } ;
203
-
204
- return is_function_a_promise ( & func_decl) ;
207
+ match any_js_binding_decl {
208
+ AnyJsBindingDeclaration :: JsFunctionDeclaration ( func_decl) => {
209
+ is_function_a_promise ( & func_decl)
210
+ }
211
+ AnyJsBindingDeclaration :: JsVariableDeclarator ( js_var_decl) => {
212
+ is_variable_initializer_a_promise ( & js_var_decl)
213
+ || is_variable_annotation_a_promise ( & js_var_decl)
214
+ }
215
+ _ => false ,
216
+ }
205
217
}
206
218
AnyJsExpression :: JsStaticMemberExpression ( static_member_expr) => {
207
- return is_member_expression_callee_a_promise ( static_member_expr, model) ;
219
+ is_member_expression_callee_a_promise ( static_member_expr, model)
208
220
}
209
- _ => { }
221
+ _ => false ,
210
222
}
211
- false
212
223
}
213
224
214
225
fn is_function_a_promise ( func_decl : & JsFunctionDeclaration ) -> bool {
215
- func_decl. async_token ( ) . is_some ( ) || is_return_type_promise ( func_decl. return_type_annotation ( ) )
226
+ func_decl. async_token ( ) . is_some ( )
227
+ || is_return_type_a_promise ( func_decl. return_type_annotation ( ) )
216
228
}
217
229
218
230
/// Checks if a TypeScript return type annotation is a `Promise`.
@@ -240,7 +252,7 @@ fn is_function_a_promise(func_decl: &JsFunctionDeclaration) -> bool {
240
252
/// ```typescript
241
253
/// function doesNotReturnPromise(): void {}
242
254
/// ```
243
- fn is_return_type_promise ( return_type : Option < TsReturnTypeAnnotation > ) -> bool {
255
+ fn is_return_type_a_promise ( return_type : Option < TsReturnTypeAnnotation > ) -> bool {
244
256
return_type
245
257
. and_then ( |ts_return_type_anno| ts_return_type_anno. ty ( ) . ok ( ) )
246
258
. and_then ( |any_ts_return_type| match any_ts_return_type {
@@ -360,32 +372,7 @@ fn is_member_expression_callee_a_promise(
360
372
return false ;
361
373
} ;
362
374
363
- match callee {
364
- AnyJsExpression :: JsStaticMemberExpression ( static_member_expr) => {
365
- return is_member_expression_callee_a_promise ( & static_member_expr, model) ;
366
- }
367
- AnyJsExpression :: JsIdentifierExpression ( ident_expr) => {
368
- let Some ( reference) = ident_expr. name ( ) . ok ( ) else {
369
- return false ;
370
- } ;
371
- let Some ( binding) = model. binding ( & reference) else {
372
- return false ;
373
- } ;
374
-
375
- let Some ( any_js_binding_decl) = binding. tree ( ) . declaration ( ) else {
376
- return false ;
377
- } ;
378
-
379
- let AnyJsBindingDeclaration :: JsFunctionDeclaration ( func_decl) = any_js_binding_decl
380
- else {
381
- return false ;
382
- } ;
383
- return is_function_a_promise ( & func_decl) ;
384
- }
385
- _ => { }
386
- }
387
-
388
- false
375
+ is_callee_a_promise ( & callee, model)
389
376
}
390
377
391
378
/// Checks if the given `JsExpressionStatement` is within an async function.
@@ -423,3 +410,75 @@ fn is_in_async_function(node: &JsExpressionStatement) -> bool {
423
410
} )
424
411
. is_some ( )
425
412
}
413
+
414
+ /// Checks if the initializer of a `JsVariableDeclarator` is an async function.
415
+ ///
416
+ /// Example TypeScript code that would return `true`:
417
+ ///
418
+ /// ```typescript
419
+ /// const returnsPromise = async (): Promise<string> => {
420
+ /// return 'value';
421
+ /// }
422
+ ///
423
+ /// const returnsPromise = async function (): Promise<string> {
424
+ /// return 'value'
425
+ /// }
426
+ /// ```
427
+ fn is_variable_initializer_a_promise ( js_variable_declarator : & JsVariableDeclarator ) -> bool {
428
+ let Some ( initializer_clause) = & js_variable_declarator. initializer ( ) else {
429
+ return false ;
430
+ } ;
431
+ let Ok ( expr) = initializer_clause. expression ( ) else {
432
+ return false ;
433
+ } ;
434
+ match expr {
435
+ AnyJsExpression :: JsArrowFunctionExpression ( arrow_func) => {
436
+ arrow_func. async_token ( ) . is_some ( )
437
+ || is_return_type_a_promise ( arrow_func. return_type_annotation ( ) )
438
+ }
439
+ AnyJsExpression :: JsFunctionExpression ( func_expr) => {
440
+ func_expr. async_token ( ) . is_some ( )
441
+ || is_return_type_a_promise ( func_expr. return_type_annotation ( ) )
442
+ }
443
+ _ => false ,
444
+ }
445
+ }
446
+
447
+ /// Checks if a `JsVariableDeclarator` has a TypeScript type annotation of `Promise`.
448
+ ///
449
+ ///
450
+ /// Example TypeScript code that would return `true`:
451
+ /// ```typescript
452
+ /// const returnsPromise: () => Promise<string> = () => {
453
+ /// return Promise.resolve("value")
454
+ /// }
455
+ /// ```
456
+ fn is_variable_annotation_a_promise ( js_variable_declarator : & JsVariableDeclarator ) -> bool {
457
+ js_variable_declarator
458
+ . variable_annotation ( )
459
+ . and_then ( |anno| match anno {
460
+ AnyTsVariableAnnotation :: TsTypeAnnotation ( type_anno) => Some ( type_anno) ,
461
+ _ => None ,
462
+ } )
463
+ . and_then ( |ts_type_anno| ts_type_anno. ty ( ) . ok ( ) )
464
+ . and_then ( |any_ts_type| match any_ts_type {
465
+ AnyTsType :: TsFunctionType ( func_type) => {
466
+ func_type
467
+ . return_type ( )
468
+ . ok ( )
469
+ . and_then ( |return_type| match return_type {
470
+ AnyTsReturnType :: AnyTsType ( AnyTsType :: TsReferenceType ( ref_type) ) => {
471
+ ref_type. name ( ) . ok ( ) . map ( |name| match name {
472
+ AnyTsName :: JsReferenceIdentifier ( identifier) => {
473
+ identifier. has_name ( "Promise" )
474
+ }
475
+ _ => false ,
476
+ } )
477
+ }
478
+ _ => None ,
479
+ } )
480
+ }
481
+ _ => None ,
482
+ } )
483
+ . unwrap_or ( false )
484
+ }
0 commit comments