13 "github.com/BurntSushi/toml"
14 "golang.org/x/tools/go/analysis"
17 // Dir looks at a list of absolute file names, which should make up a
18 // single package, and returns the path of the directory that may
19 // contain a staticcheck.conf file. It returns the empty string if no
20 // such directory could be determined, for example because all files
21 // were located in Go's build cache.
22 func Dir(files []string) string {
26 cache, err := os.UserCacheDir()
31 for _, p := range files {
32 // FIXME(dh): using strings.HasPrefix isn't technically
33 // correct, but it should be good enough for now.
34 if cache != "" && strings.HasPrefix(p, cache) {
35 // File in the build cache of the standard Go build system
43 // The package only consists of generated files.
47 dir := filepath.Dir(path)
51 func dirAST(files []*ast.File, fset *token.FileSet) string {
52 names := make([]string, len(files))
53 for i, f := range files {
54 names[i] = fset.PositionFor(f.Pos(), true).Filename
59 var Analyzer = &analysis.Analyzer{
61 Doc: "loads configuration for the current package tree",
62 Run: func(pass *analysis.Pass) (interface{}, error) {
63 dir := dirAST(pass.Files, pass.Fset)
70 return nil, fmt.Errorf("error loading staticcheck.conf: %s", err)
74 RunDespiteErrors: true,
75 ResultType: reflect.TypeOf((*Config)(nil)),
78 func For(pass *analysis.Pass) *Config {
79 return pass.ResultOf[Analyzer].(*Config)
82 func mergeLists(a, b []string) []string {
83 out := make([]string, 0, len(a)+len(b))
84 for _, el := range b {
86 out = append(out, a...)
95 func normalizeList(list []string) []string {
97 nlist := make([]string, 0, len(list))
98 nlist = append(nlist, list[0])
99 for i, el := range list[1:] {
101 nlist = append(nlist, el)
107 for _, el := range list {
109 // This should never happen, because the default config
110 // should not use "inherit"
111 panic(`unresolved "inherit"`)
118 func (cfg Config) Merge(ocfg Config) Config {
119 if ocfg.Checks != nil {
120 cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks)
122 if ocfg.Initialisms != nil {
123 cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms)
125 if ocfg.DotImportWhitelist != nil {
126 cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist)
128 if ocfg.HTTPStatusCodeWhitelist != nil {
129 cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist)
135 // TODO(dh): this implementation makes it impossible for external
136 // clients to add their own checkers with configuration. At the
137 // moment, we don't really care about that; we don't encourage
138 // that people use this package. In the future, we may. The
139 // obvious solution would be using map[string]interface{}, but
140 // that's obviously subpar.
142 Checks []string `toml:"checks"`
143 Initialisms []string `toml:"initialisms"`
144 DotImportWhitelist []string `toml:"dot_import_whitelist"`
145 HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
148 func (c Config) String() string {
149 buf := &bytes.Buffer{}
151 fmt.Fprintf(buf, "Checks: %#v\n", c.Checks)
152 fmt.Fprintf(buf, "Initialisms: %#v\n", c.Initialisms)
153 fmt.Fprintf(buf, "DotImportWhitelist: %#v\n", c.DotImportWhitelist)
154 fmt.Fprintf(buf, "HTTPStatusCodeWhitelist: %#v", c.HTTPStatusCodeWhitelist)
159 var DefaultConfig = Config{
160 Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"},
161 Initialisms: []string{
162 "ACL", "API", "ASCII", "CPU", "CSS", "DNS",
163 "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
164 "IP", "JSON", "QPS", "RAM", "RPC", "SLA",
165 "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
166 "UDP", "UI", "GID", "UID", "UUID", "URI",
167 "URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
168 "XSS", "SIP", "RTP", "AMQP", "DB", "TS",
170 DotImportWhitelist: []string{},
171 HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},
174 const ConfigName = "staticcheck.conf"
176 func parseConfigs(dir string) ([]Config, error) {
179 // TODO(dh): consider stopping at the GOPATH/module boundary
181 f, err := os.Open(filepath.Join(dir, ConfigName))
182 if os.IsNotExist(err) {
183 ndir := filepath.Dir(dir)
194 _, err = toml.DecodeReader(f, &cfg)
199 out = append(out, cfg)
200 ndir := filepath.Dir(dir)
206 out = append(out, DefaultConfig)
210 for i := 0; i < len(out)/2; i++ {
211 out[i], out[len(out)-1-i] = out[len(out)-1-i], out[i]
216 func mergeConfigs(confs []Config) Config {
218 // This shouldn't happen because we always have at least a
220 panic("trying to merge zero configs")
226 for _, oconf := range confs[1:] {
227 conf = conf.Merge(oconf)
232 func Load(dir string) (Config, error) {
233 confs, err := parseConfigs(dir)
237 conf := mergeConfigs(confs)
239 conf.Checks = normalizeList(conf.Checks)
240 conf.Initialisms = normalizeList(conf.Initialisms)
241 conf.DotImportWhitelist = normalizeList(conf.DotImportWhitelist)
242 conf.HTTPStatusCodeWhitelist = normalizeList(conf.HTTPStatusCodeWhitelist)