@@ -18,12 +18,100 @@ module.exports = {
18
18
schema : [ ] ,
19
19
} ,
20
20
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
+
21
40
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
+ } ,
22
51
CallExpression ( node ) {
23
52
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
26
56
}
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
+ } )
27
115
} ,
28
116
}
29
117
} ,
0 commit comments