All Files / internal/utils / func.go

0% Statements 0/25
0% Functions 0/3
0% Lines 0/53
// This file implements the visitor that computes the (line, column)-(line-column) range for each function.

package utils

import (
        "go/ast"
        "go/parser"
        "go/token"

        "golang.org/x/tools/cover"
)

// FuncExtent describes a function's extent in the source by file and position.
type FuncExtent struct {
        Name      string
        StartLine int
        StartCol  int
        EndLine   int
        EndCol    int
}

// FindFuncs parses the file and returns a slice of FuncExtent descriptors.
func FindFuncs(name string) ([]*FuncExtent, error) {
        fset := token.NewFileSet()
        parsedFile, err := parser.ParseFile(fset, name, nil, 0)
        if err != nil {
                return nil, err
        }
        visitor := &FuncVisitor{
                FSet:    fset,
                Name:    name,
                AstFile: parsedFile,
        }
        ast.Walk(visitor, visitor.AstFile)
        return visitor.Funcs, nil
}

// FuncVisitor implements the visitor that builds the function position list for a file.
type FuncVisitor struct {
        FSet    *token.FileSet
        Name    string // Name of file.
        AstFile *ast.File
        Funcs   []*FuncExtent
}

// Visit implements the ast.Visitor interface.
func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor {
        switch n := node.(type) {
        case *ast.FuncDecl:
                if n.Body == nil {
                        // Do not count declarations of assembly functions.
                        break
                }
                start := v.FSet.Position(n.Pos())
                end := v.FSet.Position(n.End())
                fe := &FuncExtent{
                        Name:      n.Name.Name,
                        StartLine: start.Line,
                        StartCol:  start.Column,
                        EndLine:   end.Line,
                        EndCol:    end.Column,
                }
                v.Funcs = append(v.Funcs, fe)
        }
        return v
}

// Coverage returns the fraction of the statements in the function that were covered, as a numerator and denominator.
func (f *FuncExtent) Coverage(profile *cover.Profile) (int64, int64) {
        // We could avoid making this n^2 overall by doing a single scan and annotating the functions,
        // but the sizes of the data structures is never very large and the scan is almost instantaneous.
        var covered, total int64
        // The blocks are sorted, so we can stop counting as soon as we reach the end of the relevant block.
        for _, b := range profile.Blocks {
                if b.StartLine > f.EndLine || (b.StartLine == f.EndLine && b.StartCol >= f.EndCol) {
                        // Past the end of the function.
                        break
                }
                if b.EndLine < f.StartLine || (b.EndLine == f.StartLine && b.EndCol <= f.StartCol) {
                        // Before the beginning of the function
                        continue
                }
                total += int64(b.NumStmt)
                if b.Count > 0 {
                        covered += int64(b.NumStmt)
                }
        }
        return covered, total
}