UNPKG

4.45 kBJavaScriptView Raw
1'use strict'
2
3const getDocsUrl = require('./lib/get-docs-url')
4
5function isFunctionWithBlockStatement(node) {
6 if (node.type === 'FunctionExpression') {
7 return true
8 }
9 if (node.type === 'ArrowFunctionExpression') {
10 return node.body.type === 'BlockStatement'
11 }
12 return false
13}
14
15function isThenCallExpression(node) {
16 return (
17 node.type === 'CallExpression' &&
18 node.callee.type === 'MemberExpression' &&
19 node.callee.property.name === 'then'
20 )
21}
22
23function isFirstArgument(node) {
24 return (
25 node.parent && node.parent.arguments && node.parent.arguments[0] === node
26 )
27}
28
29function isInlineThenFunctionExpression(node) {
30 return (
31 isFunctionWithBlockStatement(node) &&
32 isThenCallExpression(node.parent) &&
33 isFirstArgument(node)
34 )
35}
36
37function hasParentReturnStatement(node) {
38 if (node && node.parent && node.parent.type) {
39 // if the parent is a then, and we haven't returned anything, fail
40 if (isThenCallExpression(node.parent)) {
41 return false
42 }
43
44 if (node.parent.type === 'ReturnStatement') {
45 return true
46 }
47 return hasParentReturnStatement(node.parent)
48 }
49
50 return false
51}
52
53function peek(arr) {
54 return arr[arr.length - 1]
55}
56
57module.exports = {
58 meta: {
59 docs: {
60 url: getDocsUrl('always-return')
61 }
62 },
63 create(context) {
64 // funcInfoStack is a stack representing the stack of currently executing
65 // functions
66 // funcInfoStack[i].branchIDStack is a stack representing the currently
67 // executing branches ("codePathSegment"s) within the given function
68 // funcInfoStack[i].branchInfoMap is an object representing information
69 // about all branches within the given function
70 // funcInfoStack[i].branchInfoMap[j].good is a boolean representing whether
71 // the given branch explictly `return`s or `throw`s. It starts as `false`
72 // for every branch and is updated to `true` if a `return` or `throw`
73 // statement is found
74 // funcInfoStack[i].branchInfoMap[j].loc is a eslint SourceLocation object
75 // for the given branch
76 // example:
77 // funcInfoStack = [ { branchIDStack: [ 's1_1' ],
78 // branchInfoMap:
79 // { s1_1:
80 // { good: false,
81 // loc: <loc> } } },
82 // { branchIDStack: ['s2_1', 's2_4'],
83 // branchInfoMap:
84 // { s2_1:
85 // { good: false,
86 // loc: <loc> },
87 // s2_2:
88 // { good: true,
89 // loc: <loc> },
90 // s2_4:
91 // { good: false,
92 // loc: <loc> } } } ]
93 const funcInfoStack = []
94
95 function markCurrentBranchAsGood() {
96 const funcInfo = peek(funcInfoStack)
97 const currentBranchID = peek(funcInfo.branchIDStack)
98 if (funcInfo.branchInfoMap[currentBranchID]) {
99 funcInfo.branchInfoMap[currentBranchID].good = true
100 }
101 // else unreachable code
102 }
103
104 return {
105 ReturnStatement: markCurrentBranchAsGood,
106 ThrowStatement: markCurrentBranchAsGood,
107
108 onCodePathSegmentStart(segment, node) {
109 const funcInfo = peek(funcInfoStack)
110 funcInfo.branchIDStack.push(segment.id)
111 funcInfo.branchInfoMap[segment.id] = { good: false, node }
112 },
113
114 onCodePathSegmentEnd() {
115 const funcInfo = peek(funcInfoStack)
116 funcInfo.branchIDStack.pop()
117 },
118
119 onCodePathStart() {
120 funcInfoStack.push({
121 branchIDStack: [],
122 branchInfoMap: {}
123 })
124 },
125
126 onCodePathEnd(path, node) {
127 const funcInfo = funcInfoStack.pop()
128
129 if (!isInlineThenFunctionExpression(node)) {
130 return
131 }
132
133 path.finalSegments.forEach(segment => {
134 const id = segment.id
135 const branch = funcInfo.branchInfoMap[id]
136 if (!branch.good) {
137 if (hasParentReturnStatement(branch.node)) {
138 return
139 }
140
141 // check shortcircuit syntax like `x && x()` and `y || x()``
142 const prevSegments = segment.prevSegments
143 for (let ii = prevSegments.length - 1; ii >= 0; --ii) {
144 const prevSegment = prevSegments[ii]
145 if (funcInfo.branchInfoMap[prevSegment.id].good) return
146 }
147
148 context.report({
149 message: 'Each then() should return a value or throw',
150 node: branch.node
151 })
152 }
153 })
154 }
155 }
156 }
157}