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.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.
|
||||
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.
|
||||
|
||||
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
|
||||
linters-settings:
|
||||
govet:
|
||||
|
@ -180,10 +180,16 @@ GolangCI-Lint looks for next config paths in the current directory:
|
||||
- `.golangci.toml`
|
||||
- `.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.
|
||||
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.
|
||||
|
||||
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
|
||||
{{.GolangciYaml}}
|
||||
```
|
||||
|
@ -2,6 +2,7 @@ package commands
|
||||
|
||||
import (
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -20,6 +21,8 @@ func NewExecutor(version, commit, date string) *Executor {
|
||||
cfg: &config.Config{},
|
||||
}
|
||||
|
||||
logrus.SetLevel(logrus.WarnLevel)
|
||||
|
||||
e.initRoot()
|
||||
e.initRun()
|
||||
e.initLinters()
|
||||
|
@ -13,7 +13,14 @@ import (
|
||||
"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 {
|
||||
fmt.Fprintf(printers.StdOut, "golangci-lint has version %s built from %s on %s\n", e.version, e.commit, e.date)
|
||||
os.Exit(0)
|
||||
@ -21,12 +28,7 @@ func (e *Executor) persistentPostRun(cmd *cobra.Command, args []string) {
|
||||
|
||||
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
|
||||
|
||||
log.SetFlags(0) // don't print time
|
||||
if e.cfg.Run.IsVerbose {
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
} else {
|
||||
logrus.SetLevel(logrus.WarnLevel)
|
||||
}
|
||||
e.setupLog()
|
||||
|
||||
if 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 != "" {
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
@ -75,8 +77,8 @@ func (e *Executor) initRoot() {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
},
|
||||
PersistentPreRun: e.persistentPostRun,
|
||||
PersistentPostRun: e.persistentPreRun,
|
||||
PersistentPreRun: e.persistentPreRun,
|
||||
PersistentPostRun: e.persistentPostRun,
|
||||
}
|
||||
|
||||
e.initRootFlagSet(rootCmd.PersistentFlags())
|
||||
|
@ -8,12 +8,14 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"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/lintersdb"
|
||||
"github.com/golangci/golangci-lint/pkg/printers"
|
||||
@ -301,6 +303,8 @@ func (e *Executor) parseConfig() {
|
||||
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 {
|
||||
logrus.Fatalf("Can't bind cobra's flags to viper: %s", err)
|
||||
}
|
||||
@ -318,16 +322,80 @@ func (e *Executor) parseConfig() {
|
||||
return
|
||||
}
|
||||
|
||||
if configFile == "" {
|
||||
viper.SetConfigName(".golangci")
|
||||
viper.AddConfigPath("./")
|
||||
} else {
|
||||
if configFile != "" {
|
||||
viper.SetConfigFile(configFile)
|
||||
} else {
|
||||
setupConfigFileSearch(fs.Args())
|
||||
}
|
||||
|
||||
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() {
|
||||
commandLineConfig := *e.cfg // make copy
|
||||
|
||||
@ -338,6 +406,12 @@ func (e *Executor) parseConfigImpl() {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
|
@ -164,7 +164,7 @@ type Issues struct {
|
||||
Diff bool `mapstructure:"new"`
|
||||
}
|
||||
|
||||
type Config struct { // nolint:maligned
|
||||
type Config struct { //nolint:maligned
|
||||
Run Run
|
||||
|
||||
Output struct {
|
||||
@ -177,4 +177,6 @@ type Config struct { // nolint:maligned
|
||||
LintersSettings LintersSettings `mapstructure:"linters-settings"`
|
||||
Linters Linters
|
||||
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) {
|
||||
goErrchkBin := filepath.Join(runtime.GOROOT(), "test", "errchk")
|
||||
cmd := exec.Command(goErrchkBin, binName, "run",
|
||||
"--no-config",
|
||||
"--enable-all",
|
||||
"--dupl.threshold=20",
|
||||
"--gocyclo.min-complexity=20",
|
||||
|
@ -19,14 +19,18 @@ func installBinary(t assert.TestingT) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCongratsMessageIfNoIssues(t *testing.T) {
|
||||
out, exitCode := runGolangciLint(t, "../...")
|
||||
func checkNoIssuesRun(t *testing.T, out string, exitCode int) {
|
||||
assert.Equal(t, 0, exitCode)
|
||||
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) {
|
||||
out, exitCode := runGolangciLint(t, "--no-config", "--deadline=1ms", "../...")
|
||||
out, exitCode := runGolangciLint(t, "--deadline=1ms", "../...")
|
||||
assert.Equal(t, 4, exitCode)
|
||||
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) {
|
||||
out, exitCode := runGolangciLint(t, "--no-config", "./testdata/withtests")
|
||||
out, exitCode := runGolangciLint(t, "./testdata/withtests")
|
||||
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