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 * `origin` (`string`) - The reason that the code path was started. May be `"program"`, `"function"`, `"class-field-initializer"`, or `"class-static-block"`.
31 * `initialSegment` (`CodePathSegment`) - The initial segment of this code path.
32 * `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown.
33 * `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned.
34 * `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown.
35 * `currentSegments` (`CodePathSegment[]`) - Segments of the current position.
36 * `upper` (`CodePath|null`) - The code path of the upper function/global scope.
37 * `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains.
41 `CodePathSegment` is a part of a code path.
42 A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list.
43 Difference from doubly linked list is what there are forking and merging (the next/prev are plural).
45 `CodePathSegment` has the following properties:
47 * `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment.
48 * `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing.
49 * `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing.
50 * `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`.
54 There are five events related to code paths, and you can define event handlers in rules.
57 module.exports = function(context) {
60 * This is called at the start of analyzing a code path.
61 * In this time, the code path object has only the initial segment.
63 * @param {CodePath} codePath - The new code path.
64 * @param {ASTNode} node - The current node.
67 "onCodePathStart": function(codePath, node) {
68 // do something with codePath
72 * This is called at the end of analyzing a code path.
73 * In this time, the code path object is complete.
75 * @param {CodePath} codePath - The completed code path.
76 * @param {ASTNode} node - The current node.
79 "onCodePathEnd": function(codePath, node) {
80 // do something with codePath
84 * This is called when a code path segment was created.
85 * It meant the code path is forked or merged.
86 * In this time, the segment has the previous segments and has been
87 * judged reachable or not.
89 * @param {CodePathSegment} segment - The new code path segment.
90 * @param {ASTNode} node - The current node.
93 "onCodePathSegmentStart": function(segment, node) {
94 // do something with segment
98 * This is called when a code path segment was leaved.
99 * In this time, the segment does not have the next segments yet.
101 * @param {CodePathSegment} segment - The leaved code path segment.
102 * @param {ASTNode} node - The current node.
105 "onCodePathSegmentEnd": function(segment, node) {
106 // do something with segment
110 * This is called when a code path segment was looped.
111 * Usually segments have each previous segments when created,
112 * but when looped, a segment is added as a new previous segment into a
115 * @param {CodePathSegment} fromSegment - A code path segment of source.
116 * @param {CodePathSegment} toSegment - A code path segment of destination.
117 * @param {ASTNode} node - The current node.
120 "onCodePathSegmentLoop": function(fromSegment, toSegment, node) {
121 // do something with segment
127 ### About `onCodePathSegmentLoop`
129 This event is always fired when the next segment has existed already.
130 That timing is the end of loops mainly.
141 1. First, the analysis advances to the end of loop.
143 ![Loop Event's Example 1](./code-path-analysis/loop-event-example-while-1.svg)
145 2. Second, it creates the looping path.
146 At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
147 It fires `onCodePathSegmentLoop` instead.
149 ![Loop Event's Example 2](./code-path-analysis/loop-event-example-while-2.svg)
151 3. Last, it advances to the end.
153 ![Loop Event's Example 3](./code-path-analysis/loop-event-example-while-3.svg)
158 for (let i = 0; i < 10; ++i) {
164 1. `for` statements are more complex.
165 First, the analysis advances to `ForStatement.update`.
166 The `update` segment is hovered at first.
168 ![Loop Event's Example 1](./code-path-analysis/loop-event-example-for-1.svg)
170 2. Second, it advances to `ForStatement.body`.
171 Of course the `body` segment is preceded by the `test` segment.
172 It keeps the `update` segment hovering.
174 ![Loop Event's Example 2](./code-path-analysis/loop-event-example-for-2.svg)
176 3. Third, it creates the looping path from `body` segment to `update` segment.
177 At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
178 It fires `onCodePathSegmentLoop` instead.
180 ![Loop Event's Example 3](./code-path-analysis/loop-event-example-for-3.svg)
182 4. Fourth, also it creates the looping path from `update` segment to `test` segment.
183 At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
184 It fires `onCodePathSegmentLoop` instead.
186 ![Loop Event's Example 4](./code-path-analysis/loop-event-example-for-4.svg)
188 5. Last, it advances to the end.
190 ![Loop Event's Example 5](./code-path-analysis/loop-event-example-for-5.svg)
196 ### To check whether or not this is reachable
199 function isReachable(segment) {
200 return segment.reachable;
203 module.exports = function(context) {
204 var codePathStack = [];
207 // Stores CodePath objects.
208 "onCodePathStart": function(codePath) {
209 codePathStack.push(codePath);
211 "onCodePathEnd": function(codePath) {
215 // Checks reachable or not.
216 "ExpressionStatement": function(node) {
217 var codePath = codePathStack[codePathStack.length - 1];
219 // Checks the current code path segments.
220 if (!codePath.currentSegments.some(isReachable)) {
221 context.report({message: "Unreachable!", node: node});
229 [no-unreachable](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-unreachable.js),
230 [no-fallthrough](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-fallthrough.js),
231 [consistent-return](https://github.com/eslint/eslint/blob/HEAD/lib/rules/consistent-return.js)
233 ### To check state of a code path
235 This example is checking whether or not the parameter `cb` is called in every path.
236 Instances of `CodePath` and `CodePathSegment` are shared to every rule.
237 So a rule must not modify those instances.
238 Please use a map of information instead.
241 function hasCb(node, context) {
242 if (node.type.indexOf("Function") !== -1) {
243 return context.getDeclaredVariables(node).some(function(v) {
244 return v.type === "Parameter" && v.name === "cb";
250 function isCbCalled(info) {
251 return info.cbCalled;
254 module.exports = function(context) {
255 var funcInfoStack = [];
256 var segmentInfoMap = Object.create(null);
260 "onCodePathStart": function(codePath, node) {
263 hasCb: hasCb(node, context)
266 "onCodePathEnd": function(codePath, node) {
269 // Checks `cb` was called in every paths.
270 var cbCalled = codePath.finalSegments.every(function(segment) {
271 var info = segmentInfoMap[segment.id];
272 return info.cbCalled;
277 message: "`cb` should be called in every path.",
283 // Manages state of code paths.
284 "onCodePathSegmentStart": function(segment) {
285 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
287 // Ignores if `cb` doesn't exist.
288 if (!funcInfo.hasCb) {
292 // Initialize state of this path.
293 var info = segmentInfoMap[segment.id] = {
297 // If there are the previous paths, merges state.
298 // Checks `cb` was called in every previous path.
299 if (segment.prevSegments.length > 0) {
300 info.cbCalled = segment.prevSegments.every(isCbCalled);
304 // Checks reachable or not.
305 "CallExpression": function(node) {
306 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
308 // Ignores if `cb` doesn't exist.
309 if (!funcInfo.hasCb) {
313 // Sets marks that `cb` was called.
314 var callee = node.callee;
315 if (callee.type === "Identifier" && callee.name === "cb") {
316 funcInfo.codePath.currentSegments.forEach(function(segment) {
317 var info = segmentInfoMap[segment.id];
318 info.cbCalled = true;
327 [constructor-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/constructor-super.js),
328 [no-this-before-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-this-before-super.js)
330 ## Code Path Examples
335 console.log("Hello world!");
338 ![Hello World](./code-path-analysis/example-hello-world.svg)
350 ![`IfStatement`](./code-path-analysis/example-ifstatement.svg)
352 ### `IfStatement` (chain)
364 ![`IfStatement` (chain)](./code-path-analysis/example-ifstatement-chain.svg)
366 ### `SwitchStatement`
385 ![`SwitchStatement`](./code-path-analysis/example-switchstatement.svg)
387 ### `SwitchStatement` (has `default`)
410 ![`SwitchStatement` (has `default`)](./code-path-analysis/example-switchstatement-has-default.svg)
412 ### `TryStatement` (try-catch)
427 It creates the paths from `try` block to `catch` block at:
429 * `throw` statements.
430 * The first throwable node (e.g. a function call) in the `try` block.
431 * The end of the `try` block.
433 ![`TryStatement` (try-catch)](./code-path-analysis/example-trystatement-try-catch.svg)
435 ### `TryStatement` (try-finally)
447 If there is not `catch` block, `finally` block has two current segments.
448 At this time, `CodePath.currentSegments.length` is `2`.
449 One is the normal path, and another is the leaving path (`throw` or `return`).
451 ![`TryStatement` (try-finally)](./code-path-analysis/example-trystatement-try-finally.svg)
453 ### `TryStatement` (try-catch-finally)
467 ![`TryStatement` (try-catch-finally)](./code-path-analysis/example-trystatement-try-catch-finally.svg)
481 ![`WhileStatement`](./code-path-analysis/example-whilestatement.svg)
483 ### `DoWhileStatement`
492 ![`DoWhileStatement`](./code-path-analysis/example-dowhilestatement.svg)
497 for (let i = 0; i < 10; ++i) {
506 ![`ForStatement`](./code-path-analysis/example-forstatement.svg)
508 ### `ForStatement` (for ever)
517 ![`ForStatement` (for ever)](./code-path-analysis/example-forstatement-for-ever.svg)
522 for (let key in obj) {
527 ![`ForInStatement`](./code-path-analysis/example-forinstatement.svg)
529 ### When there is a function
542 It creates two code paths.
546 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-g.svg)
550 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-f.svg)