 bf27481efd
			
		
	
	
		bf27481efd
		
			
		
	
	
	
	
		
			
			full diff: https://github.com/dominikh/go-tools/compare/2019.2.3...2020.1.3 Also updates tests to accomodate updated rules: --- FAIL: TestSourcesFromTestdataWithIssuesDir/staticcheck.go (0.43s) linters_test.go:137: [run --disable-all --print-issued-lines=false --print-linter-name=false --out-format=line-number --max-same-issues=10 -Estaticcheck --no-config testdata/staticcheck.go] linters_test.go:33: Error Trace: linters_test.go:33 linters_test.go:138 linters_test.go:53 Error: Received unexpected error: staticcheck.go:11: no match for `self-assignment of x to x` vs ["SA4006: this value of `x` is never used"] in: staticcheck.go:11:2: SA4006: this value of `x` is never used unmatched errors staticcheck.go:11:2: SA4006: this value of `x` is never used Test: TestSourcesFromTestdataWithIssuesDir/staticcheck.go Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			246 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package config
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/token"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/BurntSushi/toml"
 | |
| 	"golang.org/x/tools/go/analysis"
 | |
| )
 | |
| 
 | |
| // Dir looks at a list of absolute file names, which should make up a
 | |
| // single package, and returns the path of the directory that may
 | |
| // contain a staticcheck.conf file. It returns the empty string if no
 | |
| // such directory could be determined, for example because all files
 | |
| // were located in Go's build cache.
 | |
| func Dir(files []string) string {
 | |
| 	if len(files) == 0 {
 | |
| 		return ""
 | |
| 	}
 | |
| 	cache, err := os.UserCacheDir()
 | |
| 	if err != nil {
 | |
| 		cache = ""
 | |
| 	}
 | |
| 	var path string
 | |
| 	for _, p := range files {
 | |
| 		// FIXME(dh): using strings.HasPrefix isn't technically
 | |
| 		// correct, but it should be good enough for now.
 | |
| 		if cache != "" && strings.HasPrefix(p, cache) {
 | |
| 			// File in the build cache of the standard Go build system
 | |
| 			continue
 | |
| 		}
 | |
| 		path = p
 | |
| 		break
 | |
| 	}
 | |
| 
 | |
| 	if path == "" {
 | |
| 		// The package only consists of generated files.
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	dir := filepath.Dir(path)
 | |
| 	return dir
 | |
| }
 | |
| 
 | |
| func dirAST(files []*ast.File, fset *token.FileSet) string {
 | |
| 	names := make([]string, len(files))
 | |
| 	for i, f := range files {
 | |
| 		names[i] = fset.PositionFor(f.Pos(), true).Filename
 | |
| 	}
 | |
| 	return Dir(names)
 | |
| }
 | |
| 
 | |
| var Analyzer = &analysis.Analyzer{
 | |
| 	Name: "config",
 | |
| 	Doc:  "loads configuration for the current package tree",
 | |
| 	Run: func(pass *analysis.Pass) (interface{}, error) {
 | |
| 		dir := dirAST(pass.Files, pass.Fset)
 | |
| 		if dir == "" {
 | |
| 			cfg := DefaultConfig
 | |
| 			return &cfg, nil
 | |
| 		}
 | |
| 		cfg, err := Load(dir)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("error loading staticcheck.conf: %s", err)
 | |
| 		}
 | |
| 		return &cfg, nil
 | |
| 	},
 | |
| 	RunDespiteErrors: true,
 | |
| 	ResultType:       reflect.TypeOf((*Config)(nil)),
 | |
| }
 | |
| 
 | |
| func For(pass *analysis.Pass) *Config {
 | |
| 	return pass.ResultOf[Analyzer].(*Config)
 | |
| }
 | |
| 
 | |
