Skip to content

Commit 1e0cacf

Browse files
authored
Merge pull request #61 from golangci/feature/search-config-file
#60: search config file in directories from file path up to root
2 parents 68e1295 + 0f6213d commit 1e0cacf

File tree

10 files changed

+141
-21
lines changed

10 files changed

+141
-21
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

+12-10
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,22 @@ import (
1313
"github.com/spf13/pflag"
1414
)
1515

16-
func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
16+
func (e *Executor) setupLog() {
17+
log.SetFlags(0) // don't print time
18+
if e.cfg.Run.IsVerbose {
19+
logrus.SetLevel(logrus.InfoLevel)
20+
}
21+
}
22+
23+
func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
1724
if e.cfg.Run.PrintVersion {
1825
fmt.Fprintf(printers.StdOut, "golangci-lint has version %s built from %s on %s\n", e.version, e.commit, e.date)
1926
os.Exit(0)
2027
}
2128

2229
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
2330

24-
log.SetFlags(0) // don't print time
25-
if e.cfg.Run.IsVerbose {
26-
logrus.SetLevel(logrus.InfoLevel)
27-
} else {
28-
logrus.SetLevel(logrus.WarnLevel)
29-
}
31+
e.setupLog()
3032

3133
if e.cfg.Run.CPUProfilePath != "" {
3234
f, err := os.Create(e.cfg.Run.CPUProfilePath)
@@ -39,7 +41,7 @@ func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
3941
}
4042
}
4143

42-
func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
44+
func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
4345
if e.cfg.Run.CPUProfilePath != "" {
4446
pprof.StopCPUProfile()
4547
}
@@ -75,8 +77,8 @@ func (e *Executor) initRoot() {
7577
logrus.Fatal(err)
7678
}
7779
},
78-
PersistentPreRun: e.persistentPostRun,
79-
PersistentPostRun: e.persistentPreRun,
80+
PersistentPreRun: e.persistentPreRun,
81+
PersistentPostRun: e.persistentPostRun,
8082
}
8183

8284
e.initRootFlagSet(rootCmd.PersistentFlags())

pkg/commands/run.go

+83-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"
@@ -301,6 +303,8 @@ func (e *Executor) parseConfig() {
301303
logrus.Fatalf("Can't parse args: %s", err)
302304
}
303305

306+
e.setupLog() // for `-v` to work until running of preRun function
307+
304308
if err := viper.BindPFlags(fs); err != nil {
305309
logrus.Fatalf("Can't bind cobra's flags to viper: %s", err)
306310
}
@@ -318,16 +322,80 @@ func (e *Executor) parseConfig() {
318322
return
319323
}
320324

321-
if configFile == "" {
322-
viper.SetConfigName(".golangci")
323-
viper.AddConfigPath("./")
324-
} else {
325+
if configFile != "" {
325326
viper.SetConfigFile(configFile)
327+
} else {
328+
setupConfigFileSearch(fs.Args())
326329
}
327330

328331
e.parseConfigImpl()
329332
}
330333

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

@@ -338,13 +406,24 @@ func (e *Executor) parseConfigImpl() {
338406
logrus.Fatalf("Can't read viper config: %s", err)
339407
}
340408

409+
usedConfigFile := viper.ConfigFileUsed()
410+
if usedConfigFile == "" {
411+
return
412+
}
413+
logrus.Infof("Used config file %s", getRelPath(usedConfigFile))
414+
341415
if err := viper.Unmarshal(&e.cfg); err != nil {
342416
logrus.Fatalf("Can't unmarshal config by viper: %s", err)
343417
}
344418

345419
if err := e.validateConfig(&commandLineConfig); err != nil {
346420
logrus.Fatal(err)
347421
}
422+
423+
if e.cfg.InternalTest { // just for testing purposes: to detect config file usage
424+
fmt.Fprintln(printers.StdOut, "test")
425+
os.Exit(0)
426+
}
348427
}
349428

350429
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/linters_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func TestSourcesFromTestdataWithIssuesDir(t *testing.T) {
4141
func testOneSource(t *testing.T, sourcePath string) {
4242
goErrchkBin := filepath.Join(runtime.GOROOT(), "test", "errchk")
4343
cmd := exec.Command(goErrchkBin, binName, "run",
44+
"--no-config",
4445
"--enable-all",
4546
"--dupl.threshold=20",
4647
"--gocyclo.min-complexity=20",

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)