@@ -18,12 +18,100 @@ module.exports = {
1818 schema : [ ] ,
1919 } ,
2020 create ( context ) {
21+ /**
22+ * Array of callback function scopes.
23+ * Scopes are in order closest to the current node.
24+ * @type {import('eslint').Scope.Scope[] }
25+ */
26+ const callbackScopes = [ ]
27+
28+ /**
29+ * @param {import('eslint').Scope.Scope } scope
30+ * @returns {Iterable<import('eslint').Scope.Reference> }
31+ */
32+ function * iterateDefinedReferences ( scope ) {
33+ for ( const variable of scope . variables ) {
34+ for ( const reference of variable . references ) {
35+ yield reference
36+ }
37+ }
38+ }
39+
2140 return {
41+ ':function' ( node ) {
42+ if ( isInsidePromise ( node ) ) {
43+ callbackScopes . unshift ( context . getScope ( ) )
44+ }
45+ } ,
46+ ':function:exit' ( node ) {
47+ if ( isInsidePromise ( node ) ) {
48+ callbackScopes . shift ( )
49+ }
50+ } ,
2251 CallExpression ( node ) {
2352 if ( ! hasPromiseCallback ( node ) ) return
24- if ( context . getAncestors ( ) . some ( isInsidePromise ) ) {
25- context . report ( { node, message : 'Avoid nesting promises.' } )
53+ if ( ! callbackScopes . length ) {
54+ // The node is not in the callback function.
55+ return
2656 }
57+
58+ // Checks if the argument callback uses variables defined in the closest callback function scope.
59+ //
60+ // e.g.
61+ // ```
62+ // doThing()
63+ // .then(a => getB(a)
64+ // .then(b => getC(a, b))
65+ // )
66+ // ```
67+ //
68+ // In the above case, Since the variables it references are undef,
69+ // we cannot refactor the nesting like following:
70+ // ```
71+ // doThing()
72+ // .then(a => getB(a))
73+ // .then(b => getC(a, b))
74+ // ```
75+ //
76+ // However, `getD` can be refactored in the following:
77+ // ```
78+ // doThing()
79+ // .then(a => getB(a)
80+ // .then(b => getC(a, b)
81+ // .then(c => getD(a, c))
82+ // )
83+ // )
84+ // ```
85+ // ↓
86+ // ```
87+ // doThing()
88+ // .then(a => getB(a)
89+ // .then(b => getC(a, b))
90+ // .then(c => getD(a, c))
91+ // )
92+ // ```
93+ // This is why we only check the closest callback function scope.
94+ //
95+ const closestCallbackScope = callbackScopes [ 0 ]
96+ for ( const reference of iterateDefinedReferences (
97+ closestCallbackScope
98+ ) ) {
99+ if (
100+ node . arguments . some (
101+ ( arg ) =>
102+ arg . range [ 0 ] <= reference . identifier . range [ 0 ] &&
103+ reference . identifier . range [ 1 ] <= arg . range [ 1 ]
104+ )
105+ ) {
106+ // Argument callbacks refer to variables defined in the callback function.
107+ return
108+ }
109+ }
110+
111+ context . report ( {
112+ node : node . callee . property ,
113+ message : 'Avoid nesting promises.' ,
114+ } )
27115 } ,
28116 }
29117 } ,
0 commit comments