text: // Copyright (c) 2013 The Go Authors. All rights reserved.
  //
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file or at
  // https://developers.google.com/open-source/licenses/bsd.
  
  // Package lintutil provides helpers for writing linter command lines.
  package lintutil // import "github.com/golangci/go-tools/lint/lintutil"
  
  import (
  	"errors"
  	"flag"
  	"fmt"
  	"go/build"
  	"go/token"
  	"log"
  	"os"
  	"regexp"
  	"runtime"
  	"runtime/pprof"
  	"strconv"
  	"strings"
  	"time"
  
  	"github.com/golangci/go-tools/config"
  	"github.com/golangci/go-tools/lint"
  	"github.com/golangci/go-tools/lint/lintutil/format"
  	"github.com/golangci/go-tools/version"
  
  	"golang.org/x/tools/go/packages"
  )
  
  func usage(name string, flags *flag.FlagSet) func() {
  	return func() {
  		fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
  		fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name)
  		fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name)
  		fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name)
  		fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name)
  		fmt.Fprintf(os.Stderr, "Flags:\n")
  		flags.PrintDefaults()
  	}
  }
  
  func parseIgnore(s string) ([]lint.Ignore, error) {
  	var out []lint.Ignore
  	if len(s) == 0 {
  		return nil, nil
  	}
  	for _, part := range strings.Fields(s) {
  		p := strings.Split(part, ":")
  		if len(p) != 2 {
  			return nil, errors.New("malformed ignore string")
  		}
  		path := p[0]
  		checks := strings.Split(p[1], ",")
  		out = append(out, &lint.GlobIgnore{Pattern: path, Checks: checks})
  	}
  	return out, nil
  }
  
  type versionFlag int
  
  func (v *versionFlag) String() string {
  	return fmt.Sprintf("1.%d", *v)
  }
  
  func (v *versionFlag) Set(s string) error {
  	if len(s) < 3 {
  		return errors.New("invalid Go version")
  	}
  	if s[0] != '1' {
  		return errors.New("invalid Go version")
  	}
  	if s[1] != '.' {
  		return errors.New("invalid Go version")
  	}
  	i, err := strconv.Atoi(s[2:])
  	*v = versionFlag(i)
  	return err
  }
  
  func (v *versionFlag) Get() interface{} {
  	return int(*v)
  }
  
  type list []string
  
  func (list *list) String() string {
  	return `"` + strings.Join(*list, ",") + `"`
  }
  
  func (list *list) Set(s string) error {
  	if s == "" {
  		*list = nil
  		return nil
  	}
  
  	*list = strings.Split(s, ",")
  	return nil
  }
  
  func FlagSet(name string) *flag.FlagSet {
  	flags := flag.NewFlagSet("", flag.ExitOnError)
  	flags.Usage = usage(name, flags)
  	flags.String("tags", "", "List of `build tags`")
  	flags.String("ignore", "", "Deprecated: use linter directives instead")
  	flags.Bool("tests", true, "Include tests")
  	flags.Bool("version", false, "Print version and exit")
  	flags.Bool("show-ignored", false, "Don't filter ignored problems")
  	flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
  
  	flags.Int("debug.max-concurrent-jobs", 0, "Number of jobs to run concurrently")
  	flags.Bool("debug.print-stats", false, "Print debug statistics")
  	flags.String("debug.cpuprofile", "", "Write CPU profile to `file`")
  	flags.String("debug.memprofile", "", "Write memory profile to `file`")
  
  	checks := list{"inherit"}
  	fail := list{"all"}
  	flags.Var(&checks, "checks", "Comma-separated list of `checks` to enable.")
  	flags.Var(&fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.")
  
  	tags := build.Default.ReleaseTags
  	v := tags[len(tags)-1][2:]
  	version := new(versionFlag)
  	if err := version.Set(v); err != nil {
  		panic(fmt.Sprintf("internal error: %s", err))
  	}
  
  	flags.Var(version, "go", "Target Go `version` in the format '1.x'")
  	return flags
  }
  
  func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
  	tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
  	ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
  	tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
  	goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
  	formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
  	printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
  	showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
  
  	maxConcurrentJobs := fs.Lookup("debug.max-concurrent-jobs").Value.(flag.Getter).Get().(int)
  	printStats := fs.Lookup("debug.print-stats").Value.(flag.Getter).Get().(bool)
  	cpuProfile := fs.Lookup("debug.cpuprofile").Value.(flag.Getter).Get().(string)
  	memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string)
  
  	cfg := config.Config{}
  	cfg.Checks = *fs.Lookup("checks").Value.(*list)
  
  	exit := func(code int) {
  		if cpuProfile != "" {
  			pprof.StopCPUProfile()
  		}
  		if memProfile != "" {
  			f, err := os.Create(memProfile)
  			if err != nil {
  				panic(err)
  			}
  			runtime.GC()
  			pprof.WriteHeapProfile(f)
  		}
  		os.Exit(code)
  	}
  	if cpuProfile != "" {
  		f, err := os.Create(cpuProfile)
  		if err != nil {
  			log.Fatal(err)
  		}
  		pprof.StartCPUProfile(f)
  	}
  
  	if printVersion {
  		version.Print()
  		exit(0)
  	}
  
  	ps, err := Lint(cs, fs.Args(), &Options{
  		Tags:          strings.Fields(tags),
  		LintTests:     tests,
  		Ignores:       ignore,
  		GoVersion:     goVersion,
  		ReturnIgnored: showIgnored,
  		Config:        cfg,
  
  		MaxConcurrentJobs: maxConcurrentJobs,
  		PrintStats:        printStats,
  	})
  	if err != nil {
  		fmt.Fprintln(os.Stderr, err)
  		exit(1)
  	}
  
  	var f format.Formatter
  	switch formatter {
  	case "text":
  		f = format.Text{W: os.Stdout}
  	case "stylish":
  		f = &format.Stylish{W: os.Stdout}
  	case "json":
  		f = format.JSON{W: os.Stdout}
  	default:
  		fmt.Fprintf(os.Stderr, "unsupported output format %q\n", formatter)
  		exit(2)
  	}
  
  	var (
  		total    int
  		errors   int
  		warnings int
  	)
  
  	fail := *fs.Lookup("fail").Value.(*list)
  	var allChecks []string
  	for _, p := range ps {
  		allChecks = append(allChecks, p.Check)
  	}
  
  	shouldExit := lint.FilterChecks(allChecks, fail)
  
  	total = len(ps)
  	for _, p := range ps {
  		if shouldExit[p.Check] {
  			errors++
  		} else {
  			p.Severity = lint.Warning
  			warnings++
  		}
  		f.Format(p)
  	}
  	if f, ok := f.(format.Statter); ok {
  		f.Stats(total, errors, warnings)
  	}
  	if errors > 0 {
  		exit(1)
  	}
  }
  
  type Options struct {
  	Config config.Config
  
  	Tags          []string
  	LintTests     bool
  	Ignores       string
  	GoVersion     int
  	ReturnIgnored bool
  
  	MaxConcurrentJobs int
  	PrintStats        bool
  }
  
  func Lint(cs []lint.Checker, paths []string, opt *Options) ([]lint.Problem, error) {
  	stats := lint.PerfStats{
  		CheckerInits: map[string]time.Duration{},
  	}
  
  	if opt == nil {
  		opt = &Options{}
  	}
  	ignores, err := parseIgnore(opt.Ignores)
  	if err != nil {
  		return nil, err
  	}
  
  	conf := &packages.Config{
  		Mode:  packages.LoadAllSyntax,
  		Tests: opt.LintTests,
  		BuildFlags: []string{
  			"-tags=" + strings.Join(opt.Tags, " "),
  		},
  	}
  
  	t := time.Now()
  	if len(paths) == 0 {
  		paths = []string{"."}
  	}
  	pkgs, err := packages.Load(conf, paths...)
  	if err != nil {
  		return nil, err
  	}
  	stats.PackageLoading = time.Since(t)
  
  	var problems []lint.Problem
  	workingPkgs := make([]*packages.Package, 0, len(pkgs))
  	for _, pkg := range pkgs {
  		if pkg.IllTyped {
  			problems = append(problems, compileErrors(pkg)...)
  		} else {
  			workingPkgs = append(workingPkgs, pkg)
  		}
  	}
  
  	if len(workingPkgs) == 0 {
  		return problems, nil
  	}
  
  	l := &lint.Linter{
  		Checkers:      cs,
  		Ignores:       ignores,
  		GoVersion:     opt.GoVersion,
  		ReturnIgnored: opt.ReturnIgnored,
  		Config:        opt.Config,
  
  		MaxConcurrentJobs: opt.MaxConcurrentJobs,
  		PrintStats:        opt.PrintStats,
  	}
  	problems = append(problems, l.Lint(workingPkgs, &stats)...)
  
  	return problems, nil
  }
  
  var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?$`)
  
  func parsePos(pos string) token.Position {
  	if pos == "-" || pos == "" {
  		return token.Position{}
  	}
  	parts := posRe.FindStringSubmatch(pos)
  	if parts == nil {
  		panic(fmt.Sprintf("internal error: malformed position %q", pos))
  	}
  	file := parts[1]
  	line, _ := strconv.Atoi(parts[2])
  	col, _ := strconv.Atoi(parts[3])
  	return token.Position{
  		Filename: file,
  		Line:     line,
  		Column:   col,
  	}
  }
  
  func compileErrors(pkg *packages.Package) []lint.Problem {
  	if !pkg.IllTyped {
  		return nil
  	}
  	if len(pkg.Errors) == 0 {
  		// transitively ill-typed
  		var ps []lint.Problem
  		for _, imp := range pkg.Imports {
  			ps = append(ps, compileErrors(imp)...)
  		}
  		return ps
  	}
  	var ps []lint.Problem
  	for _, err := range pkg.Errors {
  		p := lint.Problem{
  			Position: parsePos(err.Pos),
  			Text:     err.Msg,
  			Checker:  "compiler",
  			Check:    "compile",
  		}
  		ps = append(ps, p)
  	}
  	return ps
  }
  
  func ProcessArgs(name string, cs []lint.Checker, args []string) {
  	flags := FlagSet(name)
  	flags.Parse(args)
  
  	ProcessFlagSet(cs, flags)
  }
  