woodpecker/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go
2021-11-16 21:07:53 +01:00

226 lines
5.2 KiB
Go

package config
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"github.com/golangci/golangci-lint/pkg/fsutils"
"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/sliceutil"
)
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)
usedConfigDir := filepath.Dir(usedConfigFile)
if usedConfigDir, err = filepath.Abs(usedConfigDir); err != nil {
return fmt.Errorf("can't get config directory")
}
r.cfg.cfgDir = usedConfigDir
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
}
// find home directory for global config
if home, err := homedir.Dir(); err != nil {
r.log.Warnf("Can't get user's home directory: %s", err.Error())
} else if !sliceutil.Contains(configSearchPaths, home) {
configSearchPaths = append(configSearchPaths, home)
}
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
}