woodpecker/vendor/github.com/fzipp/gocyclo/analyze.go
6543 56a854fe14
Update deps (#789)
* update github.com/docker/cli

* update github.com/docker/distribution

* update github.com/docker/docker

* update github.com/gin-gonic/gin

* update github.com/golang-jwt/jwt/v4

* update github.com/golangci/golangci-lint

* update github.com/gorilla/securecookie

* update github.com/mattn/go-sqlite3

* update github.com/moby/moby

* update github.com/prometheus/client_golang

* update github.com/xanzy/go-gitlab
2022-02-24 17:33:24 +01:00

140 lines
3.5 KiB
Go

// Copyright 2020 Frederik Zipp. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gocyclo
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"log"
"os"
"path/filepath"
"regexp"
"strings"
)
// Analyze calculates the cyclomatic complexities of the functions and methods
// in the Go source code files in the given paths. If a path is a directory
// all Go files under that directory are analyzed recursively.
// Files with paths matching the 'ignore' regular expressions are skipped.
// The 'ignore' parameter can be nil, meaning that no files are skipped.
func Analyze(paths []string, ignore *regexp.Regexp) Stats {
var stats Stats
for _, path := range paths {
info, err := os.Stat(path)
if err != nil {
log.Printf("could not get file info for path %q: %s\n", path, err)
continue
}
if info.IsDir() {
stats = analyzeDir(path, ignore, stats)
} else {
stats = analyzeFile(path, ignore, stats)
}
}
return stats
}
func analyzeDir(dirname string, ignore *regexp.Regexp, stats Stats) Stats {
filepath.WalkDir(dirname, func(path string, entry fs.DirEntry, err error) error {
if err == nil && isGoFile(entry) {
stats = analyzeFile(path, ignore, stats)
}
return err
})
return stats
}
func isGoFile(entry fs.DirEntry) bool {
return !entry.IsDir() && strings.HasSuffix(entry.Name(), ".go")
}
func analyzeFile(path string, ignore *regexp.Regexp, stats Stats) Stats {
if isIgnored(path, ignore) {
return stats
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
return AnalyzeASTFile(f, fset, stats)
}
func isIgnored(path string, ignore *regexp.Regexp) bool {
return ignore != nil && ignore.MatchString(path)
}
// AnalyzeASTFile calculates the cyclomatic complexities of the functions
// and methods in the abstract syntax tree (AST) of a parsed Go file and
// appends the results to the given Stats slice.
func AnalyzeASTFile(f *ast.File, fs *token.FileSet, s Stats) Stats {
analyzer := &fileAnalyzer{
file: f,
fileSet: fs,
stats: s,
}
return analyzer.analyze()
}
type fileAnalyzer struct {
file *ast.File
fileSet *token.FileSet
stats Stats
}
func (a *fileAnalyzer) analyze() Stats {
for _, decl := range a.file.Decls {
a.analyzeDecl(decl)
}
return a.stats
}
func (a *fileAnalyzer) analyzeDecl(d ast.Decl) {
switch decl := d.(type) {
case *ast.FuncDecl:
a.addStatIfNotIgnored(decl, funcName(decl), decl.Doc)
case *ast.GenDecl:
for _, spec := range decl.Specs {
valueSpec, ok := spec.(*ast.ValueSpec)
if !ok {
continue
}
for _, value := range valueSpec.Values {
funcLit, ok := value.(*ast.FuncLit)
if !ok {
continue
}
a.addStatIfNotIgnored(funcLit, valueSpec.Names[0].Name, decl.Doc)
}
}
}
}
func (a *fileAnalyzer) addStatIfNotIgnored(node ast.Node, funcName string, doc *ast.CommentGroup) {
if parseDirectives(doc).HasIgnore() {
return
}
a.stats = append(a.stats, Stat{
PkgName: a.file.Name.Name,
FuncName: funcName,
Complexity: Complexity(node),
Pos: a.fileSet.Position(node.Pos()),
})
}
// funcName returns the name representation of a function or method:
// "(Type).Name" for methods or simply "Name" for functions.
func funcName(fn *ast.FuncDecl) string {
if fn.Recv != nil {
if fn.Recv.NumFields() > 0 {
typ := fn.Recv.List[0].Type
return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name)
}
}
return fn.Name.Name
}