10 "golang.org/x/tools/go/analysis"
11 "golang.org/x/tools/go/ast/astutil"
12 "honnef.co/go/tools/facts"
13 "honnef.co/go/tools/lint"
19 Fixes []analysis.SuggestedFix
20 Related []analysis.RelatedInformation
23 type Option func(*Options)
25 func ShortRange() Option {
26 return func(opts *Options) {
27 opts.ShortRange = true
31 func FilterGenerated() Option {
32 return func(opts *Options) {
33 opts.FilterGenerated = true
37 func Fixes(fixes ...analysis.SuggestedFix) Option {
38 return func(opts *Options) {
39 opts.Fixes = append(opts.Fixes, fixes...)
43 func Related(node Positioner, message string) Option {
44 return func(opts *Options) {
45 pos, end := getRange(node, opts.ShortRange)
46 r := analysis.RelatedInformation{
51 opts.Related = append(opts.Related, r)
55 type Positioner interface {
59 type fullPositioner interface {
64 type sourcer interface {
68 // shortRange returns the position and end of the main component of an
69 // AST node. For nodes that have no body, the short range is identical
70 // to the node's Pos and End. For nodes that do have a body, the short
71 // range excludes the body.
72 func shortRange(node ast.Node) (pos, end token.Pos) {
73 switch node := node.(type) {
75 return node.Pos(), node.Name.End()
77 return node.Pos(), node.Colon + 1
79 return node.Pos(), node.Colon + 1
81 return node.Pos(), node.Defer + token.Pos(len("defer"))
83 return shortRange(node.X)
86 return node.For, node.Post.End()
87 } else if node.Cond != nil {
88 return node.For, node.Cond.End()
89 } else if node.Init != nil {
90 // +1 to catch the semicolon, for gofmt'ed code
91 return node.Pos(), node.Init.End() + 1
93 return node.Pos(), node.For + token.Pos(len("for"))
96 return node.Pos(), node.Type.End()
98 return node.Pos(), node.Type.End()
100 if _, ok := astutil.Unparen(node.Call.Fun).(*ast.FuncLit); ok {
101 return node.Pos(), node.Go + token.Pos(len("go"))
103 return node.Pos(), node.End()
106 return node.Pos(), node.Cond.End()
108 return node.Pos(), node.X.End()
109 case *ast.SelectStmt:
110 return node.Pos(), node.Pos() + token.Pos(len("select"))
111 case *ast.SwitchStmt:
113 return node.Pos(), node.Tag.End()
114 } else if node.Init != nil {
115 // +1 to catch the semicolon, for gofmt'ed code
116 return node.Pos(), node.Init.End() + 1
118 return node.Pos(), node.Pos() + token.Pos(len("switch"))
120 case *ast.TypeSwitchStmt:
121 return node.Pos(), node.Assign.End()
123 return node.Pos(), node.End()
127 func getRange(node Positioner, short bool) (pos, end token.Pos) {
128 switch node := node.(type) {
134 return s.Pos(), s.End()
137 return shortRange(node)
139 return node.Pos(), node.End()
141 return node.Pos(), token.NoPos
145 func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) {
147 for _, opt := range opts {
151 file := lint.DisplayPosition(pass.Fset, node.Pos()).Filename
152 if cfg.FilterGenerated {
153 m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
154 if _, ok := m[file]; ok {
159 pos, end := getRange(node, cfg.ShortRange)
160 d := analysis.Diagnostic{
164 SuggestedFixes: cfg.Fixes,
165 Related: cfg.Related,
170 func Render(pass *analysis.Pass, x interface{}) string {
172 if err := printer.Fprint(&buf, pass.Fset, x); err != nil {
178 func RenderArgs(pass *analysis.Pass, args []ast.Expr) string {
180 for _, arg := range args {
181 ss = append(ss, Render(pass, arg))
183 return strings.Join(ss, ", ")