Skip to content

Commit 5523a81

Browse files
committed
refactor always-return
Much cleaner now :D
1 parent 28caa17 commit 5523a81

File tree

1 file changed

+65
-49
lines changed

1 file changed

+65
-49
lines changed

rules/always-return.js

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,73 +23,89 @@ function isInlineThenFunctionExpression (node) {
2323
)
2424
}
2525

26-
function includes (arr, val) {
27-
return arr.indexOf(val) !== -1
28-
}
29-
30-
function last (arr) {
26+
function peek (arr) {
3127
return arr[arr.length - 1]
3228
}
3329

3430
module.exports = {
3531
create: function (context) {
32+
// funcInfoStack is a stack representing the stack of currently executing
33+
// functions
34+
// funcInfoStack[i].branchIDStack is a stack representing the currently
35+
// executing branches ("codePathSegment"s) within the given function
36+
// funcInfoStack[i].branchInfoMap is an object representing information
37+
// about all branches within the given function
38+
// funcInfoStack[i].branchInfoMap[j].good is a boolean representing whether
39+
// the given branch explictly `return`s or `throw`s. It starts as `false`
40+
// for every branch and is updated to `true` if a `return` or `throw`
41+
// statement is found
42+
// funcInfoStack[i].branchInfoMap[j].loc is a eslint SourceLocation object
43+
// for the given branch
44+
// example:
45+
// funcInfoStack = [ { branchIDStack: [ 's1_1' ],
46+
// branchInfoMap:
47+
// { s1_1:
48+
// { good: false,
49+
// loc: <loc> } } },
50+
// { branchIDStack: ['s2_1', 's2_4'],
51+
// branchInfoMap:
52+
// { s2_1:
53+
// { good: false,
54+
// loc: <loc> },
55+
// s2_2:
56+
// { good: true,
57+
// loc: <loc> },
58+
// s2_4:
59+
// { good: false,
60+
// loc: <loc> } } } ]
3661
var funcInfoStack = []
37-
var CPSIDStack = []
38-
39-
function isEveryBranchReturning (funcInfo) {
40-
// We need to check noCurrentCPSIsOnTheCPSStack because of what
41-
// seems like a bug in eslint where 'FunctionExpression:exit' events occur
42-
// before all of their constituent codePathSegments have fired their
43-
// 'onCodePathSegmentEnd' events
44-
var currentIDs = funcInfo.codePath.currentSegments.map(x => x.id)
45-
var noCurrentCPSIsOnTheCPSStack = !currentIDs.some((id) => includes(CPSIDStack, id))
4662

47-
var finalIDs = funcInfo.codePath.finalSegments.map(x => x.id)
48-
var everyFinalCPSIsReturning = finalIDs.every((id) => includes(funcInfo.explicitlyReturningCPSIDs, id))
49-
50-
return noCurrentCPSIsOnTheCPSStack && everyFinalCPSIsReturning
63+
function markCurrentBranchAsGood () {
64+
var funcInfo = peek(funcInfoStack)
65+
var currentBranchID = peek(funcInfo.branchIDStack)
66+
funcInfo.branchInfoMap[currentBranchID].good = true
5167
}
5268

53-
function onFunctionExpressionExit (node) {
54-
if (!isInlineThenFunctionExpression(node)) {
55-
return
56-
}
69+
return {
70+
ReturnStatement: markCurrentBranchAsGood,
71+
ThrowStatement: markCurrentBranchAsGood,
5772

58-
var funcInfo = last(funcInfoStack)
59-
if (!isEveryBranchReturning(funcInfo)) {
60-
context.report(node, 'Each then() should return a value or throw')
61-
}
62-
}
73+
onCodePathSegmentStart: function (segment, node) {
74+
var funcInfo = peek(funcInfoStack)
75+
funcInfo.branchIDStack.push(segment.id)
76+
funcInfo.branchInfoMap[segment.id] = {good: false, loc: node.loc}
77+
},
6378

64-
function markCurrentCodePathSegmentAsReturning () {
65-
var funcInfo = last(funcInfoStack)
66-
var currentCPSID = last(CPSIDStack)
67-
funcInfo.explicitlyReturningCPSIDs.push(currentCPSID)
68-
}
79+
onCodePathSegmentEnd: function (segment, node) {
80+
var funcInfo = peek(funcInfoStack)
81+
funcInfo.branchIDStack.pop()
82+
},
6983

70-
return {
71-
onCodePathStart: function (codePath, node) {
84+
onCodePathStart: function (path, node) {
7285
funcInfoStack.push({
73-
codePath: codePath,
74-
explicitlyReturningCPSIDs: []
86+
branchIDStack: [],
87+
branchInfoMap: {}
7588
})
7689
},
7790

78-
onCodePathEnd: function (codePath, node) {
79-
funcInfoStack.pop()
80-
},
91+
onCodePathEnd: function (path, node) {
92+
var funcInfo = funcInfoStack.pop()
8193

82-
onCodePathSegmentEnd: function (segment, node) {
83-
CPSIDStack.pop()
84-
},
85-
onCodePathSegmentStart: function (segment, node) {
86-
CPSIDStack.push(segment.id)
87-
},
94+
if (!isInlineThenFunctionExpression(node)) {
95+
return
96+
}
8897

89-
ReturnStatement: markCurrentCodePathSegmentAsReturning,
90-
ThrowStatement: markCurrentCodePathSegmentAsReturning,
91-
'FunctionExpression:exit': onFunctionExpressionExit,
92-
'ArrowFunctionExpression:exit': onFunctionExpressionExit
98+
var finalBranchIDs = path.finalSegments.map(x => x.id)
99+
finalBranchIDs.forEach((id) => {
100+
var branch = funcInfo.branchInfoMap[id]
101+
if (!branch.good) {
102+
context.report({
103+
message: 'Each then() should return a value or throw',
104+
loc: branch.loc
105+
})
106+
}
107+
})
108+
}
93109
}
94110
}
95111
}

0 commit comments

Comments
 (0)