Skip to content

Commit 7dfc117

Browse files
authored
Merge pull request #58 from golangci/support/fix-double-parsing-of-options
#49: don't fill string slice 2 times because of double parsing of opt…
2 parents 594197d + 4fd5ebe commit 7dfc117

File tree

5 files changed

+93
-75
lines changed

5 files changed

+93
-75
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ golangci-lint linters
206206
- [depguard](https://github.com/OpenPeeDeeP/depguard) - Go linter that checks if package imports are in a list of acceptable packages
207207

208208
# Configuration
209+
Configuration file has lower priority than command-line: if the same bool/string/int option defined in the command-line
210+
and in the configuration file, option from command-line will be used.
211+
Slice options (e.g. list of enabled/disabled linters) are combined from the command-line and configuration file.
212+
209213
## Command-Line Options
210214
```
211215
golangci-lint run -h

README.md.tmpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ golangci-lint linters
164164
{{.DisabledByDefaultLinters}}
165165

166166
# Configuration
167+
Configuration file has lower priority than command-line: if the same bool/string/int option defined in the command-line
168+
and in the configuration file, option from command-line will be used.
169+
Slice options (e.g. list of enabled/disabled linters) are combined from the command-line and configuration file.
170+
167171
## Command-Line Options
168172
```
169173
golangci-lint run -h

pkg/commands/root.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/golangci/golangci-lint/pkg/printers"
1111
"github.com/sirupsen/logrus"
1212
"github.com/spf13/cobra"
13+
"github.com/spf13/pflag"
1314
)
1415

