github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/config/config.go (about)

     1  package config
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  
     7  	"github.com/BurntSushi/toml"
     8  )
     9  
    10  func mergeLists(a, b []string) []string {
    11  	out := make([]string, 0, len(a)+len(b))
    12  	for _, el := range b {
    13  		if el == "inherit" {
    14  			out = append(out, a...)
    15  		} else {
    16  			out = append(out, el)
    17  		}
    18  	}
    19  
    20  	return out
    21  }
    22  
    23  func normalizeList(list []string) []string {
    24  	if len(list) > 1 {
    25  		nlist := make([]string, 0, len(list))
    26  		nlist = append(nlist, list[0])
    27  		for i, el := range list[1:] {
    28  			if el != list[i] {
    29  				nlist = append(nlist, el)
    30  			}
    31  		}
    32  		list = nlist
    33  	}
    34  
    35  	for _, el := range list {
    36  		if el == "inherit" {
    37  			// This should never happen, because the default config
    38  			// should not use "inherit"
    39  			panic(`unresolved "inherit"`)
    40  		}
    41  	}
    42  
    43  	return list
    44  }
    45  
    46  func (cfg Config) Merge(ocfg Config) Config {
    47  	if ocfg.Checks != nil {
    48  		cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks)
    49  	}
    50  	if ocfg.Initialisms != nil {
    51  		cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms)
    52  	}
    53  	if ocfg.DotImportWhitelist != nil {
    54  		cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist)
    55  	}
    56  	if ocfg.HTTPStatusCodeWhitelist != nil {
    57  		cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist)
    58  	}
    59  	return cfg
    60  }
    61  
    62  type Config struct {
    63  	// TODO(dh): this implementation makes it impossible for external
    64  	// clients to add their own checkers with configuration. At the
    65  	// moment, we don't really care about that; we don't encourage
    66  	// that people use this package. In the future, we may. The
    67  	// obvious solution would be using map[string]interface{}, but
    68  	// that's obviously subpar.
    69  
    70  	Checks                  []string `toml:"checks"`
    71  	Initialisms             []string `toml:"initialisms"`
    72  	DotImportWhitelist      []string `toml:"dot_import_whitelist"`
    73  	HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
    74  }
    75  
    76  var defaultConfig = Config{
    77  	Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016"},
    78  	Initialisms: []string{
    79  		"ACL", "API", "ASCII", "CPU", "CSS", "DNS",
    80  		"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
    81  		"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
    82  		"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
    83  		"UDP", "UI", "GID", "UID", "UUID", "URI",
    84  		"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
    85  		"XSS",
    86  	},
    87  	DotImportWhitelist:      []string{},
    88  	HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},
    89  }
    90  
    91  const configName = "staticcheck.conf"
    92  
    93  func parseConfigs(dir string) ([]Config, error) {
    94  	var out []Config
    95  
    96  	// TODO(dh): consider stopping at the GOPATH/module boundary
    97  	for dir != "" {
    98  		f, err := os.Open(filepath.Join(dir, configName))
    99  		if os.IsNotExist(err) {
   100  			ndir := filepath.Dir(dir)
   101  			if ndir == dir {
   102  				break
   103  			}
   104  			dir = ndir
   105  			continue
   106  		}
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  		var cfg Config
   111  		_, err = toml.DecodeReader(f, &cfg)
   112  		f.Close()
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  		out = append(out, cfg)
   117  		ndir := filepath.Dir(dir)
   118  		if ndir == dir {
   119  			break
   120  		}
   121  		dir = ndir
   122  	}
   123  	out = append(out, defaultConfig)
   124  	if len(out) < 2 {
   125  		return out, nil
   126  	}
   127  	for i := 0; i < len(out)/2; i++ {
   128  		out[i], out[len(out)-1-i] = out[len(out)-1-i], out[i]
   129  	}
   130  	return out, nil
   131  }
   132  
   133  func mergeConfigs(confs []Config) Config {
   134  	if len(confs) == 0 {
   135  		// This shouldn't happen because we always have at least a
   136  		// default config.
   137  		panic("trying to merge zero configs")
   138  	}
   139  	if len(confs) == 1 {
   140  		return confs[0]
   141  	}
   142  	conf := confs[0]
   143  	for _, oconf := range confs[1:] {
   144  		conf = conf.Merge(oconf)
   145  	}
   146  	return conf
   147  }
   148  
   149  func Load(dir string) (Config, error) {
   150  	confs, err := parseConfigs(dir)
   151  	if err != nil {
   152  		return Config{}, err
   153  	}
   154  	conf := mergeConfigs(confs)
   155  
   156  	conf.Checks = normalizeList(conf.Checks)
   157  	conf.Initialisms = normalizeList(conf.Initialisms)
   158  	conf.DotImportWhitelist = normalizeList(conf.DotImportWhitelist)
   159  	conf.HTTPStatusCodeWhitelist = normalizeList(conf.HTTPStatusCodeWhitelist)
   160  
   161  	return conf, nil
   162  }