@@ -19,6 +19,7 @@ const {
1919 findNodeToComment,
2020 findIdentifierDeclaration,
2121 createInsertJSDocBeforeFixer,
22+ toFlattenedTypeArray,
2223} = require ( '../utils' ) ;
2324
2425
@@ -79,16 +80,19 @@ module.exports = createRule({
7980 if ( ! throwStatementNodes ) return ;
8081
8182 /** @type {import('typescript').Type[] } */
82- const throwTypes = throwStatementNodes
83- . map ( n => {
84- const type = services . getTypeAtLocation ( n . argument ) ;
85- const tsNode = services . esTreeNodeToTSNodeMap . get ( n . argument ) ;
86-
87- return options . useBaseTypeOfLiteral && ts . isLiteralTypeLiteral ( tsNode )
88- ? checker . getBaseTypeOfLiteralType ( type )
89- : type ;
90- } )
91- . flatMap ( t => t . isUnion ( ) ? t . types : t ) ;
83+ const throwTypes =
84+ toFlattenedTypeArray (
85+ throwStatementNodes
86+ . map ( n => {
87+ const type = services . getTypeAtLocation ( n . argument ) ;
88+ const tsNode = services . esTreeNodeToTSNodeMap . get ( n . argument ) ;
89+
90+ return options . useBaseTypeOfLiteral && ts . isLiteralTypeLiteral ( tsNode )
91+ ? checker . getBaseTypeOfLiteralType ( type )
92+ : type ;
93+ } )
94+ )
95+ . map ( t => checker . getAwaitedType ( t ) ?? t ) ;
9296
9397 if ( hasJSDocThrowsTag ( sourceCode , nodeToComment ) ) {
9498 if ( ! services . esTreeNodeToTSNodeMap . has ( nodeToComment ) ) return ;
@@ -103,8 +107,7 @@ module.exports = createRule({
103107 if ( ! throwsTagTypeNodes . length ) return ;
104108
105109 const throwsTagTypes = getJSDocThrowsTagTypes ( checker , functionDeclarationTSNode )
106- . map ( t => node . async ? checker . getAwaitedType ( t ) : t )
107- . filter ( t => ! ! t ) ;
110+ . map ( t => checker . getAwaitedType ( t ) ?? t ) ;
108111
109112 const typeGroups = groupTypesByCompatibility (
110113 services . program ,
@@ -188,12 +191,7 @@ module.exports = createRule({
188191 if ( ! utils . isPromiseConstructorLike ( services . program , calleeType ) ) {
189192 return ;
190193 }
191-
192- const nodeToComment = findNodeToComment ( functionDeclaration ) ;
193- if ( ! nodeToComment ) return ;
194194
195- if ( hasJSDocThrowsTag ( sourceCode , nodeToComment ) ) return ;
196-
197195 if ( ! node . arguments . length ) return ;
198196
199197 const firstArg = getFirst ( node . arguments ) ;
@@ -236,7 +234,9 @@ module.exports = createRule({
236234 const callbackScope = sourceCode . getScope ( callbackNode )
237235 if ( ! callbackScope ) return ;
238236
239- const rejectCallbackRefs = callbackScope . set . get ( rejectCallbackNode . name ) ?. references ;
237+ const rejectCallbackRefs =
238+ callbackScope . set . get ( rejectCallbackNode . name ) ?. references ;
239+
240240 if ( ! rejectCallbackRefs ) return ;
241241
242242 const callRefs = rejectCallbackRefs
@@ -250,29 +250,25 @@ module.exports = createRule({
250250 const argumentTypes = callRefs
251251 . map ( ref => services . getTypeAtLocation ( ref . arguments [ 0 ] ) ) ;
252252
253- rejectTypes . push (
254- ...argumentTypes . flatMap ( t => t . isUnion ( ) ? t . types : t )
255- ) ;
253+ rejectTypes . push ( ...toFlattenedTypeArray ( argumentTypes ) ) ;
256254 }
257255
258256 if ( throwStatements . has ( getNodeID ( callbackNode ) ) ) {
259257 const throwStatementTypes = throwStatements . get ( getNodeID ( callbackNode ) )
260258 ?. map ( n => services . getTypeAtLocation ( n . argument ) ) ;
261259
262260 if ( throwStatementTypes ) {
263- rejectTypes . push (
264- ...throwStatementTypes . flatMap ( t => t . isUnion ( ) ? t . types : t )
265- ) ;
261+ rejectTypes . push ( ...toFlattenedTypeArray ( throwStatementTypes ) ) ;
266262 }
267263 }
268264
269- const throwsTagTypes = getJSDocThrowsTagTypes (
265+ const callbackThrowsTagTypes = getJSDocThrowsTagTypes (
270266 checker ,
271267 services . esTreeNodeToTSNodeMap . get ( callbackNode )
272268 ) ;
273269
274- if ( throwsTagTypes . length ) {
275- rejectTypes . push ( ...throwsTagTypes ) ;
270+ if ( callbackThrowsTagTypes . length ) {
271+ rejectTypes . push ( ...callbackThrowsTagTypes ) ;
276272 }
277273
278274 if ( ! rejectTypes . length ) return ;
@@ -291,6 +287,53 @@ module.exports = createRule({
291287
292288 if ( isRejectHandled ) return ;
293289
290+ const nodeToComment = findNodeToComment ( functionDeclaration ) ;
291+ if ( ! nodeToComment ) return ;
292+
293+ if ( hasJSDocThrowsTag ( sourceCode , nodeToComment ) ) {
294+ if ( ! services . esTreeNodeToTSNodeMap . has ( nodeToComment ) ) return ;
295+
296+ const functionDeclarationTSNode = services . esTreeNodeToTSNodeMap . get ( functionDeclaration ) ;
297+
298+ const throwsTags = getJSDocThrowsTags ( functionDeclarationTSNode ) ;
299+ const throwsTagTypeNodes = throwsTags
300+ . map ( tag => tag . typeExpression ?. type )
301+ . filter ( tag => ! ! tag ) ;
302+
303+ if ( ! throwsTagTypeNodes . length ) return ;
304+
305+ // Throws tag with `Promise<...>` considered as a reject tag
306+ const rejectTagTypes = toFlattenedTypeArray (
307+ getJSDocThrowsTagTypes ( checker , functionDeclarationTSNode )
308+ . filter ( t =>
309+ utils . isPromiseLike ( services . program , t ) &&
310+ t . symbol . getName ( ) === 'Promise'
311+ )
312+ . map ( t => checker . getAwaitedType ( t ) ?? t )
313+ ) ;
314+
315+ const typeGroups = groupTypesByCompatibility (
316+ services . program ,
317+ rejectTypes ,
318+ rejectTagTypes ,
319+ ) ;
320+ if ( ! typeGroups . incompatible ) return ;
321+
322+ const lastTagtypeNode = getLast ( throwsTagTypeNodes ) ;
323+ if ( ! lastTagtypeNode ) return ;
324+
325+ context . report ( {
326+ node,
327+ messageId : 'throwTypeMismatch' ,
328+ fix ( fixer ) {
329+ return fixer . replaceTextRange (
330+ [ lastTagtypeNode . pos , lastTagtypeNode . end ] ,
331+ `Promise<${ typesToUnionString ( checker , rejectTypes ) } >` ,
332+ ) ;
333+ } ,
334+ } ) ;
335+ return ;
336+ }
294337 context . report ( {
295338 node,
296339 messageId : 'missingThrowsTag' ,
0 commit comments