12 "honnef.co/go/tools/analysis/facts"
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/ast/astutil"
21 Fixes []analysis.SuggestedFix
22 Related []analysis.RelatedInformation
25 type Option func(*Options)
27 func ShortRange() Option {
28 return func(opts *Options) {
29 opts.ShortRange = true
33 func FilterGenerated() Option {
34 return func(opts *Options) {
35 opts.FilterGenerated = true
39 func Fixes(fixes ...analysis.SuggestedFix) Option {
40 return func(opts *Options) {
41 opts.Fixes = append(opts.Fixes, fixes...)
45 func Related(node Positioner, message string) Option {
46 return func(opts *Options) {
47 pos, end := getRange(node, opts.ShortRange)
48 r := analysis.RelatedInformation{
53 opts.Related = append(opts.Related, r)
57 type Positioner interface {
61 type fullPositioner interface {
66 type sourcer interface {
70 // shortRange returns the position and end of the main component of an
71 // AST node. For nodes that have no body, the short range is identical
72 // to the node's Pos and End. For nodes that do have a body, the short
73 // range excludes the body.
74 func shortRange(node ast.Node) (pos, end token.Pos) {
75 switch node := node.(type) {
77 return node.Pos(), node.Name.End()
79 return node.Pos(), node.Colon + 1
81 return node.Pos(), node.Colon + 1
83 return node.Pos(), node.Defer + token.Pos(len("defer"))
85 return shortRange(node.X)
88 return node.For, node.Post.End()
89 } else if node.Cond != nil {
90 return node.For, node.Cond.End()
91 } else if node.Init != nil {
92 // +1 to catch the semicolon, for gofmt'ed code
93 return node.Pos(), node.Init.End() + 1
95 return node.Pos(), node.For + token.Pos(len("for"))
98 return node.Pos(), node.Type.End()
100 return node.Pos(), node.Type.End()
102 if _, ok := astutil.Unparen(node.Call.Fun).(*ast.FuncLit); ok {
103 return node.Pos(), node.Go + token.Pos(len("go"))
105 return node.Pos(), node.End()
108 return node.Pos(), node.Cond.End()
110 return node.Pos(), node.X.End()
111 case *ast.SelectStmt:
112 return node.Pos(), node.Pos() + token.Pos(len("select"))
113 case *ast.SwitchStmt:
115 return node.Pos(), node.Tag.End()
116 } else if node.Init != nil {
117 // +1 to catch the semicolon, for gofmt'ed code
118 return node.Pos(), node.Init.End() + 1
120 return node.Pos(), node.Pos() + token.Pos(len("switch"))
122 case *ast.TypeSwitchStmt:
123 return node.Pos(), node.Assign.End()
125 return node.Pos(), node.End()
129 func getRange(node Positioner, short bool) (pos, end token.Pos) {
130 switch node := node.(type) {
136 return s.Pos(), s.End()
139 return shortRange(node)
141 return node.Pos(), node.End()
143 return node.Pos(), token.NoPos
147 func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) {
149 for _, opt := range opts {
153 file := DisplayPosition(pass.Fset, node.Pos()).Filename
154 if cfg.FilterGenerated {
155 m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
156 if _, ok := m[file]; ok {
161 pos, end := getRange(node, cfg.ShortRange)
162 d := analysis.Diagnostic{
166 SuggestedFixes: cfg.Fixes,
167 Related: cfg.Related,
172 func Render(pass *analysis.Pass, x interface{}) string {
174 if err := printer.Fprint(&buf, pass.Fset, x); err != nil {
180 func RenderArgs(pass *analysis.Pass, args []ast.Expr) string {
182 for _, arg := range args {
183 ss = append(ss, Render(pass, arg))
185 return strings.Join(ss, ", ")
188 func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
189 if p == token.NoPos {
190 return token.Position{}
193 // Only use the adjusted position if it points to another Go file.
194 // This means we'll point to the original file for cgo files, but
195 // we won't point to a YACC grammar file.
196 pos := fset.PositionFor(p, false)
197 adjPos := fset.PositionFor(p, true)
199 if filepath.Ext(adjPos.Filename) == ".go" {
206 func Ordinal(n int) string {
208 if n < 10 || n > 20 {
223 return strconv.Itoa(n) + suffix