88 getNodeID,
99 getFirst,
1010 getLast,
11+ getNodeIndent,
1112 isInHandledContext,
1213 isInAsyncHandledContext,
1314 isNodeReturned,
@@ -128,15 +129,21 @@ module.exports = createRule({
128129 properties : {
129130 useBaseTypeOfLiteral : {
130131 type : 'boolean' ,
131- default : false ,
132+ } ,
133+ preferUnionType : {
134+ type : 'boolean' ,
132135 } ,
133136 } ,
134137 additionalProperties : false ,
135138 } ,
136139 ] ,
137140 } ,
138141 defaultOptions : [
139- { useBaseTypeOfLiteral : false } ,
142+ /** @type {{ useBaseTypeOfLiteral?: boolean; preferUnionType?: boolean } } */
143+ ( {
144+ useBaseTypeOfLiteral : false ,
145+ preferUnionType : true
146+ } ) ,
140147 ] ,
141148
142149 create ( context ) {
@@ -146,6 +153,7 @@ module.exports = createRule({
146153
147154 const {
148155 useBaseTypeOfLiteral = false ,
156+ preferUnionType = true ,
149157 } = context . options [ 0 ] ?? { } ;
150158
151159 /** @type {Set<string> } */
@@ -304,20 +312,31 @@ module.exports = createRule({
304312 }
305313
306314 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+ ) ;
313323
314324 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+ ]
319333 )
320- ) ;
334+ : toFlattenedTypeArray (
335+ /** @type {import('typescript').Type[] } */ (
336+ rejectTypes . get ( node )
337+ ?. map ( t => checker . getAwaitedType ( t ) ?? t )
338+ )
339+ ) ;
321340
322341 if (
323342 ! throwableTypes . length &&
@@ -366,40 +385,58 @@ module.exports = createRule({
366385 const lastThrowsTypeNode = getLast ( documentedThrowsTypeNodes ) ;
367386 if ( ! lastThrowsTypeNode ) return ;
368387
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
370392 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+
375421 context . report ( {
376422 node,
377423 messageId : 'throwTypeMismatch' ,
378424 fix ( fixer ) {
379425 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+ } >`
389432 )
390- } >`) ;
433+ . join ( `}\n${ indent } * @throws {` )
434+ ) ;
391435 } ,
392436 } ) ;
393437 return ;
394438 }
395439
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-
403440 const lastThrowsTag = getLast ( documentedThrowsTags ) ;
404441 if ( ! lastThrowsTag ) return ;
405442
@@ -426,37 +463,61 @@ module.exports = createRule({
426463 return ;
427464 }
428465
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+
429503 context . report ( {
430504 node,
431505 messageId : 'throwTypeMismatch' ,
432506 fix ( fixer ) {
433- // If there is only one throws tag, make it as a union type
434507 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 {` )
460521 ) ;
461522 } ,
462523 } ) ;
0 commit comments