]>
Commit | Line | Data |
---|---|---|
8f9d1d4d DC |
1 | --- |
2 | title: Code Path Analysis Details | |
3 | layout: doc | |
4 | ||
5 | --- | |
eb39fafa DC |
6 | |
7 | ESLint's rules can use code paths. | |
8 | The code path is execution routes of programs. | |
9 | It forks/joins at such as `if` statements. | |
10 | ||
11 | ```js | |
12 | if (a && b) { | |
13 | foo(); | |
14 | } | |
15 | bar(); | |
16 | ``` | |
17 | ||
8f9d1d4d | 18 | ![Code Path Example](../assets/images/code-path-analysis/helo.svg) |
eb39fafa DC |
19 | |
20 | ## Objects | |
21 | ||
22 | Program is expressed with several code paths. | |
23 | A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`. | |
24 | ||
25 | ### `CodePath` | |
26 | ||
27 | `CodePath` expresses whole of one code path. | |
28 | This object exists for each function and the global. | |
29 | This has references of both the initial segment and the final segments of a code path. | |
30 | ||
31 | `CodePath` has the following properties: | |
32 | ||
33 | * `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path. | |
609c276f | 34 | * `origin` (`string`) - The reason that the code path was started. May be `"program"`, `"function"`, `"class-field-initializer"`, or `"class-static-block"`. |
eb39fafa DC |
35 | * `initialSegment` (`CodePathSegment`) - The initial segment of this code path. |
36 | * `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. | |
37 | * `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. | |
38 | * `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. | |
39 | * `currentSegments` (`CodePathSegment[]`) - Segments of the current position. | |
40 | * `upper` (`CodePath|null`) - The code path of the upper function/global scope. | |
41 | * `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. | |
42 | ||
43 | ### `CodePathSegment` | |
44 | ||
45 | `CodePathSegment` is a part of a code path. | |
46 | A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list. | |
47 | Difference from doubly linked list is what there are forking and merging (the next/prev are plural). | |
48 | ||
49 | `CodePathSegment` has the following properties: | |
50 | ||
51 | * `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment. | |
52 | * `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing. | |
53 | * `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing. | |
54 | * `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`. | |
55 | ||
56 | ## Events | |
57 | ||
58 | There are five events related to code paths, and you can define event handlers in rules. | |
59 | ||
60 | ```js | |
61 | module.exports = function(context) { | |
62 | return { | |
63 | /** | |
64 | * This is called at the start of analyzing a code path. | |
65 | * In this time, the code path object has only the initial segment. | |
66 | * | |
67 | * @param {CodePath} codePath - The new code path. | |
68 | * @param {ASTNode} node - The current node. | |
69 | * @returns {void} | |
70 | */ | |
71 | "onCodePathStart": function(codePath, node) { | |
72 | // do something with codePath | |
73 | }, | |
74 | ||
75 | /** | |
76 | * This is called at the end of analyzing a code path. | |
77 | * In this time, the code path object is complete. | |
78 | * | |
79 | * @param {CodePath} codePath - The completed code path. | |
80 | * @param {ASTNode} node - The current node. | |
81 | * @returns {void} | |
82 | */ | |
83 | "onCodePathEnd": function(codePath, node) { | |
84 | // do something with codePath | |
85 | }, | |
86 | ||
87 | /** | |
88 | * This is called when a code path segment was created. | |
89 | * It meant the code path is forked or merged. | |
90 | * In this time, the segment has the previous segments and has been | |
91 | * judged reachable or not. | |
92 | * | |
93 | * @param {CodePathSegment} segment - The new code path segment. | |
94 | * @param {ASTNode} node - The current node. | |
95 | * @returns {void} | |
96 | */ | |
97 | "onCodePathSegmentStart": function(segment, node) { | |
98 | // do something with segment | |
99 | }, | |
100 | ||
101 | /** | |
8f9d1d4d | 102 | * This is called when a code path segment was left. |
eb39fafa DC |
103 | * In this time, the segment does not have the next segments yet. |
104 | * | |
8f9d1d4d | 105 | * @param {CodePathSegment} segment - The left code path segment. |
eb39fafa DC |
106 | * @param {ASTNode} node - The current node. |
107 | * @returns {void} | |
108 | */ | |
109 | "onCodePathSegmentEnd": function(segment, node) { | |
110 | // do something with segment | |
111 | }, | |
112 | ||
113 | /** | |
114 | * This is called when a code path segment was looped. | |
115 | * Usually segments have each previous segments when created, | |
116 | * but when looped, a segment is added as a new previous segment into a | |
117 | * existing segment. | |
118 | * | |
119 | * @param {CodePathSegment} fromSegment - A code path segment of source. | |
120 | * @param {CodePathSegment} toSegment - A code path segment of destination. | |
121 | * @param {ASTNode} node - The current node. | |
122 | * @returns {void} | |
123 | */ | |
124 | "onCodePathSegmentLoop": function(fromSegment, toSegment, node) { | |
125 | // do something with segment | |
126 | } | |
127 | }; | |
128 | }; | |
129 | ``` | |
130 | ||
131 | ### About `onCodePathSegmentLoop` | |
132 | ||
133 | This event is always fired when the next segment has existed already. | |
134 | That timing is the end of loops mainly. | |
135 | ||
136 | For Example 1: | |
137 | ||
138 | ```js | |
139 | while (a) { | |
140 | a = foo(); | |
141 | } | |
142 | bar(); | |
143 | ``` | |
144 | ||
145 | 1. First, the analysis advances to the end of loop. | |
146 | ||
8f9d1d4d | 147 | ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-while-1.svg) |
eb39fafa DC |
148 | |
149 | 2. Second, it creates the looping path. | |
150 | At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. | |
151 | It fires `onCodePathSegmentLoop` instead. | |
152 | ||
8f9d1d4d | 153 | ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-while-2.svg) |
eb39fafa DC |
154 | |
155 | 3. Last, it advances to the end. | |
156 | ||
8f9d1d4d | 157 | ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-while-3.svg) |
eb39fafa DC |
158 | |
159 | For example 2: | |
160 | ||
161 | ```js | |
162 | for (let i = 0; i < 10; ++i) { | |
163 | foo(i); | |
164 | } | |
165 | bar(); | |
166 | ``` | |
167 | ||
168 | 1. `for` statements are more complex. | |
169 | First, the analysis advances to `ForStatement.update`. | |
170 | The `update` segment is hovered at first. | |
171 | ||
8f9d1d4d | 172 | ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-for-1.svg) |
eb39fafa DC |
173 | |
174 | 2. Second, it advances to `ForStatement.body`. | |
175 | Of course the `body` segment is preceded by the `test` segment. | |
176 | It keeps the `update` segment hovering. | |
177 | ||
8f9d1d4d | 178 | ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-for-2.svg) |
eb39fafa DC |
179 | |
180 | 3. Third, it creates the looping path from `body` segment to `update` segment. | |
181 | At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. | |
182 | It fires `onCodePathSegmentLoop` instead. | |
183 | ||
8f9d1d4d | 184 | ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-for-3.svg) |
eb39fafa DC |
185 | |
186 | 4. Fourth, also it creates the looping path from `update` segment to `test` segment. | |
187 | At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. | |
188 | It fires `onCodePathSegmentLoop` instead. | |
189 | ||
8f9d1d4d | 190 | ![Loop Event's Example 4](../assets/images/code-path-analysis/loop-event-example-for-4.svg) |
eb39fafa DC |
191 | |
192 | 5. Last, it advances to the end. | |
193 | ||
8f9d1d4d | 194 | ![Loop Event's Example 5](../assets/images/code-path-analysis/loop-event-example-for-5.svg) |
eb39fafa | 195 | |
eb39fafa DC |
196 | ## Usage Examples |
197 | ||
198 | ### To check whether or not this is reachable | |
199 | ||
200 | ```js | |
eb39fafa DC |
201 | function isReachable(segment) { |
202 | return segment.reachable; | |
203 | } | |
204 | ||
205 | module.exports = function(context) { | |
206 | var codePathStack = []; | |
207 | ||
208 | return { | |
209 | // Stores CodePath objects. | |
210 | "onCodePathStart": function(codePath) { | |
211 | codePathStack.push(codePath); | |
212 | }, | |
213 | "onCodePathEnd": function(codePath) { | |
214 | codePathStack.pop(); | |
215 | }, | |
216 | ||
217 | // Checks reachable or not. | |
218 | "ExpressionStatement": function(node) { | |
5422a9cc | 219 | var codePath = codePathStack[codePathStack.length - 1]; |
eb39fafa DC |
220 | |
221 | // Checks the current code path segments. | |
222 | if (!codePath.currentSegments.some(isReachable)) { | |
223 | context.report({message: "Unreachable!", node: node}); | |
224 | } | |
225 | } | |
226 | }; | |
227 | }; | |
228 | ``` | |
229 | ||
230 | See Also: | |
609c276f TL |
231 | [no-unreachable](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-unreachable.js), |
232 | [no-fallthrough](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-fallthrough.js), | |
233 | [consistent-return](https://github.com/eslint/eslint/blob/HEAD/lib/rules/consistent-return.js) | |
eb39fafa DC |
234 | |
235 | ### To check state of a code path | |
236 | ||
237 | This example is checking whether or not the parameter `cb` is called in every path. | |
238 | Instances of `CodePath` and `CodePathSegment` are shared to every rule. | |
239 | So a rule must not modify those instances. | |
240 | Please use a map of information instead. | |
241 | ||
242 | ```js | |
eb39fafa DC |
243 | function hasCb(node, context) { |
244 | if (node.type.indexOf("Function") !== -1) { | |
245 | return context.getDeclaredVariables(node).some(function(v) { | |
246 | return v.type === "Parameter" && v.name === "cb"; | |
247 | }); | |
248 | } | |
249 | return false; | |
250 | } | |
251 | ||
252 | function isCbCalled(info) { | |
253 | return info.cbCalled; | |
254 | } | |
255 | ||
256 | module.exports = function(context) { | |
257 | var funcInfoStack = []; | |
258 | var segmentInfoMap = Object.create(null); | |
259 | ||
260 | return { | |
261 | // Checks `cb`. | |
262 | "onCodePathStart": function(codePath, node) { | |
263 | funcInfoStack.push({ | |
264 | codePath: codePath, | |
265 | hasCb: hasCb(node, context) | |
266 | }); | |
267 | }, | |
268 | "onCodePathEnd": function(codePath, node) { | |
269 | funcInfoStack.pop(); | |
270 | ||
271 | // Checks `cb` was called in every paths. | |
272 | var cbCalled = codePath.finalSegments.every(function(segment) { | |
273 | var info = segmentInfoMap[segment.id]; | |
274 | return info.cbCalled; | |
275 | }); | |
276 | ||
277 | if (!cbCalled) { | |
278 | context.report({ | |
279 | message: "`cb` should be called in every path.", | |
280 | node: node | |
281 | }); | |
282 | } | |
283 | }, | |
284 | ||
285 | // Manages state of code paths. | |
286 | "onCodePathSegmentStart": function(segment) { | |
5422a9cc TL |
287 | var funcInfo = funcInfoStack[funcInfoStack.length - 1]; |
288 | ||
eb39fafa | 289 | // Ignores if `cb` doesn't exist. |
5422a9cc | 290 | if (!funcInfo.hasCb) { |
eb39fafa DC |
291 | return; |
292 | } | |
293 | ||
294 | // Initialize state of this path. | |
295 | var info = segmentInfoMap[segment.id] = { | |
296 | cbCalled: false | |
297 | }; | |
298 | ||
299 | // If there are the previous paths, merges state. | |
300 | // Checks `cb` was called in every previous path. | |
301 | if (segment.prevSegments.length > 0) { | |
302 | info.cbCalled = segment.prevSegments.every(isCbCalled); | |
303 | } | |
304 | }, | |
305 | ||
306 | // Checks reachable or not. | |
307 | "CallExpression": function(node) { | |
5422a9cc | 308 | var funcInfo = funcInfoStack[funcInfoStack.length - 1]; |
eb39fafa DC |
309 | |
310 | // Ignores if `cb` doesn't exist. | |
311 | if (!funcInfo.hasCb) { | |
312 | return; | |
313 | } | |
314 | ||
315 | // Sets marks that `cb` was called. | |
316 | var callee = node.callee; | |
317 | if (callee.type === "Identifier" && callee.name === "cb") { | |
318 | funcInfo.codePath.currentSegments.forEach(function(segment) { | |
319 | var info = segmentInfoMap[segment.id]; | |
320 | info.cbCalled = true; | |
321 | }); | |
322 | } | |
323 | } | |
324 | }; | |
325 | }; | |
326 | ``` | |
327 | ||
328 | See Also: | |
609c276f TL |
329 | [constructor-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/constructor-super.js), |
330 | [no-this-before-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-this-before-super.js) | |
eb39fafa DC |
331 | |
332 | ## Code Path Examples | |
333 | ||
334 | ### Hello World | |
335 | ||
336 | ```js | |
337 | console.log("Hello world!"); | |
338 | ``` | |
339 | ||
8f9d1d4d | 340 | ![Hello World](../assets/images/code-path-analysis/example-hello-world.svg) |
eb39fafa DC |
341 | |
342 | ### `IfStatement` | |
343 | ||
344 | ```js | |
345 | if (a) { | |
346 | foo(); | |
347 | } else { | |
348 | bar(); | |
349 | } | |
350 | ``` | |
351 | ||
8f9d1d4d | 352 | ![`IfStatement`](../assets/images/code-path-analysis/example-ifstatement.svg) |
eb39fafa DC |
353 | |
354 | ### `IfStatement` (chain) | |
355 | ||
356 | ```js | |
357 | if (a) { | |
358 | foo(); | |
359 | } else if (b) { | |
360 | bar(); | |
361 | } else if (c) { | |
362 | hoge(); | |
363 | } | |
364 | ``` | |
365 | ||
8f9d1d4d | 366 | ![`IfStatement` (chain)](../assets/images/code-path-analysis/example-ifstatement-chain.svg) |
eb39fafa DC |
367 | |
368 | ### `SwitchStatement` | |
369 | ||
370 | ```js | |
371 | switch (a) { | |
372 | case 0: | |
373 | foo(); | |
374 | break; | |
375 | ||
376 | case 1: | |
377 | case 2: | |
378 | bar(); | |
379 | // fallthrough | |
380 | ||
381 | case 3: | |
382 | hoge(); | |
383 | break; | |
384 | } | |
385 | ``` | |
386 | ||
8f9d1d4d | 387 | ![`SwitchStatement`](../assets/images/code-path-analysis/example-switchstatement.svg) |
eb39fafa DC |
388 | |
389 | ### `SwitchStatement` (has `default`) | |
390 | ||
391 | ```js | |
392 | switch (a) { | |
393 | case 0: | |
394 | foo(); | |
395 | break; | |
396 | ||
397 | case 1: | |
398 | case 2: | |
399 | bar(); | |
400 | // fallthrough | |
401 | ||
402 | case 3: | |
403 | hoge(); | |
404 | break; | |
405 | ||
406 | default: | |
407 | fuga(); | |
408 | break; | |
409 | } | |
410 | ``` | |
411 | ||
8f9d1d4d | 412 | ![`SwitchStatement` (has `default`)](../assets/images/code-path-analysis/example-switchstatement-has-default.svg) |
eb39fafa DC |
413 | |
414 | ### `TryStatement` (try-catch) | |
415 | ||
416 | ```js | |
417 | try { | |
418 | foo(); | |
419 | if (a) { | |
420 | throw new Error(); | |
421 | } | |
422 | bar(); | |
423 | } catch (err) { | |
424 | hoge(err); | |
425 | } | |
426 | last(); | |
427 | ``` | |
428 | ||
429 | It creates the paths from `try` block to `catch` block at: | |
430 | ||
431 | * `throw` statements. | |
432 | * The first throwable node (e.g. a function call) in the `try` block. | |
433 | * The end of the `try` block. | |
434 | ||
8f9d1d4d | 435 | ![`TryStatement` (try-catch)](../assets/images/code-path-analysis/example-trystatement-try-catch.svg) |
eb39fafa DC |
436 | |
437 | ### `TryStatement` (try-finally) | |
438 | ||
439 | ```js | |
440 | try { | |
441 | foo(); | |
442 | bar(); | |
443 | } finally { | |
444 | fuga(); | |
445 | } | |
446 | last(); | |
447 | ``` | |
448 | ||
449 | If there is not `catch` block, `finally` block has two current segments. | |
450 | At this time, `CodePath.currentSegments.length` is `2`. | |
451 | One is the normal path, and another is the leaving path (`throw` or `return`). | |
452 | ||
8f9d1d4d | 453 | ![`TryStatement` (try-finally)](../assets/images/code-path-analysis/example-trystatement-try-finally.svg) |
eb39fafa DC |
454 | |
455 | ### `TryStatement` (try-catch-finally) | |
456 | ||
457 | ```js | |
458 | try { | |
459 | foo(); | |
460 | bar(); | |
461 | } catch (err) { | |
462 | hoge(err); | |
463 | } finally { | |
464 | fuga(); | |
465 | } | |
466 | last(); | |
467 | ``` | |
468 | ||
8f9d1d4d | 469 | ![`TryStatement` (try-catch-finally)](../assets/images/code-path-analysis/example-trystatement-try-catch-finally.svg) |
eb39fafa DC |
470 | |
471 | ### `WhileStatement` | |
472 | ||
473 | ```js | |
474 | while (a) { | |
475 | foo(); | |
476 | if (b) { | |
477 | continue; | |
478 | } | |
479 | bar(); | |
480 | } | |
481 | ``` | |
482 | ||
8f9d1d4d | 483 | ![`WhileStatement`](../assets/images/code-path-analysis/example-whilestatement.svg) |
eb39fafa DC |
484 | |
485 | ### `DoWhileStatement` | |
486 | ||
487 | ```js | |
488 | do { | |
489 | foo(); | |
490 | bar(); | |
491 | } while (a); | |
492 | ``` | |
493 | ||
8f9d1d4d | 494 | ![`DoWhileStatement`](../assets/images/code-path-analysis/example-dowhilestatement.svg) |
eb39fafa DC |
495 | |
496 | ### `ForStatement` | |
497 | ||
498 | ```js | |
499 | for (let i = 0; i < 10; ++i) { | |
500 | foo(); | |
501 | if (b) { | |
502 | break; | |
503 | } | |
504 | bar(); | |
505 | } | |
506 | ``` | |
507 | ||
8f9d1d4d | 508 | ![`ForStatement`](../assets/images/code-path-analysis/example-forstatement.svg) |
eb39fafa DC |
509 | |
510 | ### `ForStatement` (for ever) | |
511 | ||
512 | ```js | |
513 | for (;;) { | |
514 | foo(); | |
515 | } | |
516 | bar(); | |
517 | ``` | |
518 | ||
8f9d1d4d | 519 | ![`ForStatement` (for ever)](../assets/images/code-path-analysis/example-forstatement-for-ever.svg) |
eb39fafa DC |
520 | |
521 | ### `ForInStatement` | |
522 | ||
523 | ```js | |
524 | for (let key in obj) { | |
525 | foo(key); | |
526 | } | |
527 | ``` | |
528 | ||
8f9d1d4d | 529 | ![`ForInStatement`](../assets/images/code-path-analysis/example-forinstatement.svg) |
eb39fafa DC |
530 | |
531 | ### When there is a function | |
532 | ||
533 | ```js | |
534 | function foo(a) { | |
535 | if (a) { | |
536 | return; | |
537 | } | |
538 | bar(); | |
539 | } | |
540 | ||
541 | foo(false); | |
542 | ``` | |
543 | ||
544 | It creates two code paths. | |
545 | ||
546 | * The global's | |
547 | ||
8f9d1d4d | 548 | ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-g.svg) |
eb39fafa DC |
549 | |
550 | * The function's | |
551 | ||
8f9d1d4d | 552 | ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-f.svg) |