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:
Isaev Denis 2018-06-02 20:34:01 +03:00 committed by GitHub
commit 1e0cacf411
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 141 additions and 21 deletions

View File

@ -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:

View File

@ -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}}
```

View File

@ -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()

View File

@ -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())

View File

@ -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 {

View File

@ -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
}

View File

@ -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",

View File

@ -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)
}

View File

@ -0,0 +1 @@
InternalTest: true

3
test/testdata/withconfig/pkg/pkg.go vendored Normal file
View File

@ -0,0 +1,3 @@
package pkg
func SomeTestFunc() {}