233 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			5.4 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/exitcodes"
 | 
						|
	"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)
 | 
						|
 | 
						|
		// Assume YAML if the file has no extension.
 | 
						|
		if filepath.Ext(configFile) == "" {
 | 
						|
			viper.SetConfigType("yaml")
 | 
						|
		}
 | 
						|
	} 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 errors.New("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(exitcodes.Success)
 | 
						|
	}
 | 
						|
 | 
						|
	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 "", errors.New("can't combine option --config and --no-config")
 | 
						|
	}
 | 
						|
 | 
						|
	if cfg.Run.NoConfig {
 | 
						|
		return "", errConfigDisabled
 | 
						|
	}
 | 
						|
 | 
						|
	configFile, err := homedir.Expand(configFile)
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.New("failed to expand configuration path")
 | 
						|
	}
 | 
						|
 | 
						|
	return configFile, nil
 | 
						|
}
 |