@@ -12,14 +12,25 @@ const {
12
12
13
13
/**
14
14
* @typedef {import('estree').Node } Node
15
+ * @typedef {import('estree').Expression } Expression
15
16
* @typedef {import('estree').Identifier } Identifier
16
17
* @typedef {import('estree').FunctionExpression } FunctionExpression
17
18
* @typedef {import('estree').ArrowFunctionExpression } ArrowFunctionExpression
18
19
* @typedef {import('estree').SimpleCallExpression } CallExpression
20
+ * @typedef {import('estree').MemberExpression } MemberExpression
21
+ * @typedef {import('estree').NewExpression } NewExpression
22
+ * @typedef {import('estree').ImportExpression } ImportExpression
23
+ * @typedef {import('estree').YieldExpression } YieldExpression
19
24
* @typedef {import('eslint').Rule.CodePath } CodePath
20
25
* @typedef {import('eslint').Rule.CodePathSegment } CodePathSegment
21
26
*/
22
27
28
+ /**
29
+ * An expression that can throw an error.
30
+ * see https://github.com/eslint/eslint/blob/e940be7a83d0caea15b64c1e1c2785a6540e2641/lib/linter/code-path-analysis/code-path-analyzer.js#L639-L643
31
+ * @typedef {CallExpression | MemberExpression | NewExpression | ImportExpression | YieldExpression } ThrowableExpression
32
+ */
33
+
23
34
/**
24
35
* Iterate all previous path segments.
25
36
* @param {CodePathSegment } segment
@@ -150,14 +161,18 @@ class CodePathInfo {
150
161
151
162
/**
152
163
* Check all paths and return paths resolved multiple times.
164
+ * @param {PromiseCodePathContext } promiseCodePathContext
153
165
* @returns {Iterable<AlreadyResolvedData & { node: Identifier }> }
154
166
*/
155
- * iterateReports ( ) {
167
+ * iterateReports ( promiseCodePathContext ) {
156
168
const targets = [ ...this . segmentInfos . values ( ) ] . filter (
157
169
( info ) => info . resolved
158
170
)
159
171
for ( const segmentInfo of targets ) {
160
- const result = this . _getAlreadyResolvedData ( segmentInfo . segment )
172
+ const result = this . _getAlreadyResolvedData (
173
+ segmentInfo . segment ,
174
+ promiseCodePathContext
175
+ )
161
176
if ( result ) {
162
177
yield {
163
178
node : segmentInfo . resolved ,
@@ -170,14 +185,18 @@ class CodePathInfo {
170
185
/**
171
186
* Compute the previously resolved path.
172
187
* @param {CodePathSegment } segment
188
+ * @param {PromiseCodePathContext } promiseCodePathContext
173
189
* @returns {AlreadyResolvedData | null }
174
190
*/
175
- _getAlreadyResolvedData ( segment ) {
176
- if ( segment . prevSegments . length === 0 ) {
191
+ _getAlreadyResolvedData ( segment , promiseCodePathContext ) {
192
+ const prevSegments = segment . prevSegments . filter (
193
+ ( prev ) => ! promiseCodePathContext . isResolvedTryBlockCodePathSegment ( prev )
194
+ )
195
+ if ( prevSegments . length === 0 ) {
177
196
return null
178
197
}
179
- const prevSegmentInfos = segment . prevSegments . map ( ( prev ) =>
180
- this . _getProcessedSegmentInfo ( prev )
198
+ const prevSegmentInfos = prevSegments . map ( ( prev ) =>
199
+ this . _getProcessedSegmentInfo ( prev , promiseCodePathContext )
181
200
)
182
201
if ( prevSegmentInfos . every ( ( info ) => info . resolved ) ) {
183
202
// If the previous paths are all resolved, the next path is also resolved.
@@ -246,16 +265,20 @@ class CodePathInfo {
246
265
}
247
266
/**
248
267
* @param {CodePathSegment } segment
268
+ * @param {PromiseCodePathContext } promiseCodePathContext
249
269
*/
250
- _getProcessedSegmentInfo ( segment ) {
270
+ _getProcessedSegmentInfo ( segment , promiseCodePathContext ) {
251
271
const segmentInfo = this . segmentInfos . get ( segment )
252
272
if ( segmentInfo ) {
253
273
return segmentInfo
254
274
}
255
275
const newInfo = new CodePathSegmentInfo ( this , segment )
256
276
this . segmentInfos . set ( segment , newInfo )
257
277
258
- const alreadyResolvedData = this . _getAlreadyResolvedData ( segment )
278
+ const alreadyResolvedData = this . _getAlreadyResolvedData (
279
+ segment ,
280
+ promiseCodePathContext
281
+ )
259
282
if ( alreadyResolvedData ) {
260
283
if ( alreadyResolvedData . kind === 'certain' ) {
261
284
newInfo . resolved = alreadyResolvedData . resolved
@@ -291,6 +314,21 @@ class CodePathSegmentInfo {
291
314
}
292
315
}
293
316
317
+ class PromiseCodePathContext {
318
+ constructor ( ) {
319
+ /** @type {Set<string> } */
320
+ this . resolvedSegmentIds = new Set ( )
321
+ }
322
+ /** @param {CodePathSegment } */
323
+ addResolvedTryBlockCodePathSegment ( segment ) {
324
+ this . resolvedSegmentIds . add ( segment . id )
325
+ }
326
+ /** @param {CodePathSegment } */
327
+ isResolvedTryBlockCodePathSegment ( segment ) {
328
+ return this . resolvedSegmentIds . has ( segment . id )
329
+ }
330
+ }
331
+
294
332
module . exports = {
295
333
meta : {
296
334
type : 'problem' ,
@@ -308,6 +346,7 @@ module.exports = {
308
346
/** @param {import('eslint').Rule.RuleContext } context */
309
347
create ( context ) {
310
348
const reported = new Set ( )
349
+ const promiseCodePathContext = new PromiseCodePathContext ( )
311
350
/**
312
351
* @param {Identifier } node
313
352
* @param {Identifier } resolved
@@ -327,9 +366,14 @@ module.exports = {
327
366
} ,
328
367
} )
329
368
}
330
- /** @param {CodePathInfo } codePathInfo */
331
- function verifyMultipleResolvedPath ( codePathInfo ) {
332
- for ( const { node, resolved, kind } of codePathInfo . iterateReports ( ) ) {
369
+ /**
370
+ * @param {CodePathInfo } codePathInfo
371
+ * @param {PromiseCodePathContext } promiseCodePathContext
372
+ */
373
+ function verifyMultipleResolvedPath ( codePathInfo , promiseCodePathContext ) {
374
+ for ( const { node, resolved, kind } of codePathInfo . iterateReports (
375
+ promiseCodePathContext
376
+ ) ) {
333
377
report ( node , resolved , kind )
334
378
}
335
379
}
@@ -338,6 +382,8 @@ module.exports = {
338
382
const codePathInfoStack = [ ]
339
383
/** @type {Set<Identifier>[] } */
340
384
const resolverReferencesStack = [ new Set ( ) ]
385
+ /** @type {ThrowableExpression | null } */
386
+ let lastThrowableExpression = null
341
387
return {
342
388
/** @param {FunctionExpression | ArrowFunctionExpression } node */
343
389
'FunctionExpression, ArrowFunctionExpression' ( node ) {
@@ -376,7 +422,33 @@ module.exports = {
376
422
onCodePathEnd ( ) {
377
423
const codePathInfo = codePathInfoStack . shift ( )
378
424
if ( codePathInfo . resolvedCount > 1 ) {
379
- verifyMultipleResolvedPath ( codePathInfo )
425
+ verifyMultipleResolvedPath ( codePathInfo , promiseCodePathContext )
426
+ }
427
+ } ,
428
+ /** @param {ThrowableExpression } node */
429
+ 'CallExpression, MemberExpression, NewExpression, ImportExpression, YieldExpression:exit' (
430
+ node
431
+ ) {
432
+ lastThrowableExpression = node
433
+ } ,
434
+ /**
435
+ * @param {CodePathSegment } segment
436
+ * @param {Node } node
437
+ */
438
+ onCodePathSegmentEnd ( segment , node ) {
439
+ if (
440
+ node . type === 'CatchClause' &&
441
+ lastThrowableExpression &&
442
+ lastThrowableExpression . type === 'CallExpression' &&
443
+ node . parent . type === 'TryStatement' &&
444
+ node . parent . range [ 0 ] <= lastThrowableExpression . range [ 0 ] &&
445
+ lastThrowableExpression . range [ 1 ] <= node . parent . range [ 1 ]
446
+ ) {
447
+ const resolverReferences = resolverReferencesStack [ 0 ]
448
+ if ( resolverReferences . has ( lastThrowableExpression . callee ) ) {
449
+ // Mark a segment if the last expression in the try block is a call to resolve.
450
+ promiseCodePathContext . addResolvedTryBlockCodePathSegment ( segment )
451
+ }
380
452
}
381
453
} ,
382
454
/** @type {Identifier } */
0 commit comments