Merge pull request #61 from golangci/feature/search-config-file
#60: search config file in directories from file path up to root
This commit is contained in:
commit
1e0cacf411
@ -288,10 +288,16 @@ GolangCI-Lint looks for next config paths in the current directory:
|
|||||||
- `.golangci.toml`
|
- `.golangci.toml`
|
||||||
- `.golangci.json`
|
- `.golangci.json`
|
||||||
|
|
||||||
|
GolangCI-Lint also searches config file in all directories from directory of the first analyzed path up to the root.
|
||||||
|
To see which config file is used and where it was searched run golangci-lint with `-v` option.
|
||||||
|
|
||||||
Configuration options inside the file are identical to command-line options.
|
Configuration options inside the file are identical to command-line options.
|
||||||
|
You can configure specific linters options only within configuration file, it can't be done with command-line.
|
||||||
|
|
||||||
There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) with all supported options.
|
There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) with all supported options.
|
||||||
|
|
||||||
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:
|
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:
|
||||||
```yaml
|
```yaml
|
||||||
linters-settings:
|
linters-settings:
|
||||||
govet:
|
govet:
|
||||||
|
@ -180,10 +180,16 @@ GolangCI-Lint looks for next config paths in the current directory:
|
|||||||
- `.golangci.toml`
|
- `.golangci.toml`
|
||||||
- `.golangci.json`
|
- `.golangci.json`
|
||||||
|
|
||||||
|
GolangCI-Lint also searches config file in all directories from directory of the first analyzed path up to the root.
|
||||||
|
To see which config file is used and where it was searched run golangci-lint with `-v` option.
|
||||||
|
|
||||||
Configuration options inside the file are identical to command-line options.
|
Configuration options inside the file are identical to command-line options.
|
||||||
|
You can configure specific linters options only within configuration file, it can't be done with command-line.
|
||||||
|
|
||||||
There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) with all supported options.
|
There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml) with all supported options.
|
||||||
|
|
||||||
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:
|
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:
|
||||||
```yaml
|
```yaml
|
||||||
{{.GolangciYaml}}
|
{{.GolangciYaml}}
|
||||||
```
|
```
|
||||||
|
@ -2,6 +2,7 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,6 +21,8 @@ func NewExecutor(version, commit, date string) *Executor {
|
|||||||
cfg: &config.Config{},
|
cfg: &config.Config{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.SetLevel(logrus.WarnLevel)
|
||||||
|
|
||||||
e.initRoot()
|
e.initRoot()
|
||||||
e.initRun()
|
e.initRun()
|
||||||
e.initLinters()
|
e.initLinters()
|
||||||
|
@ -13,7 +13,14 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
|
func (e *Executor) setupLog() {
|
||||||
|
log.SetFlags(0) // don't print time
|
||||||
|
if e.cfg.Run.IsVerbose {
|
||||||
|
logrus.SetLevel(logrus.InfoLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
|
||||||
if e.cfg.Run.PrintVersion {
|
if e.cfg.Run.PrintVersion {
|
||||||
fmt.Fprintf(printers.StdOut, "golangci-lint has version %s built from %s on %s\n", e.version, e.commit, e.date)
|
fmt.Fprintf(printers.StdOut, "golangci-lint has version %s built from %s on %s\n", e.version, e.commit, e.date)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@ -21,12 +28,7 @@ func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
|
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
|
||||||
|
|
||||||
log.SetFlags(0) // don't print time
|
e.setupLog()
|
||||||
if e.cfg.Run.IsVerbose {
|
|
||||||
logrus.SetLevel(logrus.InfoLevel)
|
|
||||||
} else {
|
|
||||||
logrus.SetLevel(logrus.WarnLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.cfg.Run.CPUProfilePath != "" {
|
if e.cfg.Run.CPUProfilePath != "" {
|
||||||
f, err := os.Create(e.cfg.Run.CPUProfilePath)
|
f, err := os.Create(e.cfg.Run.CPUProfilePath)
|
||||||
@ -39,7 +41,7 @@ func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
|
func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
|
||||||
if e.cfg.Run.CPUProfilePath != "" {
|
if e.cfg.Run.CPUProfilePath != "" {
|
||||||
pprof.StopCPUProfile()
|
pprof.StopCPUProfile()
|
||||||
}
|
}
|
||||||
@ -75,8 +77,8 @@ func (e *Executor) initRoot() {
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PersistentPreRun: e.persistentPostRun,
|
PersistentPreRun: e.persistentPreRun,
|
||||||
PersistentPostRun: e.persistentPreRun,
|
PersistentPostRun: e.persistentPostRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
e.initRootFlagSet(rootCmd.PersistentFlags())
|
e.initRootFlagSet(rootCmd.PersistentFlags())
|
||||||
|
@ -8,12 +8,14 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"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"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||||
"github.com/golangci/golangci-lint/pkg/printers"
|
"github.com/golangci/golangci-lint/pkg/printers"
|
||||||
@ -301,6 +303,8 @@ func (e *Executor) parseConfig() {
|
|||||||
logrus.Fatalf("Can't parse args: %s", err)
|
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 {
|
if err := viper.BindPFlags(fs); err != nil {
|
||||||
logrus.Fatalf("Can't bind cobra's flags to viper: %s", err)
|
logrus.Fatalf("Can't bind cobra's flags to viper: %s", err)
|
||||||
}
|
}
|
||||||
@ -318,16 +322,80 @@ func (e *Executor) parseConfig() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if configFile == "" {
|
if configFile != "" {
|
||||||
viper.SetConfigName(".golangci")
|
|
||||||
viper.AddConfigPath("./")
|
|
||||||
} else {
|
|
||||||
viper.SetConfigFile(configFile)
|
viper.SetConfigFile(configFile)
|
||||||
|
} else {
|
||||||
|
setupConfigFileSearch(fs.Args())
|
||||||
}
|
}
|
||||||
|
|
||||||
e.parseConfigImpl()
|
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() {
|
func (e *Executor) parseConfigImpl() {
|
||||||
commandLineConfig := *e.cfg // make copy
|
commandLineConfig := *e.cfg // make copy
|
||||||
|
|
||||||
@ -338,6 +406,12 @@ func (e *Executor) parseConfigImpl() {
|
|||||||
logrus.Fatalf("Can't read viper config: %s", err)
|
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 {
|
if err := viper.Unmarshal(&e.cfg); err != nil {
|
||||||
logrus.Fatalf("Can't unmarshal config by viper: %s", err)
|
logrus.Fatalf("Can't unmarshal config by viper: %s", err)
|
||||||
}
|
}
|
||||||
@ -345,6 +419,11 @@ func (e *Executor) parseConfigImpl() {
|
|||||||
if err := e.validateConfig(&commandLineConfig); err != nil {
|
if err := e.validateConfig(&commandLineConfig); err != nil {
|
||||||
logrus.Fatal(err)
|
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 {
|
func (e *Executor) validateConfig(commandLineConfig *config.Config) error {
|
||||||
|
@ -164,7 +164,7 @@ type Issues struct {
|
|||||||
Diff bool `mapstructure:"new"`
|
Diff bool `mapstructure:"new"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct { // nolint:maligned
|
type Config struct { //nolint:maligned
|
||||||
Run Run
|
Run Run
|
||||||
|
|
||||||
Output struct {
|
Output struct {
|
||||||
@ -177,4 +177,6 @@ type Config struct { // nolint:maligned
|
|||||||
LintersSettings LintersSettings `mapstructure:"linters-settings"`
|
LintersSettings LintersSettings `mapstructure:"linters-settings"`
|
||||||
Linters Linters
|
Linters Linters
|
||||||
Issues Issues
|
Issues Issues
|
||||||
|
|
||||||
|
InternalTest bool // Option is used only for testing golangci-lint code, don't use it
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ func TestSourcesFromTestdataWithIssuesDir(t *testing.T) {
|
|||||||
func testOneSource(t *testing.T, sourcePath string) {
|
func testOneSource(t *testing.T, sourcePath string) {
|
||||||
goErrchkBin := filepath.Join(runtime.GOROOT(), "test", "errchk")
|
goErrchkBin := filepath.Join(runtime.GOROOT(), "test", "errchk")
|
||||||
cmd := exec.Command(goErrchkBin, binName, "run",
|
cmd := exec.Command(goErrchkBin, binName, "run",
|
||||||
|
"--no-config",
|
||||||
"--enable-all",
|
"--enable-all",
|
||||||
"--dupl.threshold=20",
|
"--dupl.threshold=20",
|
||||||
"--gocyclo.min-complexity=20",
|
"--gocyclo.min-complexity=20",
|
||||||
|
@ -19,14 +19,18 @@ func installBinary(t assert.TestingT) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCongratsMessageIfNoIssues(t *testing.T) {
|
func checkNoIssuesRun(t *testing.T, out string, exitCode int) {
|
||||||
out, exitCode := runGolangciLint(t, "../...")
|
|
||||||
assert.Equal(t, 0, exitCode)
|
assert.Equal(t, 0, exitCode)
|
||||||
assert.Equal(t, "Congrats! No issues were found.\n", out)
|
assert.Equal(t, "Congrats! No issues were found.\n", out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCongratsMessageIfNoIssues(t *testing.T) {
|
||||||
|
out, exitCode := runGolangciLint(t, "../...")
|
||||||
|
checkNoIssuesRun(t, out, exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeadline(t *testing.T) {
|
func TestDeadline(t *testing.T) {
|
||||||
out, exitCode := runGolangciLint(t, "--no-config", "--deadline=1ms", "../...")
|
out, exitCode := runGolangciLint(t, "--deadline=1ms", "../...")
|
||||||
assert.Equal(t, 4, exitCode)
|
assert.Equal(t, 4, exitCode)
|
||||||
assert.Equal(t, "", out) // no 'Congrats! No issues were found.'
|
assert.Equal(t, "", out) // no 'Congrats! No issues were found.'
|
||||||
}
|
}
|
||||||
@ -54,6 +58,19 @@ func runGolangciLint(t *testing.T, args ...string) (string, int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTestsAreLintedByDefault(t *testing.T) {
|
func TestTestsAreLintedByDefault(t *testing.T) {
|
||||||
out, exitCode := runGolangciLint(t, "--no-config", "./testdata/withtests")
|
out, exitCode := runGolangciLint(t, "./testdata/withtests")
|
||||||
assert.Equal(t, 0, exitCode, out)
|
assert.Equal(t, 0, exitCode, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigFileIsDetected(t *testing.T) {
|
||||||
|
checkGotConfig := func(out string, exitCode int) {
|
||||||
|
assert.Equal(t, 0, exitCode, out)
|
||||||
|
assert.Equal(t, "test\n", out) // test config contains InternalTest: true, it triggers such output
|
||||||
|
}
|
||||||
|
|
||||||
|
checkGotConfig(runGolangciLint(t, "testdata/withconfig/pkg"))
|
||||||
|
checkGotConfig(runGolangciLint(t, "testdata/withconfig/..."))
|
||||||
|
|
||||||
|
out, exitCode := runGolangciLint(t) // doesn't detect when no args
|
||||||
|
checkNoIssuesRun(t, out, exitCode)
|
||||||
|
}
|
||||||
|
1
test/testdata/withconfig/.golangci.yml
vendored
Normal file
1
test/testdata/withconfig/.golangci.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
InternalTest: true
|
3
test/testdata/withconfig/pkg/pkg.go
vendored
Normal file
3
test/testdata/withconfig/pkg/pkg.go
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
func SomeTestFunc() {}
|
Loading…
x
Reference in New Issue
Block a user