]> git.proxmox.com Git - pve-eslint.git/blame - eslint/docs/src/developer-guide/code-path-analysis.md
build: add missing dh-nodejs to build-dependencies
[pve-eslint.git] / eslint / docs / src / developer-guide / code-path-analysis.md
CommitLineData
8f9d1d4d
DC
1---
2title: Code Path Analysis Details
3layout: doc
4
5---
eb39fafa
DC
6
7ESLint's rules can use code paths.
8The code path is execution routes of programs.
9It forks/joins at such as `if` statements.
10
11```js
12if (a && b) {
13 foo();
14}
15bar();
16```
17
8f9d1d4d 18![Code Path Example](../assets/images/code-path-analysis/helo.svg)
eb39fafa
DC
19
20## Objects
21
22Program is expressed with several code paths.
23A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`.
24
25### `CodePath`
26
27`CodePath` expresses whole of one code path.
28This object exists for each function and the global.
29This has references of both the initial segment and the final segments of a code path.
30
31`CodePath` has the following properties:
32
33* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path.
609c276f 34* `origin` (`string`) - The reason that the code path was started. May be `"program"`, `"function"`, `"class-field-initializer"`, or `"class-static-block"`.
eb39fafa
DC
35* `initialSegment` (`CodePathSegment`) - The initial segment of this code path.
36* `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown.
37* `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned.
38* `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown.
39* `currentSegments` (`CodePathSegment[]`) - Segments of the current position.
40* `upper` (`CodePath|null`) - The code path of the upper function/global scope.
41* `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains.
42
43### `CodePathSegment`
44
45`CodePathSegment` is a part of a code path.
46A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list.
47Difference from doubly linked list is what there are forking and merging (the next/prev are plural).
48
49`CodePathSegment` has the following properties:
50
51* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment.
52* `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing.
53* `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing.
54* `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`.
55
56## Events
57
58There are five events related to code paths, and you can define event handlers in rules.
59
60```js
61module.exports = function(context) {
62 return {
63 /**
64 * This is called at the start of analyzing a code path.
65 * In this time, the code path object has only the initial segment.
66 *
67 * @param {CodePath} codePath - The new code path.
68 * @param {ASTNode} node - The current node.
69 * @returns {void}
70 */
71 "onCodePathStart": function(codePath, node) {
72 // do something with codePath
73 },
74
75 /**
76 * This is called at the end of analyzing a code path.
77 * In this time, the code path object is complete.
78 *
79 * @param {CodePath} codePath - The completed code path.
80 * @param {ASTNode} node - The current node.
81 * @returns {void}
82 */
83 "onCodePathEnd": function(codePath, node) {
84 // do something with codePath
85 },
86
87 /**
88 * This is called when a code path segment was created.
89 * It meant the code path is forked or merged.
90 * In this time, the segment has the previous segments and has been
91 * judged reachable or not.
92 *
93 * @param {CodePathSegment} segment - The new code path segment.
94 * @param {ASTNode} node - The current node.
95 * @returns {void}
96 */
97 "onCodePathSegmentStart": function(segment, node) {
98 // do something with segment
99 },
100
101 /**
8f9d1d4d 102 * This is called when a code path segment was left.
eb39fafa
DC
103 * In this time, the segment does not have the next segments yet.
104 *
8f9d1d4d 105 * @param {CodePathSegment} segment - The left code path segment.
eb39fafa
DC
106 * @param {ASTNode} node - The current node.
107 * @returns {void}
108 */
109 "onCodePathSegmentEnd": function(segment, node) {
110 // do something with segment
111 },
112
113 /**
114 * This is called when a code path segment was looped.
115 * Usually segments have each previous segments when created,
116 * but when looped, a segment is added as a new previous segment into a
117 * existing segment.
118 *
119 * @param {CodePathSegment} fromSegment - A code path segment of source.
120 * @param {CodePathSegment} toSegment - A code path segment of destination.
121 * @param {ASTNode} node - The current node.
122 * @returns {void}
123 */
124 "onCodePathSegmentLoop": function(fromSegment, toSegment, node) {
125 // do something with segment
126 }
127 };
128};
129```
130
131### About `onCodePathSegmentLoop`
132
133This event is always fired when the next segment has existed already.
134That timing is the end of loops mainly.
135
136For Example 1:
137
138```js
139while (a) {
140 a = foo();
141}
142bar();
143```
144
1451. First, the analysis advances to the end of loop.
146
8f9d1d4d 147 ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-while-1.svg)
eb39fafa
DC
148
1492. Second, it creates the looping path.
150 At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
151 It fires `onCodePathSegmentLoop` instead.
152
8f9d1d4d 153 ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-while-2.svg)
eb39fafa
DC
154
1553. Last, it advances to the end.
156
8f9d1d4d 157 ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-while-3.svg)
eb39fafa
DC
158
159For example 2:
160
161```js
162for (let i = 0; i < 10; ++i) {
163 foo(i);
164}
165bar();
166```
167
1681. `for` statements are more complex.
169 First, the analysis advances to `ForStatement.update`.
170 The `update` segment is hovered at first.
171
8f9d1d4d 172 ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-for-1.svg)
eb39fafa
DC
173
1742. Second, it advances to `ForStatement.body`.
175 Of course the `body` segment is preceded by the `test` segment.
176 It keeps the `update` segment hovering.
177
8f9d1d4d 178 ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-for-2.svg)
eb39fafa
DC
179
1803. Third, it creates the looping path from `body` segment to `update` segment.
181 At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
182 It fires `onCodePathSegmentLoop` instead.
183
8f9d1d4d 184 ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-for-3.svg)
eb39fafa
DC
185
1864. Fourth, also it creates the looping path from `update` segment to `test` segment.
187 At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
188 It fires `onCodePathSegmentLoop` instead.
189
8f9d1d4d 190 ![Loop Event's Example 4](../assets/images/code-path-analysis/loop-event-example-for-4.svg)
eb39fafa
DC
191
1925. Last, it advances to the end.
193
8f9d1d4d 194 ![Loop Event's Example 5](../assets/images/code-path-analysis/loop-event-example-for-5.svg)
eb39fafa 195
eb39fafa
DC
196## Usage Examples
197
198### To check whether or not this is reachable
199
200```js
eb39fafa
DC
201function isReachable(segment) {
202 return segment.reachable;
203}
204
205module.exports = function(context) {
206 var codePathStack = [];
207
208 return {
209 // Stores CodePath objects.
210 "onCodePathStart": function(codePath) {
211 codePathStack.push(codePath);
212 },
213 "onCodePathEnd": function(codePath) {
214 codePathStack.pop();
215 },
216
217 // Checks reachable or not.
218 "ExpressionStatement": function(node) {
5422a9cc 219 var codePath = codePathStack[codePathStack.length - 1];
eb39fafa
DC
220
221 // Checks the current code path segments.
222 if (!codePath.currentSegments.some(isReachable)) {
223 context.report({message: "Unreachable!", node: node});
224 }
225 }
226 };
227};
228```
229
230See Also:
609c276f
TL
231[no-unreachable](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-unreachable.js),
232[no-fallthrough](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-fallthrough.js),
233[consistent-return](https://github.com/eslint/eslint/blob/HEAD/lib/rules/consistent-return.js)
eb39fafa
DC
234
235### To check state of a code path
236
237This example is checking whether or not the parameter `cb` is called in every path.
238Instances of `CodePath` and `CodePathSegment` are shared to every rule.
239So a rule must not modify those instances.
240Please use a map of information instead.
241
242```js
eb39fafa
DC
243function hasCb(node, context) {
244 if (node.type.indexOf("Function") !== -1) {
245 return context.getDeclaredVariables(node).some(function(v) {
246 return v.type === "Parameter" && v.name === "cb";
247 });
248 }
249 return false;
250}
251
252function isCbCalled(info) {
253 return info.cbCalled;
254}
255
256module.exports = function(context) {
257 var funcInfoStack = [];
258 var segmentInfoMap = Object.create(null);
259
260 return {
261 // Checks `cb`.
262 "onCodePathStart": function(codePath, node) {
263 funcInfoStack.push({
264 codePath: codePath,
265 hasCb: hasCb(node, context)
266 });
267 },
268 "onCodePathEnd": function(codePath, node) {
269 funcInfoStack.pop();
270
271 // Checks `cb` was called in every paths.
272 var cbCalled = codePath.finalSegments.every(function(segment) {
273 var info = segmentInfoMap[segment.id];
274 return info.cbCalled;
275 });
276
277 if (!cbCalled) {
278 context.report({
279 message: "`cb` should be called in every path.",
280 node: node
281 });
282 }
283 },
284
285 // Manages state of code paths.
286 "onCodePathSegmentStart": function(segment) {
5422a9cc
TL
287 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
288
eb39fafa 289 // Ignores if `cb` doesn't exist.
5422a9cc 290 if (!funcInfo.hasCb) {
eb39fafa
DC
291 return;
292 }
293
294 // Initialize state of this path.
295 var info = segmentInfoMap[segment.id] = {
296 cbCalled: false
297 };
298
299 // If there are the previous paths, merges state.
300 // Checks `cb` was called in every previous path.
301 if (segment.prevSegments.length > 0) {
302 info.cbCalled = segment.prevSegments.every(isCbCalled);
303 }
304 },
305
306 // Checks reachable or not.
307 "CallExpression": function(node) {
5422a9cc 308 var funcInfo = funcInfoStack[funcInfoStack.length - 1];
eb39fafa
DC
309
310 // Ignores if `cb` doesn't exist.
311 if (!funcInfo.hasCb) {
312 return;
313 }
314
315 // Sets marks that `cb` was called.
316 var callee = node.callee;
317 if (callee.type === "Identifier" && callee.name === "cb") {
318 funcInfo.codePath.currentSegments.forEach(function(segment) {
319 var info = segmentInfoMap[segment.id];
320 info.cbCalled = true;
321 });
322 }
323 }
324 };
325};
326```
327
328See Also:
609c276f
TL
329[constructor-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/constructor-super.js),
330[no-this-before-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-this-before-super.js)
eb39fafa
DC
331
332## Code Path Examples
333
334### Hello World
335
336```js
337console.log("Hello world!");
338```
339
8f9d1d4d 340![Hello World](../assets/images/code-path-analysis/example-hello-world.svg)
eb39fafa
DC
341
342### `IfStatement`
343
344```js
345if (a) {
346 foo();
347} else {
348 bar();
349}
350```
351
8f9d1d4d 352![`IfStatement`](../assets/images/code-path-analysis/example-ifstatement.svg)
eb39fafa
DC
353
354### `IfStatement` (chain)
355
356```js
357if (a) {
358 foo();
359} else if (b) {
360 bar();
361} else if (c) {
362 hoge();
363}
364```
365
8f9d1d4d 366![`IfStatement` (chain)](../assets/images/code-path-analysis/example-ifstatement-chain.svg)
eb39fafa
DC
367
368### `SwitchStatement`
369
370```js
371switch (a) {
372 case 0:
373 foo();
374 break;
375
376 case 1:
377 case 2:
378 bar();
379 // fallthrough
380
381 case 3:
382 hoge();
383 break;
384}
385```
386
8f9d1d4d 387![`SwitchStatement`](../assets/images/code-path-analysis/example-switchstatement.svg)
eb39fafa
DC
388
389### `SwitchStatement` (has `default`)
390
391```js
392switch (a) {
393 case 0:
394 foo();
395 break;
396
397 case 1:
398 case 2:
399 bar();
400 // fallthrough
401
402 case 3:
403 hoge();
404 break;
405
406 default:
407 fuga();
408 break;
409}
410```
411
8f9d1d4d 412![`SwitchStatement` (has `default`)](../assets/images/code-path-analysis/example-switchstatement-has-default.svg)
eb39fafa
DC
413
414### `TryStatement` (try-catch)
415
416```js
417try {
418 foo();
419 if (a) {
420 throw new Error();
421 }
422 bar();
423} catch (err) {
424 hoge(err);
425}
426last();
427```
428
429It creates the paths from `try` block to `catch` block at:
430
431* `throw` statements.
432* The first throwable node (e.g. a function call) in the `try` block.
433* The end of the `try` block.
434
8f9d1d4d 435![`TryStatement` (try-catch)](../assets/images/code-path-analysis/example-trystatement-try-catch.svg)
eb39fafa
DC
436
437### `TryStatement` (try-finally)
438
439```js
440try {
441 foo();
442 bar();
443} finally {
444 fuga();
445}
446last();
447```
448
449If there is not `catch` block, `finally` block has two current segments.
450At this time, `CodePath.currentSegments.length` is `2`.
451One is the normal path, and another is the leaving path (`throw` or `return`).
452
8f9d1d4d 453![`TryStatement` (try-finally)](../assets/images/code-path-analysis/example-trystatement-try-finally.svg)
eb39fafa
DC
454
455### `TryStatement` (try-catch-finally)
456
457```js
458try {
459 foo();
460 bar();
461} catch (err) {
462 hoge(err);
463} finally {
464 fuga();
465}
466last();
467```
468
8f9d1d4d 469![`TryStatement` (try-catch-finally)](../assets/images/code-path-analysis/example-trystatement-try-catch-finally.svg)
eb39fafa
DC
470
471### `WhileStatement`
472
473```js
474while (a) {
475 foo();
476 if (b) {
477 continue;
478 }
479 bar();
480}
481```
482
8f9d1d4d 483![`WhileStatement`](../assets/images/code-path-analysis/example-whilestatement.svg)
eb39fafa
DC
484
485### `DoWhileStatement`
486
487```js
488do {
489 foo();
490 bar();
491} while (a);
492```
493
8f9d1d4d 494![`DoWhileStatement`](../assets/images/code-path-analysis/example-dowhilestatement.svg)
eb39fafa
DC
495
496### `ForStatement`
497
498```js
499for (let i = 0; i < 10; ++i) {
500 foo();
501 if (b) {
502 break;
503 }
504 bar();
505}
506```
507
8f9d1d4d 508![`ForStatement`](../assets/images/code-path-analysis/example-forstatement.svg)
eb39fafa
DC
509
510### `ForStatement` (for ever)
511
512```js
513for (;;) {
514 foo();
515}
516bar();
517```
518
8f9d1d4d 519![`ForStatement` (for ever)](../assets/images/code-path-analysis/example-forstatement-for-ever.svg)
eb39fafa
DC
520
521### `ForInStatement`
522
523```js
524for (let key in obj) {
525 foo(key);
526}
527```
528
8f9d1d4d 529![`ForInStatement`](../assets/images/code-path-analysis/example-forinstatement.svg)
eb39fafa
DC
530
531### When there is a function
532
533```js
534function foo(a) {
535 if (a) {
536 return;
537 }
538 bar();
539}
540
541foo(false);
542```
543
544It creates two code paths.
545
546* The global's
547
8f9d1d4d 548 ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-g.svg)
eb39fafa
DC
549
550* The function's
551
8f9d1d4d 552 ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-f.svg)