@@ -12,14 +12,25 @@ const {
1212
1313/**
1414 * @typedef {import('estree').Node } Node
15+ * @typedef {import('estree').Expression } Expression
1516 * @typedef {import('estree').Identifier } Identifier
1617 * @typedef {import('estree').FunctionExpression } FunctionExpression
1718 * @typedef {import('estree').ArrowFunctionExpression } ArrowFunctionExpression
1819 * @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
1924 * @typedef {import('eslint').Rule.CodePath } CodePath
2025 * @typedef {import('eslint').Rule.CodePathSegment } CodePathSegment
2126 */
2227
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+
2334/**
2435 * Iterate all previous path segments.
2536 * @param {CodePathSegment } segment
@@ -150,14 +161,18 @@ class CodePathInfo {
150161
151162 /**
152163 * Check all paths and return paths resolved multiple times.
164+ * @param {PromiseCodePathContext } promiseCodePathContext
153165 * @returns {Iterable<AlreadyResolvedData & { node: Identifier }> }
154166 */
155- * iterateReports ( ) {
167+ * iterateReports ( promiseCodePathContext ) {
156168 const targets = [ ...this . segmentInfos . values ( ) ] . filter (
157169 ( info ) => info . resolved
158170 )
159171 for ( const segmentInfo of targets ) {
160- const result = this . _getAlreadyResolvedData ( segmentInfo . segment )
172+ const result = this . _getAlreadyResolvedData (
173+ segmentInfo . segment ,
174+ promiseCodePathContext
175+ )
161176 if ( result ) {
162177 yield {
163178 node : segmentInfo . resolved ,
@@ -170,14 +185,18 @@ class CodePathInfo {
170185 /**
171186 * Compute the previously resolved path.
172187 * @param {CodePathSegment } segment
188+ * @param {PromiseCodePathContext } promiseCodePathContext
173189 * @returns {AlreadyResolvedData | null }
174190 */
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 ) {
177196 return null
178197 }
179- const prevSegmentInfos = segment . prevSegments . map ( ( prev ) =>
180- this . _getProcessedSegmentInfo ( prev )
198+ const prevSegmentInfos = prevSegments . map ( ( prev ) =>
199+ this . _getProcessedSegmentInfo ( prev , promiseCodePathContext )
181200 )
182201 if ( prevSegmentInfos . every ( ( info ) => info . resolved ) ) {
183202 // If the previous paths are all resolved, the next path is also resolved.
@@ -246,16 +265,20 @@ class CodePathInfo {
246265 }
247266 /**
248267 * @param {CodePathSegment } segment
268+ * @param {PromiseCodePathContext } promiseCodePathContext
249269 */
250- _getProcessedSegmentInfo ( segment ) {
270+ _getProcessedSegmentInfo ( segment , promiseCodePathContext ) {
251271 const segmentInfo = this . segmentInfos . get ( segment )
252272 if ( segmentInfo ) {
253273 return segmentInfo
254274 }
255275 const newInfo = new CodePathSegmentInfo ( this , segment )
256276 this . segmentInfos . set ( segment , newInfo )
257277
258- const alreadyResolvedData = this . _getAlreadyResolvedData ( segment )
278+ const alreadyResolvedData = this . _getAlreadyResolvedData (
279+ segment ,
280+ promiseCodePathContext
281+ )
259282 if ( alreadyResolvedData ) {
260283 if ( alreadyResolvedData . kind === 'certain' ) {
261284 newInfo . resolved = alreadyResolvedData . resolved
@@ -291,6 +314,21 @@ class CodePathSegmentInfo {
291314 }
292315}
293316
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+
294332module . exports = {
295333 meta : {
296334 type : 'problem' ,
@@ -308,6 +346,7 @@ module.exports = {
308346 /** @param {import('eslint').Rule.RuleContext } context */
309347 create ( context ) {
310348 const reported = new Set ( )
349+ const promiseCodePathContext = new PromiseCodePathContext ( )
311350 /**
312351 * @param {Identifier } node
313352 * @param {Identifier } resolved
@@ -327,9 +366,14 @@ module.exports = {
327366 } ,
328367 } )
329368 }
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+ ) ) {
333377 report ( node , resolved , kind )
334378 }
335379 }
@@ -338,6 +382,8 @@ module.exports = {
338382 const codePathInfoStack = [ ]
339383 /** @type {Set<Identifier>[] } */
340384 const resolverReferencesStack = [ new Set ( ) ]
385+ /** @type {ThrowableExpression | null } */
386+ let lastThrowableExpression = null
341387 return {
342388 /** @param {FunctionExpression | ArrowFunctionExpression } node */
343389 'FunctionExpression, ArrowFunctionExpression' ( node ) {
@@ -376,7 +422,33 @@ module.exports = {
376422 onCodePathEnd ( ) {
377423 const codePathInfo = codePathInfoStack . shift ( )
378424 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+ }
380452 }
381453 } ,
382454 /** @type {Identifier } */
0 commit comments