#66: properly merge (not overwrite) slice flags from config and command-line

This commit is contained in:
Denis Isaev 2018-06-05 23:54:05 +03:00
parent 9c07341b36
commit afc4b4344f
No known key found for this signature in database
GPG Key ID: A36A0EC8E27A1A01
6 changed files with 531 additions and 199 deletions

View File

@ -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"))
}
}

View File

@ -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
View 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, ","))
})
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)
}
}