Skip to content

Commit 4ff575f

Browse files
committed
#60: search config file in directories from file path up to root
1 parent b082671 commit 4ff575f

File tree

9 files changed

+130
-17
lines changed

9 files changed

+130
-17
lines changed

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,16 @@ GolangCI-Lint looks for next config paths in the current directory:
288288
- `.golangci.toml`
289289
- `.golangci.json`
290290

291+
GolangCI-Lint also searches config file in all directories from directory of the first analyzed path up to the root.
292+
To see which config file is used and where it was searched run golangci-lint with `-v` option.
293+
291294
Configuration options inside the file are identical to command-line options.
295+
You can configure specific linters options only within configuration file, it can't be done with command-line.
296+
292297
There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) with all supported options.
293298

294-
It's a [.golangci.yml](https://github.com/golangci/golangci-lint/blob/master/.golangci.yml) of this repo: we enable more linters than by default and make their settings more strict:
299+
It's a [.golangci.yml](https://github.com/golangci/golangci-lint/blob/master/.golangci.yml) of this repo: we enable more linters
300+
than by default and make their settings more strict:
295301
```yaml
296302
linters-settings:
297303
govet:

README.md.tmpl

+7-1
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,16 @@ GolangCI-Lint looks for next config paths in the current directory:
180180
- `.golangci.toml`
181181
- `.golangci.json`
182182

183+
GolangCI-Lint also searches config file in all directories from directory of the first analyzed path up to the root.
184+
To see which config file is used and where it was searched run golangci-lint with `-v` option.
185+
183186
Configuration options inside the file are identical to command-line options.
187+
You can configure specific linters options only within configuration file, it can't be done with command-line.
188+
184189
There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) with all supported options.
185190

186-
It's a [.golangci.yml](https://github.com/golangci/golangci-lint/blob/master/.golangci.yml) of this repo: we enable more linters than by default and make their settings more strict:
191+
It's a [.golangci.yml](https://github.com/golangci/golangci-lint/blob/master/.golangci.yml) of this repo: we enable more linters
192+
than by default and make their settings more strict:
187193
```yaml
188194
{{.GolangciYaml}}
189195
```

pkg/commands/executor.go

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package commands
22

33
import (
44
"github.com/golangci/golangci-lint/pkg/config"
5+
"github.com/sirupsen/logrus"
56
"github.com/spf13/cobra"
67
)
78

@@ -20,6 +21,8 @@ func NewExecutor(version, commit, date string) *Executor {
2021
cfg: &config.Config{},
2122
}
2223

24+
logrus.SetLevel(logrus.WarnLevel)
25+
2326
e.initRoot()
2427
e.initRun()
2528
e.initLinters()

pkg/commands/root.go

+4-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"github.com/spf13/pflag"
1414
)
1515

16-
func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
16+
func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
1717
if e.cfg.Run.PrintVersion {
1818
fmt.Fprintf(printers.StdOut, "golangci-lint has version %s built from %s on %s\n", e.version, e.commit, e.date)
1919
os.Exit(0)
@@ -24,8 +24,6 @@ func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
2424
log.SetFlags(0) // don't print time
2525
if e.cfg.Run.IsVerbose {
2626
logrus.SetLevel(logrus.InfoLevel)
27-
} else {
28-
logrus.SetLevel(logrus.WarnLevel)
2927
}
3028

3129
if e.cfg.Run.CPUProfilePath != "" {
@@ -39,7 +37,7 @@ func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
3937
}
4038
}
4139

42-
func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
40+
func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
4341
if e.cfg.Run.CPUProfilePath != "" {
4442
pprof.StopCPUProfile()
4543
}
@@ -75,8 +73,8 @@ func (e *Executor) initRoot() {
7573
logrus.Fatal(err)
7674
}
7775
},
78-
PersistentPreRun: e.persistentPostRun,
79-
PersistentPostRun: e.persistentPreRun,
76+
PersistentPreRun: e.persistentPreRun,
77+
PersistentPostRun: e.persistentPostRun,
8078
}
8179

8280
e.initRootFlagSet(rootCmd.PersistentFlags())

pkg/commands/run.go

+81-4
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import (
88
"io/ioutil"
99
"log"
1010
"os"
11+
"path/filepath"
1112
"runtime"
1213
"strings"
1314
"time"
1415

1516
"github.com/fatih/color"
1617
"github.com/golangci/golangci-lint/pkg/config"
18+
"github.com/golangci/golangci-lint/pkg/fsutils"
1719
"github.com/golangci/golangci-lint/pkg/lint"
1820
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
1921
"github.com/golangci/golangci-lint/pkg/printers"
@@ -318,16 +320,80 @@ func (e *Executor) parseConfig() {
318320
return
319321
}
320322

321-
if configFile == "" {
322-
viper.SetConfigName(".golangci")
323-
viper.AddConfigPath("./")
324-
} else {
323+
if configFile != "" {
325324
viper.SetConfigFile(configFile)
325+
} else {
326+
setupConfigFileSearch(fs.Args())
326327
}
327328

328329
e.parseConfigImpl()
329330
}
330331

332+
func setupConfigFileSearch(args []string) {
333+
// skip all args ([golangci-lint, run/linters]) before files/dirs list
334+
for len(args) != 0 {
335+
if args[0] == "run" {
336+
args = args[1:]
337+
break
338+
}
339+
340+
args = args[1:]
341+
}
342+
343+
// find first file/dir arg
344+
firstArg := "./..."
345+
if len(args) != 0 {
346+
firstArg = args[0]
347+
}
348+
349+
absStartPath, err := filepath.Abs(firstArg)
350+
if err != nil {
351+
logrus.Infof("Can't make abs path for %q: %s", firstArg, err)
352+
absStartPath = filepath.Clean(firstArg)
353+
}
354+
355+
// start from it
356+
var curDir string
357+
if fsutils.IsDir(absStartPath) {
358+
curDir = absStartPath
359+
} else {
360+
curDir = filepath.Dir(absStartPath)
361+
}
362+
363+
// find all dirs from it up to the root
364+
configSearchPaths := []string{"./"}
365+
for {
366+
configSearchPaths = append(configSearchPaths, curDir)
367+
newCurDir := filepath.Dir(curDir)
368+
if curDir == newCurDir || newCurDir == "" {
369+
break
370+
}
371+
curDir = newCurDir
372+
}
373+
374+
logrus.Infof("Config search paths: %s", configSearchPaths)
375+
viper.SetConfigName(".golangci")
376+
for _, p := range configSearchPaths {
377+
viper.AddConfigPath(p)
378+
}
379+
}
380+
381+
func getRelPath(p string) string {
382+
wd, err := os.Getwd()
383+
if err != nil {
384+
logrus.Infof("Can't get wd: %s", err)
385+
return p
386+
}
387+
388+
r, err := filepath.Rel(wd, p)
389+
if err != nil {
390+
logrus.Infof("Can't make path %s relative to %s: %s", p, wd, err)
391+
return p
392+
}
393+
394+
return r
395+
}
396+
331397
func (e *Executor) parseConfigImpl() {
332398
commandLineConfig := *e.cfg // make copy
333399

@@ -338,13 +404,24 @@ func (e *Executor) parseConfigImpl() {
338404
logrus.Fatalf("Can't read viper config: %s", err)
339405
}
340406

407+
usedConfigFile := viper.ConfigFileUsed()
408+
if usedConfigFile == "" {
409+
return
410+
}
411+
logrus.Infof("Used config file %s", getRelPath(usedConfigFile))
412+
341413
if err := viper.Unmarshal(&e.cfg); err != nil {
342414
logrus.Fatalf("Can't unmarshal config by viper: %s", err)
343415
}
344416

345417
if err := e.validateConfig(&commandLineConfig); err != nil {
346418
logrus.Fatal(err)
347419
}
420+
421+
if e.cfg.InternalTest { // just for testing purposes: to detect config file usage
422+
fmt.Fprintln(printers.StdOut, "test")
423+
os.Exit(0)
424+
}
348425
}
349426

350427
func (e *Executor) validateConfig(commandLineConfig *config.Config) error {

pkg/config/config.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ type Issues struct {
164164
Diff bool `mapstructure:"new"`
165165
}
166166

167-
type Config struct { // nolint:maligned
167+
type Config struct { //nolint:maligned
168168
Run Run
169169

170170
Output struct {
@@ -177,4 +177,6 @@ type Config struct { // nolint:maligned
177177
LintersSettings LintersSettings `mapstructure:"linters-settings"`
178178
Linters Linters
179179
Issues Issues
180+
181+
InternalTest bool // Option is used only for testing golangci-lint code, don't use it
180182
}

test/run_test.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@ func installBinary(t assert.TestingT) {
1919
})
2020
}
2121

22-
func TestCongratsMessageIfNoIssues(t *testing.T) {
23-
out, exitCode := runGolangciLint(t, "../...")
22+
func checkNoIssuesRun(t *testing.T, out string, exitCode int) {
2423
assert.Equal(t, 0, exitCode)
2524
assert.Equal(t, "Congrats! No issues were found.\n", out)
2625
}
2726

27+
func TestCongratsMessageIfNoIssues(t *testing.T) {
28+
out, exitCode := runGolangciLint(t, "../...")
29+
checkNoIssuesRun(t, out, exitCode)
30+
}
31+
2832
func TestDeadline(t *testing.T) {
29-
out, exitCode := runGolangciLint(t, "--no-config", "--deadline=1ms", "../...")
33+
out, exitCode := runGolangciLint(t, "--deadline=1ms", "../...")
3034
assert.Equal(t, 4, exitCode)
3135
assert.Equal(t, "", out) // no 'Congrats! No issues were found.'
3236
}
@@ -54,6 +58,19 @@ func runGolangciLint(t *testing.T, args ...string) (string, int) {
5458
}
5559

5660
func TestTestsAreLintedByDefault(t *testing.T) {
57-
out, exitCode := runGolangciLint(t, "--no-config", "./testdata/withtests")
61+
out, exitCode := runGolangciLint(t, "./testdata/withtests")
5862
assert.Equal(t, 0, exitCode, out)
5963
}
64+
65+
func TestConfigFileIsDetected(t *testing.T) {
66+
checkGotConfig := func(out string, exitCode int) {
67+
assert.Equal(t, 0, exitCode, out)
68+
assert.Equal(t, "test\n", out) // test config contains InternalTest: true, it triggers such output
69+
}
70+
71+
checkGotConfig(runGolangciLint(t, "testdata/withconfig/pkg"))
72+
checkGotConfig(runGolangciLint(t, "testdata/withconfig/..."))
73+
74+
out, exitCode := runGolangciLint(t) // doesn't detect when no args
75+
checkNoIssuesRun(t, out, exitCode)
76+
}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
InternalTest: true

test/testdata/withconfig/pkg/pkg.go

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package pkg
2+
3+
func SomeTestFunc() {}

0 commit comments

Comments
 (0)