]> git.proxmox.com Git - pve-eslint.git/blob - eslint/docs/developer-guide/code-path-analysis.md
buildsys: change upload dist to bullseye
[pve-eslint.git] / eslint / docs / developer-guide / code-path-analysis.md
1 # Code Path Analysis Details
2
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.
6
7 ```js
8 if (a && b) {
9 foo();
10 }
11 bar();
12 ```
13
14 ![Code Path Example](./code-path-analysis/helo.svg)
15
16 ## Objects
17
18 Program is expressed with several code paths.
19 A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`.
20
21 ### `CodePath`
22
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.
26
27 `CodePath` has the following properties:
28
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.
37
38 ### `CodePathSegment`
39
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).
43
44 `CodePathSegment` has the following properties:
45
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`.
50
51 ## Events
52
53 There are five events related to code paths, and you can define event handlers in rules.
54
55 ```js
56 module.exports = function(context) {
57 return {
58 /**
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.
61 *
62 * @param {CodePath} codePath - The new code path.
63 * @param {ASTNode} node - The current node.
64 * @returns {void}
65 */
66 "onCodePathStart": function(codePath, node) {
67 // do something with codePath
68 },
69
70 /**
71 * This is called at the end of analyzing a code path.
72 * In this time, the code path object is complete.
73 *
74 * @param {CodePath} codePath - The completed code path.
75 * @param {ASTNode} node - The current node.
76 * @returns {void}
77 */
78 "onCodePathEnd": function(codePath, node) {
79 // do something with codePath
80 },
81
82 /**
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.
87 *
88 * @param {CodePathSegment} segment - The new code path segment.
89 * @param {ASTNode} node - The current node.
90 * @returns {void}
91 */
92 "onCodePathSegmentStart": function(segment, node) {
93 // do something with segment
94 },
95
96 /**
97 * This is called when a code path segment was leaved.
98 * In this time, the segment does not have the next segments yet.
99 *
100 * @param {CodePathSegment} segment - The leaved code path segment.
101 * @param {ASTNode} node - The current node.
102 * @returns {void}
103 */
104 "onCodePathSegmentEnd": function(segment, node) {
105 // do something with segment
106 },
107
108 /**
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
112 * existing segment.
113 *
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.
117 * @returns {void}
118 */
119 "onCodePathSegmentLoop": function(fromSegment, toSegment, node) {
120 // do something with segment
121 }
122 };
123 };
124 ```
125
126 ### About `onCodePathSegmentLoop`
127
128 This event is always fired when the next segment has existed already.
129 That timing is the end of loops mainly.
130
131 For Example 1:
132
133 ```js
134 while (a) {
135 a = foo();
136 }
137 bar();
138 ```
139
140 1. First, the analysis advances to the end of loop.
141
142 ![Loop Event's Example 1](./code-path-analysis/loop-event-example-while-1.svg)
143
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.
147
148 ![Loop Event's Example 2](./code-path-analysis/loop-event-example-while-2.svg)
149
150 3. Last, it advances to the end.
151
152 ![Loop Event's Example 3](./code-path-analysis/loop-event-example-while-3.svg)
153
154 For example 2:
155
156 ```js
157 for (let i = 0; i < 10; ++i) {
158 foo(i);
159 }
160 bar();
161 ```
162
163 1. `for` statements are more complex.
164 First, the analysis advances to `ForStatement.update`.
165 The `update` segment is hovered at first.
166
167 ![Loop Event's Example 1](./code-path-analysis/loop-event-example-for-1.svg)
168
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.
172
173 ![Loop Event's Example 2](./code-path-analysis/loop-event-example-for-2.svg)
174
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.
178
179 ![Loop Event's Example 3](./code-path-analysis/loop-event-example-for-3.svg)
180
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.
184
185 ![Loop Event's Example 4](./code-path-analysis/loop-event-example-for-4.svg)
186
187 5. Last, it advances to the end.
188
189 ![Loop Event's Example 5](./code-path-analysis/loop-event-example-for-5.svg)
190
191
192
193 ## Usage Examples
194
195 ### To check whether or not this is reachable
196
197 ```js
198 var last = require("lodash").last;
199
200 function isReachable(segment) {
201 return segment.reachable;
202 }
203
204 module.exports = function(context) {
205 var codePathStack = [];
206
207 return {
208 // Stores CodePath objects.
209 "onCodePathStart": function(codePath) {
210 codePathStack.push(codePath);
211 },
212 "onCodePathEnd": function(codePath) {
213 codePathStack.pop();
214 },
215
216 // Checks reachable or not.
217 "ExpressionStatement": function(node) {
218 var codePath = last(codePathStack);
219
220 // Checks the current code path segments.
221 if (!codePath.currentSegments.some(isReachable)) {
222 context.report({message: "Unreachable!", node: node});
223 }
224 }
225 };
226 };
227 ```
228
229 See Also:
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)
233
234 ### To check state of a code path
235
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.
240
241 ```js
242 var last = require("lodash").last;
243
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";
248 });
249 }
250 return false;
251 }
252
253 function isCbCalled(info) {
254 return info.cbCalled;
255 }
256
257 module.exports = function(context) {
258 var funcInfoStack = [];
259 var segmentInfoMap = Object.create(null);
260
261 return {
262 // Checks `cb`.
263 "onCodePathStart": function(codePath, node) {
264 funcInfoStack.push({
265 codePath: codePath,
266 hasCb: hasCb(node, context)
267 });
268 },
269 "onCodePathEnd": function(codePath, node) {
270 funcInfoStack.pop();
271
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;
276 });
277
278 if (!cbCalled) {
279 context.report({
280 message: "`cb` should be called in every path.",
281 node: node
282 });
283 }
284 },
285
286 // Manages state of code paths.
287 "onCodePathSegmentStart": function(segment) {
288 // Ignores if `cb` doesn't exist.
289 if (!last(funcInfoStack).hasCb) {
290 return;
291 }
292
293 // Initialize state of this path.
294 var info = segmentInfoMap[segment.id] = {
295 cbCalled: false
296 };
297
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);
302 }
303 },
304
305 // Checks reachable or not.
306 "CallExpression": function(node) {
307 var funcInfo = last(funcInfoStack);
308
309 // Ignores if `cb` doesn't exist.
310 if (!funcInfo.hasCb) {
311 return;
312 }
313
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;
320 });
321 }
322 }
323 };
324 };
325 ```
326
327 See Also:
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)
330
331 ## Code Path Examples
332
333 ### Hello World
334
335 ```js
336 console.log("Hello world!");
337 ```
338
339 ![Hello World](./code-path-analysis/example-hello-world.svg)
340
341 ### `IfStatement`
342
343 ```js
344 if (a) {
345 foo();
346 } else {
347 bar();
348 }
349 ```
350
351 ![`IfStatement`](./code-path-analysis/example-ifstatement.svg)
352
353 ### `IfStatement` (chain)
354
355 ```js
356 if (a) {
357 foo();
358 } else if (b) {
359 bar();
360 } else if (c) {
361 hoge();
362 }
363 ```
364
365 ![`IfStatement` (chain)](./code-path-analysis/example-ifstatement-chain.svg)
366
367 ### `SwitchStatement`
368
369 ```js
370 switch (a) {
371 case 0:
372 foo();
373 break;
374
375 case 1:
376 case 2:
377 bar();
378 // fallthrough
379
380 case 3:
381 hoge();
382 break;
383 }
384 ```
385
386 ![`SwitchStatement`](./code-path-analysis/example-switchstatement.svg)
387
388 ### `SwitchStatement` (has `default`)
389
390 ```js
391 switch (a) {
392 case 0:
393 foo();
394 break;
395
396 case 1:
397 case 2:
398 bar();
399 // fallthrough
400
401 case 3:
402 hoge();
403 break;
404
405 default:
406 fuga();
407 break;
408 }
409 ```
410
411 ![`SwitchStatement` (has `default`)](./code-path-analysis/example-switchstatement-has-default.svg)
412
413 ### `TryStatement` (try-catch)
414
415 ```js
416 try {
417 foo();
418 if (a) {
419 throw new Error();
420 }
421 bar();
422 } catch (err) {
423 hoge(err);
424 }
425 last();
426 ```
427
428 It creates the paths from `try` block to `catch` block at:
429
430 * `throw` statements.
431 * The first throwable node (e.g. a function call) in the `try` block.
432 * The end of the `try` block.
433
434 ![`TryStatement` (try-catch)](./code-path-analysis/example-trystatement-try-catch.svg)
435
436 ### `TryStatement` (try-finally)
437
438 ```js
439 try {
440 foo();
441 bar();
442 } finally {
443 fuga();
444 }
445 last();
446 ```
447
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`).
451
452 ![`TryStatement` (try-finally)](./code-path-analysis/example-trystatement-try-finally.svg)
453
454 ### `TryStatement` (try-catch-finally)
455
456 ```js
457 try {
458 foo();
459 bar();
460 } catch (err) {
461 hoge(err);
462 } finally {
463 fuga();
464 }
465 last();
466 ```
467
468 ![`TryStatement` (try-catch-finally)](./code-path-analysis/example-trystatement-try-catch-finally.svg)
469
470 ### `WhileStatement`
471
472 ```js
473 while (a) {
474 foo();
475 if (b) {
476 continue;
477 }
478 bar();
479 }
480 ```
481
482 ![`WhileStatement`](./code-path-analysis/example-whilestatement.svg)
483
484 ### `DoWhileStatement`
485
486 ```js
487 do {
488 foo();
489 bar();
490 } while (a);
491 ```
492
493 ![`DoWhileStatement`](./code-path-analysis/example-dowhilestatement.svg)
494
495 ### `ForStatement`
496
497 ```js
498 for (let i = 0; i < 10; ++i) {
499 foo();
500 if (b) {
501 break;
502 }
503 bar();
504 }
505 ```
506
507 ![`ForStatement`](./code-path-analysis/example-forstatement.svg)
508
509 ### `ForStatement` (for ever)
510
511 ```js
512 for (;;) {
513 foo();
514 }
515 bar();
516 ```
517
518 ![`ForStatement` (for ever)](./code-path-analysis/example-forstatement-for-ever.svg)
519
520 ### `ForInStatement`
521
522 ```js
523 for (let key in obj) {
524 foo(key);
525 }
526 ```
527
528 ![`ForInStatement`](./code-path-analysis/example-forinstatement.svg)
529
530 ### When there is a function
531
532 ```js
533 function foo(a) {
534 if (a) {
535 return;
536 }
537 bar();
538 }
539
540 foo(false);
541 ```
542
543 It creates two code paths.
544
545 * The global's
546
547 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-g.svg)
548
549 * The function's
550
551 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-f.svg)