Skip to content

Commit

Permalink
Add tracing capability
Browse files Browse the repository at this point in the history
  • Loading branch information
uudashr committed Dec 6, 2024
1 parent 8b1a11f commit d5b98c8
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 30 deletions.
23 changes: 13 additions & 10 deletions cmd/gocognit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func main() {
format string
jsonEncode bool
ignoreExpr string
trace bool
)

flag.IntVar(&over, "over", defaultOverFlagVal, "show functions with complexity > N only")
Expand All @@ -119,6 +120,7 @@ func main() {
flag.StringVar(&format, "f", defaultFormat, "the format to use")
flag.BoolVar(&jsonEncode, "json", false, "encode the output as JSON")
flag.StringVar(&ignoreExpr, "ignore", "", "ignore files matching the given regexp")
flag.BoolVar(&trace, "trace", false, "include trace information of the complexity")

log.SetFlags(0)
log.SetPrefix("gocognit: ")
Expand All @@ -136,7 +138,8 @@ func main() {
log.Fatal(err)
}

stats, err := analyze(args, includeTests)
traceEnabled := trace && jsonEncode
stats, err := analyze(args, includeTests, traceEnabled)
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -170,19 +173,19 @@ func main() {
}
}

func analyzePath(path string, includeTests bool) ([]gocognit.Stat, error) {
func analyzePath(path string, includeTests bool, trace bool) ([]gocognit.Stat, error) {
if isDir(path) {
return analyzeDir(path, includeTests, nil)
return analyzeDir(path, includeTests, nil, trace)
}

return analyzeFile(path, nil)
return analyzeFile(path, nil, trace)
}

func analyze(paths []string, includeTests bool) (stats []gocognit.Stat, err error) {
func analyze(paths []string, includeTests bool, trace bool) (stats []gocognit.Stat, err error) {
var out []gocognit.Stat

for _, path := range paths {
stats, err := analyzePath(path, includeTests)
stats, err := analyzePath(path, includeTests, trace)
if err != nil {
return nil, err
}
Expand All @@ -199,18 +202,18 @@ func isDir(filename string) bool {
return err == nil && fi.IsDir()
}

func analyzeFile(fname string, stats []gocognit.Stat) ([]gocognit.Stat, error) {
func analyzeFile(fname string, stats []gocognit.Stat, trace bool) ([]gocognit.Stat, error) {
fset := token.NewFileSet()

f, err := parser.ParseFile(fset, fname, nil, parser.ParseComments)
if err != nil {
return nil, err
}

return gocognit.ComplexityStats(f, fset, stats), nil
return gocognit.ComplexityStats(f, fset, stats, trace), nil
}

func analyzeDir(dirname string, includeTests bool, stats []gocognit.Stat) ([]gocognit.Stat, error) {
func analyzeDir(dirname string, includeTests bool, stats []gocognit.Stat, trace bool) ([]gocognit.Stat, error) {
err := filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
Expand All @@ -228,7 +231,7 @@ func analyzeDir(dirname string, includeTests bool, stats []gocognit.Stat) ([]goc
return nil
}

stats, err = analyzeFile(path, stats)
stats, err = analyzeFile(path, stats, trace)
if err != nil {
return err
}
Expand Down
100 changes: 80 additions & 20 deletions gocognit.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Stat struct {
PkgName string
FuncName string
Complexity int
Traces []Trace `json:",omitempty"`
Pos token.Position
}

Expand All @@ -23,18 +24,21 @@ func (s Stat) String() string {
}

// ComplexityStats builds the complexity statistics.
func ComplexityStats(f *ast.File, fset *token.FileSet, stats []Stat) []Stat {
func ComplexityStats(f *ast.File, fset *token.FileSet, stats []Stat, includeTrace bool) []Stat {
for _, decl := range f.Decls {
if fn, ok := decl.(*ast.FuncDecl); ok {
d := parseDirective(fn.Doc)
if d.Ignore {
continue
}

res := Scan(fn, includeTrace)

stats = append(stats, Stat{
PkgName: f.Name.Name,
FuncName: funcName(fn),
Complexity: Complexity(fn),
Complexity: res.Complexity,
Traces: res.Traces,
Pos: fset.Position(fn.Pos()),
})
}
Expand Down Expand Up @@ -77,13 +81,44 @@ func funcName(fn *ast.FuncDecl) string {

// Complexity calculates the cognitive complexity of a function.
func Complexity(fn *ast.FuncDecl) int {
res := Scan(fn, false)

return res.Complexity
}

// Scan scans the function declaration.
func Scan(fn *ast.FuncDecl, trace bool) Result {
v := complexityVisitor{
name: fn.Name,
name: fn.Name,
trace: trace,
}

ast.Walk(&v, fn)

return v.complexity
return Result{
Traces: v.traces,
Complexity: v.complexity,
}
}

type Result struct {
Traces []Trace
Complexity int
}

type Trace struct {
Inc int
Nesting int `json:",omitempty"`
Text string
Pos token.Pos
}

func (t Trace) String() string {
if t.Nesting == 0 {
return fmt.Sprintf("+%d", t.Inc)
}

return fmt.Sprintf("+%d (nesting=%d)", t.Inc, t.Nesting)
}

type complexityVisitor struct {
Expand All @@ -92,6 +127,11 @@ type complexityVisitor struct {
nesting int
elseNodes map[ast.Node]bool
calculatedExprs map[ast.Expr]bool

fset *token.FileSet

trace bool
traces []Trace
}

func (v *complexityVisitor) incNesting() {
Expand All @@ -102,12 +142,33 @@ func (v *complexityVisitor) decNesting() {
v.nesting--
}

func (v *complexityVisitor) incComplexity() {
func (v *complexityVisitor) incComplexity(text string, pos token.Pos) {
v.complexity++

if !v.trace {
return
}

v.traces = append(v.traces, Trace{
Inc: 1,
Text: text,
Pos: pos,
})
}

func (v *complexityVisitor) nestIncComplexity() {
func (v *complexityVisitor) nestIncComplexity(text string, pos token.Pos) {
v.complexity += (v.nesting + 1)

if !v.trace {
return
}

v.traces = append(v.traces, Trace{
Inc: v.nesting + 1,
Nesting: v.nesting,
Text: text,
Pos: pos,
})
}

func (v *complexityVisitor) markAsElseNode(n ast.Node) {
Expand Down Expand Up @@ -171,7 +232,7 @@ func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor {
}

func (v *complexityVisitor) visitIfStmt(n *ast.IfStmt) ast.Visitor {
v.incIfComplexity(n)
v.incIfComplexity(n, "if", n.Pos())

if n := n.Init; n != nil {
ast.Walk(v, n)
Expand All @@ -184,7 +245,7 @@ func (v *complexityVisitor) visitIfStmt(n *ast.IfStmt) ast.Visitor {
v.decNesting()

if _, ok := n.Else.(*ast.BlockStmt); ok {
v.incComplexity()
v.incComplexity("else", n.Else.Pos())

ast.Walk(v, n.Else)
} else if _, ok := n.Else.(*ast.IfStmt); ok {
Expand All @@ -196,7 +257,7 @@ func (v *complexityVisitor) visitIfStmt(n *ast.IfStmt) ast.Visitor {
}

func (v *complexityVisitor) visitSwitchStmt(n *ast.SwitchStmt) ast.Visitor {
v.nestIncComplexity()
v.nestIncComplexity("switch", n.Pos())

if n := n.Init; n != nil {
ast.Walk(v, n)
Expand All @@ -214,7 +275,7 @@ func (v *complexityVisitor) visitSwitchStmt(n *ast.SwitchStmt) ast.Visitor {
}

func (v *complexityVisitor) visitTypeSwitchStmt(n *ast.TypeSwitchStmt) ast.Visitor {
v.nestIncComplexity()
v.nestIncComplexity("switch", n.Pos())

if n := n.Init; n != nil {
ast.Walk(v, n)
Expand All @@ -232,7 +293,7 @@ func (v *complexityVisitor) visitTypeSwitchStmt(n *ast.TypeSwitchStmt) ast.Visit
}

func (v *complexityVisitor) visitSelectStmt(n *ast.SelectStmt) ast.Visitor {
v.nestIncComplexity()
v.nestIncComplexity("select", n.Pos())

v.incNesting()
ast.Walk(v, n.Body)
Expand All @@ -242,7 +303,7 @@ func (v *complexityVisitor) visitSelectStmt(n *ast.SelectStmt) ast.Visitor {
}

func (v *complexityVisitor) visitForStmt(n *ast.ForStmt) ast.Visitor {
v.nestIncComplexity()
v.nestIncComplexity("for", n.Pos())

if n := n.Init; n != nil {
ast.Walk(v, n)
Expand All @@ -264,7 +325,7 @@ func (v *complexityVisitor) visitForStmt(n *ast.ForStmt) ast.Visitor {
}

func (v *complexityVisitor) visitRangeStmt(n *ast.RangeStmt) ast.Visitor {
v.nestIncComplexity()
v.nestIncComplexity("for", n.Pos())

if n := n.Key; n != nil {
ast.Walk(v, n)
Expand Down Expand Up @@ -294,7 +355,7 @@ func (v *complexityVisitor) visitFuncLit(n *ast.FuncLit) ast.Visitor {

func (v *complexityVisitor) visitBranchStmt(n *ast.BranchStmt) ast.Visitor {
if n.Label != nil {
v.incComplexity()
v.incComplexity(n.Tok.String(), n.Pos())
}
return v
}
Expand All @@ -306,8 +367,7 @@ func (v *complexityVisitor) visitBinaryExpr(n *ast.BinaryExpr) ast.Visitor {
var lastOp token.Token
for _, op := range ops {
if lastOp != op {
v.incComplexity()

v.incComplexity(op.String(), n.OpPos)
lastOp = op
}
}
Expand All @@ -321,7 +381,7 @@ func (v *complexityVisitor) visitCallExpr(n *ast.CallExpr) ast.Visitor {
obj, name := callIdent.Obj, callIdent.Name
if obj == v.name.Obj && name == v.name.Name {
// called by same function directly (direct recursion)
v.incComplexity()
v.incComplexity(name, n.Pos())
}
}

Expand All @@ -337,11 +397,11 @@ func (v *complexityVisitor) collectBinaryOps(exp ast.Expr) []token.Token {
return nil
}

func (v *complexityVisitor) incIfComplexity(n *ast.IfStmt) {
func (v *complexityVisitor) incIfComplexity(n *ast.IfStmt, text string, pos token.Pos) {
if v.markedAsElseNode(n) {
v.incComplexity()
v.incComplexity(text, pos)
} else {
v.nestIncComplexity()
v.nestIncComplexity(text, pos)
}
}

Expand Down

0 comments on commit d5b98c8

Please sign in to comment.