435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
185 lines
4.2 KiB
Go
185 lines
4.2 KiB
Go
package report
|
|
|
|
import (
|
|
"bytes"
|
|
"go/ast"
|
|
"go/printer"
|
|
"go/token"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
"golang.org/x/tools/go/ast/astutil"
|
|
"honnef.co/go/tools/facts"
|
|
"honnef.co/go/tools/lint"
|
|
)
|
|
|
|
type Options struct {
|
|
ShortRange bool
|
|
FilterGenerated bool
|
|
Fixes []analysis.SuggestedFix
|
|
Related []analysis.RelatedInformation
|
|
}
|
|
|
|
type Option func(*Options)
|
|
|
|
func ShortRange() Option {
|
|
return func(opts *Options) {
|
|
opts.ShortRange = true
|
|
}
|
|
}
|
|
|
|
func FilterGenerated() Option {
|
|
return func(opts *Options) {
|
|
opts.FilterGenerated = true
|
|
}
|
|
}
|
|
|
|
func Fixes(fixes ...analysis.SuggestedFix) Option {
|
|
return func(opts *Options) {
|
|
opts.Fixes = append(opts.Fixes, fixes...)
|
|
}
|
|
}
|
|
|
|
func Related(node Positioner, message string) Option {
|
|
return func(opts *Options) {
|
|
pos, end := getRange(node, opts.ShortRange)
|
|
r := analysis.RelatedInformation{
|
|
Pos: pos,
|
|
End: end,
|
|
Message: message,
|
|
}
|
|
opts.Related = append(opts.Related, r)
|
|
}
|
|
}
|
|
|
|
type Positioner interface {
|
|
Pos() token.Pos
|
|
}
|
|
|
|
type fullPositioner interface {
|
|
Pos() token.Pos
|
|
End() token.Pos
|
|
}
|
|
|
|
type sourcer interface {
|
|
Source() ast.Node
|
|
}
|
|
|
|
// shortRange returns the position and end of the main component of an
|
|
// AST node. For nodes that have no body, the short range is identical
|
|
// to the node's Pos and End. For nodes that do have a body, the short
|
|
// range excludes the body.
|
|
func shortRange(node ast.Node) (pos, end token.Pos) {
|
|
switch node := node.(type) {
|
|
case *ast.File:
|
|
return node.Pos(), node.Name.End()
|
|
case *ast.CaseClause:
|
|
return node.Pos(), node.Colon + 1
|
|
case *ast.CommClause:
|
|
return node.Pos(), node.Colon + 1
|
|
case *ast.DeferStmt:
|
|
return node.Pos(), node.Defer + token.Pos(len("defer"))
|
|
case *ast.ExprStmt:
|
|
return shortRange(node.X)
|
|
case *ast.ForStmt:
|
|
if node.Post != nil {
|
|
return node.For, node.Post.End()
|
|
} else if node.Cond != nil {
|
|
return node.For, node.Cond.End()
|
|
} else if node.Init != nil {
|
|
// +1 to catch the semicolon, for gofmt'ed code
|
|
return node.Pos(), node.Init.End() + 1
|
|
} else {
|
|
return node.Pos(), node.For + token.Pos(len("for"))
|
|
}
|
|
case *ast.FuncDecl:
|
|
return node.Pos(), node.Type.End()
|
|
case *ast.FuncLit:
|
|
return node.Pos(), node.Type.End()
|
|
case *ast.GoStmt:
|
|
if _, ok := astutil.Unparen(node.Call.Fun).(*ast.FuncLit); ok {
|
|
return node.Pos(), node.Go + token.Pos(len("go"))
|
|
} else {
|
|
return node.Pos(), node.End()
|
|
}
|
|
case *ast.IfStmt:
|
|
return node.Pos(), node.Cond.End()
|
|
case *ast.RangeStmt:
|
|
return node.Pos(), node.X.End()
|
|
case *ast.SelectStmt:
|
|
return node.Pos(), node.Pos() + token.Pos(len("select"))
|
|
case *ast.SwitchStmt:
|
|
if node.Tag != nil {
|
|
return node.Pos(), node.Tag.End()
|
|
} else if node.Init != nil {
|
|
// +1 to catch the semicolon, for gofmt'ed code
|
|
return node.Pos(), node.Init.End() + 1
|
|
} else {
|
|
return node.Pos(), node.Pos() + token.Pos(len("switch"))
|
|
}
|
|
case *ast.TypeSwitchStmt:
|
|
return node.Pos(), node.Assign.End()
|
|
default:
|
|
return node.Pos(), node.End()
|
|
}
|
|
}
|
|
|
|
func getRange(node Positioner, short bool) (pos, end token.Pos) {
|
|
switch node := node.(type) {
|
|
case sourcer:
|
|
s := node.Source()
|
|
if short {
|
|
return shortRange(s)
|
|
}
|
|
return s.Pos(), s.End()
|
|
case fullPositioner:
|
|
if short {
|
|
return shortRange(node)
|
|
}
|
|
return node.Pos(), node.End()
|
|
default:
|
|
return node.Pos(), token.NoPos
|
|
}
|
|
}
|
|
|
|
func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) {
|
|
cfg := &Options{}
|
|
for _, opt := range opts {
|
|
opt(cfg)
|
|
}
|
|
|
|
file := lint.DisplayPosition(pass.Fset, node.Pos()).Filename
|
|
if cfg.FilterGenerated {
|
|
m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
|
|
if _, ok := m[file]; ok {
|
|
return
|
|
}
|
|
}
|
|
|
|
pos, end := getRange(node, cfg.ShortRange)
|
|
d := analysis.Diagnostic{
|
|
Pos: pos,
|
|
End: end,
|
|
Message: message,
|
|
SuggestedFixes: cfg.Fixes,
|
|
Related: cfg.Related,
|
|
}
|
|
pass.Report(d)
|
|
}
|
|
|
|
func Render(pass *analysis.Pass, x interface{}) string {
|
|
var buf bytes.Buffer
|
|
if err := printer.Fprint(&buf, pass.Fset, x); err != nil {
|
|
panic(err)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func RenderArgs(pass *analysis.Pass, args []ast.Expr) string {
|
|
var ss []string
|
|
for _, arg := range args {
|
|
ss = append(ss, Render(pass, arg))
|
|
}
|
|
return strings.Join(ss, ", ")
|
|
}
|