UNPKG

3.11 kBJavaScriptView Raw
1/**
2 * Rule: no-nesting
3 * Avoid nesting your promises.
4 */
5
6'use strict'
7
8const getDocsUrl = require('./lib/get-docs-url')
9const hasPromiseCallback = require('./lib/has-promise-callback')
10const isInsidePromise = require('./lib/is-inside-promise')
11
12module.exports = {
13 meta: {
14 type: 'suggestion',
15 docs: {
16 url: getDocsUrl('no-nesting'),
17 },
18 schema: [],
19 },
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
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 },
51 CallExpression(node) {
52 if (!hasPromiseCallback(node)) return
53 if (!callbackScopes.length) {
54 // The node is not in the callback function.
55 return
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 })
115 },
116 }
117 },
118}