#66: properly merge (not overwrite) slice flags from config and command-line
This commit is contained in:
parent
9c07341b36
commit
afc4b4344f
@ -7,15 +7,16 @@ import (
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/printers"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func (e *Executor) setupLog() {
|
||||
func setupLog(isVerbose bool) {
|
||||
log.SetFlags(0) // don't print time
|
||||
if e.cfg.Run.IsVerbose {
|
||||
if isVerbose {
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
}
|
||||
}
|
||||
@ -28,7 +29,7 @@ func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
|
||||
|
||||
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
|
||||
|
||||
e.setupLog()
|
||||
setupLog(e.cfg.Run.IsVerbose)
|
||||
|
||||
if e.cfg.Run.CPUProfilePath != "" {
|
||||
f, err := os.Create(e.cfg.Run.CPUProfilePath)
|
||||
@ -81,16 +82,16 @@ func (e *Executor) initRoot() {
|
||||
PersistentPostRun: e.persistentPostRun,
|
||||
}
|
||||
|
||||
e.initRootFlagSet(rootCmd.PersistentFlags())
|
||||
initRootFlagSet(rootCmd.PersistentFlags(), e.cfg, e.needVersionOption())
|
||||
e.rootCmd = rootCmd
|
||||
}
|
||||
|
||||
func (e *Executor) initRootFlagSet(fs *pflag.FlagSet) {
|
||||
fs.BoolVarP(&e.cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
|
||||
fs.StringVar(&e.cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
|
||||
fs.StringVar(&e.cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file"))
|
||||
fs.IntVarP(&e.cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), wh("Concurrency (default NumCPU)"))
|
||||
if e.date != "" {
|
||||
fs.BoolVar(&e.cfg.Run.PrintVersion, "version", false, wh("Print version"))
|
||||
func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bool) {
|
||||
fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
|
||||
fs.StringVar(&cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
|
||||
fs.StringVar(&cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file"))
|
||||
fs.IntVarP(&cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), wh("Concurrency (default NumCPU)"))
|
||||
if needVersionOption {
|
||||
fs.BoolVar(&cfg.Run.PrintVersion, "version", false, wh("Print version"))
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,17 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
"github.com/golangci/golangci-lint/pkg/lint"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
"github.com/golangci/golangci-lint/pkg/printers"
|
||||
@ -24,7 +21,6 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -48,7 +44,7 @@ func wh(text string) string {
|
||||
return color.GreenString(text)
|
||||
}
|
||||
|
||||
func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
|
||||
func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
|
||||
hideFlag := func(name string) {
|
||||
if err := fs.MarkHidden(name); err != nil {
|
||||
panic(err)
|
||||
@ -56,7 +52,7 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
|
||||
}
|
||||
|
||||
// Output config
|
||||
oc := &e.cfg.Output
|
||||
oc := &cfg.Output
|
||||
fs.StringVar(&oc.Format, "out-format",
|
||||
config.OutFormatColoredLineNumber,
|
||||
wh(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))))
|
||||
@ -66,10 +62,10 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
|
||||
hideFlag("print-welcome") // no longer used
|
||||
|
||||
// Run config
|
||||
rc := &e.cfg.Run
|
||||
rc := &cfg.Run
|
||||
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
|
||||
1, wh("Exit code when issues were found"))
|
||||
fs.StringSliceVar(&rc.BuildTags, "build-tags", []string{}, wh("Build tags (not all linters support them)"))
|
||||
fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags (not all linters support them)"))
|
||||
fs.DurationVar(&rc.Deadline, "deadline", time.Minute, wh("Deadline for total work"))
|
||||
fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)"))
|
||||
fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false, wh("Print avg and max memory usage of golangci-lint and total time"))
|
||||
@ -77,7 +73,7 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
|
||||
fs.BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config"))
|
||||
|
||||
// Linters settings config
|
||||
lsc := &e.cfg.LintersSettings
|
||||
lsc := &cfg.LintersSettings
|
||||
|
||||
// Hide all linters settings flags: they were initially visible,
|
||||
// but when number of linters started to grow it became ovious that
|
||||
@ -126,18 +122,18 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
|
||||
hideFlag("depguard.include-go-root")
|
||||
|
||||
// Linters config
|
||||
lc := &e.cfg.Linters
|
||||
fs.StringSliceVarP(&lc.Enable, "enable", "E", []string{}, wh("Enable specific linter"))
|
||||
fs.StringSliceVarP(&lc.Disable, "disable", "D", []string{}, wh("Disable specific linter"))
|
||||
lc := &cfg.Linters
|
||||
fs.StringSliceVarP(&lc.Enable, "enable", "E", nil, wh("Enable specific linter"))
|
||||
fs.StringSliceVarP(&lc.Disable, "disable", "D", nil, wh("Disable specific linter"))
|
||||
fs.BoolVar(&lc.EnableAll, "enable-all", false, wh("Enable all linters"))
|
||||
fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters"))
|
||||
fs.StringSliceVarP(&lc.Presets, "presets", "p", []string{},
|
||||
fs.StringSliceVarP(&lc.Presets, "presets", "p", nil,
|
||||
wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see them. This option implies option --disable-all", strings.Join(lintersdb.AllPresets(), "|"))))
|
||||
fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set"))
|
||||
|
||||
// Issues config
|
||||
ic := &e.cfg.Issues
|
||||
fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", []string{}, wh("Exclude issue by regexp"))
|
||||
ic := &cfg.Issues
|
||||
fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", nil, wh("Exclude issue by regexp"))
|
||||
fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultExcludeHelp())
|
||||
|
||||
fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50, wh("Maximum issues count per one linter. Set to 0 to disable"))
|
||||
@ -162,9 +158,15 @@ func (e *Executor) initRun() {
|
||||
|
||||
fs := runCmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
e.initFlagSet(fs)
|
||||
initFlagSet(fs, e.cfg)
|
||||
|
||||
// init e.cfg by values from config: flags parse will see these values
|
||||
// like the default ones. It will overwrite them only if the same option
|
||||
// is found in command-line: it's ok, command-line has higher priority.
|
||||
e.parseConfig()
|
||||
|
||||
// Slice options must be explicitly set for properly merging.
|
||||
fixSlicesFlags(fs)
|
||||
}
|
||||
|
||||
func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) {
|
||||
@ -289,172 +291,6 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) parseConfig() {
|
||||
// XXX: hack with double parsing for 2 purposes:
|
||||
// 1. to access "config" option here.
|
||||
// 2. to give config less priority than command line.
|
||||
|
||||
// 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)
|
||||
|
||||
// Don't do `fs.AddFlagSet(cmd.Flags())` because it shared flags representations:
|
||||
// `changed` variable inside string slice vars will be shared.
|
||||
e.initFlagSet(fs)
|
||||
e.initRootFlagSet(fs)
|
||||
|
||||
fs.Usage = func() {} // otherwise help text will be printed twice
|
||||
if err := fs.Parse(os.Args); err != nil {
|
||||
if err == pflag.ErrHelp {
|
||||
return
|
||||
}
|
||||
logrus.Fatalf("Can't parse args: %s", err)
|
||||
}
|
||||
|
||||
e.setupLog() // for `-v` to work until running of preRun function
|
||||
|
||||
if err := viper.BindPFlags(fs); err != nil {
|
||||
logrus.Fatalf("Can't bind cobra's flags to viper: %s", err)
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix("GOLANGCI")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
|
||||
configFile := e.cfg.Run.Config
|
||||
if e.cfg.Run.NoConfig && configFile != "" {
|
||||
logrus.Fatal("can't combine option --config and --no-config")
|
||||
}
|
||||
|
||||
if e.cfg.Run.NoConfig {
|
||||
return
|
||||
}
|
||||
|
||||
if configFile != "" {
|
||||
viper.SetConfigFile(configFile)
|
||||
} else {
|
||||
setupConfigFileSearch(fs.Args())
|
||||
}
|
||||
|
||||
e.parseConfigImpl()
|
||||
}
|
||||
|
||||
func 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 {
|
||||
logrus.Infof("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
|
||||
}
|
||||
|
||||
logrus.Infof("Config search paths: %s", configSearchPaths)
|
||||
viper.SetConfigName(".golangci")
|
||||
for _, p := range configSearchPaths {
|
||||
viper.AddConfigPath(p)
|
||||
}
|
||||
}
|
||||
|
||||
func getRelPath(p string) string {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
logrus.Infof("Can't get wd: %s", err)
|
||||
return p
|
||||
}
|
||||
|
||||
r, err := filepath.Rel(wd, p)
|
||||
if err != nil {
|
||||
logrus.Infof("Can't make path %s relative to %s: %s", p, wd, err)
|
||||
return p
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (e *Executor) parseConfigImpl() {
|
||||
commandLineConfig := *e.cfg // make copy
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
return
|
||||
}
|
||||
logrus.Fatalf("Can't read viper config: %s", err)
|
||||
}
|
||||
|
||||
usedConfigFile := viper.ConfigFileUsed()
|
||||
if usedConfigFile == "" {
|
||||
return
|
||||
}
|
||||
logrus.Infof("Used config file %s", getRelPath(usedConfigFile))
|
||||
|
||||
if err := viper.Unmarshal(&e.cfg); err != nil {
|
||||
logrus.Fatalf("Can't unmarshal config by viper: %s", err)
|
||||
}
|
||||
|
||||
if err := e.validateConfig(&commandLineConfig); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if e.cfg.InternalTest { // just for testing purposes: to detect config file usage
|
||||
fmt.Fprintln(printers.StdOut, "test")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) validateConfig(commandLineConfig *config.Config) error {
|
||||
c := e.cfg
|
||||
if len(c.Run.Args) != 0 {
|
||||
return errors.New("option run.args in config isn't supported now")
|
||||
}
|
||||
|
||||
if commandLineConfig.Run.CPUProfilePath == "" && c.Run.CPUProfilePath != "" {
|
||||
return errors.New("option run.cpuprofilepath in config isn't allowed")
|
||||
}
|
||||
|
||||
if commandLineConfig.Run.MemProfilePath == "" && c.Run.MemProfilePath != "" {
|
||||
return errors.New("option run.memprofilepath in config isn't allowed")
|
||||
}
|
||||
|
||||
if !commandLineConfig.Run.IsVerbose && c.Run.IsVerbose {
|
||||
return errors.New("can't set run.verbose option with config: only on command-line")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func watchResources(ctx context.Context, done chan struct{}) {
|
||||
startedAt := time.Now()
|
||||
|
||||
|
216
pkg/commands/run_config.go
Normal file
216
pkg/commands/run_config.go
Normal file
@ -0,0 +1,216 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
"github.com/golangci/golangci-lint/pkg/printers"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func (e *Executor) parseConfigImpl() {
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
return
|
||||
}
|
||||
logrus.Fatalf("Can't read viper config: %s", err)
|
||||
}
|
||||
|
||||
usedConfigFile := viper.ConfigFileUsed()
|
||||
if usedConfigFile == "" {
|
||||
return
|
||||
}
|
||||
logrus.Infof("Used config file %s", getRelPath(usedConfigFile))
|
||||
|
||||
if err := viper.Unmarshal(&e.cfg); err != nil {
|
||||
logrus.Fatalf("Can't unmarshal config by viper: %s", err)
|
||||
}
|
||||
|
||||
if err := e.validateConfig(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if e.cfg.InternalTest { // just for testing purposes: to detect config file usage
|
||||
fmt.Fprintln(printers.StdOut, "test")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) validateConfig() error {
|
||||
c := e.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 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 {
|
||||
logrus.Infof("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
|
||||
}
|
||||
|
||||
logrus.Infof("Config search paths: %s", configSearchPaths)
|
||||
viper.SetConfigName(".golangci")
|
||||
for _, p := range configSearchPaths {
|
||||
viper.AddConfigPath(p)
|
||||
}
|
||||
}
|
||||
|
||||
func getRelPath(p string) string {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
logrus.Infof("Can't get wd: %s", err)
|
||||
return p
|
||||
}
|
||||
|
||||
r, err := filepath.Rel(wd, p)
|
||||
if err != nil {
|
||||
logrus.Infof("Can't make path %s relative to %s: %s", p, wd, err)
|
||||
return p
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (e *Executor) needVersionOption() bool {
|
||||
return e.date != ""
|
||||
}
|
||||
|
||||
func 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)
|
||||
|
||||
// Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations:
|
||||
// `changed` variable inside string slice vars will be shared.
|
||||
// Use another config variable here, not e.cfg, to not
|
||||
// affect main parsing by this parsing of only config option.
|
||||
var cfg config.Config
|
||||
initFlagSet(fs, &cfg)
|
||||
|
||||
// Parse max options, even force version option: don't want
|
||||
// to get access to Executor here: it's error-prone to use
|
||||
// cfg vs e.cfg.
|
||||
initRootFlagSet(fs, &cfg, true)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
logrus.Fatalf("Can't parse args: %s", err)
|
||||
}
|
||||
|
||||
setupLog(cfg.Run.IsVerbose) // for `-v` to work until running of preRun function
|
||||
|
||||
configFile := cfg.Run.Config
|
||||
if cfg.Run.NoConfig && configFile != "" {
|
||||
logrus.Fatal("can't combine option --config and --no-config")
|
||||
}
|
||||
|
||||
if cfg.Run.NoConfig {
|
||||
return "", nil, fmt.Errorf("no need to use config")
|
||||
}
|
||||
|
||||
return configFile, fs.Args(), nil
|
||||
}
|
||||
|
||||
func (e *Executor) parseConfig() {
|
||||
// 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 := parseConfigOption()
|
||||
if err != nil {
|
||||
return // skippable error, e.g. --no-config
|
||||
}
|
||||
|
||||
if configFile != "" {
|
||||
viper.SetConfigFile(configFile)
|
||||
} else {
|
||||
setupConfigFileSearch(restArgs)
|
||||
}
|
||||
|
||||
e.parseConfigImpl()
|
||||
}
|
||||
|
||||
func fixSlicesFlags(fs *pflag.FlagSet) {
|
||||
// It's a dirty hack to set flag.Changed to true for every string slice flag.
|
||||
// It's necessary to merge config and command-line slices: otherwise command-line
|
||||
// flags will always overwrite ones from the config.
|
||||
fs.VisitAll(func(f *pflag.Flag) {
|
||||
if f.Value.Type() != "stringSlice" {
|
||||
return
|
||||
}
|
||||
|
||||
s, err := fs.GetStringSlice(f.Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s == nil { // assume that every string slice flag has nil as the default
|
||||
return
|
||||
}
|
||||
|
||||
// calling Set sets Changed to true: next Set calls will append, not overwrite
|
||||
_ = f.Value.Set(strings.Join(s, ","))
|
||||
})
|
||||
}
|
@ -117,7 +117,7 @@ func GetPathsForAnalysis(ctx context.Context, inputPaths []string, includeTests
|
||||
pr := NewPathResolver(stdExcludeDirs, []string{".go"}, includeTests)
|
||||
paths, err := pr.Resolve(inputPaths...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't resolve paths: %s", err)
|
||||
return nil, fmt.Errorf("can't resolve paths %v: %s", inputPaths, err)
|
||||
}
|
||||
|
||||
return processResolvedPaths(paths)
|
||||
|
@ -3,6 +3,7 @@ package lintersdb
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -192,7 +193,7 @@ func GetAllSupportedLinterConfigs() []linter.Config {
|
||||
})
|
||||
}
|
||||
|
||||
func getAllEnabledByDefaultLinters() []linter.Config {
|
||||
func GetAllEnabledByDefaultLinters() []linter.Config {
|
||||
var ret []linter.Config
|
||||
for _, lc := range GetAllSupportedLinterConfigs() {
|
||||
if lc.EnabledByDefault {
|
||||
@ -402,7 +403,7 @@ func GetEnabledLinters(cfg *config.Config) ([]linter.Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resultLintersSet := getEnabledLintersSet(&cfg.Linters, getAllEnabledByDefaultLinters())
|
||||
resultLintersSet := getEnabledLintersSet(&cfg.Linters, GetAllEnabledByDefaultLinters())
|
||||
|
||||
var resultLinters []linter.Config
|
||||
for _, lc := range resultLintersSet {
|
||||
@ -419,9 +420,11 @@ func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config) {
|
||||
for _, lc := range lcs {
|
||||
linterNames = append(linterNames, lc.Linter.Name())
|
||||
}
|
||||
logrus.Infof("Active linters: %s", linterNames)
|
||||
sort.StringSlice(linterNames).Sort()
|
||||
logrus.Infof("Active %d linters: %s", len(linterNames), linterNames)
|
||||
|
||||
if len(cfg.Linters.Presets) != 0 {
|
||||
sort.StringSlice(cfg.Linters.Presets).Sort()
|
||||
logrus.Infof("Active presets: %s", cfg.Linters.Presets)
|
||||
}
|
||||
}
|
||||
|
280
test/run_test.go
280
test/run_test.go
@ -1,12 +1,20 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -32,15 +40,17 @@ func TestCongratsMessageIfNoIssues(t *testing.T) {
|
||||
func TestDeadline(t *testing.T) {
|
||||
out, exitCode := runGolangciLint(t, "--deadline=1ms", "../...")
|
||||
assert.Equal(t, 4, exitCode)
|
||||
assert.Equal(t, "", out) // no 'Congrats! No issues were found.'
|
||||
assert.Contains(t, out, "deadline exceeded: try increase it by passing --deadline option")
|
||||
assert.NotContains(t, out, "Congrats! No issues were found.")
|
||||
}
|
||||
|
||||
func runGolangciLint(t *testing.T, args ...string) (string, int) {
|
||||
installBinary(t)
|
||||
|
||||
runArgs := append([]string{"run"}, args...)
|
||||
log.Printf("golangci-lint %s", strings.Join(runArgs, " "))
|
||||
cmd := exec.Command("golangci-lint", runArgs...)
|
||||
out, err := cmd.Output()
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
t.Logf("stderr: %s", exitError.Stderr)
|
||||
@ -57,6 +67,34 @@ func runGolangciLint(t *testing.T, args ...string) (string, int) {
|
||||
return string(out), ws.ExitStatus()
|
||||
}
|
||||
|
||||
func runGolangciLintWithYamlConfig(t *testing.T, cfg string, args ...string) string {
|
||||
out, ec := runGolangciLintWithYamlConfigWithCode(t, cfg, args...)
|
||||
assert.Equal(t, 0, ec)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func runGolangciLintWithYamlConfigWithCode(t *testing.T, cfg string, args ...string) (string, int) {
|
||||
f, err := ioutil.TempFile("", "golangci_lint_test")
|
||||
assert.NoError(t, err)
|
||||
f.Close()
|
||||
|
||||
cfgPath := f.Name() + ".yml"
|
||||
err = os.Rename(f.Name(), cfgPath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer os.Remove(cfgPath)
|
||||
|
||||
cfg = strings.TrimSpace(cfg)
|
||||
cfg = strings.Replace(cfg, "\t", " ", -1)
|
||||
|
||||
err = ioutil.WriteFile(cfgPath, []byte(cfg), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pargs := append([]string{"-c", cfgPath}, args...)
|
||||
return runGolangciLint(t, pargs...)
|
||||
}
|
||||
|
||||
func TestTestsAreLintedByDefault(t *testing.T) {
|
||||
out, exitCode := runGolangciLint(t, "./testdata/withtests")
|
||||
assert.Equal(t, 0, exitCode, out)
|
||||
@ -74,3 +112,241 @@ func TestConfigFileIsDetected(t *testing.T) {
|
||||
out, exitCode := runGolangciLint(t) // doesn't detect when no args
|
||||
checkNoIssuesRun(t, out, exitCode)
|
||||
}
|
||||
|
||||
func inSlice(s []string, v string) bool {
|
||||
for _, sv := range s {
|
||||
if sv == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getEnabledByDefaultFastLintersExcept(except ...string) []string {
|
||||
ebdl := lintersdb.GetAllEnabledByDefaultLinters()
|
||||
ret := []string{}
|
||||
for _, linter := range ebdl {
|
||||
if linter.DoesFullImport {
|
||||
continue
|
||||
}
|
||||
|
||||
if !inSlice(except, linter.Linter.Name()) {
|
||||
ret = append(ret, linter.Linter.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func getEnabledByDefaultLinters() []string {
|
||||
ebdl := lintersdb.GetAllEnabledByDefaultLinters()
|
||||
ret := []string{}
|
||||
for _, linter := range ebdl {
|
||||
ret = append(ret, linter.Linter.Name())
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func getEnabledByDefaultFastLintersWith(with ...string) []string {
|
||||
ebdl := lintersdb.GetAllEnabledByDefaultLinters()
|
||||
ret := append([]string{}, with...)
|
||||
for _, linter := range ebdl {
|
||||
if linter.DoesFullImport {
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, linter.Linter.Name())
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func mergeMegacheck(linters []string) []string {
|
||||
if inSlice(linters, "staticcheck") &&
|
||||
inSlice(linters, "gosimple") &&
|
||||
inSlice(linters, "unused") {
|
||||
ret := []string{"megacheck"}
|
||||
for _, linter := range linters {
|
||||
if !inSlice([]string{"staticcheck", "gosimple", "unused"}, linter) {
|
||||
ret = append(ret, linter)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
return linters
|
||||
}
|
||||
|
||||
func TestEnabledLinters(t *testing.T) {
|
||||
type tc struct {
|
||||
name string
|
||||
cfg string
|
||||
el []string
|
||||
args string
|
||||
noImplicitFast bool
|
||||
}
|
||||
|
||||
cases := []tc{
|
||||
{
|
||||
name: "disable govet in config",
|
||||
cfg: `
|
||||
linters:
|
||||
disable:
|
||||
- govet
|
||||
`,
|
||||
el: getEnabledByDefaultFastLintersExcept("govet"),
|
||||
},
|
||||
{
|
||||
name: "enable golint in config",
|
||||
cfg: `
|
||||
linters:
|
||||
enable:
|
||||
- golint
|
||||
`,
|
||||
el: getEnabledByDefaultFastLintersWith("golint"),
|
||||
},
|
||||
{
|
||||
name: "disable govet in cmd",
|
||||
args: "-Dgovet",
|
||||
el: getEnabledByDefaultFastLintersExcept("govet"),
|
||||
},
|
||||
{
|
||||
name: "enable gofmt in cmd and enable golint in config",
|
||||
args: "-Egofmt",
|
||||
cfg: `
|
||||
linters:
|
||||
enable:
|
||||
- golint
|
||||
`,
|
||||
el: getEnabledByDefaultFastLintersWith("golint", "gofmt"),
|
||||
},
|
||||
{
|
||||
name: "fast option in config",
|
||||
cfg: `
|
||||
linters:
|
||||
fast: true
|
||||
`,
|
||||
el: getEnabledByDefaultFastLintersWith(),
|
||||
noImplicitFast: true,
|
||||
},
|
||||
{
|
||||
name: "explicitly unset fast option in config",
|
||||
cfg: `
|
||||
linters:
|
||||
fast: false
|
||||
`,
|
||||
el: getEnabledByDefaultLinters(),
|
||||
noImplicitFast: true,
|
||||
},
|
||||
{
|
||||
name: "set fast option in command-line",
|
||||
args: "--fast",
|
||||
el: getEnabledByDefaultFastLintersWith(),
|
||||
noImplicitFast: true,
|
||||
},
|
||||
{
|
||||
name: "fast option in command-line has higher priority to enable",
|
||||
cfg: `
|
||||
linters:
|
||||
fast: false
|
||||
`,
|
||||
args: "--fast",
|
||||
el: getEnabledByDefaultFastLintersWith(),
|
||||
noImplicitFast: true,
|
||||
},
|
||||
{
|
||||
name: "fast option in command-line has higher priority to disable",
|
||||
cfg: `
|
||||
linters:
|
||||
fast: true
|
||||
`,
|
||||
args: "--fast=false",
|
||||
el: getEnabledByDefaultLinters(),
|
||||
noImplicitFast: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
runArgs := []string{"-v"}
|
||||
if !c.noImplicitFast {
|
||||
runArgs = append(runArgs, "--fast")
|
||||
}
|
||||
if c.args != "" {
|
||||
runArgs = append(runArgs, strings.Split(c.args, " ")...)
|
||||
}
|
||||
out := runGolangciLintWithYamlConfig(t, c.cfg, runArgs...)
|
||||
el := mergeMegacheck(c.el)
|
||||
sort.StringSlice(el).Sort()
|
||||
expectedLine := fmt.Sprintf("Active %d linters: [%s]", len(el), strings.Join(el, " "))
|
||||
assert.Contains(t, out, expectedLine)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnabledPresetsAreNotDuplicated(t *testing.T) {
|
||||
out, ec := runGolangciLint(t, "--no-config", "-v", "-p", "style,bugs")
|
||||
assert.Equal(t, 0, ec)
|
||||
assert.Contains(t, out, "Active presets: [bugs style]")
|
||||
}
|
||||
|
||||
func TestDisallowedOptionsInConfig(t *testing.T) {
|
||||
type tc struct {
|
||||
cfg string
|
||||
option string
|
||||
}
|
||||
|
||||
cases := []tc{
|
||||
{
|
||||
cfg: `
|
||||
ruN:
|
||||
Args:
|
||||
- 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
run:
|
||||
CPUProfilePath: path
|
||||
`,
|
||||
option: "--cpu-profile-path=path",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
run:
|
||||
MemProfilePath: path
|
||||
`,
|
||||
option: "--mem-profile-path=path",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
run:
|
||||
Verbose: true
|
||||
`,
|
||||
option: "-v",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
// Run with disallowed option set only in config
|
||||
_, ec := runGolangciLintWithYamlConfigWithCode(t, c.cfg)
|
||||
assert.Equal(t, 1, ec)
|
||||
|
||||
if c.option == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
args := []string{c.option, "--fast"}
|
||||
|
||||
// Run with disallowed option set only in command-line
|
||||
_, ec = runGolangciLint(t, args...)
|
||||
assert.Equal(t, 0, ec)
|
||||
|
||||
// Run with disallowed option set both in command-line and in config
|
||||
_, ec = runGolangciLintWithYamlConfigWithCode(t, c.cfg, args...)
|
||||
assert.Equal(t, 1, ec)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user