
* add ability to set issue severity for out formats that support it based on severity rules * fix lint issues * change log child name * code climate omit severity if empty * add tests for severity rules, add support for case sensitive rules, fix lint issues, better doc comments, share processor test * deduplicated rule logic into a base rule that can be used by multiple rule types, moved severity config to it's own parent key named severity, reduced size of NewRunner function to make it easier to read * put validate function under base rule struct * better validation error wording * add Fingerprint and Description methods to Issue struct, made codeclimate reporter easier to read, checkstyle output is now pretty printed
213 lines
4.7 KiB
Go
213 lines
4.7 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
homedir "github.com/mitchellh/go-homedir"
|
|
"github.com/spf13/viper"
|
|
|
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
|
)
|
|
|
|
type FileReader struct {
|
|
log logutils.Log
|
|
cfg *Config
|
|
commandLineCfg *Config
|
|
}
|
|
|
|
func NewFileReader(toCfg, commandLineCfg *Config, log logutils.Log) *FileReader {
|
|
return &FileReader{
|
|
log: log,
|
|
cfg: toCfg,
|
|
commandLineCfg: commandLineCfg,
|
|
}
|
|
}
|
|
|
|
func (r *FileReader) Read() error {
|
|
// XXX: hack with double parsing for 2 purposes:
|
|
// 1. to access "config" option here.
|
|
// 2. to give config less priority than command line.
|
|
|
|
configFile, err := r.parseConfigOption()
|
|
if err != nil {
|
|
if err == errConfigDisabled {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("can't parse --config option: %s", err)
|
|
}
|
|
|
|
if configFile != "" {
|
|
viper.SetConfigFile(configFile)
|
|
} else {
|
|
r.setupConfigFileSearch()
|
|
}
|
|
|
|
return r.parseConfig()
|
|
}
|
|
|
|
func (r *FileReader) parseConfig() error {
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("can't read viper config: %s", err)
|
|
}
|
|
|
|
usedConfigFile := viper.ConfigFileUsed()
|
|
if usedConfigFile == "" {
|
|
return nil
|
|
}
|
|
|
|
usedConfigFile, err := fsutils.ShortestRelPath(usedConfigFile, "")
|
|
if err != nil {
|
|
r.log.Warnf("Can't pretty print config file path: %s", err)
|
|
}
|
|
r.log.Infof("Used config file %s", usedConfigFile)
|
|
|
|
if err := viper.Unmarshal(r.cfg); err != nil {
|
|
return fmt.Errorf("can't unmarshal config by viper: %s", err)
|
|
}
|
|
|
|
if err := r.validateConfig(); err != nil {
|
|
return fmt.Errorf("can't validate config: %s", err)
|
|
}
|
|
|
|
if r.cfg.InternalTest { // just for testing purposes: to detect config file usage
|
|
fmt.Fprintln(logutils.StdOut, "test")
|
|
os.Exit(0)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *FileReader) validateConfig() error {
|
|
c := r.cfg
|
|
if len(c.Run.Args) != 0 {
|
|
return errors.New("option run.args in config isn't supported now")
|
|
}
|
|
|
|
if c.Run.CPUProfilePath != "" {
|
|
return errors.New("option run.cpuprofilepath in config isn't allowed")
|
|
}
|
|
|
|
if c.Run.MemProfilePath != "" {
|
|
return errors.New("option run.memprofilepath in config isn't allowed")
|
|
}
|
|
|
|
if c.Run.TracePath != "" {
|
|
return errors.New("option run.tracepath in config isn't allowed")
|
|
}
|
|
|
|
if c.Run.IsVerbose {
|
|
return errors.New("can't set run.verbose option with config: only on command-line")
|
|
}
|
|
for i, rule := range c.Issues.ExcludeRules {
|
|
if err := rule.Validate(); err != nil {
|
|
return fmt.Errorf("error in exclude rule #%d: %v", i, err)
|
|
}
|
|
}
|
|
if len(c.Severity.Rules) > 0 && c.Severity.Default == "" {
|
|
return errors.New("can't set severity rule option: no default severity defined")
|
|
}
|
|
for i, rule := range c.Severity.Rules {
|
|
if err := rule.Validate(); err != nil {
|
|
return fmt.Errorf("error in severity rule #%d: %v", i, err)
|
|
}
|
|
}
|
|
if err := c.LintersSettings.Govet.Validate(); err != nil {
|
|
return fmt.Errorf("error in govet config: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getFirstPathArg() string {
|
|
args := os.Args
|
|
|
|
// skip all args ([golangci-lint, run/linters]) before files/dirs list
|
|
for len(args) != 0 {
|
|
if args[0] == "run" {
|
|
args = args[1:]
|
|
break
|
|
}
|
|
|
|
args = args[1:]
|
|
}
|
|
|
|
// find first file/dir arg
|
|
firstArg := "./..."
|
|
for _, arg := range args {
|
|
if !strings.HasPrefix(arg, "-") {
|
|
firstArg = arg
|
|
break
|
|
}
|
|
}
|
|
|
|
return firstArg
|
|
}
|
|
|
|
func (r *FileReader) setupConfigFileSearch() {
|
|
firstArg := getFirstPathArg()
|
|
absStartPath, err := filepath.Abs(firstArg)
|
|
if err != nil {
|
|
r.log.Warnf("Can't make abs path for %q: %s", firstArg, err)
|
|
absStartPath = filepath.Clean(firstArg)
|
|
}
|
|
|
|
// start from it
|
|
var curDir string
|
|
if fsutils.IsDir(absStartPath) {
|
|
curDir = absStartPath
|
|
} else {
|
|
curDir = filepath.Dir(absStartPath)
|
|
}
|
|
|
|
// find all dirs from it up to the root
|
|
configSearchPaths := []string{"./"}
|
|
for {
|
|
configSearchPaths = append(configSearchPaths, curDir)
|
|
newCurDir := filepath.Dir(curDir)
|
|
if curDir == newCurDir || newCurDir == "" {
|
|
break
|
|
}
|
|
curDir = newCurDir
|
|
}
|
|
|
|
r.log.Infof("Config search paths: %s", configSearchPaths)
|
|
viper.SetConfigName(".golangci")
|
|
for _, p := range configSearchPaths {
|
|
viper.AddConfigPath(p)
|
|
}
|
|
}
|
|
|
|
var errConfigDisabled = errors.New("config is disabled by --no-config")
|
|
|
|
func (r *FileReader) parseConfigOption() (string, error) {
|
|
cfg := r.commandLineCfg
|
|
if cfg == nil {
|
|
return "", nil
|
|
}
|
|
|
|
configFile := cfg.Run.Config
|
|
if cfg.Run.NoConfig && configFile != "" {
|
|
return "", fmt.Errorf("can't combine option --config and --no-config")
|
|
}
|
|
|
|
if cfg.Run.NoConfig {
|
|
return "", errConfigDisabled
|
|
}
|
|
|
|
configFile, err := homedir.Expand(configFile)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to expand configuration path")
|
|
}
|
|
|
|
return configFile, nil
|
|
}
|