#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"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
"github.com/golangci/golangci-lint/pkg/printers"
|
"github.com/golangci/golangci-lint/pkg/printers"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e *Executor) setupLog() {
|
func setupLog(isVerbose bool) {
|
||||||
log.SetFlags(0) // don't print time
|
log.SetFlags(0) // don't print time
|
||||||
if e.cfg.Run.IsVerbose {
|
if isVerbose {
|
||||||
logrus.SetLevel(logrus.InfoLevel)
|
logrus.SetLevel(logrus.InfoLevel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +29,7 @@ func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
|
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
|
||||||
|
|
||||||
e.setupLog()
|
setupLog(e.cfg.Run.IsVerbose)
|
||||||
|
|
||||||
if e.cfg.Run.CPUProfilePath != "" {
|
if e.cfg.Run.CPUProfilePath != "" {
|
||||||
f, err := os.Create(e.cfg.Run.CPUProfilePath)
|
f, err := os.Create(e.cfg.Run.CPUProfilePath)
|
||||||
@ -81,16 +82,16 @@ func (e *Executor) initRoot() {
|
|||||||
PersistentPostRun: e.persistentPostRun,
|
PersistentPostRun: e.persistentPostRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
e.initRootFlagSet(rootCmd.PersistentFlags())
|
initRootFlagSet(rootCmd.PersistentFlags(), e.cfg, e.needVersionOption())
|
||||||
e.rootCmd = rootCmd
|
e.rootCmd = rootCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) initRootFlagSet(fs *pflag.FlagSet) {
|
func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bool) {
|
||||||
fs.BoolVarP(&e.cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
|
fs.BoolVarP(&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(&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.StringVar(&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)"))
|
fs.IntVarP(&cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), wh("Concurrency (default NumCPU)"))
|
||||||
if e.date != "" {
|
if needVersionOption {
|
||||||
fs.BoolVar(&e.cfg.Run.PrintVersion, "version", false, wh("Print version"))
|
fs.BoolVar(&cfg.Run.PrintVersion, "version", false, wh("Print version"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,17 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/token"
|
"go/token"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"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"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||||
"github.com/golangci/golangci-lint/pkg/printers"
|
"github.com/golangci/golangci-lint/pkg/printers"
|
||||||
@ -24,7 +21,6 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -48,7 +44,7 @@ func wh(text string) string {
|
|||||||
return color.GreenString(text)
|
return color.GreenString(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
|
func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
|
||||||
hideFlag := func(name string) {
|
hideFlag := func(name string) {
|
||||||
if err := fs.MarkHidden(name); err != nil {
|
if err := fs.MarkHidden(name); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -56,7 +52,7 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output config
|
// Output config
|
||||||
oc := &e.cfg.Output
|
oc := &cfg.Output
|
||||||
fs.StringVar(&oc.Format, "out-format",
|
fs.StringVar(&oc.Format, "out-format",
|
||||||
config.OutFormatColoredLineNumber,
|
config.OutFormatColoredLineNumber,
|
||||||
wh(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))))
|
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
|
hideFlag("print-welcome") // no longer used
|
||||||
|
|
||||||
// Run config
|
// Run config
|
||||||
rc := &e.cfg.Run
|
rc := &cfg.Run
|
||||||
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
|
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
|
||||||
1, wh("Exit code when issues were found"))
|
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.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.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"))
|
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"))
|
fs.BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config"))
|
||||||
|
|
||||||
// Linters settings config
|
// Linters settings config
|
||||||
lsc := &e.cfg.LintersSettings
|
lsc := &cfg.LintersSettings
|
||||||
|
|
||||||
// Hide all linters settings flags: they were initially visible,
|
// Hide all linters settings flags: they were initially visible,
|
||||||
// but when number of linters started to grow it became ovious that
|
// 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")
|
hideFlag("depguard.include-go-root")
|
||||||
|
|
||||||
// Linters config
|
// Linters config
|
||||||
lc := &e.cfg.Linters
|
lc := &cfg.Linters
|
||||||
fs.StringSliceVarP(&lc.Enable, "enable", "E", []string{}, wh("Enable specific linter"))
|
fs.StringSliceVarP(&lc.Enable, "enable", "E", nil, wh("Enable specific linter"))
|
||||||
fs.StringSliceVarP(&lc.Disable, "disable", "D", []string{}, wh("Disable 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.EnableAll, "enable-all", false, wh("Enable all linters"))
|
||||||
fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable 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(), "|"))))
|
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"))
|
fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set"))
|
||||||
|
|
||||||
// Issues config
|
// Issues config
|
||||||
ic := &e.cfg.Issues
|
ic := &cfg.Issues
|
||||||
fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", []string{}, wh("Exclude issue by regexp"))
|
fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", nil, wh("Exclude issue by regexp"))
|
||||||
fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultExcludeHelp())
|
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"))
|
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 := runCmd.Flags()
|
||||||
fs.SortFlags = false // sort them as they are defined here
|
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()
|
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) {
|
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{}) {
|
func watchResources(ctx context.Context, done chan struct{}) {
|
||||||
startedAt := time.Now()
|
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)
|
pr := NewPathResolver(stdExcludeDirs, []string{".go"}, includeTests)
|
||||||
paths, err := pr.Resolve(inputPaths...)
|
paths, err := pr.Resolve(inputPaths...)
|
||||||
if err != nil {
|
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)
|
return processResolvedPaths(paths)
|
||||||
|
@ -3,6 +3,7 @@ package lintersdb
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -192,7 +193,7 @@ func GetAllSupportedLinterConfigs() []linter.Config {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAllEnabledByDefaultLinters() []linter.Config {
|
func GetAllEnabledByDefaultLinters() []linter.Config {
|
||||||
var ret []linter.Config
|
var ret []linter.Config
|
||||||
for _, lc := range GetAllSupportedLinterConfigs() {
|
for _, lc := range GetAllSupportedLinterConfigs() {
|
||||||
if lc.EnabledByDefault {
|
if lc.EnabledByDefault {
|
||||||
@ -402,7 +403,7 @@ func GetEnabledLinters(cfg *config.Config) ([]linter.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resultLintersSet := getEnabledLintersSet(&cfg.Linters, getAllEnabledByDefaultLinters())
|
resultLintersSet := getEnabledLintersSet(&cfg.Linters, GetAllEnabledByDefaultLinters())
|
||||||
|
|
||||||
var resultLinters []linter.Config
|
var resultLinters []linter.Config
|
||||||
for _, lc := range resultLintersSet {
|
for _, lc := range resultLintersSet {
|
||||||
@ -419,9 +420,11 @@ func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config) {
|
|||||||
for _, lc := range lcs {
|
for _, lc := range lcs {
|
||||||
linterNames = append(linterNames, lc.Linter.Name())
|
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 {
|
if len(cfg.Linters.Presets) != 0 {
|
||||||
|
sort.StringSlice(cfg.Linters.Presets).Sort()
|
||||||
logrus.Infof("Active presets: %s", cfg.Linters.Presets)
|
logrus.Infof("Active presets: %s", cfg.Linters.Presets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
280
test/run_test.go
280
test/run_test.go
@ -1,12 +1,20 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,15 +40,17 @@ func TestCongratsMessageIfNoIssues(t *testing.T) {
|
|||||||
func TestDeadline(t *testing.T) {
|
func TestDeadline(t *testing.T) {
|
||||||
out, exitCode := runGolangciLint(t, "--deadline=1ms", "../...")
|
out, exitCode := runGolangciLint(t, "--deadline=1ms", "../...")
|
||||||
assert.Equal(t, 4, exitCode)
|
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) {
|
func runGolangciLint(t *testing.T, args ...string) (string, int) {
|
||||||
installBinary(t)
|
installBinary(t)
|
||||||
|
|
||||||
runArgs := append([]string{"run"}, args...)
|
runArgs := append([]string{"run"}, args...)
|
||||||
|
log.Printf("golangci-lint %s", strings.Join(runArgs, " "))
|
||||||
cmd := exec.Command("golangci-lint", runArgs...)
|
cmd := exec.Command("golangci-lint", runArgs...)
|
||||||
out, err := cmd.Output()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
t.Logf("stderr: %s", exitError.Stderr)
|
t.Logf("stderr: %s", exitError.Stderr)
|
||||||
@ -57,6 +67,34 @@ func runGolangciLint(t *testing.T, args ...string) (string, int) {
|
|||||||
return string(out), ws.ExitStatus()
|
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) {
|
func TestTestsAreLintedByDefault(t *testing.T) {
|
||||||
out, exitCode := runGolangciLint(t, "./testdata/withtests")
|
out, exitCode := runGolangciLint(t, "./testdata/withtests")
|
||||||
assert.Equal(t, 0, exitCode, out)
|
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
|
out, exitCode := runGolangciLint(t) // doesn't detect when no args
|
||||||
checkNoIssuesRun(t, out, exitCode)
|
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