summaryrefslogtreecommitdiff
path: root/tools/eslint/lib/code-path-analysis
diff options
context:
space:
mode:
authorsilverwind <me@silverwind.io>2016-04-09 14:11:01 +0200
committersilverwind <me@silverwind.io>2016-04-10 11:46:08 +0200
commit2f6ff1bb64ac4f3e201039c8e83f8eb95f73c769 (patch)
tree710fe0778ca523281965244fdca60c4a031980e6 /tools/eslint/lib/code-path-analysis
parent8f4fdc93f07a06a62d4f867c6e0fd2f6287bb8be (diff)
downloadnode-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')
-rw-r--r--tools/eslint/lib/code-path-analysis/code-path-analyzer.js50
-rw-r--r--tools/eslint/lib/code-path-analysis/code-path-segment.js30
-rw-r--r--tools/eslint/lib/code-path-analysis/code-path-state.js199
-rw-r--r--tools/eslint/lib/code-path-analysis/code-path.js118
-rw-r--r--tools/eslint/lib/code-path-analysis/debug-helpers.js4
-rw-r--r--tools/eslint/lib/code-path-analysis/fork-context.js6
-rw-r--r--tools/eslint/lib/code-path-analysis/id-generator.js2
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;
};