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