From 5523a810bb8c62c6228fd157d779d61ed614448a Mon Sep 17 00:00:00 2001 From: Andrew Huff Date: Thu, 6 Oct 2016 23:11:34 -0600 Subject: [PATCH] refactor always-return Much cleaner now :D --- rules/always-return.js | 114 +++++++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/rules/always-return.js b/rules/always-return.js index 1fae69ac..549634f1 100644 --- a/rules/always-return.js +++ b/rules/always-return.js @@ -23,73 +23,89 @@ function isInlineThenFunctionExpression (node) { ) } -function includes (arr, val) { - return arr.indexOf(val) !== -1 -} - -function last (arr) { +function peek (arr) { return arr[arr.length - 1] } module.exports = { create: function (context) { + // funcInfoStack is a stack representing the stack of currently executing + // functions + // funcInfoStack[i].branchIDStack is a stack representing the currently + // executing branches ("codePathSegment"s) within the given function + // funcInfoStack[i].branchInfoMap is an object representing information + // about all branches within the given function + // funcInfoStack[i].branchInfoMap[j].good is a boolean representing whether + // the given branch explictly `return`s or `throw`s. It starts as `false` + // for every branch and is updated to `true` if a `return` or `throw` + // statement is found + // funcInfoStack[i].branchInfoMap[j].loc is a eslint SourceLocation object + // for the given branch + // example: + // funcInfoStack = [ { branchIDStack: [ 's1_1' ], + // branchInfoMap: + // { s1_1: + // { good: false, + // loc: } } }, + // { branchIDStack: ['s2_1', 's2_4'], + // branchInfoMap: + // { s2_1: + // { good: false, + // loc: }, + // s2_2: + // { good: true, + // loc: }, + // s2_4: + // { good: false, + // loc: } } } ] var funcInfoStack = [] - var CPSIDStack = [] - - function isEveryBranchReturning (funcInfo) { - // We need to check noCurrentCPSIsOnTheCPSStack because of what - // seems like a bug in eslint where 'FunctionExpression:exit' events occur - // before all of their constituent codePathSegments have fired their - // 'onCodePathSegmentEnd' events - var currentIDs = funcInfo.codePath.currentSegments.map(x => x.id) - var noCurrentCPSIsOnTheCPSStack = !currentIDs.some((id) => includes(CPSIDStack, id)) - var finalIDs = funcInfo.codePath.finalSegments.map(x => x.id) - var everyFinalCPSIsReturning = finalIDs.every((id) => includes(funcInfo.explicitlyReturningCPSIDs, id)) - - return noCurrentCPSIsOnTheCPSStack && everyFinalCPSIsReturning + function markCurrentBranchAsGood () { + var funcInfo = peek(funcInfoStack) + var currentBranchID = peek(funcInfo.branchIDStack) + funcInfo.branchInfoMap[currentBranchID].good = true } - function onFunctionExpressionExit (node) { - if (!isInlineThenFunctionExpression(node)) { - return - } + return { + ReturnStatement: markCurrentBranchAsGood, + ThrowStatement: markCurrentBranchAsGood, - var funcInfo = last(funcInfoStack) - if (!isEveryBranchReturning(funcInfo)) { - context.report(node, 'Each then() should return a value or throw') - } - } + onCodePathSegmentStart: function (segment, node) { + var funcInfo = peek(funcInfoStack) + funcInfo.branchIDStack.push(segment.id) + funcInfo.branchInfoMap[segment.id] = {good: false, loc: node.loc} + }, - function markCurrentCodePathSegmentAsReturning () { - var funcInfo = last(funcInfoStack) - var currentCPSID = last(CPSIDStack) - funcInfo.explicitlyReturningCPSIDs.push(currentCPSID) - } + onCodePathSegmentEnd: function (segment, node) { + var funcInfo = peek(funcInfoStack) + funcInfo.branchIDStack.pop() + }, - return { - onCodePathStart: function (codePath, node) { + onCodePathStart: function (path, node) { funcInfoStack.push({ - codePath: codePath, - explicitlyReturningCPSIDs: [] + branchIDStack: [], + branchInfoMap: {} }) }, - onCodePathEnd: function (codePath, node) { - funcInfoStack.pop() - }, + onCodePathEnd: function (path, node) { + var funcInfo = funcInfoStack.pop() - onCodePathSegmentEnd: function (segment, node) { - CPSIDStack.pop() - }, - onCodePathSegmentStart: function (segment, node) { - CPSIDStack.push(segment.id) - }, + if (!isInlineThenFunctionExpression(node)) { + return + } - ReturnStatement: markCurrentCodePathSegmentAsReturning, - ThrowStatement: markCurrentCodePathSegmentAsReturning, - 'FunctionExpression:exit': onFunctionExpressionExit, - 'ArrowFunctionExpression:exit': onFunctionExpressionExit + var finalBranchIDs = path.finalSegments.map(x => x.id) + finalBranchIDs.forEach((id) => { + var branch = funcInfo.branchInfoMap[id] + if (!branch.good) { + context.report({ + message: 'Each then() should return a value or throw', + loc: branch.loc + }) + } + }) + } } } }