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