]> git.proxmox.com Git - pve-eslint.git/blob - eslint/docs/developer-guide/code-path-analysis.md
import eslint 7.28.0
[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 function isReachable(segment) {
199 return segment.reachable;
200 }
201
202 module.exports = function(context) {
203 var codePathStack = [];
204
205 return {
206 // Stores CodePath objects.
207 "onCodePathStart": function(codePath) {
208 codePathStack.push(codePath);
209 },
210 "onCodePathEnd": function(codePath) {
211 codePathStack.pop();
212 },
213
214 // Checks reachable or not.
215 "ExpressionStatement": function(node) {
216 var codePath = codePathStack[codePathStack.length - 1];
217
218 // Checks the current code path segments.
219 if (!codePath.currentSegments.some(isReachable)) {
220 context.report({message: "Unreachable!", node: node});
221 }
222 }
223 };
224 };
225 ```
226
227 See Also:
228 [no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js),
229 [no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js),
230 [consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js)
231
232 ### To check state of a code path
233
234 This example is checking whether or not the parameter `cb` is called in every path.
235 Instances of `CodePath` and `CodePathSegment` are shared to every rule.
236 So a rule must not modify those instances.
237 Please use a map of information instead.
238
239 ```js
240 function hasCb(node, context) {
241 if (node.type.indexOf("Function") !== -1) {
242 return context.getDeclaredVariables(node).some(function(v) {
243 return v.type === "Parameter" && v.name === "cb";
244 });
245 }
246 return false;
247 }
248
249 function isCbCalled(info) {
250 return info.cbCalled;
251 }
252
253 module.exports = function(context) {
254 var funcInfoStack = [];
255 var segmentInfoMap = Object.create(null);
256
257 return {
258 // Checks `cb`.
259 "onCodePathStart": function(codePath, node) {
260 funcInfoStack.push({
261 codePath: codePath,
262 hasCb: hasCb(node, context)
263 });
264 },
265 "onCodePathEnd": function(codePath, node) {
266 funcInfoStack.pop();
267
268 // Checks `cb` was called in every paths.
269 var cbCalled = codePath.finalSegments.every(function(segment) {
270 var info = segmentInfoMap[segment.id];
271 return info.cbCalled;
272 });
273
274 if (!cbCalled) {
275 context.report({
276 message: "`cb` should be called in every path.",
277 node: node
278 });
279 }
280 },
281
282 // Manages state of code paths.
283 "onCodePathSegmentStart": function(segment) {
284 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
285
286 // Ignores if `cb` doesn't exist.
287 if (!funcInfo.hasCb) {
288 return;
289 }
290
291 // Initialize state of this path.
292 var info = segmentInfoMap[segment.id] = {
293 cbCalled: false
294 };
295
296 // If there are the previous paths, merges state.
297 // Checks `cb` was called in every previous path.
298 if (segment.prevSegments.length > 0) {
299 info.cbCalled = segment.prevSegments.every(isCbCalled);
300 }
301 },
302
303 // Checks reachable or not.
304 "CallExpression": function(node) {
305 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
306
307 // Ignores if `cb` doesn't exist.
308 if (!funcInfo.hasCb) {
309 return;
310 }
311
312 // Sets marks that `cb` was called.
313 var callee = node.callee;
314 if (callee.type === "Identifier" && callee.name === "cb") {
315 funcInfo.codePath.currentSegments.forEach(function(segment) {
316 var info = segmentInfoMap[segment.id];
317 info.cbCalled = true;
318 });
319 }
320 }
321 };
322 };
323 ```
324
325 See Also:
326 [constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js),
327 [no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js)
328
329 ## Code Path Examples
330
331 ### Hello World
332
333 ```js
334 console.log("Hello world!");
335 ```
336
337 ![Hello World](./code-path-analysis/example-hello-world.svg)
338
339 ### `IfStatement`
340
341 ```js
342 if (a) {
343 foo();
344 } else {
345 bar();
346 }
347 ```
348
349 ![`IfStatement`](./code-path-analysis/example-ifstatement.svg)
350
351 ### `IfStatement` (chain)
352
353 ```js
354 if (a) {
355 foo();
356 } else if (b) {
357 bar();
358 } else if (c) {
359 hoge();
360 }
361 ```
362
363 ![`IfStatement` (chain)](./code-path-analysis/example-ifstatement-chain.svg)
364
365 ### `SwitchStatement`
366
367 ```js
368 switch (a) {
369 case 0:
370 foo();
371 break;
372
373 case 1:
374 case 2:
375 bar();
376 // fallthrough
377
378 case 3:
379 hoge();
380 break;
381 }
382 ```
383
384 ![`SwitchStatement`](./code-path-analysis/example-switchstatement.svg)
385
386 ### `SwitchStatement` (has `default`)
387
388 ```js
389 switch (a) {
390 case 0:
391 foo();
392 break;
393
394 case 1:
395 case 2:
396 bar();
397 // fallthrough
398
399 case 3:
400 hoge();
401 break;
402
403 default:
404 fuga();
405 break;
406 }
407 ```
408
409 ![`SwitchStatement` (has `default`)](./code-path-analysis/example-switchstatement-has-default.svg)
410
411 ### `TryStatement` (try-catch)
412
413 ```js
414 try {
415 foo();
416 if (a) {
417 throw new Error();
418 }
419 bar();
420 } catch (err) {
421 hoge(err);
422 }
423 last();
424 ```
425
426 It creates the paths from `try` block to `catch` block at:
427
428 * `throw` statements.
429 * The first throwable node (e.g. a function call) in the `try` block.
430 * The end of the `try` block.
431
432 ![`TryStatement` (try-catch)](./code-path-analysis/example-trystatement-try-catch.svg)
433
434 ### `TryStatement` (try-finally)
435
436 ```js
437 try {
438 foo();
439 bar();
440 } finally {
441 fuga();
442 }
443 last();
444 ```
445
446 If there is not `catch` block, `finally` block has two current segments.
447 At this time, `CodePath.currentSegments.length` is `2`.
448 One is the normal path, and another is the leaving path (`throw` or `return`).
449
450 ![`TryStatement` (try-finally)](./code-path-analysis/example-trystatement-try-finally.svg)
451
452 ### `TryStatement` (try-catch-finally)
453
454 ```js
455 try {
456 foo();
457 bar();
458 } catch (err) {
459 hoge(err);
460 } finally {
461 fuga();
462 }
463 last();
464 ```
465
466 ![`TryStatement` (try-catch-finally)](./code-path-analysis/example-trystatement-try-catch-finally.svg)
467
468 ### `WhileStatement`
469
470 ```js
471 while (a) {
472 foo();
473 if (b) {
474 continue;
475 }
476 bar();
477 }
478 ```
479
480 ![`WhileStatement`](./code-path-analysis/example-whilestatement.svg)
481
482 ### `DoWhileStatement`
483
484 ```js
485 do {
486 foo();
487 bar();
488 } while (a);
489 ```
490
491 ![`DoWhileStatement`](./code-path-analysis/example-dowhilestatement.svg)
492
493 ### `ForStatement`
494
495 ```js
496 for (let i = 0; i < 10; ++i) {
497 foo();
498 if (b) {
499 break;
500 }
501 bar();
502 }
503 ```
504
505 ![`ForStatement`](./code-path-analysis/example-forstatement.svg)
506
507 ### `ForStatement` (for ever)
508
509 ```js
510 for (;;) {
511 foo();
512 }
513 bar();
514 ```
515
516 ![`ForStatement` (for ever)](./code-path-analysis/example-forstatement-for-ever.svg)
517
518 ### `ForInStatement`
519
520 ```js
521 for (let key in obj) {
522 foo(key);
523 }
524 ```
525
526 ![`ForInStatement`](./code-path-analysis/example-forinstatement.svg)
527
528 ### When there is a function
529
530 ```js
531 function foo(a) {
532 if (a) {
533 return;
534 }
535 bar();
536 }
537
538 foo(false);
539 ```
540
541 It creates two code paths.
542
543 * The global's
544
545 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-g.svg)
546
547 * The function's
548
549 ![When there is a function](./code-path-analysis/example-when-there-is-a-function-f.svg)