@@ -2,20 +2,17 @@ package commands
2
2
3
3
import (
4
4
"context"
5
- "errors"
6
5
"fmt"
7
6
"go/token"
8
7
"io/ioutil"
9
8
"log"
10
9
"os"
11
- "path/filepath"
12
10
"runtime"
13
11
"strings"
14
12
"time"
15
13
16
14
"github.com/fatih/color"
17
15
"github.com/golangci/golangci-lint/pkg/config"
18
- "github.com/golangci/golangci-lint/pkg/fsutils"
19
16
"github.com/golangci/golangci-lint/pkg/lint"
20
17
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
21
18
"github.com/golangci/golangci-lint/pkg/printers"
@@ -24,7 +21,6 @@ import (
24
21
"github.com/sirupsen/logrus"
25
22
"github.com/spf13/cobra"
26
23
"github.com/spf13/pflag"
27
- "github.com/spf13/viper"
28
24
)
29
25
30
26
const (
@@ -48,15 +44,15 @@ func wh(text string) string {
48
44
return color .GreenString (text )
49
45
}
50
46
51
- func ( e * Executor ) initFlagSet (fs * pflag.FlagSet ) {
47
+ func initFlagSet (fs * pflag.FlagSet , cfg * config. Config ) {
52
48
hideFlag := func (name string ) {
53
49
if err := fs .MarkHidden (name ); err != nil {
54
50
panic (err )
55
51
}
56
52
}
57
53
58
54
// Output config
59
- oc := & e . cfg .Output
55
+ oc := & cfg .Output
60
56
fs .StringVar (& oc .Format , "out-format" ,
61
57
config .OutFormatColoredLineNumber ,
62
58
wh (fmt .Sprintf ("Format of output: %s" , strings .Join (config .OutFormats , "|" ))))
@@ -66,18 +62,18 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
66
62
hideFlag ("print-welcome" ) // no longer used
67
63
68
64
// Run config
69
- rc := & e . cfg .Run
65
+ rc := & cfg .Run
70
66
fs .IntVar (& rc .ExitCodeIfIssuesFound , "issues-exit-code" ,
71
67
1 , wh ("Exit code when issues were found" ))
72
- fs .StringSliceVar (& rc .BuildTags , "build-tags" , [] string {} , wh ("Build tags (not all linters support them)" ))
68
+ fs .StringSliceVar (& rc .BuildTags , "build-tags" , nil , wh ("Build tags (not all linters support them)" ))
73
69
fs .DurationVar (& rc .Deadline , "deadline" , time .Minute , wh ("Deadline for total work" ))
74
70
fs .BoolVar (& rc .AnalyzeTests , "tests" , true , wh ("Analyze tests (*_test.go)" ))
75
71
fs .BoolVar (& rc .PrintResourcesUsage , "print-resources-usage" , false , wh ("Print avg and max memory usage of golangci-lint and total time" ))
76
72
fs .StringVarP (& rc .Config , "config" , "c" , "" , wh ("Read config from file path `PATH`" ))
77
73
fs .BoolVar (& rc .NoConfig , "no-config" , false , wh ("Don't read config" ))
78
74
79
75
// Linters settings config
80
- lsc := & e . cfg .LintersSettings
76
+ lsc := & cfg .LintersSettings
81
77
82
78
// Hide all linters settings flags: they were initially visible,
83
79
// but when number of linters started to grow it became ovious that
@@ -126,18 +122,18 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
126
122
hideFlag ("depguard.include-go-root" )
127
123
128
124
// Linters config
129
- lc := & e . cfg .Linters
130
- fs .StringSliceVarP (& lc .Enable , "enable" , "E" , [] string {} , wh ("Enable specific linter" ))
131
- fs .StringSliceVarP (& lc .Disable , "disable" , "D" , [] string {} , wh ("Disable specific linter" ))
125
+ lc := & cfg .Linters
126
+ fs .StringSliceVarP (& lc .Enable , "enable" , "E" , nil , wh ("Enable specific linter" ))
127
+ fs .StringSliceVarP (& lc .Disable , "disable" , "D" , nil , wh ("Disable specific linter" ))
132
128
fs .BoolVar (& lc .EnableAll , "enable-all" , false , wh ("Enable all linters" ))
133
129
fs .BoolVar (& lc .DisableAll , "disable-all" , false , wh ("Disable all linters" ))
134
- fs .StringSliceVarP (& lc .Presets , "presets" , "p" , [] string {} ,
130
+ fs .StringSliceVarP (& lc .Presets , "presets" , "p" , nil ,
135
131
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 (), "|" ))))
136
132
fs .BoolVar (& lc .Fast , "fast" , false , wh ("Run only fast linters from enabled linters set" ))
137
133
138
134
// Issues config
139
- ic := & e . cfg .Issues
140
- fs .StringSliceVarP (& ic .ExcludePatterns , "exclude" , "e" , [] string {} , wh ("Exclude issue by regexp" ))
135
+ ic := & cfg .Issues
136
+ fs .StringSliceVarP (& ic .ExcludePatterns , "exclude" , "e" , nil , wh ("Exclude issue by regexp" ))
141
137
fs .BoolVar (& ic .UseDefaultExcludes , "exclude-use-default" , true , getDefaultExcludeHelp ())
142
138
143
139
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() {
162
158
163
159
fs := runCmd .Flags ()
164
160
fs .SortFlags = false // sort them as they are defined here
165
- e . initFlagSet (fs )
161
+ initFlagSet (fs , e . cfg )
166
162
163
+ // init e.cfg by values from config: flags parse will see these values
164
+ // like the default ones. It will overwrite them only if the same option
165
+ // is found in command-line: it's ok, command-line has higher priority.
167
166
e .parseConfig ()
167
+
168
+ // Slice options must be explicitly set for properly merging.
169
+ fixSlicesFlags (fs )
168
170
}
169
171
170
172
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) {
289
291
}
290
292
}
291
293
292
- func (e * Executor ) parseConfig () {
293
- // XXX: hack with double parsing for 2 purposes:
294
- // 1. to access "config" option here.
295
- // 2. to give config less priority than command line.
296
-
297
- // We use another pflag.FlagSet here to not set `changed` flag
298
- // on cmd.Flags() options. Otherwise string slice options will be duplicated.
299
- fs := pflag .NewFlagSet ("config flag set" , pflag .ContinueOnError )
300
-
301
- // Don't do `fs.AddFlagSet(cmd.Flags())` because it shared flags representations:
302
- // `changed` variable inside string slice vars will be shared.
303
- e .initFlagSet (fs )
304
- e .initRootFlagSet (fs )
305
-
306
- fs .Usage = func () {} // otherwise help text will be printed twice
307
- if err := fs .Parse (os .Args ); err != nil {
308
- if err == pflag .ErrHelp {
309
- return
310
- }
311
- logrus .Fatalf ("Can't parse args: %s" , err )
312
- }
313
-
314
- e .setupLog () // for `-v` to work until running of preRun function
315
-
316
- if err := viper .BindPFlags (fs ); err != nil {
317
- logrus .Fatalf ("Can't bind cobra's flags to viper: %s" , err )
318
- }
319
-
320
- viper .SetEnvPrefix ("GOLANGCI" )
321
- viper .SetEnvKeyReplacer (strings .NewReplacer ("." , "_" ))
322
- viper .AutomaticEnv ()
323
-
324
- configFile := e .cfg .Run .Config
325
- if e .cfg .Run .NoConfig && configFile != "" {
326
- logrus .Fatal ("can't combine option --config and --no-config" )
327
- }
328
-
329
- if e .cfg .Run .NoConfig {
330
- return
331
- }
332
-
333
- if configFile != "" {
334
- viper .SetConfigFile (configFile )
335
- } else {
336
- setupConfigFileSearch (fs .Args ())
337
- }
338
-
339
- e .parseConfigImpl ()
340
- }
341
-
342
- func setupConfigFileSearch (args []string ) {
343
- // skip all args ([golangci-lint, run/linters]) before files/dirs list
344
- for len (args ) != 0 {
345
- if args [0 ] == "run" {
346
- args = args [1 :]
347
- break
348
- }
349
-
350
- args = args [1 :]
351
- }
352
-
353
- // find first file/dir arg
354
- firstArg := "./..."
355
- if len (args ) != 0 {
356
- firstArg = args [0 ]
357
- }
358
-
359
- absStartPath , err := filepath .Abs (firstArg )
360
- if err != nil {
361
- logrus .Infof ("Can't make abs path for %q: %s" , firstArg , err )
362
- absStartPath = filepath .Clean (firstArg )
363
- }
364
-
365
- // start from it
366
- var curDir string
367
- if fsutils .IsDir (absStartPath ) {
368
- curDir = absStartPath
369
- } else {
370
- curDir = filepath .Dir (absStartPath )
371
- }
372
-
373
- // find all dirs from it up to the root
374
- configSearchPaths := []string {"./" }
375
- for {
376
- configSearchPaths = append (configSearchPaths , curDir )
377
- newCurDir := filepath .Dir (curDir )
378
- if curDir == newCurDir || newCurDir == "" {
379
- break
380
- }
381
- curDir = newCurDir
382
- }
383
-
384
- logrus .Infof ("Config search paths: %s" , configSearchPaths )
385
- viper .SetConfigName (".golangci" )
386
- for _ , p := range configSearchPaths {
387
- viper .AddConfigPath (p )
388
- }
389
- }
390
-
391
- func getRelPath (p string ) string {
392
- wd , err := os .Getwd ()
393
- if err != nil {
394
- logrus .Infof ("Can't get wd: %s" , err )
395
- return p
396
- }
397
-
398
- r , err := filepath .Rel (wd , p )
399
- if err != nil {
400
- logrus .Infof ("Can't make path %s relative to %s: %s" , p , wd , err )
401
- return p
402
- }
403
-
404
- return r
405
- }
406
-
407
- func (e * Executor ) parseConfigImpl () {
408
- commandLineConfig := * e .cfg // make copy
409
-
410
- if err := viper .ReadInConfig (); err != nil {
411
- if _ , ok := err .(viper.ConfigFileNotFoundError ); ok {
412
- return
413
- }
414
- logrus .Fatalf ("Can't read viper config: %s" , err )
415
- }
416
-
417
- usedConfigFile := viper .ConfigFileUsed ()
418
- if usedConfigFile == "" {
419
- return
420
- }
421
- logrus .Infof ("Used config file %s" , getRelPath (usedConfigFile ))
422
-
423
- if err := viper .Unmarshal (& e .cfg ); err != nil {
424
- logrus .Fatalf ("Can't unmarshal config by viper: %s" , err )
425
- }
426
-
427
- if err := e .validateConfig (& commandLineConfig ); err != nil {
428
- logrus .Fatal (err )
429
- }
430
-
431
- if e .cfg .InternalTest { // just for testing purposes: to detect config file usage
432
- fmt .Fprintln (printers .StdOut , "test" )
433
- os .Exit (0 )
434
- }
435
- }
436
-
437
- func (e * Executor ) validateConfig (commandLineConfig * config.Config ) error {
438
- c := e .cfg
439
- if len (c .Run .Args ) != 0 {
440
- return errors .New ("option run.args in config isn't supported now" )
441
- }
442
-
443
- if commandLineConfig .Run .CPUProfilePath == "" && c .Run .CPUProfilePath != "" {
444
- return errors .New ("option run.cpuprofilepath in config isn't allowed" )
445
- }
446
-
447
- if commandLineConfig .Run .MemProfilePath == "" && c .Run .MemProfilePath != "" {
448
- return errors .New ("option run.memprofilepath in config isn't allowed" )
449
- }
450
-
451
- if ! commandLineConfig .Run .IsVerbose && c .Run .IsVerbose {
452
- return errors .New ("can't set run.verbose option with config: only on command-line" )
453
- }
454
-
455
- return nil
456
- }
457
-
458
294
func watchResources (ctx context.Context , done chan struct {}) {
459
295
startedAt := time .Now ()
460
296
0 commit comments