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
110
111
112
113
114
115
116
117
118
119
120
121
|
import * as _ from 'lodash';
import * as SolidityParser from 'solidity-parser-sc';
import { BranchMap, FnMap, LocationByOffset, SingleFileSourceRange, StatementMap } from './types';
export interface CoverageEntriesDescription {
fnMap: FnMap;
branchMap: BranchMap;
statementMap: StatementMap;
}
enum BranchType {
If = 'if',
ConditionalExpression = 'cond-expr',
BinaryExpression = 'binary-expr',
}
export class ASTVisitor {
private _entryId = 0;
private _fnMap: FnMap = {};
private _branchMap: BranchMap = {};
private _statementMap: StatementMap = {};
private _locationByOffset: LocationByOffset;
private static _doesLookLikeAnASTNode(ast: any): boolean {
const isAST = _.isObject(ast) && _.isString(ast.type) && _.isNumber(ast.start) && _.isNumber(ast.end);
return isAST;
}
constructor(locationByOffset: LocationByOffset) {
this._locationByOffset = locationByOffset;
}
public walkAST(astNode: SolidityParser.AST): void {
if (_.isArray(astNode) || _.isObject(astNode)) {
if (ASTVisitor._doesLookLikeAnASTNode(astNode)) {
const nodeType = astNode.type;
const visitorFunctionName = `_visit${nodeType}`;
// tslint:disable-next-line:no-this-assignment
const self: { [visitorFunctionName: string]: (ast: SolidityParser.AST) => void } = this as any;
if (_.isFunction(self[visitorFunctionName])) {
self[visitorFunctionName](astNode);
}
}
_.forEach(astNode, subtree => {
this.walkAST(subtree);
});
}
}
public getCollectedCoverageEntries(): CoverageEntriesDescription {
const coverageEntriesDescription = {
fnMap: this._fnMap,
branchMap: this._branchMap,
statementMap: this._statementMap,
};
return coverageEntriesDescription;
}
private _visitConditionalExpression(ast: SolidityParser.AST): void {
this._visitBinaryBranch(ast, ast.consequent, ast.alternate, BranchType.ConditionalExpression);
}
private _visitFunctionDeclaration(ast: SolidityParser.AST): void {
const loc = this._getExpressionRange(ast);
this._fnMap[this._entryId++] = {
name: ast.name,
line: loc.start.line,
loc,
};
}
private _visitBinaryExpression(ast: SolidityParser.AST): void {
this._visitBinaryBranch(ast, ast.left, ast.right, BranchType.BinaryExpression);
}
private _visitIfStatement(ast: SolidityParser.AST): void {
this._visitStatement(ast);
this._visitBinaryBranch(ast, ast.consequent, ast.alternate || ast, BranchType.If);
}
private _visitBreakStatement(ast: SolidityParser.AST): void {
this._visitStatement(ast);
}
private _visitContractStatement(ast: SolidityParser.AST): void {
this._visitStatement(ast);
}
private _visitExpressionStatement(ast: SolidityParser.AST): void {
this._visitStatement(ast);
}
private _visitForStatement(ast: SolidityParser.AST): void {
this._visitStatement(ast);
}
private _visitPlaceholderStatement(ast: SolidityParser.AST): void {
this._visitStatement(ast);
}
private _visitReturnStatement(ast: SolidityParser.AST): void {
this._visitStatement(ast);
}
private _visitModifierArgument(ast: SolidityParser.AST): void {
const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure'];
if (!_.includes(BUILTIN_MODIFIERS, ast.name)) {
this._visitStatement(ast);
}
}
private _visitBinaryBranch(
ast: SolidityParser.AST,
left: SolidityParser.AST,
right: SolidityParser.AST,
type: BranchType,
): void {
this._branchMap[this._entryId++] = {
line: this._getExpressionRange(ast).start.line,
type,
locations: [this._getExpressionRange(left), this._getExpressionRange(right)],
};
}
private _visitStatement(ast: SolidityParser.AST): void {
this._statementMap[this._entryId++] = this._getExpressionRange(ast);
}
private _getExpressionRange(ast: SolidityParser.AST): SingleFileSourceRange {
const start = this._locationByOffset[ast.start - 1];
const end = this._locationByOffset[ast.end - 1];
const range = {
start,
end,
};
return range;
}
}
|