193 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package config
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
 | 
						|
	"github.com/golangci/golangci-lint/pkg/fsutils"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/logutils"
 | 
						|
	"github.com/spf13/pflag"
 | 
						|
	"github.com/spf13/viper"
 | 
						|
)
 | 
						|
 | 
						|
type FlagSetInit func(fs *pflag.FlagSet, cfg *Config)
 | 
						|
 | 
						|
type FileReader struct {
 | 
						|
	log         logutils.Log
 | 
						|
	cfg         *Config
 | 
						|
	flagSetInit FlagSetInit
 | 
						|
}
 | 
						|
 | 
						|
func NewFileReader(toCfg *Config, log logutils.Log, flagSetInit FlagSetInit) *FileReader {
 | 
						|
	return &FileReader{
 | 
						|
		log:         log,
 | 
						|
		cfg:         toCfg,
 | 
						|
		flagSetInit: flagSetInit,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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, restArgs, err := r.parseConfigOption()
 | 
						|
	if err != nil {
 | 
						|
		if err == errConfigDisabled || err == pflag.ErrHelp {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		return fmt.Errorf("can't parse --config option: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if configFile != "" {
 | 
						|
		viper.SetConfigFile(configFile)
 | 
						|
	} else {
 | 
						|
		r.setupConfigFileSearch(restArgs)
 | 
						|
	}
 | 
						|
 | 
						|
	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.IsVerbose {
 | 
						|
		return errors.New("can't set run.verbose option with config: only on command-line")
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *FileReader) setupConfigFileSearch(args []string) {
 | 
						|
	// 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 := "./..."
 | 
						|
	if len(args) != 0 {
 | 
						|
		firstArg = args[0]
 | 
						|
	}
 | 
						|
 | 
						|
	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, []string, error) {
 | 
						|
	// We use another pflag.FlagSet here to not set `changed` flag
 | 
						|
	// on cmd.Flags() options. Otherwise string slice options will be duplicated.
 | 
						|
	fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError)
 | 
						|
 | 
						|
	var cfg Config
 | 
						|
	r.flagSetInit(fs, &cfg)
 | 
						|
 | 
						|
	fs.Usage = func() {} // otherwise help text will be printed twice
 | 
						|
	if err := fs.Parse(os.Args); err != nil {
 | 
						|
		if err == pflag.ErrHelp {
 | 
						|
			return "", nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		return "", nil, fmt.Errorf("can't parse args: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// for `-v` to work until running of preRun function
 | 
						|
	logutils.SetupVerboseLog(r.log, cfg.Run.IsVerbose)
 | 
						|
 | 
						|
	configFile := cfg.Run.Config
 | 
						|
	if cfg.Run.NoConfig && configFile != "" {
 | 
						|
		return "", nil, fmt.Errorf("can't combine option --config and --no-config")
 | 
						|
	}
 | 
						|
 | 
						|
	if cfg.Run.NoConfig {
 | 
						|
		return "", nil, errConfigDisabled
 | 
						|
	}
 | 
						|
 | 
						|
	return configFile, fs.Args(), nil
 | 
						|
}
 |