| func mergeLists(a, b []string) []string {
 | |
| 	out := make([]string, 0, len(a)+len(b))
 | |
| 	for _, el := range b {
 | |
| 		if el == "inherit" {
 | |
| 			out = append(out, a...)
 | |
| 		} else {
 | |
| 			out = append(out, el)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| func normalizeList(list []string) []string {
 | |
| 	if len(list) > 1 {
 | |
| 		nlist := make([]string, 0, len(list))
 | |
| 		nlist = append(nlist, list[0])
 | |
| 		for i, el := range list[1:] {
 | |
| 			if el != list[i] {
 | |
| 				nlist = append(nlist, el)
 | |
| 			}
 | |
| 		}
 | |
| 		list = nlist
 | |
| 	}
 | |
| 
 | |
| 	for _, el := range list {
 | |
| 		if el == "inherit" {
 | |
| 			// This should never happen, because the default config
 | |
| 			// should not use "inherit"
 | |
| 			panic(`unresolved "inherit"`)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return list
 | |
| }
 | |
| 
 | |
| func (cfg Config) Merge(ocfg Config) Config {
 | |
| 	if ocfg.Checks != nil {
 | |
| 		cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks)
 | |
| 	}
 | |
| 	if ocfg.Initialisms != nil {
 | |
| 		cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms)
 | |
| 	}
 | |
| 	if ocfg.DotImportWhitelist != nil {
 | |
| 		cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist)
 | |
| 	}
 | |
| 	if ocfg.HTTPStatusCodeWhitelist != nil {
 | |
| 		cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist)
 | |
| 	}
 | |
| 	return cfg
 | |
| }
 | |
| 
 | |
| type Config struct {
 | |
| 	// TODO(dh): this implementation makes it impossible for external
 | |
| 	// clients to add their own checkers with configuration. At the
 | |
| 	// moment, we don't really care about that; we don't encourage
 | |
| 	// that people use this package. In the future, we may. The
 | |
| 	// obvious solution would be using map[string]interface{}, but
 | |
| 	// that's obviously subpar.
 | |
| 
 | |
| 	Checks                  []string `toml:"checks"`
 | |
| 	Initialisms             []string `toml:"initialisms"`
 | |
| 	DotImportWhitelist      []string `toml:"dot_import_whitelist"`
 | |
| 	HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
 | |
| }
 | |
| 
 | |
| func (c Config) String() string {
 | |
| 	buf := &bytes.Buffer{}
 | |
| 
 | |
| 	fmt.Fprintf(buf, "Checks: %#v\n", c.Checks)
 | |
| 	fmt.Fprintf(buf, "Initialisms: %#v\n", c.Initialisms)
 | |
| 	fmt.Fprintf(buf, "DotImportWhitelist: %#v\n", c.DotImportWhitelist)
 | |
| 	fmt.Fprintf(buf, "HTTPStatusCodeWhitelist: %#v", c.HTTPStatusCodeWhitelist)
 | |
| 
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| var DefaultConfig = Config{
 | |
| 	Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"},
 | |
| 	Initialisms: []string{
 | |
| 		"ACL", "API", "ASCII", "CPU", "CSS", "DNS",
 | |
| 		"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
 | |
| 		"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
 | |
| 		"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
 | |
| 		"UDP", "UI", "GID", "UID", "UUID", "URI",
 | |
| 		"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
 | |
| 		"XSS", "SIP", "RTP", "AMQP", "DB", "TS",
 | |
| 	},
 | |
| 	DotImportWhitelist:      []string{},
 | |
| 	HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},
 | |
| }
 | |
| 
 | |
| const ConfigName = "staticcheck.conf"
 | |
| 
 | |
| func parseConfigs(dir string) ([]Config, error) {
 | |
| 	var out []Config
 | |
| 
 | |
| 	// TODO(dh): consider stopping at the GOPATH/module boundary
 | |
| 	for dir != "" {
 | |
| 		f, err := os.Open(filepath.Join(dir, ConfigName))
 | |
| 		if os.IsNotExist(err) {
 | |
| 			ndir := filepath.Dir(dir)
 | |
| 			if ndir == dir {
 | |
| 				break
 | |
| 			}
 | |
| 			dir = ndir
 | |
| 			continue
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		var cfg Config
 | |
| 		_, err = toml.DecodeReader(f, &cfg)
 | |
| 		f.Close()
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		out = append(out, cfg)
 | |
| 		ndir := filepath.Dir(dir)
 | |
| 		if ndir == dir {
 | |
| 			break
 | |
| 		}
 | |
| 		dir = ndir
 | |
| 	}
 | |
| 	out = append(out, DefaultConfig)
 | |
| 	if len(out) < 2 {
 | |
| 		return out, nil
 | |
| 	}
 | |
| 	for i := 0; i < len(out)/2; i++ {
 | |
| 		out[i], out[len(out)-1-i] = out[len(out)-1-i], out[i]
 | |
| 	}
 | |
| 	return out, nil
 | |
| }
 | |
| 
 | |
| func mergeConfigs(confs []Config) Config {
 | |
| 	if len(confs) == 0 {
 | |
| 		// This shouldn't happen because we always have at least a
 | |
| 		// default config.
 | |
| 		panic("trying to merge zero configs")
 | |
| 	}
 | |
| 	if len(confs) == 1 {
 | |
| 		return confs[0]
 | |
| 	}
 | |
| 	conf := confs[0]
 | |
| 	for _, oconf := range confs[1:] {
 | |
| 		conf = conf.Merge(oconf)
 | |
| 	}
 | |
| 	return conf
 | |
| }
 | |
| 
 | |
| func Load(dir string) (Config, error) {
 | |
| 	confs, err := parseConfigs(dir)
 | |
| 	if err != nil {
 | |
| 		return Config{}, err
 | |
| 	}
 | |
| 	conf := mergeConfigs(confs)
 | |
| 
 | |
| 	conf.Checks = normalizeList(conf.Checks)
 | |
| 	conf.Initialisms = normalizeList(conf.Initialisms)
 | |
| 	conf.DotImportWhitelist = normalizeList(conf.DotImportWhitelist)
 | |
| 	conf.HTTPStatusCodeWhitelist = normalizeList(conf.HTTPStatusCodeWhitelist)
 | |
| 
 | |
| 	return conf, nil
 | |
| }
 |