]> git.proxmox.com Git - pve-eslint.git/blame - eslint/docs/developer-guide/code-path-analysis.md
first commit
[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
198var last = require("lodash").last;
199
200function isReachable(segment) {
201 return segment.reachable;
202}
203
204module.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
229See 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
236This example is checking whether or not the parameter `cb` is called in every path.
237Instances of `CodePath` and `CodePathSegment` are shared to every rule.
238So a rule must not modify those instances.
239Please use a map of information instead.
240
241```js
242var last = require("lodash").last;
243
244function 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
253function isCbCalled(info) {
254 return info.cbCalled;
255}
256
257module.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
327See 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
336console.log("Hello world!");
337```
338
339![Hello World](./code-path-analysis/example-hello-world.svg)
340
341### `IfStatement`
342
343```js
344if (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
356if (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
370switch (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
391switch (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
416try {
417 foo();
418 if (a) {
419 throw new Error();
420 }
421 bar();
422} catch (err) {
423 hoge(err);
424}
425last();
426```
427
428It 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
439try {
440 foo();
441 bar();
442} finally {
443 fuga();
444}
445last();
446```
447
448If there is not `catch` block, `finally` block has two current segments.
449At this time, `CodePath.currentSegments.length` is `2`.
450One 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
457try {
458 foo();
459 bar();
460} catch (err) {
461 hoge(err);
462} finally {
463 fuga();
464}
465last();
466```
467
468![`TryStatement` (try-catch-finally)](./code-path-analysis/example-trystatement-try-catch-finally.svg)
469
470### `WhileStatement`
471
472```js
473while (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
487do {
488 foo();
489 bar();
490} while (a);
491```
492
493![`DoWhileStatement`](./code-path-analysis/example-dowhilestatement.svg)
494
495### `ForStatement`
496
497```js
498for (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
512for (;;) {
513 foo();
514}
515bar();
516```
517
518![`ForStatement` (for ever)](./code-path-analysis/example-forstatement-for-ever.svg)
519
520### `ForInStatement`
521
522```js
523for (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
533function foo(a) {
534 if (a) {
535 return;
536 }
537 bar();
538}
539
540foo(false);
541```
542
543It 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)