aboutsummaryrefslogblamecommitdiffstats
path: root/packages/sol-cov/src/ast_visitor.ts
blob: 701cbbc885e97becb5da0f1e1ccd05c174be0c31 (plain) (tree)
1
2
3
4
5
6
7
8
9








                                                                                                  
                                    

 





                                        



                                       
                                                  





























                                                                                                               
                                                               



                                                                        
                                                                                                      









                                                                      
                                                                                       


                                                              
                                                                                          



















                                                                       
                                                                                                            
                                                       
                                                            






                                      
                         



















                                                                                         
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;
    modifiersStatementIds: number[];
}

enum BranchType {
    If = 'if',
    ConditionalExpression = 'cond-expr',
    BinaryExpression = 'binary-expr',
}

export class ASTVisitor {
    private _entryId = 0;
    private _fnMap: FnMap = {};
    private _branchMap: BranchMap = {};
    private _modifiersStatementIds: number[] = [];
    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,
            modifiersStatementIds: this._modifiersStatementIds,
        };
        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', 'constant'];
        if (!_.includes(BUILTIN_MODIFIERS, ast.name)) {
            this._modifiersStatementIds.push(this._entryId);
            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;
    }
}