1516
func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
@@ -77,13 +78,17 @@ func (e *Executor) initRoot() {
7778
PersistentPreRun: e.persistentPostRun,
7879
PersistentPostRun: e.persistentPreRun,
7980
}
80-
rootCmd.PersistentFlags().BoolVarP(&e.cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
81-
rootCmd.PersistentFlags().StringVar(&e.cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
82-
rootCmd.PersistentFlags().StringVar(&e.cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file"))
83-
rootCmd.PersistentFlags().IntVarP(&e.cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), wh("Concurrency (default NumCPU)"))
84-
if e.commit != "" {
85-
rootCmd.PersistentFlags().BoolVar(&e.cfg.Run.PrintVersion, "version", false, wh("Print version"))
86-
}
8781

82+
e.initRootFlagSet(rootCmd.PersistentFlags())
8883
e.rootCmd = rootCmd
8984
}
85+
86+
func (e *Executor) initRootFlagSet(fs *pflag.FlagSet) {
87+
fs.BoolVarP(&e.cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
88+
fs.StringVar(&e.cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
89+
fs.StringVar(&e.cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file"))
90+
fs.IntVarP(&e.cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), wh("Concurrency (default NumCPU)"))
91+
if e.commit != "" {
92+
fs.BoolVar(&e.cfg.Run.PrintVersion, "version", false, wh("Print version"))
93+
}
94+
}

pkg/commands/run.go

Lines changed: 71 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -46,43 +46,33 @@ func wh(text string) string {
4646
return color.GreenString(text)
4747
}
4848

49-
func (e *Executor) initRun() {
50-
var runCmd = &cobra.Command{
51-
Use: "run",
52-
Short: welcomeMessage,
53-
Run: e.executeRun,
54-
}
55-
e.rootCmd.AddCommand(runCmd)
56-
57-
runCmd.SetOutput(printers.StdOut) // use custom output to properly color it in Windows terminals
58-
runCmd.Flags().SortFlags = false // sort them as they are defined here
59-
49+
func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
6050
hideFlag := func(name string) {
61-
if err := runCmd.Flags().MarkHidden(name); err != nil {
51+
if err := fs.MarkHidden(name); err != nil {
6252
panic(err)
6353
}
6454
}
6555

6656
// Output config
6757
oc := &e.cfg.Output
68-
runCmd.Flags().StringVar(&oc.Format, "out-format",
58+
fs.StringVar(&oc.Format, "out-format",
6959
config.OutFormatColoredLineNumber,
7060
wh(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))))
71-
runCmd.Flags().BoolVar(&oc.PrintIssuedLine, "print-issued-lines", true, wh("Print lines of code with issue"))
72-
runCmd.Flags().BoolVar(&oc.PrintLinterName, "print-linter-name", true, wh("Print linter name in issue line"))
73-
runCmd.Flags().BoolVar(&oc.PrintWelcomeMessage, "print-welcome", false, wh("Print welcome message"))
61+
fs.BoolVar(&oc.PrintIssuedLine, "print-issued-lines", true, wh("Print lines of code with issue"))
62+
fs.BoolVar(&oc.PrintLinterName, "print-linter-name", true, wh("Print linter name in issue line"))
63+
fs.BoolVar(&oc.PrintWelcomeMessage, "print-welcome", false, wh("Print welcome message"))
7464
hideFlag("print-welcome") // no longer used
7565

7666
// Run config
7767
rc := &e.cfg.Run
78-
runCmd.Flags().IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
68+
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
7969
1, wh("Exit code when issues were found"))
80-
runCmd.Flags().StringSliceVar(&rc.BuildTags, "build-tags", []string{}, wh("Build tags (not all linters support them)"))
81-
runCmd.Flags().DurationVar(&rc.Deadline, "deadline", time.Minute, wh("Deadline for total work"))
82-
runCmd.Flags().BoolVar(&rc.AnalyzeTests, "tests", false, wh("Analyze tests (*_test.go)"))
83-
runCmd.Flags().BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false, wh("Print avg and max memory usage of golangci-lint and total time"))
84-
runCmd.Flags().StringVarP(&rc.Config, "config", "c", "", wh("Read config from file path `PATH`"))
85-
runCmd.Flags().BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config"))
70+
fs.StringSliceVar(&rc.BuildTags, "build-tags", []string{}, wh("Build tags (not all linters support them)"))
71+
fs.DurationVar(&rc.Deadline, "deadline", time.Minute, wh("Deadline for total work"))
72+
fs.BoolVar(&rc.AnalyzeTests, "tests", false, wh("Analyze tests (*_test.go)"))
73+
fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false, wh("Print avg and max memory usage of golangci-lint and total time"))
74+
fs.StringVarP(&rc.Config, "config", "c", "", wh("Read config from file path `PATH`"))
75+
fs.BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config"))
8676

8777
// Linters settings config
8878
lsc := &e.cfg.LintersSettings
@@ -91,72 +81,88 @@ func (e *Executor) initRun() {
9181
// but when number of linters started to grow it became ovious that
9282
// we can't fill 90% of flags by linters settings: common flags became hard to find.
9383
// New linters settings should be done only through config file.
94-
runCmd.Flags().BoolVar(&lsc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions", false, "Errcheck: check for ignored type assertion results")
84+
fs.BoolVar(&lsc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions", false, "Errcheck: check for ignored type assertion results")
9585
hideFlag("errcheck.check-type-assertions")
9686

97-
runCmd.Flags().BoolVar(&lsc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false, "Errcheck: check for errors assigned to blank identifier: _ = errFunc()")
87+
fs.BoolVar(&lsc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false, "Errcheck: check for errors assigned to blank identifier: _ = errFunc()")
9888
hideFlag("errcheck.check-blank")
9989

100-
runCmd.Flags().BoolVar(&lsc.Govet.CheckShadowing, "govet.check-shadowing", false, "Govet: check for shadowed variables")
90+
fs.BoolVar(&lsc.Govet.CheckShadowing, "govet.check-shadowing", false, "Govet: check for shadowed variables")
10191
hideFlag("govet.check-shadowing")
10292

103-
runCmd.Flags().Float64Var(&lsc.Golint.MinConfidence, "golint.min-confidence", 0.8, "Golint: minimum confidence of a problem to print it")
93+
fs.Float64Var(&lsc.Golint.MinConfidence, "golint.min-confidence", 0.8, "Golint: minimum confidence of a problem to print it")
10494
hideFlag("golint.min-confidence")
10595

106-
runCmd.Flags().BoolVar(&lsc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code")
96+
fs.BoolVar(&lsc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code")
10797
hideFlag("gofmt.simplify")
10898

109-
runCmd.Flags().IntVar(&lsc.Gocyclo.MinComplexity, "gocyclo.min-complexity",
99+
fs.IntVar(&lsc.Gocyclo.MinComplexity, "gocyclo.min-complexity",
110100
30, "Minimal complexity of function to report it")
111101
hideFlag("gocyclo.min-complexity")
112102

113-
runCmd.Flags().BoolVar(&lsc.Maligned.SuggestNewOrder, "maligned.suggest-new", false, "Maligned: print suggested more optimal struct fields ordering")
103+
fs.BoolVar(&lsc.Maligned.SuggestNewOrder, "maligned.suggest-new", false, "Maligned: print suggested more optimal struct fields ordering")
114104
hideFlag("maligned.suggest-new")
115105

116-
runCmd.Flags().IntVar(&lsc.Dupl.Threshold, "dupl.threshold",
106+
fs.IntVar(&lsc.Dupl.Threshold, "dupl.threshold",
117107
150, "Dupl: Minimal threshold to detect copy-paste")
118108
hideFlag("dupl.threshold")
119109

120-
runCmd.Flags().IntVar(&lsc.Goconst.MinStringLen, "goconst.min-len",
110+
fs.IntVar(&lsc.Goconst.MinStringLen, "goconst.min-len",
121111
3, "Goconst: minimum constant string length")
122112
hideFlag("goconst.min-len")
123-
runCmd.Flags().IntVar(&lsc.Goconst.MinOccurrencesCount, "goconst.min-occurrences",
113+
fs.IntVar(&lsc.Goconst.MinOccurrencesCount, "goconst.min-occurrences",
124114
3, "Goconst: minimum occurrences of constant string count to trigger issue")
125115
hideFlag("goconst.min-occurrences")
126116

127117
// (@dixonwille) These flag is only used for testing purposes.
128-
runCmd.Flags().StringSliceVar(&lsc.Depguard.Packages, "depguard.packages", nil,
118+
fs.StringSliceVar(&lsc.Depguard.Packages, "depguard.packages", nil,
129119
"Depguard: packages to add to the list")
130120
hideFlag("depguard.packages")
131121

132-
runCmd.Flags().BoolVar(&lsc.Depguard.IncludeGoRoot, "depguard.include-go-root", false,
122+
fs.BoolVar(&lsc.Depguard.IncludeGoRoot, "depguard.include-go-root", false,
133123
"Depguard: check list against standard lib")
134124
hideFlag("depguard.include-go-root")
135125

136126
// Linters config
137127
lc := &e.cfg.Linters
138-
runCmd.Flags().StringSliceVarP(&lc.Enable, "enable", "E", []string{}, wh("Enable specific linter"))
139-
runCmd.Flags().StringSliceVarP(&lc.Disable, "disable", "D", []string{}, wh("Disable specific linter"))
140-
runCmd.Flags().BoolVar(&lc.EnableAll, "enable-all", false, wh("Enable all linters"))
141-
runCmd.Flags().BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters"))
142-
runCmd.Flags().StringSliceVarP(&lc.Presets, "presets", "p", []string{},
128+
fs.StringSliceVarP(&lc.Enable, "enable", "E", []string{}, wh("Enable specific linter"))
129+
fs.StringSliceVarP(&lc.Disable, "disable", "D", []string{}, wh("Disable specific linter"))
130+
fs.BoolVar(&lc.EnableAll, "enable-all", false, wh("Enable all linters"))
131+
fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters"))
132+
fs.StringSliceVarP(&lc.Presets, "presets", "p", []string{},
143133
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(), "|"))))
144-
runCmd.Flags().BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set"))
134+
fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set"))
145135

146136
// Issues config
147137
ic := &e.cfg.Issues
148-
runCmd.Flags().StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", []string{}, wh("Exclude issue by regexp"))
149-
runCmd.Flags().BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultExcludeHelp())
138+
fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", []string{}, wh("Exclude issue by regexp"))
139+
fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultExcludeHelp())
150140

151-
runCmd.Flags().IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50, wh("Maximum issues count per one linter. Set to 0 to disable"))
152-
runCmd.Flags().IntVar(&ic.MaxSameIssues, "max-same-issues", 3, wh("Maximum count of issues with the same text. Set to 0 to disable"))
141+
fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50, wh("Maximum issues count per one linter. Set to 0 to disable"))
142+
fs.IntVar(&ic.MaxSameIssues, "max-same-issues", 3, wh("Maximum count of issues with the same text. Set to 0 to disable"))
153143

154-
runCmd.Flags().BoolVarP(&ic.Diff, "new", "n", false,
144+
fs.BoolVarP(&ic.Diff, "new", "n", false,
155145
wh("Show only new issues: if there are unstaged changes or untracked files, only those changes are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at the moment of integration: much better don't allow issues in new code"))
156-
runCmd.Flags().StringVar(&ic.DiffFromRevision, "new-from-rev", "", wh("Show only new issues created after git revision `REV`"))
157-
runCmd.Flags().StringVar(&ic.DiffPatchFilePath, "new-from-patch", "", wh("Show only new issues created in git patch with file path `PATH`"))
146+
fs.StringVar(&ic.DiffFromRevision, "new-from-rev", "", wh("Show only new issues created after git revision `REV`"))
147+
fs.StringVar(&ic.DiffPatchFilePath, "new-from-patch", "", wh("Show only new issues created in git patch with file path `PATH`"))
148+
149+
}
150+
151+
func (e *Executor) initRun() {
152+
var runCmd = &cobra.Command{
153+
Use: "run",
154+
Short: welcomeMessage,
155+
Run: e.executeRun,
156+
}
157+
e.rootCmd.AddCommand(runCmd)
158158

159-
e.parseConfig(runCmd)
159+
runCmd.SetOutput(printers.StdOut) // use custom output to properly color it in Windows terminals
160+
161+
fs := runCmd.Flags()
162+
fs.SortFlags = false // sort them as they are defined here
163+
e.initFlagSet(fs)
164+
165+
e.parseConfig()
160166
}
161167

162168
func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) {
@@ -274,16 +280,28 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
274280
}
275281
}
276282

277-
func (e *Executor) parseConfig(cmd *cobra.Command) {
278-
// XXX: hack with double parsing to access "config" option here
279-
if err := cmd.ParseFlags(os.Args); err != nil {
283+
func (e *Executor) parseConfig() {
284+
// XXX: hack with double parsing for 2 purposes:
285+
// 1. to access "config" option here.
286+
// 2. to give config less priority than command line.
287+
288+
// We use another pflag.FlagSet here to not set `changed` flag
289+
// on cmd.Flags() options. Otherwise string slice options will be duplicated.
290+
fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError)
291+
292+
// Don't do `fs.AddFlagSet(cmd.Flags())` because it shared flags representations:
293+
// `changed` variable inside string slice vars will be shared.
294+
e.initFlagSet(fs)
295+
e.initRootFlagSet(fs)
296+
297+
if err := fs.Parse(os.Args); err != nil {
280298
if err == pflag.ErrHelp {
281299
return
282300
}
283301
logrus.Fatalf("Can't parse args: %s", err)
284302
}
285303

286-
if err := viper.BindPFlags(cmd.Flags()); err != nil {
304+
if err := viper.BindPFlags(fs); err != nil {
287305
logrus.Fatalf("Can't bind cobra's flags to viper: %s", err)
288306
}
289307

pkg/lint/lintersdb/lintersdb.go

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ func getEnabledLintersSet(lcfg *config.Linters, enabledByDefaultLinters []linter
323323
// --presets can only add linters to default set
324324
for _, p := range lcfg.Presets {
325325
for _, lc := range GetAllLinterConfigsForPreset(p) {
326+
lc := lc
326327
resultLintersSet[lc.Linter.Name()] = &lc
327328
}
328329
}
@@ -413,20 +414,6 @@ func GetEnabledLinters(cfg *config.Config) ([]linter.Config, error) {
413414
return resultLinters, nil
414415
}
415416

416-
func uniqStrings(ss []string) []string {
417-
us := map[string]bool{}
418-
for _, s := range ss {
419-
us[s] = true
420-
}
421-
422-
var ret []string
423-
for k := range us {
424-
ret = append(ret, k)
425-
}
426-
427-
return ret
428-
}
429-
430417
func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config) {
431418
var linterNames []string
432419
for _, lc := range lcs {
@@ -435,6 +422,6 @@ func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config) {
435422
logrus.Infof("Active linters: %s", linterNames)
436423

437424
if len(cfg.Linters.Presets) != 0 {
438-
logrus.Infof("Active presets: %s", uniqStrings(cfg.Linters.Presets))
425+
logrus.Infof("Active presets: %s", cfg.Linters.Presets)
439426
}
440427
}

0 commit comments

Comments
 (0)