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, ", ") }