github.com/pankona/gometalinter@v2.0.11+incompatible/_linters/src/honnef.co/go/tools/lint/lintutil/util.go (about) 1 // Copyright (c) 2013 The Go Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file or at 5 // https://developers.google.com/open-source/licenses/bsd. 6 7 // Package lintutil provides helpers for writing linter command lines. 8 package lintutil // import "honnef.co/go/tools/lint/lintutil" 9 10 import ( 11 "encoding/json" 12 "errors" 13 "flag" 14 "fmt" 15 "go/build" 16 "go/parser" 17 "go/token" 18 "go/types" 19 "io" 20 "os" 21 "path/filepath" 22 "strconv" 23 "strings" 24 25 "honnef.co/go/tools/lint" 26 "honnef.co/go/tools/version" 27 28 "github.com/kisielk/gotool" 29 "golang.org/x/tools/go/loader" 30 ) 31 32 type OutputFormatter interface { 33 Format(p lint.Problem) 34 } 35 36 type TextOutput struct { 37 w io.Writer 38 } 39 40 func (o TextOutput) Format(p lint.Problem) { 41 fmt.Fprintf(o.w, "%v: %s\n", relativePositionString(p.Position), p.String()) 42 } 43 44 type JSONOutput struct { 45 w io.Writer 46 } 47 48 func (o JSONOutput) Format(p lint.Problem) { 49 type location struct { 50 File string `json:"file"` 51 Line int `json:"line"` 52 Column int `json:"column"` 53 } 54 jp := struct { 55 Checker string `json:"checker"` 56 Code string `json:"code"` 57 Severity string `json:"severity,omitempty"` 58 Location location `json:"location"` 59 Message string `json:"message"` 60 Ignored bool `json:"ignored"` 61 }{ 62 p.Checker, 63 p.Check, 64 "", // TODO(dh): support severity 65 location{ 66 p.Position.Filename, 67 p.Position.Line, 68 p.Position.Column, 69 }, 70 p.Text, 71 p.Ignored, 72 } 73 _ = json.NewEncoder(o.w).Encode(jp) 74 } 75 func usage(name string, flags *flag.FlagSet) func() { 76 return func() { 77 fmt.Fprintf(os.Stderr, "Usage of %s:\n", name) 78 fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name) 79 fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name) 80 fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name) 81 fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name) 82 fmt.Fprintf(os.Stderr, "Flags:\n") 83 flags.PrintDefaults() 84 } 85 } 86 87 type runner struct { 88 checker lint.Checker 89 tags []string 90 ignores []lint.Ignore 91 version int 92 returnIgnored bool 93 } 94 95 func resolveRelative(importPaths []string, tags []string) (goFiles bool, err error) { 96 if len(importPaths) == 0 { 97 return false, nil 98 } 99 if strings.HasSuffix(importPaths[0], ".go") { 100 // User is specifying a package in terms of .go files, don't resolve 101 return true, nil 102 } 103 wd, err := os.Getwd() 104 if err != nil { 105 return false, err 106 } 107 ctx := build.Default 108 ctx.BuildTags = tags 109 for i, path := range importPaths { 110 bpkg, err := ctx.Import(path, wd, build.FindOnly) 111 if err != nil { 112 return false, fmt.Errorf("can't load package %q: %v", path, err) 113 } 114 importPaths[i] = bpkg.ImportPath 115 } 116 return false, nil 117 } 118 119 func parseIgnore(s string) ([]lint.Ignore, error) { 120 var out []lint.Ignore 121 if len(s) == 0 { 122 return nil, nil 123 } 124 for _, part := range strings.Fields(s) { 125 p := strings.Split(part, ":") 126 if len(p) != 2 { 127 return nil, errors.New("malformed ignore string") 128 } 129 path := p[0] 130 checks := strings.Split(p[1], ",") 131 out = append(out, &lint.GlobIgnore{Pattern: path, Checks: checks}) 132 } 133 return out, nil 134 } 135 136 type versionFlag int 137 138 func (v *versionFlag) String() string { 139 return fmt.Sprintf("1.%d", *v) 140 } 141 142 func (v *versionFlag) Set(s string) error { 143 if len(s) < 3 { 144 return errors.New("invalid Go version") 145 } 146 if s[0] != '1' { 147 return errors.New("invalid Go version") 148 } 149 if s[1] != '.' { 150 return errors.New("invalid Go version") 151 } 152 i, err := strconv.Atoi(s[2:]) 153 *v = versionFlag(i) 154 return err 155 } 156 157 func (v *versionFlag) Get() interface{} { 158 return int(*v) 159 } 160 161 func FlagSet(name string) *flag.FlagSet { 162 flags := flag.NewFlagSet("", flag.ExitOnError) 163 flags.Usage = usage(name, flags) 164 flags.Float64("min_confidence", 0, "Deprecated; use -ignore instead") 165 flags.String("tags", "", "List of `build tags`") 166 flags.String("ignore", "", "Space separated list of checks to ignore, in the following format: 'import/path/file.go:Check1,Check2,...' Both the import path and file name sections support globbing, e.g. 'os/exec/*_test.go'") 167 flags.Bool("tests", true, "Include tests") 168 flags.Bool("version", false, "Print version and exit") 169 flags.Bool("show-ignored", false, "Don't filter ignored problems") 170 flags.String("f", "text", "Output `format` (valid choices are 'text' and 'json')") 171 172 tags := build.Default.ReleaseTags 173 v := tags[len(tags)-1][2:] 174 version := new(versionFlag) 175 if err := version.Set(v); err != nil { 176 panic(fmt.Sprintf("internal error: %s", err)) 177 } 178 179 flags.Var(version, "go", "Target Go `version` in the format '1.x'") 180 return flags 181 } 182 183 type CheckerConfig struct { 184 Checker lint.Checker 185 ExitNonZero bool 186 } 187 188 func ProcessFlagSet(confs []CheckerConfig, fs *flag.FlagSet) { 189 tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string) 190 ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string) 191 tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool) 192 goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int) 193 format := fs.Lookup("f").Value.(flag.Getter).Get().(string) 194 printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool) 195 showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool) 196 197 if printVersion { 198 version.Print() 199 os.Exit(0) 200 } 201 202 var cs []lint.Checker 203 for _, conf := range confs { 204 cs = append(cs, conf.Checker) 205 } 206 pss, err := Lint(cs, fs.Args(), &Options{ 207 Tags: strings.Fields(tags), 208 LintTests: tests, 209 Ignores: ignore, 210 GoVersion: goVersion, 211 ReturnIgnored: showIgnored, 212 }) 213 if err != nil { 214 fmt.Fprintln(os.Stderr, err) 215 os.Exit(1) 216 } 217 218 var ps []lint.Problem 219 for _, p := range pss { 220 ps = append(ps, p...) 221 } 222 223 var f OutputFormatter 224 switch format { 225 case "text": 226 f = TextOutput{os.Stdout} 227 case "json": 228 f = JSONOutput{os.Stdout} 229 default: 230 fmt.Fprintf(os.Stderr, "unsupported output format %q\n", format) 231 os.Exit(2) 232 } 233 234 for _, p := range ps { 235 f.Format(p) 236 } 237 for i, p := range pss { 238 if len(p) != 0 && confs[i].ExitNonZero { 239 os.Exit(1) 240 } 241 } 242 } 243 244 type Options struct { 245 Tags []string 246 LintTests bool 247 Ignores string 248 GoVersion int 249 ReturnIgnored bool 250 } 251 252 func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, error) { 253 if opt == nil { 254 opt = &Options{} 255 } 256 ignores, err := parseIgnore(opt.Ignores) 257 if err != nil { 258 return nil, err 259 } 260 paths := gotool.ImportPaths(pkgs) 261 goFiles, err := resolveRelative(paths, opt.Tags) 262 if err != nil { 263 return nil, err 264 } 265 ctx := build.Default 266 ctx.BuildTags = opt.Tags 267 hadError := false 268 conf := &loader.Config{ 269 Build: &ctx, 270 ParserMode: parser.ParseComments, 271 ImportPkgs: map[string]bool{}, 272 TypeChecker: types.Config{ 273 Error: func(err error) { 274 // Only print the first error found 275 if hadError { 276 return 277 } 278 hadError = true 279 fmt.Fprintln(os.Stderr, err) 280 }, 281 }, 282 } 283 if goFiles { 284 conf.CreateFromFilenames("adhoc", paths...) 285 } else { 286 for _, path := range paths { 287 conf.ImportPkgs[path] = opt.LintTests 288 } 289 } 290 lprog, err := conf.Load() 291 if err != nil { 292 return nil, err 293 } 294 295 var problems [][]lint.Problem 296 for _, c := range cs { 297 runner := &runner{ 298 checker: c, 299 tags: opt.Tags, 300 ignores: ignores, 301 version: opt.GoVersion, 302 returnIgnored: opt.ReturnIgnored, 303 } 304 problems = append(problems, runner.lint(lprog, conf)) 305 } 306 return problems, nil 307 } 308 309 func shortPath(path string) string { 310 cwd, err := os.Getwd() 311 if err != nil { 312 return path 313 } 314 if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) { 315 return rel 316 } 317 return path 318 } 319 320 func relativePositionString(pos token.Position) string { 321 s := shortPath(pos.Filename) 322 if pos.IsValid() { 323 if s != "" { 324 s += ":" 325 } 326 s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) 327 } 328 if s == "" { 329 s = "-" 330 } 331 return s 332 } 333 334 func ProcessArgs(name string, cs []CheckerConfig, args []string) { 335 flags := FlagSet(name) 336 flags.Parse(args) 337 338 ProcessFlagSet(cs, flags) 339 } 340 341 func (runner *runner) lint(lprog *loader.Program, conf *loader.Config) []lint.Problem { 342 l := &lint.Linter{ 343 Checker: runner.checker, 344 Ignores: runner.ignores, 345 GoVersion: runner.version, 346 ReturnIgnored: runner.returnIgnored, 347 } 348 return l.Lint(lprog, conf) 349 }