diff options
author | silverwind <me@silverwind.io> | 2016-04-09 14:11:01 +0200 |
---|---|---|
committer | silverwind <me@silverwind.io> | 2016-04-10 11:46:08 +0200 |
commit | 2f6ff1bb64ac4f3e201039c8e83f8eb95f73c769 (patch) | |
tree | 710fe0778ca523281965244fdca60c4a031980e6 /tools/eslint/lib/code-path-analysis | |
parent | 8f4fdc93f07a06a62d4f867c6e0fd2f6287bb8be (diff) | |
download | node-new-2f6ff1bb64ac4f3e201039c8e83f8eb95f73c769.tar.gz |
tools: update ESLint to 2.7.0
PR-URL: https://github.com/nodejs/node/pull/6132
Reviewed-By: Brian White <mscdex@mscdex.net>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: thefourtheye <thechargingvolcano@gmail.com>
Diffstat (limited to 'tools/eslint/lib/code-path-analysis')
7 files changed, 346 insertions, 63 deletions
diff --git a/tools/eslint/lib/code-path-analysis/code-path-analyzer.js b/tools/eslint/lib/code-path-analysis/code-path-analyzer.js index aa2a6ff4ac..afb9980430 100644 --- a/tools/eslint/lib/code-path-analysis/code-path-analyzer.js +++ b/tools/eslint/lib/code-path-analysis/code-path-analyzer.js @@ -41,6 +41,7 @@ function isCaseNode(node) { */ function isForkingByTrueOrFalse(node) { var parent = node.parent; + switch (parent.type) { case "ConditionalExpression": case "IfStatement": @@ -128,8 +129,8 @@ function isIdentifierReference(node) { * * To separate the current and the head is in order to not make useless segments. * - * In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd" events - * are fired. + * In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd" + * events are fired. * * @param {CodePathAnalyzer} analyzer - The instance. * @param {ASTNode} node - The current AST node. @@ -206,6 +207,7 @@ function leaveFromCurrentSegment(analyzer, node) { node); } } + state.currentSegments = []; } @@ -234,9 +236,12 @@ function preprocess(analyzer, node) { case "ConditionalExpression": case "IfStatement": - // Fork if this node is at `consequent`/`alternate`. - // `popForkContext()` exists at `IfStatement:exit` and - // `ConditionalExpression:exit`. + + /* + * Fork if this node is at `consequent`/`alternate`. + * `popForkContext()` exists at `IfStatement:exit` and + * `ConditionalExpression:exit`. + */ if (parent.consequent === node) { state.makeIfConsequent(); } else if (parent.alternate === node) { @@ -299,9 +304,12 @@ function preprocess(analyzer, node) { break; case "AssignmentPattern": - // Fork if this node is at `right`. - // `left` is executed always, so it uses the current path. - // `popForkContext()` exists at `AssignmentPattern:exit`. + + /* + * Fork if this node is at `right`. + * `left` is executed always, so it uses the current path. + * `popForkContext()` exists at `AssignmentPattern:exit`. + */ if (parent.right === node) { state.pushForkContext(); state.forkBypassPath(); @@ -332,6 +340,7 @@ function processCodePathToEnter(analyzer, node) { case "FunctionExpression": case "ArrowFunctionExpression": if (codePath) { + // Emits onCodePathSegmentStart events if updated. forwardCurrentToHead(analyzer, node); debug.dumpState(node, state, false); @@ -370,9 +379,12 @@ function processCodePathToEnter(analyzer, node) { break; case "SwitchCase": - // Fork if this node is after the 2st node in `cases`. - // It's similar to `else` blocks. - // The next `test` node is processed in this path. + + /* + * Fork if this node is after the 2st node in `cases`. + * It's similar to `else` blocks. + * The next `test` node is processed in this path. + */ if (parent.discriminant !== node && parent.cases[0] !== node) { state.forkPath(); } @@ -425,9 +437,12 @@ function processCodePathToExit(analyzer, node) { break; case "SwitchCase": - // This is the same as the process at the 1st `consequent` node in - // `preprocess` function. - // Must do if this `consequent` is empty. + + /* + * This is the same as the process at the 1st `consequent` node in + * `preprocess` function. + * Must do if this `consequent` is empty. + */ if (node.consequent.length === 0) { state.makeSwitchCaseBody(true, !node.test); } @@ -499,9 +514,12 @@ function processCodePathToExit(analyzer, node) { break; } - // Skip updating the current segment to avoid creating useless segments if - // the node type is the same as the parent node type. + /* + * Skip updating the current segment to avoid creating useless segments if + * the node type is the same as the parent node type. + */ if (!dontForward && (!node.parent || node.type !== node.parent.type)) { + // Emits onCodePathSegmentStart events if updated. forwardCurrentToHead(analyzer, node); } diff --git a/tools/eslint/lib/code-path-analysis/code-path-segment.js b/tools/eslint/lib/code-path-analysis/code-path-segment.js index 94deafbfbb..ea292efc36 100644 --- a/tools/eslint/lib/code-path-analysis/code-path-segment.js +++ b/tools/eslint/lib/code-path-analysis/code-path-segment.js @@ -79,6 +79,7 @@ function isReachable(segment) { * @param {boolean} reachable - A flag which shows this is reachable. */ function CodePathSegment(id, allPrevSegments, reachable) { + /** * The identifier of this code path. * Rules use it to store additional information of each rule. @@ -120,7 +121,8 @@ function CodePathSegment(id, allPrevSegments, reachable) { // Internal data. Object.defineProperty(this, "internal", {value: { - used: false + used: false, + loopedPrevSegments: [] }}); /* istanbul ignore if */ @@ -130,6 +132,20 @@ function CodePathSegment(id, allPrevSegments, reachable) { } } +CodePathSegment.prototype = { + constructor: CodePathSegment, + + /** + * Checks a given previous segment is coming from the end of a loop. + * + * @param {CodePathSegment} segment - A previous segment to check. + * @returns {boolean} `true` if the segment is coming from the end of a loop. + */ + isLoopedPrevSegment: function(segment) { + return this.internal.loopedPrevSegments.indexOf(segment) !== -1; + } +}; + /** * Creates the root segment. * @@ -191,6 +207,7 @@ CodePathSegment.markUsed = function(segment) { segment.internal.used = true; var i; + if (segment.reachable) { for (i = 0; i < segment.allPrevSegments.length; ++i) { var prevSegment = segment.allPrevSegments[i]; @@ -205,4 +222,15 @@ CodePathSegment.markUsed = function(segment) { } }; +/** + * Marks a previous segment as looped. + * + * @param {CodePathSegment} segment - A segment. + * @param {CodePathSegment} prevSegment - A previous segment to mark. + * @returns {void} + */ +CodePathSegment.markPrevSegmentAsLooped = function(segment, prevSegment) { + segment.internal.loopedPrevSegments.push(prevSegment); +}; + module.exports = CodePathSegment; diff --git a/tools/eslint/lib/code-path-analysis/code-path-state.js b/tools/eslint/lib/code-path-analysis/code-path-state.js index 121df319fa..b620ed718b 100644 --- a/tools/eslint/lib/code-path-analysis/code-path-state.js +++ b/tools/eslint/lib/code-path-analysis/code-path-state.js @@ -55,6 +55,7 @@ function getContinueContext(state, label) { } var context = state.loopContext; + while (context) { if (context.label === label) { return context; @@ -75,6 +76,7 @@ function getContinueContext(state, label) { */ function getBreakContext(state, label) { var context = state.breakContext; + while (context) { if (label ? context.label === label : context.breakable) { return context; @@ -94,6 +96,7 @@ function getBreakContext(state, label) { */ function getReturnContext(state) { var context = state.tryContext; + while (context) { if (context.hasFinalizer && context.position !== "finally") { return context; @@ -112,6 +115,7 @@ function getReturnContext(state) { */ function getThrowContext(state) { var context = state.tryContext; + while (context) { if (context.position === "try" || (context.hasFinalizer && context.position === "catch") @@ -168,6 +172,7 @@ function removeConnection(prevSegments, nextSegments) { */ function makeLooped(state, fromSegments, toSegments) { var end = Math.min(fromSegments.length, toSegments.length); + for (var i = 0; i < end; ++i) { var fromSegment = fromSegments[i]; var toSegment = toSegments[i]; @@ -181,6 +186,10 @@ function makeLooped(state, fromSegments, toSegments) { fromSegment.allNextSegments.push(toSegment); toSegment.allPrevSegments.push(fromSegment); + if (toSegment.allPrevSegments.length >= 2) { + CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment); + } + state.notifyLooped(fromSegment, toSegment); } } @@ -237,6 +246,7 @@ function CodePathState(idGenerator, onLooped) { var final = this.finalSegments = []; var returned = this.returnedForkContext = []; var thrown = this.thrownForkContext = []; + returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final); thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final); } @@ -259,6 +269,7 @@ CodePathState.prototype = { */ get parentForkContext() { var current = this.forkContext; + return current && current.upper; }, @@ -362,6 +373,7 @@ CodePathState.prototype = { */ popChoiceContext: function() { var context = this.choiceContext; + this.choiceContext = context.upper; var forkContext = this.forkContext; @@ -370,43 +382,61 @@ CodePathState.prototype = { switch (context.kind) { case "&&": case "||": - // If any result were not transferred from child contexts, - // this sets the head segments to both cases. - // The head segments are the path of the right-hand operand. + + /* + * If any result were not transferred from child contexts, + * this sets the head segments to both cases. + * The head segments are the path of the right-hand operand. + */ if (!context.processed) { context.trueForkContext.add(headSegments); context.falseForkContext.add(headSegments); } - // Transfers results to upper context if this context is in - // test chunk. + /* + * Transfers results to upper context if this context is in + * test chunk. + */ if (context.isForkingAsResult) { var parentContext = this.choiceContext; + parentContext.trueForkContext.addAll(context.trueForkContext); parentContext.falseForkContext.addAll(context.falseForkContext); parentContext.processed = true; return context; } + break; case "test": if (!context.processed) { - // The head segments are the path of the `if` block here. - // Updates the `true` path with the end of the `if` block. + + /* + * The head segments are the path of the `if` block here. + * Updates the `true` path with the end of the `if` block. + */ context.trueForkContext.clear(); context.trueForkContext.add(headSegments); } else { - // The head segments are the path of the `else` block here. - // Updates the `false` path with the end of the `else` block. + + /* + * The head segments are the path of the `else` block here. + * Updates the `false` path with the end of the `else` + * block. + */ context.falseForkContext.clear(); context.falseForkContext.add(headSegments); } + break; case "loop": - // Loops are addressed in popLoopContext(). - // This is called from popLoopContext(). + + /* + * Loops are addressed in popLoopContext(). + * This is called from popLoopContext(). + */ return context; /* istanbul ignore next */ @@ -416,6 +446,7 @@ CodePathState.prototype = { // Merges all paths. var prevForkContext = context.trueForkContext; + prevForkContext.addAll(context.falseForkContext); forkContext.replaceHead(prevForkContext.makeNext(0, -1)); @@ -433,8 +464,11 @@ CodePathState.prototype = { var forkContext = this.forkContext; if (context.processed) { - // This got segments already from the child choice context. - // Creates the next path from own true/false fork context. + + /* + * This got segments already from the child choice context. + * Creates the next path from own true/false fork context. + */ var prevForkContext = context.kind === "&&" ? context.trueForkContext : /* kind === "||" */ context.falseForkContext; @@ -444,13 +478,18 @@ CodePathState.prototype = { context.processed = false; } else { - // This did not get segments from the child choice context. - // So addresses the head segments. - // The head segments are the path of the left-hand operand. + + /* + * This did not get segments from the child choice context. + * So addresses the head segments. + * The head segments are the path of the left-hand operand. + */ if (context.kind === "&&") { + // The path does short-circuit if false. context.falseForkContext.add(forkContext.head); } else { + // The path does short-circuit if true. context.trueForkContext.add(forkContext.head); } @@ -468,13 +507,16 @@ CodePathState.prototype = { var context = this.choiceContext; var forkContext = this.forkContext; - // If any result were not transferred from child contexts, - // this sets the head segments to both cases. - // The head segments are the path of the test expression. + /* + * If any result were not transferred from child contexts, + * this sets the head segments to both cases. + * The head segments are the path of the test expression. + */ if (!context.processed) { context.trueForkContext.add(forkContext.head); context.falseForkContext.add(forkContext.head); } + context.processed = false; // Creates new path from the `true` case. @@ -492,8 +534,10 @@ CodePathState.prototype = { var context = this.choiceContext; var forkContext = this.forkContext; - // The head segments are the path of the `if` block. - // Updates the `true` path with the end of the `if` block. + /* + * The head segments are the path of the `if` block. + * Updates the `true` path with the end of the `if` block. + */ context.trueForkContext.clear(); context.trueForkContext.add(forkContext.head); context.processed = true; @@ -526,6 +570,7 @@ CodePathState.prototype = { lastIsDefault: false, countForks: 0 }; + this.pushBreakContext(true, label); }, @@ -541,41 +586,57 @@ CodePathState.prototype = { */ popSwitchContext: function() { var context = this.switchContext; + this.switchContext = context.upper; var forkContext = this.forkContext; var brokenForkContext = this.popBreakContext().brokenForkContext; if (context.countForks === 0) { - // When there is only one `default` chunk and there is one or more - // `break` statements, even if forks are nothing, it needs to merge - // those. + + /* + * When there is only one `default` chunk and there is one or more + * `break` statements, even if forks are nothing, it needs to merge + * those. + */ if (!brokenForkContext.empty) { brokenForkContext.add(forkContext.makeNext(-1, -1)); forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); } + return; } var lastSegments = forkContext.head; + this.forkBypassPath(); var lastCaseSegments = forkContext.head; - // `brokenForkContext` is used to make the next segment. - // It must add the last segment into `brokenForkContext`. + /* + * `brokenForkContext` is used to make the next segment. + * It must add the last segment into `brokenForkContext`. + */ brokenForkContext.add(lastSegments); - // A path which is failed in all case test should be connected to path - // of `default` chunk. + /* + * A path which is failed in all case test should be connected to path + * of `default` chunk. + */ if (!context.lastIsDefault) { if (context.defaultBodySegments) { - // Remove a link from `default` label to its chunk. - // It's false route. + + /* + * Remove a link from `default` label to its chunk. + * It's false route. + */ removeConnection(context.defaultSegments, context.defaultBodySegments); makeLooped(this, lastCaseSegments, context.defaultBodySegments); } else { - // It handles the last case body as broken if `default` chunk - // does not exist. + + /* + * It handles the last case body as broken if `default` chunk + * does not exist. + */ brokenForkContext.add(lastCaseSegments); } } @@ -584,8 +645,11 @@ CodePathState.prototype = { for (var i = 0; i < context.countForks; ++i) { this.forkContext = this.forkContext.upper; } - // Creates a path from all brokenForkContext paths. - // This is a path after switch statement. + + /* + * Creates a path from all brokenForkContext paths. + * This is a path after switch statement. + */ this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); }, @@ -598,20 +662,26 @@ CodePathState.prototype = { */ makeSwitchCaseBody: function(isEmpty, isDefault) { var context = this.switchContext; + if (!context.hasCase) { return; } - // Merge forks. - // The parent fork context has two segments. - // Those are from the current case and the body of the previous case. + /* + * Merge forks. + * The parent fork context has two segments. + * Those are from the current case and the body of the previous case. + */ var parentForkContext = this.forkContext; var forkContext = this.pushForkContext(); + forkContext.add(parentForkContext.makeNext(0, -1)); - // Save `default` chunk info. - // If the `default` label is not at the last, we must make a path from - // the last `case` to the `default` chunk. + /* + * Save `default` chunk info. + * If the `default` label is not at the last, we must make a path from + * the last `case` to the `default` chunk. + */ if (isDefault) { context.defaultSegments = parentForkContext.head; if (isEmpty) { @@ -625,6 +695,7 @@ CodePathState.prototype = { context.defaultBodySegments = forkContext.head; } } + context.lastIsDefault = isDefault; context.countForks += 1; }, @@ -645,9 +716,11 @@ CodePathState.prototype = { upper: this.tryContext, position: "try", hasFinalizer: hasFinalizer, + returnedForkContext: hasFinalizer ? ForkContext.newEmpty(this.forkContext) : null, + thrownForkContext: ForkContext.newEmpty(this.forkContext), lastOfTryIsReachable: false, lastOfCatchIsReachable: false @@ -661,24 +734,31 @@ CodePathState.prototype = { */ popTryContext: function() { var context = this.tryContext; + this.tryContext = context.upper; if (context.position === "catch") { + // Merges two paths from the `try` block and `catch` block merely. this.popForkContext(); return; } - // The following process is executed only when there is the `finally` - // block. + + /* + * The following process is executed only when there is the `finally` + * block. + */ var returned = context.returnedForkContext; var thrown = context.thrownForkContext; + if (returned.empty && thrown.empty) { return; } // Separate head to normal paths and leaving paths. var headSegments = this.forkContext.head; + this.forkContext = this.forkContext.upper; var normalSegments = headSegments.slice(0, headSegments.length / 2 | 0); var leavingSegments = headSegments.slice(headSegments.length / 2 | 0); @@ -744,6 +824,7 @@ CodePathState.prototype = { // Update state. if (context.position === "catch") { + // Merges two paths from the `try` block and `catch` block. this.popForkContext(); forkContext = this.forkContext; @@ -755,14 +836,18 @@ CodePathState.prototype = { context.position = "finally"; if (returned.empty && thrown.empty) { + // This path does not leave. return; } - // Create a parallel segment from merging returned and thrown. - // This segment will leave at the end of this finally block. + /* + * Create a parallel segment from merging returned and thrown. + * This segment will leave at the end of this finally block. + */ var segments = forkContext.makeNext(-1, -1); var j; + for (var i = 0; i < forkContext.count; ++i) { var prevSegsOfLeavingSegment = [headOfLeavingSegments[i]]; @@ -790,11 +875,13 @@ CodePathState.prototype = { */ makeFirstThrowablePathInTryBlock: function() { var forkContext = this.forkContext; + if (!forkContext.reachable) { return; } var context = getThrowContext(this); + if (context === this || context.position !== "try" || !context.thrownForkContext.empty @@ -893,6 +980,7 @@ CodePathState.prototype = { */ popLoopContext: function() { var context = this.loopContext; + this.loopContext = context.upper; var forkContext = this.forkContext; @@ -923,6 +1011,7 @@ CodePathState.prototype = { // `true` paths go to looping. var segmentsList = choiceContext.trueForkContext.segmentsList; + for (var i = 0; i < segmentsList.length; ++i) { makeLooped( this, @@ -1069,6 +1158,7 @@ CodePathState.prototype = { // Update state. var updateSegments = forkContext.makeDisconnected(-1, -1); + context.continueDestSegments = context.updateSegments = updateSegments; forkContext.replaceHead(updateSegments); }, @@ -1104,10 +1194,15 @@ CodePathState.prototype = { } var bodySegments = context.endOfTestSegments; + if (!bodySegments) { - // If there is not the `test` part, the `body` path comes from the - // `init` part and the `update` part. + + /* + * If there is not the `test` part, the `body` path comes from the + * `init` part and the `update` part. + */ var prevForkContext = ForkContext.newEmpty(forkContext); + prevForkContext.add(context.endOfInitSegments); if (context.endOfUpdateSegments) { prevForkContext.add(context.endOfUpdateSegments); @@ -1146,6 +1241,7 @@ CodePathState.prototype = { var context = this.loopContext; var forkContext = this.forkContext; var temp = ForkContext.newEmpty(forkContext); + temp.add(context.prevSegments); var rightSegments = temp.makeNext(-1, -1); @@ -1164,6 +1260,7 @@ CodePathState.prototype = { var context = this.loopContext; var forkContext = this.forkContext; var temp = ForkContext.newEmpty(forkContext); + temp.add(context.endOfLeftSegments); var bodySegments = temp.makeNext(-1, -1); @@ -1205,11 +1302,13 @@ CodePathState.prototype = { popBreakContext: function() { var context = this.breakContext; var forkContext = this.forkContext; + this.breakContext = context.upper; // Process this context here for other than switches and loops. if (!context.breakable) { var brokenForkContext = context.brokenForkContext; + if (!brokenForkContext.empty) { brokenForkContext.add(forkContext.head); forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); @@ -1230,15 +1329,18 @@ CodePathState.prototype = { */ makeBreak: function(label) { var forkContext = this.forkContext; + if (!forkContext.reachable) { return; } var context = getBreakContext(this, label); + /* istanbul ignore else: foolproof (syntax error) */ if (context) { context.brokenForkContext.add(forkContext.head); } + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); }, @@ -1253,11 +1355,13 @@ CodePathState.prototype = { */ makeContinue: function(label) { var forkContext = this.forkContext; + if (!forkContext.reachable) { return; } var context = getContinueContext(this, label); + /* istanbul ignore else: foolproof (syntax error) */ if (context) { if (context.continueDestSegments) { @@ -1286,6 +1390,7 @@ CodePathState.prototype = { */ makeReturn: function() { var forkContext = this.forkContext; + if (forkContext.reachable) { getReturnContext(this).returnedForkContext.add(forkContext.head); forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); @@ -1302,6 +1407,7 @@ CodePathState.prototype = { */ makeThrow: function() { var forkContext = this.forkContext; + if (forkContext.reachable) { getThrowContext(this).thrownForkContext.add(forkContext.head); forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); @@ -1314,6 +1420,7 @@ CodePathState.prototype = { */ makeFinal: function() { var segments = this.currentSegments; + if (segments.length > 0 && segments[0].reachable) { this.returnedForkContext.add(segments); } diff --git a/tools/eslint/lib/code-path-analysis/code-path.js b/tools/eslint/lib/code-path-analysis/code-path.js index 200ff434b8..2b45ed944f 100644 --- a/tools/eslint/lib/code-path-analysis/code-path.js +++ b/tools/eslint/lib/code-path-analysis/code-path.js @@ -27,6 +27,7 @@ var IdGenerator = require("./id-generator"); * @param {function} onLooped - A callback function to notify looping. */ function CodePath(id, upper, onLooped) { + /** * The identifier of this code path. * Rules use it to store additional information of each rule. @@ -102,6 +103,123 @@ CodePath.prototype = { */ get currentSegments() { return this.internal.currentSegments; + }, + + /** + * Traverses all segments in this code path. + * + * codePath.traverseSegments(function(segment, controller) { + * // do something. + * }); + * + * This method enumerates segments in order from the head. + * + * The `controller` object has two methods. + * + * - `controller.skip()` - Skip the following segments in this branch. + * - `controller.break()` - Skip all following segments. + * + * @param {object} [options] - Omittable. + * @param {CodePathSegment} [options.first] - The first segment to traverse. + * @param {CodePathSegment} [options.last] - The last segment to traverse. + * @param {function} callback - A callback function. + * @returns {void} + */ + traverseSegments: function(options, callback) { + if (typeof options === "function") { + callback = options; + options = null; + } + + options = options || {}; + var startSegment = options.first || this.internal.initialSegment; + var lastSegment = options.last; + + var item = null; + var index = 0; + var end = 0; + var segment = null; + var visited = Object.create(null); + var stack = [[startSegment, 0]]; + var skippedSegment = null; + var broken = false; + var controller = { + skip: function() { + if (stack.length <= 1) { + broken = true; + } else { + skippedSegment = stack[stack.length - 2][0]; + } + }, + break: function() { + broken = true; + } + }; + + /** + * Checks a given previous segment has been visited. + * @param {CodePathSegment} prevSegment - A previous segment to check. + * @returns {boolean} `true` if the segment has been visited. + */ + function isVisited(prevSegment) { + return ( + visited[prevSegment.id] || + segment.isLoopedPrevSegment(prevSegment) + ); + } + + while (stack.length > 0) { + item = stack[stack.length - 1]; + segment = item[0]; + index = item[1]; + + if (index === 0) { + + // Skip if this segment has been visited already. + if (visited[segment.id]) { + stack.pop(); + continue; + } + + // Skip if all previous segments have not been visited. + if (segment !== startSegment && + segment.prevSegments.length > 0 && + !segment.prevSegments.every(isVisited) + ) { + stack.pop(); + continue; + } + + // Reset the flag of skipping if all branches have been skipped. + if (skippedSegment && segment.prevSegments.indexOf(skippedSegment) !== -1) { + skippedSegment = null; + } + visited[segment.id] = true; + + // Call the callback when the first time. + if (!skippedSegment) { + callback.call(this, segment, controller); // eslint-disable-line callback-return + if (segment === lastSegment) { + controller.skip(); + } + if (broken) { + break; + } + } + } + + // Update the stack. + end = segment.nextSegments.length - 1; + if (index < end) { + item[1] += 1; + stack.push([segment.nextSegments[index], 0]); + } else if (index === end) { + item[0] = segment.nextSegments[index]; + item[1] = 0; + } else { + stack.pop(); + } + } } }; diff --git a/tools/eslint/lib/code-path-analysis/debug-helpers.js b/tools/eslint/lib/code-path-analysis/debug-helpers.js index d8c26ad1b8..6ecfabe955 100644 --- a/tools/eslint/lib/code-path-analysis/debug-helpers.js +++ b/tools/eslint/lib/code-path-analysis/debug-helpers.js @@ -32,6 +32,7 @@ function getId(segment) { // eslint-disable-line require-jsdoc //------------------------------------------------------------------------------ module.exports = { + /** * A flag that debug dumping is enabled or not. * @type {boolean} @@ -57,6 +58,7 @@ module.exports = { dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) { for (var i = 0; i < state.currentSegments.length; ++i) { var segInternal = state.currentSegments[i].internal; + if (leaving) { segInternal.exitNodes.push(node); } else { @@ -153,12 +155,14 @@ module.exports = { var item = stack.pop(); var segment = item[0]; var index = item[1]; + if (done[segment.id] && index === 0) { continue; } done[segment.id] = segment; var nextSegment = segment.allNextSegments[index]; + if (!nextSegment) { continue; } diff --git a/tools/eslint/lib/code-path-analysis/fork-context.js b/tools/eslint/lib/code-path-analysis/fork-context.js index 8716ddd6ff..af82e68e0c 100644 --- a/tools/eslint/lib/code-path-analysis/fork-context.js +++ b/tools/eslint/lib/code-path-analysis/fork-context.js @@ -47,6 +47,7 @@ function isReachable(segment) { */ function makeSegments(context, begin, end, create) { var list = context.segmentsList; + if (begin < 0) { begin = list.length + begin; } @@ -55,6 +56,7 @@ function makeSegments(context, begin, end, create) { } var segments = []; + for (var i = 0; i < context.count; ++i) { var allPrevSegments = []; @@ -81,6 +83,7 @@ function makeSegments(context, begin, end, create) { function mergeExtraSegments(context, segments) { while (segments.length > context.count) { var merged = []; + for (var i = 0, length = segments.length / 2 | 0; i < length; ++i) { merged.push(CodePathSegment.newNext( context.idGenerator.next(), @@ -120,6 +123,7 @@ ForkContext.prototype = { */ get head() { var list = this.segmentsList; + return list.length === 0 ? [] : list[list.length - 1]; }, @@ -137,6 +141,7 @@ ForkContext.prototype = { */ get reachable() { var segments = this.head; + return segments.length > 0 && segments.some(isReachable); }, @@ -212,6 +217,7 @@ ForkContext.prototype = { assert(context.count === this.count); var source = context.segmentsList; + for (var i = 0; i < source.length; ++i) { this.segmentsList.push(source[i]); } diff --git a/tools/eslint/lib/code-path-analysis/id-generator.js b/tools/eslint/lib/code-path-analysis/id-generator.js index c37e7d7e5b..530ea95844 100644 --- a/tools/eslint/lib/code-path-analysis/id-generator.js +++ b/tools/eslint/lib/code-path-analysis/id-generator.js @@ -33,10 +33,12 @@ function IdGenerator(prefix) { */ IdGenerator.prototype.next = function() { this.n = 1 + this.n | 0; + /* istanbul ignore if */ if (this.n < 0) { this.n = 1; } + return this.prefix + this.n; }; |