@@ -24,6 +24,7 @@ const {
24
24
getCalleeDeclaration,
25
25
getJSDocThrowsTags,
26
26
getJSDocThrowsTagTypes,
27
+ getQualifiedTypeName,
27
28
findParent,
28
29
findClosest,
29
30
findClosestFunctionNode,
@@ -54,13 +55,21 @@ module.exports = createRule({
54
55
type : 'boolean' ,
55
56
default : false ,
56
57
} ,
58
+ preferUnionType : {
59
+ type : 'boolean' ,
60
+ default : true ,
61
+ }
57
62
} ,
58
63
additionalProperties : false ,
59
64
} ,
60
65
] ,
61
66
} ,
62
67
defaultOptions : [
63
- { useBaseTypeOfLiteral : false } ,
68
+ /** @type {{ useBaseTypeOfLiteral?: boolean; preferUnionType?: boolean } } */
69
+ ( {
70
+ useBaseTypeOfLiteral : false ,
71
+ preferUnionType : true ,
72
+ } ) ,
64
73
] ,
65
74
66
75
create ( context ) {
@@ -70,6 +79,7 @@ module.exports = createRule({
70
79
71
80
const {
72
81
useBaseTypeOfLiteral = false ,
82
+ preferUnionType = true ,
73
83
} = context . options [ 0 ] ?? { } ;
74
84
75
85
/** @type {Set<string> } */
@@ -274,45 +284,92 @@ module.exports = createRule({
274
284
return ;
275
285
}
276
286
277
- context . report ( {
278
- node : nodeToComment ,
279
- messageId : 'missingThrowsTag' ,
280
- fix ( fixer ) {
281
- const newType =
282
- node . async
283
- ? `Promise<${
284
- typesToUnionString (
285
- checker ,
286
- toSortedByMetadata ( [
287
- ...throwableTypes ,
288
- ...rejectableTypes ,
289
- ] ) ,
290
- { useBaseTypeOfLiteral }
291
- )
292
- } >`
293
- : typeStringsToUnionString ( [
294
- ...throwableTypes . length
295
- ? [
296
- typesToUnionString (
297
- checker ,
298
- toSortedByMetadata ( throwableTypes ) ,
299
- { useBaseTypeOfLiteral }
300
- )
301
- ]
302
- : [ ] ,
303
- ...rejectableTypes . length
304
- ? [
305
- `Promise<${
287
+ if ( preferUnionType ) {
288
+ context . report ( {
289
+ node : nodeToComment ,
290
+ messageId : 'missingThrowsTag' ,
291
+ fix ( fixer ) {
292
+ const newType =
293
+ node . async
294
+ ? `Promise<${
295
+ typesToUnionString (
296
+ checker ,
297
+ toSortedByMetadata ( [
298
+ ...throwableTypes ,
299
+ ...rejectableTypes ,
300
+ ] ) ,
301
+ { useBaseTypeOfLiteral }
302
+ )
303
+ } >`
304
+ : typeStringsToUnionString ( [
305
+ ...throwableTypes . length
306
+ ? [
306
307
typesToUnionString (
307
308
checker ,
308
- toSortedByMetadata ( rejectableTypes ) ,
309
+ toSortedByMetadata ( throwableTypes ) ,
309
310
{ useBaseTypeOfLiteral }
310
311
)
311
- } >`
312
- ]
313
- : [ ] ,
314
- ] ) ;
312
+ ]
313
+ : [ ] ,
314
+ ...rejectableTypes . length
315
+ ? [
316
+ `Promise<${
317
+ typesToUnionString (
318
+ checker ,
319
+ toSortedByMetadata ( rejectableTypes ) ,
320
+ { useBaseTypeOfLiteral }
321
+ )
322
+ } >`
323
+ ]
324
+ : [ ] ,
325
+ ] ) ;
326
+
327
+ const indent = getNodeIndent ( sourceCode , node ) ;
328
+
329
+ if ( hasJSDoc ( sourceCode , nodeToComment ) ) {
330
+ const comments = sourceCode . getCommentsBefore ( nodeToComment ) ;
331
+ const comment = comments
332
+ . find ( ( { value } ) => value . startsWith ( '*' ) ) ;
333
+
334
+ if ( comment ) {
335
+ let newCommentText = sourceCode . getText ( comment ) ;
336
+ if ( ! / ^ \/ \* \* [ \t ] * \n / . test ( newCommentText ) ) {
337
+ newCommentText = newCommentText
338
+ . replace ( / ^ \/ \* \* \s * / , `/**\n${ indent } * ` )
339
+ . replace ( / \s * \* \/ $ / , `\n${ indent } * @throws {${ newType } }\n${ indent } */` )
340
+ } else {
341
+ newCommentText = appendThrowsTags (
342
+ newCommentText ,
343
+ [ newType ] ,
344
+ ) ;
345
+ }
346
+ return fixer . replaceTextRange (
347
+ comment . range ,
348
+ newCommentText ,
349
+ ) ;
350
+ }
351
+ }
352
+
353
+ return fixer
354
+ . insertTextBefore (
355
+ nodeToComment ,
356
+ `/**\n` +
357
+ `${ indent } * @throws {${ newType } }\n` +
358
+ `${ indent } */\n` +
359
+ `${ indent } `
360
+ ) ;
361
+ }
362
+ } ) ;
363
+ return ;
364
+ }
315
365
366
+ context . report ( {
367
+ node : nodeToComment ,
368
+ messageId : 'missingThrowsTag' ,
369
+ fix ( fixer ) {
370
+ const sortedThrowableTypes = toSortedByMetadata ( throwableTypes ) ;
371
+ const sortedRejectableTypes = toSortedByMetadata ( rejectableTypes ) ;
372
+
316
373
const indent = getNodeIndent ( sourceCode , node ) ;
317
374
318
375
if ( hasJSDoc ( sourceCode , nodeToComment ) ) {
@@ -322,14 +379,33 @@ module.exports = createRule({
322
379
323
380
if ( comment ) {
324
381
let newCommentText = sourceCode . getText ( comment ) ;
325
- if ( ! / ^ \/ \* \* [ \t ] * \n / . test ( newCommentText ) ) {
382
+ const isOneLiner = ! / ^ \/ \* \* [ \t ] * \n / . test ( newCommentText ) ;
383
+ if ( isOneLiner ) {
326
384
newCommentText = newCommentText
327
385
. replace ( / ^ \/ \* \* \s * / , `/**\n${ indent } * ` )
328
- . replace ( / \s * \* \/ $ / , `\n${ indent } * @throws {${ newType } }\n${ indent } */` )
386
+ . replace (
387
+ / \s * \* \/ $ / ,
388
+ sortedThrowableTypes . map ( ( t ) =>
389
+ `\n${ indent } * @throws {${ getQualifiedTypeName ( checker , t ) } }`
390
+ ) . join ( '' ) +
391
+ '\n' +
392
+ sortedRejectableTypes . map ( ( t ) =>
393
+ `${ indent } * @throws {Promise<${ getQualifiedTypeName ( checker , t ) } >}`
394
+ ) . join ( '\n' ) +
395
+ '\n' +
396
+ `${ indent } */`
397
+ ) ;
329
398
} else {
330
399
newCommentText = appendThrowsTags (
331
400
newCommentText ,
332
- [ newType ] ,
401
+ [
402
+ ...sortedThrowableTypes . map ( ( t ) =>
403
+ getQualifiedTypeName ( checker , t )
404
+ ) ,
405
+ ...sortedRejectableTypes . map ( ( t ) =>
406
+ `Promise<${ getQualifiedTypeName ( checker , t ) } >`
407
+ ) ,
408
+ ] ,
333
409
) ;
334
410
}
335
411
return fixer . replaceTextRange (
@@ -343,7 +419,12 @@ module.exports = createRule({
343
419
. insertTextBefore (
344
420
nodeToComment ,
345
421
`/**\n` +
346
- `${ indent } * @throws {${ newType } }\n` +
422
+ sortedThrowableTypes . map ( ( t ) =>
423
+ `${ indent } * @throws {${ getQualifiedTypeName ( checker , t ) } }\n`
424
+ ) . join ( '' ) +
425
+ sortedRejectableTypes . map ( ( t ) =>
426
+ `${ indent } * @throws {Promise<${ getQualifiedTypeName ( checker , t ) } >}\n`
427
+ ) . join ( '' ) +
347
428
`${ indent } */\n` +
348
429
`${ indent } `
349
430
) ;
0 commit comments