176 lines
4.2 KiB
Go
176 lines
4.2 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"go/ast"
|
||
|
"go/token"
|
||
|
"go/types"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"golang.org/x/tools/go/loader"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
recursivelyIgnoredPkgs arrayFlag
|
||
|
ignoredPkgs arrayFlag
|
||
|
ignoredFiles arrayFlag
|
||
|
allowedFinishers arrayFlag = []string{"Msg", "Msgf"}
|
||
|
rootPkg string
|
||
|
)
|
||
|
|
||
|
// parse input flags and args
|
||
|
func init() {
|
||
|
flag.Var(&recursivelyIgnoredPkgs, "ignorePkgRecursively", "ignore the specified package and all subpackages recursively")
|
||
|
flag.Var(&ignoredPkgs, "ignorePkg", "ignore the specified package")
|
||
|
flag.Var(&ignoredFiles, "ignoreFile", "ignore the specified file by its path and/or go path (package/file.go)")
|
||
|
flag.Var(&allowedFinishers, "finisher", "allowed finisher for the event chain")
|
||
|
flag.Parse()
|
||
|
|
||
|
// add zerolog to recursively ignored packages
|
||
|
recursivelyIgnoredPkgs = append(recursivelyIgnoredPkgs, "github.com/rs/zerolog")
|
||
|
args := flag.Args()
|
||
|
if len(args) != 1 {
|
||
|
fmt.Fprintln(os.Stderr, "you must provide exactly one package path")
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
rootPkg = args[0]
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
// load the package and all its dependencies
|
||
|
conf := loader.Config{}
|
||
|
conf.Import(rootPkg)
|
||
|
p, err := conf.Load()
|
||
|
if err != nil {
|
||
|
fmt.Fprintf(os.Stderr, "Error: unable to load the root package. %s\n", err.Error())
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
// get the github.com/rs/zerolog.Event type
|
||
|
event := getEvent(p)
|
||
|
if event == nil {
|
||
|
fmt.Fprintln(os.Stderr, "Error: github.com/rs/zerolog.Event declaration not found, maybe zerolog is not imported in the scanned package?")
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
// get all selections (function calls) with the github.com/rs/zerolog.Event (or pointer) receiver
|
||
|
selections := getSelectionsWithReceiverType(p, event)
|
||
|
|
||
|
// print the violations (if any)
|
||
|
hasViolations := false
|
||
|
for _, s := range selections {
|
||
|
if hasBadFinisher(p, s) {
|
||
|
hasViolations = true
|
||
|
fmt.Printf("Error: missing or bad finisher for log chain, last call: %q at: %s:%v\n", s.fn.Name(), p.Fset.File(s.Pos()).Name(), p.Fset.Position(s.Pos()).Line)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if no violations detected, return normally
|
||
|
if !hasViolations {
|
||
|
fmt.Println("No violations found")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// if violations were detected, return error code
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
func getEvent(p *loader.Program) types.Type {
|
||
|
for _, pkg := range p.AllPackages {
|
||
|
if strings.HasSuffix(pkg.Pkg.Path(), "github.com/rs/zerolog") {
|
||
|
for _, d := range pkg.Defs {
|
||
|
if d != nil && d.Name() == "Event" {
|
||
|
return d.Type()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func getSelectionsWithReceiverType(p *loader.Program, targetType types.Type) map[token.Pos]selection {
|
||
|
selections := map[token.Pos]selection{}
|
||
|
|
||
|
for _, z := range p.AllPackages {
|
||
|
for i, t := range z.Selections {
|
||
|
switch o := t.Obj().(type) {
|
||
|
case *types.Func:
|
||
|
// this is not a bug, o.Type() is always *types.Signature, see docs
|
||
|
if vt := o.Type().(*types.Signature).Recv(); vt != nil {
|
||
|
typ := vt.Type()
|
||
|
if pointer, ok := typ.(*types.Pointer); ok {
|
||
|
typ = pointer.Elem()
|
||
|
}
|
||
|
|
||
|
if typ == targetType {
|
||
|
if s, ok := selections[i.Pos()]; !ok || i.End() > s.End() {
|
||
|
selections[i.Pos()] = selection{i, o, z.Pkg}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
// skip
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return selections
|
||
|
}
|
||
|
|
||
|
func hasBadFinisher(p *loader.Program, s selection) bool {
|
||
|
pkgPath := strings.TrimPrefix(s.pkg.Path(), rootPkg+"/vendor/")
|
||
|
absoluteFilePath := strings.TrimPrefix(p.Fset.File(s.Pos()).Name(), rootPkg+"/vendor/")
|
||
|
goFilePath := pkgPath + "/" + filepath.Base(p.Fset.Position(s.Pos()).Filename)
|
||
|
|
||
|
for _, f := range allowedFinishers {
|
||
|
if f == s.fn.Name() {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, ignoredPkg := range recursivelyIgnoredPkgs {
|
||
|
if strings.HasPrefix(pkgPath, ignoredPkg) {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, ignoredPkg := range ignoredPkgs {
|
||
|
if pkgPath == ignoredPkg {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, ignoredFile := range ignoredFiles {
|
||
|
if absoluteFilePath == ignoredFile {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if goFilePath == ignoredFile {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
type arrayFlag []string
|
||
|
|
||
|
func (i *arrayFlag) String() string {
|
||
|
return fmt.Sprintf("%v", []string(*i))
|
||
|
}
|
||
|
|
||
|
func (i *arrayFlag) Set(value string) error {
|
||
|
*i = append(*i, value)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type selection struct {
|
||
|
*ast.SelectorExpr
|
||
|
fn *types.Func
|
||
|
pkg *types.Package
|
||
|
}
|