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 var last = require("lodash").last;
200 function isReachable(segment) {
201 return segment.reachable;
204 module.exports = function(context) {
205 var codePathStack = [];
208 // Stores CodePath objects.
209 "onCodePathStart": function(codePath) {
210 codePathStack.push(codePath);
212 "onCodePathEnd": function(codePath) {
216 // Checks reachable or not.
217 "ExpressionStatement": function(node) {
218 var codePath = last(codePathStack);
220 // Checks the current code path segments.
221 if (!codePath.currentSegments.some(isReachable)) {
222 context.report({message: "Unreachable!", node: node});
230 [no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js),
231 [no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js),
232 [consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js)
234 ### To check state of a code path
236 This example is checking whether or not the parameter `cb` is called in every path.
237 Instances of `CodePath` and `CodePathSegment` are shared to every rule.
238 So a rule must not modify those instances.
239 Please use a map of information instead.
242 var last = require("lodash").last;
244 function hasCb(node, context) {
245 if (node.type.indexOf("Function") !== -1) {
246 return context.getDeclaredVariables(node).some(function(v) {
247 return v.type === "Parameter" && v.name === "cb";
253 function isCbCalled(info) {
254 return info.cbCalled;
257 module.exports = function(context) {
258 var funcInfoStack = [];
259 var segmentInfoMap = Object.create(null);
263 "onCodePathStart": function(codePath, node) {
266 hasCb: hasCb(node, context)
269 "onCodePathEnd": function(codePath, node) {
272 // Checks `cb` was called in every paths.
273 var cbCalled = codePath.finalSegments.every(function(segment) {
274 var info = segmentInfoMap[segment.id];
275 return info.cbCalled;
280 message: "`cb` should be called in every path.",
286 // Manages state of code paths.
287 "onCodePathSegmentStart": function(segment) {
288 // Ignores if `cb` doesn't exist.
289 if (!last(funcInfoStack).hasCb) {
293 // Initialize state of this path.
294 var info = segmentInfoMap[segment.id] = {
298 // If there are the previous paths, merges state.
299 // Checks `cb` was called in every previous path.
300 if (segment.prevSegments.length > 0) {
301 info.cbCalled = segment.prevSegments.every(isCbCalled);
305 // Checks reachable or not.
306 "CallExpression": function(node) {
307 var funcInfo = last(funcInfoStack);
309 // Ignores if `cb` doesn't exist.
310 if (!funcInfo.hasCb) {
314 // Sets marks that `cb` was called.
315 var callee = node.callee;
316 if (callee.type === "Identifier" && callee.name === "cb") {
317 funcInfo.codePath.currentSegments.forEach(function(segment) {
318 var info = segmentInfoMap[segment.id];
319 info.cbCalled = true;
328 [constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js),
329 [no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js)
331 ## Code Path Examples
336 console.log("Hello world!");
339 ![Hello World](./code-path-analysis/example-hello-world.svg)
351 ![`IfStatement`](./code-path-analysis/example-ifstatement.svg)
353 ### `IfStatement` (chain)
365 ![`IfStatement` (chain)](./code-path-analysis/example-ifstatement-chain.svg)
367 ### `SwitchStatement`
386 ![`SwitchStatement`](./code-path-analysis/example-switchstatement.svg)
388 ### `SwitchStatement` (has `default`)
411 ![`SwitchStatement` (has `default`)](./code-path-analysis/example-switchstatement-has-default.svg)
413 ### `TryStatement` (try-catch)
428 It creates the paths from `try` block to `catch` block at:
430 * `throw` statements.
431 * The first throwable node (e.g. a function call) in the `try` block.
432 * The end of the `try` block.
434 ![`TryStatement` (try-catch)](./code-path-analysis/example-trystatement-try-catch.svg)
436 ### `TryStatement` (try-finally)
448 If there is not `catch` block, `finally` block has two current segments.
449 At this time, `CodePath.currentSegments.length` is `2`.
450 One is the normal path, and another is the leaving path (`throw` or `return`).
452 ![`TryStatement` (try-finally)](./code-path-analysis/example-trystatement-try-finally.svg)
454 ### `TryStatement` (try-catch-finally)
468 ![`TryStatement` (try-catch-finally)](./code-path-analysis/example-trystatement-try-catch-finally.svg)
482 ![`WhileStatement`](./code-path-analysis/example-whilestatement.svg)
484 ### `DoWhileStatement`
493 ![`DoWhileStatement`](./code-path-analysis/example-dowhilestatement.svg)
498 for (let i = 0; i < 10; ++i) {
507 ![`ForStatement`](./code-path-analysis/example-forstatement.svg)
509 ### `ForStatement` (for ever)
518 ![`ForStatement` (for ever)](./code-path-analysis/example-forstatement-for-ever.svg)
523 for (let key in obj) {
528 ![`ForInStatement`](./code-path-analysis/example-forinstatement.svg)
530 ### When there is a function
543 It creates two code paths.
547 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-g.svg)
551 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-f.svg)