1 # Code Path Analysis Details
3 ESLint's rules can use code paths.
4 The code path is execution routes of programs.
5 It forks/joins at such as `if` statements.
14 ![Code Path Example](./code-path-analysis/helo.svg)
18 Program is expressed with several code paths.
19 A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`.
23 `CodePath` expresses whole of one code path.
24 This object exists for each function and the global.
25 This has references of both the initial segment and the final segments of a code path.
27 `CodePath` has the following properties:
29 * `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path.
30 * `initialSegment` (`CodePathSegment`) - The initial segment of this code path.
31 * `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown.
32 * `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned.
33 * `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown.
34 * `currentSegments` (`CodePathSegment[]`) - Segments of the current position.
35 * `upper` (`CodePath|null`) - The code path of the upper function/global scope.
36 * `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains.
40 `CodePathSegment` is a part of a code path.
41 A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list.
42 Difference from doubly linked list is what there are forking and merging (the next/prev are plural).
44 `CodePathSegment` has the following properties:
46 * `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment.
47 * `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing.
48 * `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing.
49 * `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`.
53 There are five events related to code paths, and you can define event handlers in rules.
56 module.exports = function(context) {
59 * This is called at the start of analyzing a code path.
60 * In this time, the code path object has only the initial segment.
62 * @param {CodePath} codePath - The new code path.
63 * @param {ASTNode} node - The current node.
66 "onCodePathStart": function(codePath, node) {
67 // do something with codePath
71 * This is called at the end of analyzing a code path.
72 * In this time, the code path object is complete.
74 * @param {CodePath} codePath - The completed code path.
75 * @param {ASTNode} node - The current node.
78 "onCodePathEnd": function(codePath, node) {
79 // do something with codePath
83 * This is called when a code path segment was created.
84 * It meant the code path is forked or merged.
85 * In this time, the segment has the previous segments and has been
86 * judged reachable or not.
88 * @param {CodePathSegment} segment - The new code path segment.
89 * @param {ASTNode} node - The current node.
92 "onCodePathSegmentStart": function(segment, node) {
93 // do something with segment
97 * This is called when a code path segment was leaved.
98 * In this time, the segment does not have the next segments yet.
100 * @param {CodePathSegment} segment - The leaved code path segment.
101 * @param {ASTNode} node - The current node.
104 "onCodePathSegmentEnd": function(segment, node) {
105 // do something with segment
109 * This is called when a code path segment was looped.
110 * Usually segments have each previous segments when created,
111 * but when looped, a segment is added as a new previous segment into a
114 * @param {CodePathSegment} fromSegment - A code path segment of source.
115 * @param {CodePathSegment} toSegment - A code path segment of destination.
116 * @param {ASTNode} node - The current node.
119 "onCodePathSegmentLoop": function(fromSegment, toSegment, node) {
120 // do something with segment
126 ### About `onCodePathSegmentLoop`
128 This event is always fired when the next segment has existed already.
129 That timing is the end of loops mainly.
140 1. First, the analysis advances to the end of loop.
142 ![Loop Event's Example 1](./code-path-analysis/loop-event-example-while-1.svg)
144 2. Second, it creates the looping path.
145 At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
146 It fires `onCodePathSegmentLoop` instead.
148 ![Loop Event's Example 2](./code-path-analysis/loop-event-example-while-2.svg)
150 3. Last, it advances to the end.
152 ![Loop Event's Example 3](./code-path-analysis/loop-event-example-while-3.svg)
157 for (let i = 0; i < 10; ++i) {
163 1. `for` statements are more complex.
164 First, the analysis advances to `ForStatement.update`.
165 The `update` segment is hovered at first.
167 ![Loop Event's Example 1](./code-path-analysis/loop-event-example-for-1.svg)
169 2. Second, it advances to `ForStatement.body`.
170 Of course the `body` segment is preceded by the `test` segment.
171 It keeps the `update` segment hovering.
173 ![Loop Event's Example 2](./code-path-analysis/loop-event-example-for-2.svg)
175 3. Third, it creates the looping path from `body` segment to `update` segment.
176 At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
177 It fires `onCodePathSegmentLoop` instead.
179 ![Loop Event's Example 3](./code-path-analysis/loop-event-example-for-3.svg)
181 4. Fourth, also it creates the looping path from `update` segment to `test` segment.
182 At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
183 It fires `onCodePathSegmentLoop` instead.
185 ![Loop Event's Example 4](./code-path-analysis/loop-event-example-for-4.svg)
187 5. Last, it advances to the end.
189 ![Loop Event's Example 5](./code-path-analysis/loop-event-example-for-5.svg)
195 ### To check whether or not this is reachable
198 function isReachable(segment) {
199 return segment.reachable;
202 module.exports = function(context) {
203 var codePathStack = [];
206 // Stores CodePath objects.
207 "onCodePathStart": function(codePath) {
208 codePathStack.push(codePath);
210 "onCodePathEnd": function(codePath) {
214 // Checks reachable or not.
215 "ExpressionStatement": function(node) {
216 var codePath = codePathStack[codePathStack.length - 1];
218 // Checks the current code path segments.
219 if (!codePath.currentSegments.some(isReachable)) {
220 context.report({message: "Unreachable!", node: node});
228 [no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js),
229 [no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js),
230 [consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js)
232 ### To check state of a code path
234 This example is checking whether or not the parameter `cb` is called in every path.
235 Instances of `CodePath` and `CodePathSegment` are shared to every rule.
236 So a rule must not modify those instances.
237 Please use a map of information instead.
240 function hasCb(node, context) {
241 if (node.type.indexOf("Function") !== -1) {
242 return context.getDeclaredVariables(node).some(function(v) {
243 return v.type === "Parameter" && v.name === "cb";
249 function isCbCalled(info) {
250 return info.cbCalled;
253 module.exports = function(context) {
254 var funcInfoStack = [];
255 var segmentInfoMap = Object.create(null);
259 "onCodePathStart": function(codePath, node) {
262 hasCb: hasCb(node, context)
265 "onCodePathEnd": function(codePath, node) {
268 // Checks `cb` was called in every paths.
269 var cbCalled = codePath.finalSegments.every(function(segment) {
270 var info = segmentInfoMap[segment.id];
271 return info.cbCalled;
276 message: "`cb` should be called in every path.",
282 // Manages state of code paths.
283 "onCodePathSegmentStart": function(segment) {
284 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
286 // Ignores if `cb` doesn't exist.
287 if (!funcInfo.hasCb) {
291 // Initialize state of this path.
292 var info = segmentInfoMap[segment.id] = {
296 // If there are the previous paths, merges state.
297 // Checks `cb` was called in every previous path.
298 if (segment.prevSegments.length > 0) {
299 info.cbCalled = segment.prevSegments.every(isCbCalled);
303 // Checks reachable or not.
304 "CallExpression": function(node) {
305 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
307 // Ignores if `cb` doesn't exist.
308 if (!funcInfo.hasCb) {
312 // Sets marks that `cb` was called.
313 var callee = node.callee;
314 if (callee.type === "Identifier" && callee.name === "cb") {
315 funcInfo.codePath.currentSegments.forEach(function(segment) {
316 var info = segmentInfoMap[segment.id];
317 info.cbCalled = true;
326 [constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js),
327 [no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js)
329 ## Code Path Examples
334 console.log("Hello world!");
337 ![Hello World](./code-path-analysis/example-hello-world.svg)
349 ![`IfStatement`](./code-path-analysis/example-ifstatement.svg)
351 ### `IfStatement` (chain)
363 ![`IfStatement` (chain)](./code-path-analysis/example-ifstatement-chain.svg)
365 ### `SwitchStatement`
384 ![`SwitchStatement`](./code-path-analysis/example-switchstatement.svg)
386 ### `SwitchStatement` (has `default`)
409 ![`SwitchStatement` (has `default`)](./code-path-analysis/example-switchstatement-has-default.svg)
411 ### `TryStatement` (try-catch)
426 It creates the paths from `try` block to `catch` block at:
428 * `throw` statements.
429 * The first throwable node (e.g. a function call) in the `try` block.
430 * The end of the `try` block.
432 ![`TryStatement` (try-catch)](./code-path-analysis/example-trystatement-try-catch.svg)
434 ### `TryStatement` (try-finally)
446 If there is not `catch` block, `finally` block has two current segments.
447 At this time, `CodePath.currentSegments.length` is `2`.
448 One is the normal path, and another is the leaving path (`throw` or `return`).
450 ![`TryStatement` (try-finally)](./code-path-analysis/example-trystatement-try-finally.svg)
452 ### `TryStatement` (try-catch-finally)
466 ![`TryStatement` (try-catch-finally)](./code-path-analysis/example-trystatement-try-catch-finally.svg)
480 ![`WhileStatement`](./code-path-analysis/example-whilestatement.svg)
482 ### `DoWhileStatement`
491 ![`DoWhileStatement`](./code-path-analysis/example-dowhilestatement.svg)
496 for (let i = 0; i < 10; ++i) {
505 ![`ForStatement`](./code-path-analysis/example-forstatement.svg)
507 ### `ForStatement` (for ever)
516 ![`ForStatement` (for ever)](./code-path-analysis/example-forstatement-for-ever.svg)
521 for (let key in obj) {
526 ![`ForInStatement`](./code-path-analysis/example-forinstatement.svg)
528 ### When there is a function
541 It creates two code paths.
545 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-g.svg)
549 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-f.svg)