# Code Path Analysis Details ESLint's rules can use code paths. The code path is execution routes of programs. It forks/joins at such as `if` statements. ```js if (a && b) { foo(); } bar(); ``` ![Code Path Example](./helo.svg) ## Objects Program is expressed with several code paths. A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`. ### `CodePath` `CodePath` expresses whole of one code path. This object exists for each function and the global. This has references of both the initial segment and the final segments of a code path. `CodePath` has the following properties: * `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path. * `initialSegment` (`CodePathSegment`) - The initial segment of this code path. * `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. * `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. * `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. * `currentSegments` (`CodePathSegment[]`) - Segments of the current position. * `upper` (`CodePath|null`) - The code path of the upper function/global scope. * `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. ### `CodePathSegment` `CodePathSegment` is a part of a code path. A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list. Difference from doubly linked list is what there are forking and merging (the next/prev are plural). `CodePathSegment` has the following properties: * `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment. * `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing. * `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing. * `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`. ## Events There are five events related to code paths, and you can define event handlers in rules. ```js module.exports = function(context) { return { /** * This is called at the start of analyzing a code path. * In this time, the code path object has only the initial segment. * * @param {CodePath} codePath - The new code path. * @param {ASTNode} node - The current node. * @returns {void} */ "onCodePathStart": function(codePath, node) { // do something with codePath }, /** * This is called at the end of analyzing a code path. * In this time, the code path object is complete. * * @param {CodePath} codePath - The completed code path. * @param {ASTNode} node - The current node. * @returns {void} */ "onCodePathEnd": function(codePath, node) { // do something with codePath }, /** * This is called when a code path segment was created. * It meant the code path is forked or merged. * In this time, the segment has the previous segments and has been * judged reachable or not. * * @param {CodePathSegment} segment - The new code path segment. * @param {ASTNode} node - The current node. * @returns {void} */ "onCodePathSegmentStart": function(segment, node) { // do something with segment }, /** * This is called when a code path segment was leaved. * In this time, the segment does not have the next segments yet. * * @param {CodePathSegment} segment - The leaved code path segment. * @param {ASTNode} node - The current node. * @returns {void} */ "onCodePathSegmentEnd": function(segment, node) { // do something with segment }, /** * This is called when a code path segment was looped. * Usually segments have each previous segments when created, * but when looped, a segment is added as a new previous segment into a * existing segment. * * @param {CodePathSegment} fromSegment - A code path segment of source. * @param {CodePathSegment} toSegment - A code path segment of destination. * @param {ASTNode} node - The current node. * @returns {void} */ "onCodePathSegmentLoop": function(fromSegment, toSegment, node) { // do something with segment } }; }; ``` ### About `onCodePathSegmentLoop` This event is always fired when the next segment has existed already. That timing is the end of loops mainly. For Example 1: ```js while (a) { a = foo(); } bar(); ``` 1. First, the analysis advances to the end of loop. ![Loop Event's Example 1](./loop-event-example-while-1.svg) 2. Second, it creates the looping path. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. ![Loop Event's Example 2](./loop-event-example-while-2.svg) 3. Last, it advances to the end. ![Loop Event's Example 3](./loop-event-example-while-3.svg) For example 2: ```js for (let i = 0; i < 10; ++i) { foo(i); } bar(); ``` 1. `for` statements are more complex. First, the analysis advances to `ForStatement.update`. The `update` segment is hovered at first. ![Loop Event's Example 1](./loop-event-example-for-1.svg) 2. Second, it advances to `ForStatement.body`. Of course the `body` segment is preceded by the `test` segment. It keeps the `update` segment hovering. ![Loop Event's Example 2](./loop-event-example-for-2.svg) 3. Third, it creates the looping path from `body` segment to `update` segment. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. ![Loop Event's Example 3](./loop-event-example-for-3.svg) 4. Fourth, also it creates the looping path from `update` segment to `test` segment. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. ![Loop Event's Example 4](./loop-event-example-for-4.svg) 5. Last, it advances to the end. ![Loop Event's Example 5](./loop-event-example-for-5.svg) ## Usage Examples ### To check whether or not this is reachable ```js var last = require("lodash").last; function isReachable(segment) { return segment.reachable; } module.exports = function(context) { var codePathStack = []; return { // Stores CodePath objects. "onCodePathStart": function(codePath) { codePathStack.push(codePath); }, "onCodePathEnd": function(codePath) { codePathStack.pop(); }, // Checks reachable or not. "ExpressionStatement": function(node) { var codePath = last(codePathStack); // Checks the current code path segments. if (!codePath.currentSegments.some(isReachable)) { context.report({message: "Unreachable!", node: node}); } } }; }; ``` See Also: [no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js), [no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js), [consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js) ### To check state of a code path This example is checking whether or not the parameter `cb` is called in every path. Instances of `CodePath` and `CodePathSegment` are shared to every rule. So a rule must not modify those instances. Please use a map of information instead. ```js var last = require("lodash").last; function hasCb(node, context) { if (node.type.indexOf("Function") !== -1) { return context.getDeclaredVariables(node).some(function(v) { return v.type === "Parameter" && v.name === "cb"; }); } return false; } function isCbCalled(info) { return info.cbCalled; } module.exports = function(context) { var funcInfoStack = []; var segmentInfoMap = Object.create(null); return { // Checks `cb`. "onCodePathStart": function(codePath, node) { funcInfoStack.push({ codePath: codePath, hasCb: hasCb(node, context) }); }, "onCodePathEnd": function(codePath, node) { funcInfoStack.pop(); // Checks `cb` was called in every paths. var cbCalled = codePath.finalSegments.every(function(segment) { var info = segmentInfoMap[segment.id]; return info.cbCalled; }); if (!cbCalled) { context.report({ message: "`cb` should be called in every path.", node: node }); } }, // Manages state of code paths. "onCodePathSegmentStart": function(segment) { // Ignores if `cb` doesn't exist. if (!last(funcInfoStack).hasCb) { return; } // Initialize state of this path. var info = segmentInfoMap[segment.id] = { cbCalled: false }; // If there are the previous paths, merges state. // Checks `cb` was called in every previous path. if (segment.prevSegments.length > 0) { info.cbCalled = segment.prevSegments.every(isCbCalled); } }, // Checks reachable or not. "CallExpression": function(node) { var funcInfo = last(funcInfoStack); // Ignores if `cb` doesn't exist. if (!funcInfo.hasCb) { return; } // Sets marks that `cb` was called. var callee = node.callee; if (callee.type === "Identifier" && callee.name === "cb") { funcInfo.codePath.currentSegments.forEach(function(segment) { var info = segmentInfoMap[segment.id]; info.cbCalled = true; }); } } }; }; ``` See Also: [constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js), [no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js) ## Code Path Examples ### Hello World ```js console.log("Hello world!"); ``` ![Hello World](./example-hello-world.svg) ### `IfStatement` ```js if (a) { foo(); } else { bar(); } ``` ![`IfStatement`](./example-ifstatement.svg) ### `IfStatement` (chain) ```js if (a) { foo(); } else if (b) { bar(); } else if (c) { hoge(); } ``` ![`IfStatement` (chain)](./example-ifstatement-chain.svg) ### `SwitchStatement` ```js switch (a) { case 0: foo(); break; case 1: case 2: bar(); // fallthrough case 3: hoge(); break; } ``` ![`SwitchStatement`](./example-switchstatement.svg) ### `SwitchStatement` (has `default`) ```js switch (a) { case 0: foo(); break; case 1: case 2: bar(); // fallthrough case 3: hoge(); break; default: fuga(); break; } ``` ![`SwitchStatement` (has `default`)](./example-switchstatement-has-default.svg) ### `TryStatement` (try-catch) ```js try { foo(); if (a) { throw new Error(); } bar(); } catch (err) { hoge(err); } last(); ``` It creates the paths from `try` block to `catch` block at: * `throw` statements. * The first throwable node (e.g. a function call) in the `try` block. * The end of the `try` block. ![`TryStatement` (try-catch)](./example-trystatement-try-catch.svg) ### `TryStatement` (try-finally) ```js try { foo(); bar(); } finally { fuga(); } last(); ``` If there is not `catch` block, `finally` block has two current segments. At this time, `CodePath.currentSegments.length` is `2`. One is the normal path, and another is the leaving path (`throw` or `return`). ![`TryStatement` (try-finally)](./example-trystatement-try-finally.svg) ### `TryStatement` (try-catch-finally) ```js try { foo(); bar(); } catch (err) { hoge(err); } finally { fuga(); } last(); ``` ![`TryStatement` (try-catch-finally)](./example-trystatement-try-catch-finally.svg) ### `WhileStatement` ```js while (a) { foo(); if (b) { continue; } bar(); } ``` ![`WhileStatement`](./example-whilestatement.svg) ### `DoWhileStatement` ```js do { foo(); bar(); } while (a); ``` ![`DoWhileStatement`](./example-dowhilestatement.svg) ### `ForStatement` ```js for (let i = 0; i < 10; ++i) { foo(); if (b) { break; } bar(); } ``` ![`ForStatement`](./example-forstatement.svg) ### `ForStatement` (for ever) ```js for (;;) { foo(); } bar(); ``` ![`ForStatement` (for ever)](./example-forstatement-for-ever.svg) ### `ForInStatement` ```js for (let key in obj) { foo(key); } ``` ![`ForInStatement`](./example-forinstatement.svg) ### When there is a function ```js function foo(a) { if (a) { return; } bar(); } foo(false); ``` It creates two code paths. * The global's ![When there is a function](./example-when-there-is-a-function-g.svg) * The function's ![When there is a function](./example-when-there-is-a-function-f.svg)