8
8
getNodeID,
9
9
getFirst,
10
10
getLast,
11
+ getNodeIndent,
11
12
isInHandledContext,
12
13
isInAsyncHandledContext,
13
14
isNodeReturned,
@@ -128,15 +129,21 @@ module.exports = createRule({
128
129
properties : {
129
130
useBaseTypeOfLiteral : {
130
131
type : 'boolean' ,
131
- default : false ,
132
+ } ,
133
+ preferUnionType : {
134
+ type : 'boolean' ,
132
135
} ,
133
136
} ,
134
137
additionalProperties : false ,
135
138
} ,
136
139
] ,
137
140
} ,
138
141
defaultOptions : [
139
- { useBaseTypeOfLiteral : false } ,
142
+ /** @type {{ useBaseTypeOfLiteral?: boolean; preferUnionType?: boolean } } */
143
+ ( {
144
+ useBaseTypeOfLiteral : false ,
145
+ preferUnionType : true
146
+ } ) ,
140
147
] ,
141
148
142
149
create ( context ) {
@@ -146,6 +153,7 @@ module.exports = createRule({
146
153
147
154
const {
148
155
useBaseTypeOfLiteral = false ,
156
+ preferUnionType = true ,
149
157
} = context . options [ 0 ] ?? { } ;
150
158
151
159
/** @type {Set<string> } */
@@ -304,20 +312,31 @@ module.exports = createRule({
304
312
}
305
313
306
314
const throwableTypes =
307
- toFlattenedTypeArray (
308
- /** @type {import('typescript').Type[] } */ (
309
- throwTypes . get ( node )
310
- ?. map ( t => checker . getAwaitedType ( t ) ?? t )
311
- )
312
- ) ;
315
+ node . async
316
+ ? [ ]
317
+ : toFlattenedTypeArray (
318
+ /** @type {import('typescript').Type[] } */ (
319
+ throwTypes . get ( node )
320
+ ?. map ( t => checker . getAwaitedType ( t ) ?? t )
321
+ )
322
+ ) ;
313
323
314
324
const rejectableTypes =
315
- toFlattenedTypeArray (
316
- /** @type {import('typescript').Type[] } */ (
317
- rejectTypes . get ( node )
318
- ?. map ( t => checker . getAwaitedType ( t ) ?? t )
325
+ node . async
326
+ ? toFlattenedTypeArray (
327
+ [
328
+ ...throwTypes . get ( node )
329
+ ?. map ( t => checker . getAwaitedType ( t ) ?? t ) ,
330
+ ...rejectTypes . get ( node )
331
+ ?. map ( t => checker . getAwaitedType ( t ) ?? t )
332
+ ]
319
333
)
320
- ) ;
334
+ : toFlattenedTypeArray (
335
+ /** @type {import('typescript').Type[] } */ (
336
+ rejectTypes . get ( node )
337
+ ?. map ( t => checker . getAwaitedType ( t ) ?? t )
338
+ )
339
+ ) ;
321
340
322
341
if (
323
342
! throwableTypes . length &&
@@ -366,40 +385,58 @@ module.exports = createRule({
366
385
const lastThrowsTypeNode = getLast ( documentedThrowsTypeNodes ) ;
367
386
if ( ! lastThrowsTypeNode ) return ;
368
387
369
- // Thrown types inside async function should be wrapped into Promise
388
+ const indent = getNodeIndent ( sourceCode , node ) ;
389
+
390
+ // If all callee thrown types are compatible with caller's throws tags,
391
+ // we don't need to report anything
370
392
if (
371
- node . async &&
372
- ! getJSDocThrowsTagTypes ( checker , callerDeclarationTSNode )
373
- . every ( type => isPromiseType ( services , type ) )
374
- ) {
393
+ ! throwTypeGroups . source . incompatible &&
394
+ ! rejectTypeGroups . source . incompatible
395
+ ) return ;
396
+
397
+ // Thrown types inside async function should be wrapped into Promise
398
+ if ( node . async ) {
399
+ if ( preferUnionType ) {
400
+ context . report ( {
401
+ node,
402
+ messageId : 'throwTypeMismatch' ,
403
+ fix ( fixer ) {
404
+ return fixer . replaceTextRange (
405
+ [ lastThrowsTypeNode . getStart ( ) , lastThrowsTypeNode . getEnd ( ) ] ,
406
+ `Promise<${
407
+ typesToUnionString (
408
+ checker ,
409
+ toSortedByMetadata ( [
410
+ ...throwableTypes ,
411
+ ...rejectableTypes ,
412
+ ] ) ,
413
+ { useBaseTypeOfLiteral }
414
+ )
415
+ } >`) ;
416
+ } ,
417
+ } ) ;
418
+ return ;
419
+ }
420
+
375
421
context . report ( {
376
422
node,
377
423
messageId : 'throwTypeMismatch' ,
378
424
fix ( fixer ) {
379
425
return fixer . replaceTextRange (
380
- [ lastThrowsTypeNode . pos , lastThrowsTypeNode . end ] ,
381
- `Promise<${
382
- typesToUnionString (
383
- checker ,
384
- toSortedByMetadata ( [
385
- ...throwableTypes ,
386
- ...rejectableTypes ,
387
- ] ) ,
388
- { useBaseTypeOfLiteral }
426
+ [ lastThrowsTypeNode . getStart ( ) , lastThrowsTypeNode . getEnd ( ) ] ,
427
+ toSortedByMetadata ( [ ...throwableTypes , ...rejectableTypes ] )
428
+ . map ( t =>
429
+ `Promise<${
430
+ getQualifiedTypeName ( checker , t , { useBaseTypeOfLiteral } )
431
+ } >`
389
432
)
390
- } >`) ;
433
+ . join ( `}\n${ indent } * @throws {` )
434
+ ) ;
391
435
} ,
392
436
} ) ;
393
437
return ;
394
438
}
395
439
396
- // If all callee thrown types are compatible with caller's throws tags,
397
- // we don't need to report anything
398
- if (
399
- ! throwTypeGroups . source . incompatible &&
400
- ! rejectTypeGroups . source . incompatible
401
- ) return ;
402
-
403
440
const lastThrowsTag = getLast ( documentedThrowsTags ) ;
404
441
if ( ! lastThrowsTag ) return ;
405
442
@@ -426,37 +463,61 @@ module.exports = createRule({
426
463
return ;
427
464
}
428
465
466
+ if ( preferUnionType ) {
467
+ context . report ( {
468
+ node,
469
+ messageId : 'throwTypeMismatch' ,
470
+ fix ( fixer ) {
471
+ return fixer . replaceTextRange (
472
+ [ lastThrowsTypeNode . getStart ( ) , lastThrowsTypeNode . getEnd ( ) ] ,
473
+ node . async
474
+ ? `Promise<${
475
+ typesToUnionString (
476
+ checker ,
477
+ toSortedByMetadata ( [ ...throwableTypes , ...rejectableTypes ] ) ,
478
+ { useBaseTypeOfLiteral }
479
+ )
480
+ } >`
481
+ : typeStringsToUnionString ( [
482
+ throwableTypes . length
483
+ ? typesToUnionString (
484
+ checker , toSortedByMetadata ( throwableTypes ) ,
485
+ { useBaseTypeOfLiteral }
486
+ )
487
+ : '' ,
488
+ rejectableTypes . length
489
+ ? `Promise<${
490
+ typesToUnionString (
491
+ checker ,
492
+ toSortedByMetadata ( rejectableTypes ) ,
493
+ { useBaseTypeOfLiteral }
494
+ ) } >`
495
+ : '' ,
496
+ ] . filter ( t => ! ! t ) )
497
+ ) ;
498
+ } ,
499
+ } ) ;
500
+ return ;
501
+ }
502
+
429
503
context . report ( {
430
504
node,
431
505
messageId : 'throwTypeMismatch' ,
432
506
fix ( fixer ) {
433
- // If there is only one throws tag, make it as a union type
434
507
return fixer . replaceTextRange (
435
- [ lastThrowsTypeNode . pos , lastThrowsTypeNode . end ] ,
436
- node . async
437
- ? `Promise<${
438
- typesToUnionString (
439
- checker ,
440
- toSortedByMetadata ( [ ...throwableTypes , ...rejectableTypes ] ) ,
441
- { useBaseTypeOfLiteral }
442
- )
443
- } >`
444
- : typeStringsToUnionString ( [
445
- throwableTypes . length
446
- ? typesToUnionString (
447
- checker , toSortedByMetadata ( throwableTypes ) ,
448
- { useBaseTypeOfLiteral }
449
- )
450
- : '' ,
451
- rejectableTypes . length
452
- ? `Promise<${
453
- typesToUnionString (
454
- checker ,
455
- toSortedByMetadata ( rejectableTypes ) ,
456
- { useBaseTypeOfLiteral }
457
- ) } >`
458
- : '' ,
459
- ] . filter ( t => ! ! t ) )
508
+ [ lastThrowsTypeNode . getStart ( ) , lastThrowsTypeNode . getEnd ( ) ] ,
509
+ toSortedByMetadata ( throwableTypes )
510
+ . map ( t =>
511
+ getQualifiedTypeName ( checker , t , { useBaseTypeOfLiteral } )
512
+ )
513
+ . join ( `}\n${ indent } * @throws {` ) +
514
+ toSortedByMetadata ( rejectableTypes )
515
+ . map ( t =>
516
+ `Promise<${
517
+ getQualifiedTypeName ( checker , t , { useBaseTypeOfLiteral } )
518
+ } >`
519
+ )
520
+ . join ( `}\n${ indent } * @throws {` )
460
521
) ;
461
522
} ,
462
523
} ) ;
0 commit comments