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
 | 
						|
}
 |