A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://eslint.org/docs/latest/developer-guide/code-path-analysis below:

Code Path Analysis Details - ESLint

Code Path Analysis Details Table of Contents

ESLint’s rules can use code paths. The code path is execution routes of programs. It forks/joins at such as if statements.

if (a && b) {
	foo();
}
bar();

1
2
3
4

Tip

You can view code path diagrams for any JavaScript code using Code Explorer.

Objects

Program is expressed with several code paths. A code path is expressed with objects of two kinds: CodePath and CodePathSegment.

CodePath

CodePath expresses whole of one code path. This object exists for each function and the global. This has references of both the initial segment and the final segments of a code path.

CodePath has the following properties:

CodePathSegment

CodePathSegment is a part of a code path. A code path is expressed with plural CodePathSegment objects, it’s similar to doubly linked list. Difference from doubly linked list is what there are forking and merging (the next/prev are plural).

CodePathSegment has the following properties:

Events

There are seven events related to code paths, and you can define event handlers by adding them alongside node visitors in the object exported from the create() method of your rule.

module.exports = {
	meta: {
		
	},
	create(context) {
		return {
			
			onCodePathStart(codePath, node) {
				
			},

			
			onCodePathEnd(codePath, node) {
				
			},

			
			onCodePathSegmentStart(segment, node) {
				
			},

			
			onCodePathSegmentEnd(segment, node) {
				
			},

			
			onUnreachableCodePathSegmentStart(segment, node) {
				
			},

			
			onUnreachableCodePathSegmentEnd(segment, node) {
				
			},

			
			onCodePathSegmentLoop(fromSegment, toSegment, node) {
				
			},
		};
	},
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

About onCodePathSegmentLoop

This event is always fired when the next segment has existed already. That timing is the end of loops mainly.

For Example 1:

while (a) {
	a = foo();
}
bar();

1
2
3
4

  1. First, the analysis advances to the end of loop.

  1. Second, it creates the looping path. At this time, the next segment has existed already, so the onCodePathSegmentStart event is not fired. It fires onCodePathSegmentLoop instead.

  1. Last, it advances to the end.

For example 2:

for (let i = 0; i < 10; ++i) {
	foo(i);
}
bar();

1
2
3
4

  1. for statements are more complex. First, the analysis advances to ForStatement.update. The update segment is hovered at first.

  1. Second, it advances to ForStatement.body. Of course the body segment is preceded by the test segment. It keeps the update segment hovering.

  1. Third, it creates the looping path from body segment to update segment. At this time, the next segment has existed already, so the onCodePathSegmentStart event is not fired. It fires onCodePathSegmentLoop instead.

  1. Fourth, also it creates the looping path from update segment to test segment. At this time, the next segment has existed already, so the onCodePathSegmentStart event is not fired. It fires onCodePathSegmentLoop instead.

  1. Last, it advances to the end.

Usage Examples Track current segment position

To track the current code path segment position, you can define a rule like this:

module.exports = {
	meta: {
		
	},
	create(context) {
		
		let currentCodePath;

		
		let currentSegments;

		
		const allCurrentSegments = [];

		return {
			onCodePathStart(codePath) {
				currentCodePath = codePath;
				allCurrentSegments.push(currentSegments);
				currentSegments = new Set();
			},

			onCodePathEnd(codePath) {
				currentCodePath = codePath.upper;
				currentSegments = allCurrentSegments.pop();
			},

			onCodePathSegmentStart(segment) {
				currentSegments.add(segment);
			},

			onCodePathSegmentEnd(segment) {
				currentSegments.delete(segment);
			},

			onUnreachableCodePathSegmentStart(segment) {
				currentSegments.add(segment);
			},

			onUnreachableCodePathSegmentEnd(segment) {
				currentSegments.delete(segment);
			},
		};
	},
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

In this example, the currentCodePath variable is used to access the code path that is currently being traversed and the currentSegments variable tracks the segments in that code path that have been traversed to that point. Note that currentSegments both starts and ends as an empty set, constantly being updated as the traversal progresses.

Tracking the current segment position is helpful for analyzing the code path that led to a particular node, as in the next example.

Find an unreachable node

To find an unreachable node, track the current segment position and then use a node visitor to check if any of the segments are reachable. For example, the following looks for any ExpressionStatement that is unreachable.

function areAnySegmentsReachable(segments) {
	for (const segment of segments) {
		if (segment.reachable) {
			return true;
		}
	}

	return false;
}

module.exports = {
	meta: {
		
	},
	create(context) {
		
		let currentCodePath;

		
		let currentSegments;

		
		const allCurrentSegments = [];

		return {
			onCodePathStart(codePath) {
				currentCodePath = codePath;
				allCurrentSegments.push(currentSegments);
				currentSegments = new Set();
			},

			onCodePathEnd(codePath) {
				currentCodePath = codePath.upper;
				currentSegments = allCurrentSegments.pop();
			},

			onCodePathSegmentStart(segment) {
				currentSegments.add(segment);
			},

			onCodePathSegmentEnd(segment) {
				currentSegments.delete(segment);
			},

			onUnreachableCodePathSegmentStart(segment) {
				currentSegments.add(segment);
			},

			onUnreachableCodePathSegmentEnd(segment) {
				currentSegments.delete(segment);
			},

			ExpressionStatement(node) {
				
				if (!areAnySegmentsReachable(currentSegments)) {
					context.report({ message: "Unreachable!", node });
				}
			},
		};
	},
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

See Also: no-unreachable, no-fallthrough, consistent-return

Check if a function is called in every path

This example checks whether or not the parameter cb is called in every path. Instances of CodePath and CodePathSegment are shared to every rule. So a rule must not modify those instances. Please use a map of information instead.

function hasCb(node, context) {
	if (node.type.indexOf("Function") !== -1) {
		const sourceCode = context.sourceCode;
		return sourceCode.getDeclaredVariables(node).some(function (v) {
			return v.type === "Parameter" && v.name === "cb";
		});
	}
	return false;
}

function isCbCalled(info) {
	return info.cbCalled;
}

module.exports = {
	meta: {
		
	},
	create(context) {
		let funcInfo;
		const funcInfoStack = [];
		const segmentInfoMap = Object.create(null);

		return {
			
			onCodePathStart(codePath, node) {
				funcInfoStack.push(funcInfo);

				funcInfo = {
					codePath: codePath,
					hasCb: hasCb(node, context),
					currentSegments: new Set(),
				};
			},

			onCodePathEnd(codePath, node) {
				funcInfo = funcInfoStack.pop();

				
				const cbCalled = codePath.finalSegments.every(
					function (segment) {
						const info = segmentInfoMap[segment.id];
						return info.cbCalled;
					},
				);

				if (!cbCalled) {
					context.report({
						message: "`cb` should be called in every path.",
						node: node,
					});
				}
			},

			
			onCodePathSegmentStart(segment) {
				funcInfo.currentSegments.add(segment);

				
				if (!funcInfo.hasCb) {
					return;
				}

				
				const info = (segmentInfoMap[segment.id] = {
					cbCalled: false,
				});

				
				
				if (segment.prevSegments.length > 0) {
					info.cbCalled = segment.prevSegments.every(isCbCalled);
				}
			},

			
			onUnreachableCodePathSegmentStart(segment) {
				funcInfo.currentSegments.add(segment);
			},

			
			onCodePathSegmentEnd(segment) {
				funcInfo.currentSegments.delete(segment);
			},

			
			onUnreachableCodePathSegmentEnd(segment) {
				funcInfo.currentSegments.delete(segment);
			},

			
			CallExpression(node) {
				
				if (!funcInfo.hasCb) {
					return;
				}

				
				const callee = node.callee;
				if (callee.type === "Identifier" && callee.name === "cb") {
					funcInfo.currentSegments.forEach(segment => {
						const info = segmentInfoMap[segment.id];
						info.cbCalled = true;
					});
				}
			},
		};
	},
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

See Also: constructor-super, no-this-before-super

Code Path Examples Hello World
console.log("Hello world!");

1

IfStatement
if (a) {
	foo();
} else {
	bar();
}

1
2
3
4
5

IfStatement (chain)
if (a) {
	foo();
} else if (b) {
	bar();
} else if (c) {
	hoge();
}

1
2
3
4
5
6
7

SwitchStatement
switch (a) {
	case 0:
		foo();
		break;

	case 1:
	case 2:
		bar();
	

	case 3:
		hoge();
		break;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

SwitchStatement (has default)
switch (a) {
	case 0:
		foo();
		break;

	case 1:
	case 2:
		bar();
	

	case 3:
		hoge();
		break;

	default:
		fuga();
		break;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

TryStatement (try-catch)
try {
	foo();
	if (a) {
		throw new Error();
	}
	bar();
} catch (err) {
	hoge(err);
}
last();

1
2
3
4
5
6
7
8
9
10

It creates the paths from try block to catch block at:

TryStatement (try-finally)
try {
	foo();
	bar();
} finally {
	fuga();
}
last();

1
2
3
4
5
6
7

If there is not catch block, finally block has two current segments. At this time when running the previous example to find unreachable nodes, currentSegments.length is 2. One is the normal path, and another is the leaving path (throw or return).

TryStatement (try-catch-finally)
try {
	foo();
	bar();
} catch (err) {
	hoge(err);
} finally {
	fuga();
}
last();

1
2
3
4
5
6
7
8
9

WhileStatement
while (a) {
	foo();
	if (b) {
		continue;
	}
	bar();
}

1
2
3
4
5
6
7

DoWhileStatement
do {
	foo();
	bar();
} while (a);

1
2
3
4

ForStatement
for (let i = 0; i < 10; ++i) {
	foo();
	if (b) {
		break;
	}
	bar();
}

1
2
3
4
5
6
7

ForStatement (for ever)
for (;;) {
	foo();
}
bar();

1
2
3
4

ForInStatement
for (let key in obj) {
	foo(key);
}

1
2
3

When there is a function
function foo(a) {
	if (a) {
		return;
	}
	bar();
}

foo(false);

1
2
3
4
5
6
7
8

It creates two code paths.


RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4