Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 7 additions & 1 deletion README.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
```
Expand Down
3 changes: 3 additions & 0 deletions pkg/commands/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"github.com/golangci/golangci-lint/pkg/config"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

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

logrus.SetLevel(logrus.WarnLevel)

e.initRoot()
e.initRun()
e.initLinters()
Expand Down
22 changes: 12 additions & 10 deletions pkg/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@ 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)
}

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)
Expand All @@ -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()
}
Expand Down Expand Up @@ -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())
Expand Down
87 changes: 83 additions & 4 deletions pkg/commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand All @@ -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

Expand All @@ -338,13 +406,24 @@ 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)
}

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 {
Expand Down
4 changes: 3 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
1 change: 1 addition & 0 deletions test/linters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
25 changes: 21 additions & 4 deletions test/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
}
Expand Down Expand Up @@ -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 change: 1 addition & 0 deletions test/testdata/withconfig/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
InternalTest: true
3 changes: 3 additions & 0 deletions test/testdata/withconfig/pkg/pkg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package pkg

func SomeTestFunc() {}