]> git.proxmox.com Git - pve-eslint.git/blame - eslint/docs/developer-guide/code-path-analysis.md
import eslint 7.28.0
[pve-eslint.git] / eslint / docs / developer-guide / code-path-analysis.md
CommitLineData
eb39fafa
DC
1# Code Path Analysis Details
2
3ESLint's rules can use code paths.
4The code path is execution routes of programs.
5It forks/joins at such as `if` statements.
6
7```js
8if (a && b) {
9 foo();
10}
11bar();
12```
13
14![Code Path Example](./code-path-analysis/helo.svg)
15
16## Objects
17
18Program is expressed with several code paths.
19A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`.
20
21### `CodePath`
22
23`CodePath` expresses whole of one code path.
24This object exists for each function and the global.
25This 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.
41A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list.
42Difference 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
53There are five events related to code paths, and you can define event handlers in rules.
54
55```js
56module.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
128This event is always fired when the next segment has existed already.
129That timing is the end of loops mainly.
130
131For Example 1:
132
133```js
134while (a) {
135 a = foo();
136}
137bar();
138```
139
1401. 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
1442. 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
1503. Last, it advances to the end.
151
152 ![Loop Event's Example 3](./code-path-analysis/loop-event-example-while-3.svg)
153
154For example 2:
155
156```js
157for (let i = 0; i < 10; ++i) {
158 foo(i);
159}
160bar();
161```
162
1631. `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
1692. 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
1753. 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
1814. 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
1875. 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
eb39fafa
DC
198function isReachable(segment) {
199 return segment.reachable;
200}
201
202module.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) {
5422a9cc 216 var codePath = codePathStack[codePathStack.length - 1];
eb39fafa
DC
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
227See 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
234This example is checking whether or not the parameter `cb` is called in every path.
235Instances of `CodePath` and `CodePathSegment` are shared to every rule.
236So a rule must not modify those instances.
237Please use a map of information instead.
238
239```js
eb39fafa
DC
240function 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
249function isCbCalled(info) {
250 return info.cbCalled;
251}
252
253module.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) {
5422a9cc
TL
284 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
285
eb39fafa 286 // Ignores if `cb` doesn't exist.
5422a9cc 287 if (!funcInfo.hasCb) {
eb39fafa
DC
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) {
5422a9cc 305 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
eb39fafa
DC
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
325See 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
334console.log("Hello world!");
335```
336
337![Hello World](./code-path-analysis/example-hello-world.svg)
338
339### `IfStatement`
340
341```js
342if (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
354if (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
368switch (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
389switch (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
414try {
415 foo();
416 if (a) {
417 throw new Error();
418 }
419 bar();
420} catch (err) {
421 hoge(err);
422}
423last();
424```
425
426It 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
437try {
438 foo();
439 bar();
440} finally {
441 fuga();
442}
443last();
444```
445
446If there is not `catch` block, `finally` block has two current segments.
447At this time, `CodePath.currentSegments.length` is `2`.
448One 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
455try {
456 foo();
457 bar();
458} catch (err) {
459 hoge(err);
460} finally {
461 fuga();
462}
463last();
464```
465
466![`TryStatement` (try-catch-finally)](./code-path-analysis/example-trystatement-try-catch-finally.svg)
467
468### `WhileStatement`
469
470```js
471while (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
485do {
486 foo();
487 bar();
488} while (a);
489```
490
491![`DoWhileStatement`](./code-path-analysis/example-dowhilestatement.svg)
492
493### `ForStatement`
494
495```js
496for (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
510for (;;) {
511 foo();
512}
513bar();
514```
515
516![`ForStatement` (for ever)](./code-path-analysis/example-forstatement-for-ever.svg)
517
518### `ForInStatement`
519
520```js
521for (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
531function foo(a) {
532 if (a) {
533 return;
534 }
535 bar();
536}
537
538foo(false);
539```
540
541It 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)