From 034728ec9406111ceb90c2ee6015ccb4459a1ccc Mon Sep 17 00:00:00 2001 From: golangci Date: Sat, 2 Jun 2018 11:36:50 +0300 Subject: [PATCH] generate parts of README automatically --- README.md | 121 +++--- README.md.tmpl | 360 ++++++++++++++++++ pkg/commands/linters.go | 13 +- pkg/commands/run.go | 18 +- pkg/golinters/deadcode.go | 4 +- pkg/golinters/depguard.go | 4 +- pkg/golinters/dupl.go | 4 +- pkg/golinters/errcheck.go | 4 +- pkg/golinters/gas.go | 4 +- pkg/golinters/goconst.go | 4 +- pkg/golinters/gocyclo.go | 4 +- pkg/golinters/gofmt.go | 4 +- pkg/golinters/golint.go | 4 +- pkg/golinters/govet.go | 4 +- pkg/golinters/ineffassign.go | 4 +- pkg/golinters/interfacer.go | 4 +- pkg/golinters/maligned.go | 4 +- pkg/golinters/megacheck.go | 8 +- pkg/golinters/structcheck.go | 6 +- pkg/golinters/typecheck.go | 4 +- pkg/golinters/unconvert.go | 4 +- pkg/golinters/varcheck.go | 4 +- pkg/lint/linter/config.go | 69 ++++ pkg/lint/linter/context.go | 23 ++ pkg/lint/{ => linter}/linter.go | 2 +- .../lintersdb/lintersdb.go} | 235 ++++++------ .../lintersdb/lintersdb_test.go} | 5 +- pkg/lint/{context.go => load.go} | 18 +- pkg/lint/{context_test.go => load_test.go} | 17 +- pkg/lint/runner.go | 45 ++- scripts/gen_readme/main.go | 122 ++++++ 31 files changed, 849 insertions(+), 277 deletions(-) create mode 100644 README.md.tmpl create mode 100644 pkg/lint/linter/config.go create mode 100644 pkg/lint/linter/context.go rename pkg/lint/{ => linter}/linter.go (92%) rename pkg/{enabled_linters.go => lint/lintersdb/lintersdb.go} (59%) rename pkg/{enabled_linters_test.go => lint/lintersdb/lintersdb_test.go} (92%) rename pkg/lint/{context.go => load.go} (89%) rename pkg/lint/{context_test.go => load_test.go} (81%) create mode 100644 scripts/gen_readme/main.go diff --git a/README.md b/README.md index 253f8aa9..5646976c 100644 --- a/README.md +++ b/README.md @@ -77,16 +77,17 @@ GolangCI-Lint can be used with zero configuration. By default next linters are e ``` $ golangci-lint linters Enabled by default linters: -govet: Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string -errcheck: Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases -staticcheck: Staticcheck is go vet on steroids, applying a ton of static analysis checks -unused: Checks Go code for unused constants, variables, functions and types -gosimple: Linter for Go source code that specialises on simplifying code -gas: Inspects source code for security problems -structcheck: Finds unused struct fields -varcheck: Finds unused global variables and constants -ineffassign: Detects when assignments to existing variables are not used -deadcode: Finds unused code +govet: Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true] +errcheck: Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false] +staticcheck: Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false] +unused: Checks Go code for unused constants, variables, functions and types [fast: false] +gosimple: Linter for Go source code that specializes in simplifying a code [fast: false] +gas: Inspects source code for security problems [fast: false] +structcheck: Finds an unused struct fields [fast: false] +varcheck: Finds unused global variables and constants [fast: false] +ineffassign: Detects when assignments to existing variables are not used [fast: true] +deadcode: Finds unused code [fast: false] +typecheck: Like the front-end of a Go compiler, parses and type-checks Go code [fast: false] ``` and next linters are disabled by default: @@ -94,17 +95,17 @@ and next linters are disabled by default: $ golangci-lint linters ... Disabled by default linters: -golint: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes -interfacer: Linter that suggests narrower interface types -unconvert: Remove unnecessary type conversions -dupl: Tool for code clone detection -goconst: Finds repeated strings that could be replaced by a constant -gocyclo: Computes and checks the cyclomatic complexity of functions -gofmt: Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification -goimports: Goimports does everything that gofmt does. Additionally it checks unused imports -maligned: Tool to detect Go structs that would take less memory if their fields were sorted -megacheck: 3 sub-linters in one: unused, gosimple and staticcheck -depguard: Go linter that checks if package imports are in a list of acceptable packages +golint: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true] +interfacer: Linter that suggests narrower interface types [fast: false] +unconvert: Remove unnecessary type conversions [fast: false] +dupl: Tool for code clone detection [fast: true] +goconst: Finds repeated strings that could be replaced by a constant [fast: true] +gocyclo: Computes and checks the cyclomatic complexity of functions [fast: true] +gofmt: Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true] +goimports: Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true] +maligned: Tool to detect Go structs that would take less memory if their fields were sorted [fast: false] +megacheck: 3 sub-linters in one: unused, gosimple and staticcheck [fast: false] +depguard: Go linter that checks if package imports are in a list of acceptable packages [fast: false] ``` Pass `-E/--enable` to enable linter and `-D/--disable` to disable: @@ -174,30 +175,30 @@ golangci-lint linters ``` ## Enabled By Default Linters -- [go vet](https://golang.org/cmd/vet/) - Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string -- [errcheck](https://github.com/kisielk/errcheck): Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases -- [staticcheck](https://staticcheck.io/): Staticcheck is a go vet on steroids, applying a ton of static analysis checks -- [unused](https://github.com/dominikh/go-tools/tree/master/cmd/unused): Checks Go code for unused constants, variables, functions, and types -- [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple): Linter for Go source code that specializes in simplifying a code -- [gas](https://github.com/GoASTScanner/gas): Inspects source code for security problems -- [structcheck](https://github.com/opennota/check): Finds an unused struct fields -- [varcheck](https://github.com/opennota/check): Finds unused global variables and constants -- [ineffassign](https://github.com/gordonklaus/ineffassign): Detects when assignments to existing variables are not used -- [deadcode](https://github.com/remyoudompheng/go-misc/tree/master/deadcode): Finds unused code -- typecheck: Like the front-end of a Go compiler, parses and type-checks Go code. Similar to [gotype](https://godoc.org/golang.org/x/tools/cmd/gotype). +- [govet](https://golang.org/cmd/vet/) - Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string +- [errcheck](https://github.com/kisielk/errcheck) - Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases +- [staticcheck](https://staticcheck.io/) - Staticcheck is a go vet on steroids, applying a ton of static analysis checks +- [unused](https://github.com/dominikh/go-tools/tree/master/cmd/unused) - Checks Go code for unused constants, variables, functions and types +- [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple) - Linter for Go source code that specializes in simplifying a code +- [gas](https://github.com/GoASTScanner/gas) - Inspects source code for security problems +- [structcheck](https://github.com/opennota/check) - Finds an unused struct fields +- [varcheck](https://github.com/opennota/check) - Finds unused global variables and constants +- [ineffassign](https://github.com/gordonklaus/ineffassign) - Detects when assignments to existing variables are not used +- [deadcode](https://github.com/remyoudompheng/go-misc/tree/master/deadcode) - Finds unused code +- typecheck - Like the front-end of a Go compiler, parses and type-checks Go code ## Disabled By Default Linters (`-E/--enable`) -- [golint](https://github.com/golang/lint): Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes -- [interfacer](https://github.com/mvdan/interfacer): Linter that suggests narrower interface types -- [unconvert](https://github.com/mdempsky/unconvert): Remove unnecessary type conversions -- [dupl](https://github.com/mibk/dupl): Tool for code clone detection -- [goconst](https://github.com/jgautheron/goconst): Finds repeated strings that could be replaced by a constant -- [gocyclo](https://github.com/alecthomas/gocyclo): Computes and checks the cyclomatic complexity of functions -- [gofmt](https://golang.org/cmd/gofmt/): Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification -- [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports): Goimports does everything that gofmt does. Additionally it checks unused imports -- [maligned](https://github.com/mdempsky/maligned): Tool to detect Go structs that would take less memory if their fields were sorted -- [megacheck](https://github.com/dominikh/go-tools/tree/master/cmd/megacheck): 3 sub-linters in one: unused, gosimple and staticcheck -- [depguard](https://github.com/OpenPeeDeeP/depguard): Go linter that checks if package imports are in a list of acceptable packages +- [golint](https://github.com/golang/lint) - Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes +- [interfacer](https://github.com/mvdan/interfacer) - Linter that suggests narrower interface types +- [unconvert](https://github.com/mdempsky/unconvert) - Remove unnecessary type conversions +- [dupl](https://github.com/mibk/dupl) - Tool for code clone detection +- [goconst](https://github.com/jgautheron/goconst) - Finds repeated strings that could be replaced by a constant +- [gocyclo](https://github.com/alecthomas/gocyclo) - Computes and checks the cyclomatic complexity of functions +- [gofmt](https://golang.org/cmd/gofmt/) - Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification +- [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) - Goimports does everything that gofmt does. Additionally it checks unused imports +- [maligned](https://github.com/mdempsky/maligned) - Tool to detect Go structs that would take less memory if their fields were sorted +- [megacheck](https://github.com/dominikh/go-tools/tree/master/cmd/megacheck) - 3 sub-linters in one: unused, gosimple and staticcheck +- [depguard](https://github.com/OpenPeeDeeP/depguard) - Go linter that checks if package imports are in a list of acceptable packages # Configuration ## Command-Line Options @@ -306,7 +307,6 @@ There is a [`.golangci.yml`](https://github.com/golangci/golangci-lint/blob/mast 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 run: - deadline: 30s tests: true linters-settings: @@ -410,26 +410,19 @@ Thanks to [alecthomas/gometalinter](https://github.com/alecthomas/gometalinter) Thanks to [bradleyfalzon/revgrep](https://github.com/bradleyfalzon/revgrep) for cool diff tool. Thanks to developers and authors of used linters: -- [golang/vet](https://golang.org/cmd/vet/) -- [kisielk/errcheck](https://github.com/kisielk/errcheck) -- [staticcheck](https://staticcheck.io/) -- [dominikh/go-tools/unused](https://github.com/dominikh/go-tools/tree/master/cmd/unused) -- [dominikh/go-tools/gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple) -- [GoASTScanner/gas](https://github.com/GoASTScanner/gas) -- [opennota/check](https://github.com/opennota/check) -- [gordonklaus/ineffassign](https://github.com/gordonklaus/ineffassign) -- [remyoudompheng/go-misc/deadcode](https://github.com/remyoudompheng/go-misc/tree/master/deadcode) -- [golang/lint](https://github.com/golang/lint) -- [mvdan/interfacer](https://github.com/mvdan/interfacer) -- [mdempsky/unconvert](https://github.com/mdempsky/unconvert) -- [mibk/dupl](https://github.com/mibk/dupl) -- [jgautheron/goconst](https://github.com/jgautheron/goconst) -- [alecthomas/gocyclo](https://github.com/alecthomas/gocyclo) -- [golang/gofmt](https://golang.org/cmd/gofmt/) -- [golang/x/tools/goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) -- [mdempsky/maligned](https://github.com/mdempsky/maligned) -- [dominikh/go-tools/megacheck](https://github.com/dominikh/go-tools/tree/master/cmd/megacheck) -- [OpenPeeDeeP/depguard](https://github.com/OpenPeeDeeP/depguard) +- [kisielk](https://github.com/kisielk) +- [golang](https://github.com/golang) +- [dominikh](https://github.com/dominikh) +- [GoASTScanner](https://github.com/GoASTScanner) +- [opennota](https://github.com/opennota) +- [mvdan](https://github.com/mvdan) +- [mdempsky](https://github.com/mdempsky) +- [gordonklaus](https://github.com/gordonklaus) +- [mibk](https://github.com/mibk) +- [jgautheron](https://github.com/jgautheron) +- [remyoudompheng](https://github.com/remyoudompheng) +- [alecthomas](https://github.com/alecthomas) +- [OpenPeeDeeP](https://github.com/OpenPeeDeeP) # Future Plans 1. Upstream all changes of forked linters. diff --git a/README.md.tmpl b/README.md.tmpl new file mode 100644 index 00000000..5666bb74 --- /dev/null +++ b/README.md.tmpl @@ -0,0 +1,360 @@ +# GolangCI-Lint +[![Build Status](https://travis-ci.com/golangci/golangci-lint.svg?branch=master)](https://travis-ci.com/golangci/golangci-lint) + +GolangCI-Lint is a linters aggregator. It's fast: on average [5 times faster](#performance) than gometalinter. It's [easy to integrate and use](#issues-options), has [nice output](#quick-start) and has a minimum number of false positives. + +GolangCI-Lint has [integrations](#ide-integrations) with VS Code, GNU Emacs, Sublime Text. + +Sponsored by [GolangCI.com](https://golangci.com): SaaS service for running linters on Github pull requests. Free for Open Source. + + + + * [Install](#install) + * [Demo](#demo) + * [Quick Start](#quick-start) + * [Comparison](#comparison) + * [golangci-lint vs gometalinter](#golangci-lint-vs-gometalinter) + * [golangci-lint vs Run Needed Linters Manually](#golangci-lint-vs-run-needed-linters-manually) + * [Performance](#performance) + * [Comparison with gometalinter](#comparison-with-gometalinter) + * [Supported Linters](#supported-linters) + * [Enabled By Default Linters](#enabled-by-default-linters) + * [Disabled By Default Linters (-E/--enable)](#disabled-by-default-linters--e--enable) + * [Configuration](#configuration) + * [Command-Line Options](#command-line-options) + * [Configuration File](#configuration-file) + * [False Positives](#false-positives) + * [IDE integrations](#ide-integrations) + * [Internals](#internals) + * [FAQ](#faq) + * [Thanks](#thanks) + * [Future Plans](#future-plans) + * [Contact Information](#contact-information) + +# Install +Recommended way to install is: +```bash +go get -u github.com/golangci/golangci-lint/cmd/golangci-lint +``` + +You can also install it by brew: +```bash +brew install golangci/tap/golangci-lint +``` + +For CI you can use fast local installation: +```bash +curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh +``` + +Check the [releases page](https://github.com/golangci/golangci-lint/releases) to fix the version. + +# Demo +Example of output: +![Screenshot of sample output](docs/run_screenshot.png) + +Short 1.5 min video demo of analyzing [beego](https://github.com/astaxie/beego). +[![asciicast](https://asciinema.org/a/1a1qaEXMlOSeRyvASbnuFomah.png)](https://asciinema.org/a/1a1qaEXMlOSeRyvASbnuFomah) + +# Quick Start +To run golangci-lint execute: +```bash +golangci-lint run +``` + +It's an equivalent of executing: +```bash +golangci-lint run ./... +``` + +You can choose which directories and files to analyze: +```bash +golangci-lint run dir1 dir2/... dir3/file1.go +``` +Directories are analyzed NOT recursively, to analyze them recursively append `/...` to their path. + +GolangCI-Lint can be used with zero configuration. By default next linters are enabled: +``` +$ golangci-lint linters +{{.LintersCommandOutputEnabledOnly}} +``` + +and next linters are disabled by default: +``` +$ golangci-lint linters +... +{{.LintersCommandOutputDisabledOnly}} +``` + +Pass `-E/--enable` to enable linter and `-D/--disable` to disable: +```bash +$ golangci-lint run --disable-all -E errcheck +``` + +# Comparison +## `golangci-lint` vs `gometalinter` +GolangCI-Lint was created to fix next issues with `gometalinter`: +1. Slow work: `gometalinter` usually works for minutes in average projects. **GolangCI-Lint works [2-7x times faster](#performance)** by [reusing work](#internals). +2. Huge memory consumption: parallel linters don't share the same program representation and can eat `n` times more memory (`n` - concurrency). GolangCI-Lint fixes it by sharing representation and **eats 1.35x less memory**. +3. Can't set honest concurrency: if you set it to `n` it can take up to `n*n` threads because of forced threads in specific linters. `gometalinter` can't do anything about it, because it runs linters as black-boxes in forked processes. In GolangCI-Lint we run all linters in one process and fully control them. Configured concurrency will be honest. +This issue is important because often you'd like to set concurrency to CPUs count minus one to **not freeze your PC** and be able to work on it while analyzing code. +4. Lack of nice output. We like how compilers `gcc` and `clang` format their warnings: **using colors, printing of warned line and showing position in line**. +5. Too many issues. GolangCI-Lint cuts a lot of issues by using default exclude list of common false-positives. Also, it has enabled by default **smart issues processing**: merge multiple issues for one line, merge issues with the same text or from the same linter. All of these smart processors can be configured by the user. +6. Integration to large codebases. A good way to start using linters in a large project is not to fix all hundreds on existing issues, but setup CI and **fix only issues in new commits**. You can use `revgrep` for it, but it's yet another utility to install and configure. With `golangci-lint` it's much easier: `revgrep` is already built into `golangci-lint` and you can use it with one option (`-n, --new` or `--new-from-rev`). +7. Installation. With `gometalinter`, you need to run linters installation step. It's easy to forget this step and have stale linters. It also complicates CI setup. GolangCI-Lint requires **no installation of linters**. +8. **Yaml or toml config**. Gometalinter's JSON isn't convenient for configuration files. + +## `golangci-lint` vs Run Needed Linters Manually +1. It will be much slower because `golangci-lint` runs all linters in parallel and shares 50-80% of linters work. +2. It will have less control and more false-positives: some linters can't be properly configured without hacks. +3. It will take more time because of different usages and need of tracking of versions of `n` linters. + +# Performance +Benchmarks were executed on MacBook Pro (Retina, 13-inch, Late 2013), 2,4 GHz Intel Core i5, 8 GB 1600 MHz DDR3. It has 4 cores and concurrency for linters was default: number of cores. Benchmark runs and measures timings automatically, it's code is [here](https://github.com/golangci/golangci-lint/blob/master/test/bench.go) (`BenchmarkWithGometalinter`). + +We measure peak memory usage (RSS) by tracking of processes RSS every 5 ms. + +## Comparison with gometalinter +We compare golangci-lint and gometalinter in default mode, but explicitly specify all linters to enable because of small differences in the default configuration. +```bash +$ golangci-lint run --no-config --issues-exit-code=0 --deadline=30m \ + --disable-all --enable=deadcode --enable=gocyclo --enable=golint --enable=varcheck \ + --enable=structcheck --enable=maligned --enable=errcheck --enable=dupl --enable=ineffassign \ + --enable=interfacer --enable=unconvert --enable=goconst --enable=gas --enable=megacheck +$ gometalinter --deadline=30m --vendor --cyclo-over=30 --dupl-threshold=150 \ + --exclude= --skip=testdata --skip=builtin \ + --disable-all --enable=deadcode --enable=gocyclo --enable=golint --enable=varcheck \ + --enable=structcheck --enable=maligned --enable=errcheck --enable=dupl --enable=ineffassign \ + --enable=interfacer --enable=unconvert --enable=goconst --enable=gas --enable=megacheck + ./... +``` + +| Repository | GolangCI Time | GolangCI Is Faster than Gometalinter | GolangCI Memory | GolangCI eats less memory than Gometalinter | +| ---------- | ------------- | ------------------------------------ | --------------- | ------------------------------------------- | +| gometalinter repo, 4 kLoC | 6s | **6.4x** | 0.7GB | 1.5x | +| self-repo, 4 kLoC | 12s | **7.5x** | 1.2GB | 1.7x | +| beego, 50 kLoC | 10s | **4.2x** | 1.4GB | 1.1x | +| hugo, 70 kLoC | 15s | **6.1x** | 1.6GB | 1.8x | +| consul, 127 kLoC | 58s | **4x** | 2.7GB | 1.7x | +| terraform, 190 kLoC | 2m13s | **1.6x** | 4.8GB | 1x | +| go-ethereum, 250 kLoC | 33s | **5x** | 3.6GB | 1x | +| go source, 1300 kLoC | 2m45s | **2x** | 4.7GB | 1x | + + +**On average golangci-lint is 4.6 times faster** than gometalinter. Maximum difference is in the +self-repo: **7.5 times faster**, minimum difference is in terraform source code repo: 1.8 times faster. + +On average golangci-lint consumes 1.35 times less memory. + +# Supported Linters +To see a list of supported linters and which linters are enabled/disabled by default execute a command +```bash +golangci-lint linters +``` + +## Enabled By Default Linters +{{.EnabledByDefaultLinters}} + +## Disabled By Default Linters (`-E/--enable`) +{{.DisabledByDefaultLinters}} + +# Configuration +## Command-Line Options +Run next command to see their description and defaults. +```bash +golangci-lint run -h +``` + +### Run Options +- `-c, --config` - path to [config file](#configuration-file) if you don't like using default config path `.golangci.(yml|toml|json)`. +- `-j, --concurrency` - the number of threads used. By default, it's a number of CPUs. Unlike `gometalinter`, it's an honest value, since we do not fork linter processes. +- `--build-tags` - build tags to take into account. +- `--issues-exit-code` - exit code if issues were found. The default is `1`. +- `--deadline` - timeout for running golangci-lint, `1m` by default. +- `--tests` - analyze `*_test.go` files. It's `false` by default. +- `-v, --verbose` - enable verbose output. Use this options to see which linters were enabled, to see timings of steps and another helpful information. +- `--print-resources-usage` - print memory usage and total time elapsed. + +### Linters +- `-E, --enable` - enable specific linter. You can pass option multiple times or use a comma: +```bash +golangci-lint run --disable-all -E golint -E govet -E errcheck +golangci-lint run --disable-all --enable golint,govet,errcheck +``` +- `-D, --disable` - disable specific linter. Similar to enabling option. +- `--enable-all` - enable all supported linters. +- `--disable-all` - disable all supported linters. +- `-p, --presets` - enable specific presets. To list all presets run +```bash +$ golangci-lint linters +... +Linters presets: +bugs: govet, errcheck, staticcheck, gas, megacheck +unused: unused, structcheck, varcheck, ineffassign, deadcode, megacheck +format: gofmt, goimports +style: golint, gosimple, interfacer, unconvert, dupl, goconst, megacheck, depguard +complexity: gocyclo +performance: maligned +``` +Usage example: +```bash +$ golangci-lint run -v --disable-all -p bugs,style,complexity,format +INFO[0000] Active linters: [govet goconst gocyclo gofmt gas dupl goimports megacheck interfacer unconvert errcheck golint] +``` +- `--fast` - run only fast linters from the enabled set of linters. To find out which linters are fast run `golangci-lint linters`. + +### Linters Options +- `--errcheck.check-type-assertions` - errcheck: check for ignored type assertion results. Disabled by default. +- `--errcheck.check-blank` - errcheck: check for errors assigned to blank identifier: `_ = errFunc()`. Disabled by default +- `--govet.check-shadowing` - govet: check for shadowed variables. Disabled by default. +- `--golint.min-confidence` - golint: minimum confidence of a problem to print it. The default is `0.8`. +- `--gofmt.simplify` - gofmt: simplify code (`gofmt -s`), enabled by default. +- `--gocyclo.min-complexity` - gocyclo: a minimal complexity of function to report it. The default is `30` (it's very high limit). +- `--maligned.suggest-new` - Maligned: print suggested more optimal struct fields ordering. Disabled by default. Example: +``` +crypto/tls/ticket.go:20: struct of size 64 bytes could be of size 56 bytes: +struct{ + masterSecret []byte, + certificates [][]byte, + vers uint16, + cipherSuite uint16, + usedOldKey bool, +} +``` +- `--dupl.threshold` - dupl: Minimal threshold to detect copy-paste, `150` by default. +- `--goconst.min-len` - goconst: minimum constant string length, `3` by default. +- `--goconst.min-occurrences` - goconst: minimum occurences of constant string count to trigger issue. Default is `3`. + +### Issues Options +- `-n, --new` - show only new issues: if there are unstaged changes or untracked files, only those changes are analyzed, else only changes in HEAD~ are analyzed. It's a super-useful option for integration `golangci-lint` into existing large codebase. It's not practical to fix all existing issues at the moment of integration: much better don't allow issues in new code. Disabled by default. +- `--new-from-rev` - show only new issues created after specified git revision. +- `--new-from-patch` - show only new issues created in git patch with the specified file path. +- `-e, --exclude` - exclude issue by regexp on issue text. +- `--exclude-use-default` - use or not use default excludes. We tested our linter on large codebases and marked common false positives. By default we ignore common false positives by next regexps: + - `Error return value of .((os\.)?std(out|err)\..*|.*Close|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked` - ercheck: almost all programs ignore errors on these functions and in most cases it's ok. + - `(should have comment|comment on exported method)` - golint: annoying issues about not having a comment. The rare codebase has such comments. + - `G103:` - gas: `Use of unsafe calls should be audited` + - `G104:` - gas: `disable what errcheck does: it reports on Close etc` + - `G204:` - gas: `Subprocess launching should be audited: too lot false - positives` + - `G301:` - gas: `Expect directory permissions to be 0750 or less` + - `G302:` - gas: `Expect file permissions to be 0600 or less` + - `G304:` - gas: ``Potential file inclusion via variable: `src, err := ioutil.ReadFile(filename)`.`` + + - `(possible misuse of unsafe.Pointer|should have signature)` - common false positives by govet. + - `ineffective break statement. Did you mean to break out of the outer loop` - megacheck: developers tend to write in C-style with an explicit `break` in a switch, so it's ok to ignore. + + Use option `--exclude-use-default=false` to disable these default exclude regexps. +- `--max-issues-per-linter` - maximum issues count per one linter. Set to `0` to disable. The default value is `50` to not being annoying. +- `--max-same-issues` - maximum count of issues with the same text. Set to 0 to disable. The default value is `3` to not being annoying. + +### Output Options +- `--out-format` - format of output: `colored-line-number|line-number|json`, default is `colored-line-number`. +- `--print-issued-lines` - print line of source code where the issue occurred. Enabled by default. +- `--print-linter-name` - print linter name in issue line. Enabled by default. +- `--print-welcome` - print welcome message. Enabled by default. + +## Configuration File +GolangCI-Lint looks for next config paths in the current directory: +- `.golangci.yml` +- `.golangci.toml` +- `.golangci.json` + +Configuration options inside the file are identical to command-line 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: +```yaml +{{.GolangciYaml}} +``` + +# False Positives +False positives are inevitable, but we did our best to reduce their count. For example, we have an enabled by default set of [exclude patterns](#issues-options). If false positive occurred you have next choices: +1. Exclude issue by text using command-line option `-e` or config option `issues.exclude`. It's helpful when you decided to ignore all issues of this type. +2. Exclude this one issue by using special comment `// nolint[:linter1,linter2,...]` on issued line. +Comment `// nolint` disables all issues reporting on this line. Comment e.g. `// nolint:govet` disables only govet issues for this line. + +Please create [GitHub Issues here](https://github.com/golangci/golangci-lint/issues/new) about found false positives. We will add it to default exclude list if it's common or we will fix underlying linter. + +# IDE integrations +1. VS Code - [pull request](https://github.com/Microsoft/vscode-go/pull/1693) to vscode-go (thanks to [pdf](https://github.com/pdf)). +2. Vim - [issue](https://github.com/fatih/vim-go/issues/1841) for vim-go. +3. GNU Emacs - [flycheck checker](https://github.com/weijiangan/flycheck-golangci-lint) (thanks to [weijiangan](https://github.com/weijiangan)). +4. Sublime Text - [plugin](https://github.com/alecthomas/SublimeLinter-contrib-golang-cilint) for SublimeLinter (thanks to [alecthomas](https://github.com/alecthomas)). + +# Internals +The key difference with gometalinter is that golangci-lint shares work between specific linters (golint, govet, ...). +For small and medium projects 50-80% of work between linters can be reused. +Now we share `loader.Program` and `SSA` representation building. `SSA` representation is used from +a [fork of go-tools](https://github.com/dominikh/go-tools), not the official one. Also, we are going to +reuse `AST` parsing and traversal. + +We don't fork to call specific linter but use its API. We forked GitHub repos of almost all linters +to make API. It also allows us to be more performant and control actual count of used threads. + +All linters are vendored in `/vendor` folder: their version is fixed, they are builtin +and you don't need to install them separately. + +We use chains for issues and independent processors to post-process them: exclude issues by limits, +nolint comment, diff, regexps; prettify paths etc. + +We use `cobra` for command-line action. + +# FAQ +**Q: How to add custom linter?** + +A: You can integrate it yourself, see this [wiki page](https://github.com/golangci/golangci-lint/wiki/How-to-add-a-custom-linter) with documentation. Or you can create [GitHub Issue](https://github.com/golangci/golangci-lint/issues/new) and we will integrate it soon. + +**Q: It's cool to use `golangci-lint` when starting a project, but what about existing projects with large codebase? It will take days to fix all found issues** + +A: We are sure that every project can easily integrate `golangci-lint`, even the large one. The idea is to not fix all existing issues. Fix only newly added issue: issues in new code. To do this setup CI (or better use [GolangCI](https://golangci.com) to run `golangci-lint` with option `--new-from-rev=origin/master`. Also, take a look at option `-n`. +By doing this you won't create new issues in code and can smoothly fix existing issues (or not). + +**Q: How to use `golangci-lint` in CI (Continuous Integration)?** + +A: You have 2 choices: +1. Use [GolangCI](https://golangci.com): this service is highly integrated with GitHub (issues are commented in the pull request) and uses a `golangci-lint` tool. For configuration use `.golangci.yml` (or toml/json). +2. Use custom CI: just run `golangci-lint` in CI and check the exit code. If it's non-zero - fail the build. The main disadvantage is that you can't see found issues in pull request code and should view build log, then open needed source file to see a context. +If you'd like to vendor `golangci-lint` in your repo, run: +```bash +go get -u github.com/golang/dep/cmd/dep +dep init +dep ensure -v -add github.com/golangci/golangci-lint/cmd/golangci-lint +``` +Then add these lines to your `Gopkg.toml` file, so `dep ensure -update` won't delete the vendored `golangci-lint` code. +```toml +required = [ + "github.com/golangci/golangci-lint/cmd/golangci-lint", +] +``` +In your CI scripts, install the vendored `golangci-lint` like this: +```bash +go install ./vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/` +``` +Vendoring `golangci-lint` saves a network request, potentially making your CI system a little more reliable. + +**Q: `golangci-lint` doesn't work** +1. Update it: `go get -u github.com/golangci/golangci-lint/cmd/golangci-lint` +2. Run it with `-v` option and check the output. +3. If it doesn't help create [GitHub issue](https://github.com/golangci/golangci-lint/issues/new) with the output. + +# Thanks +Thanks to [alecthomas/gometalinter](https://github.com/alecthomas/gometalinter) for inspiration and amazing work. +Thanks to [bradleyfalzon/revgrep](https://github.com/bradleyfalzon/revgrep) for cool diff tool. + +Thanks to developers and authors of used linters: +{{.ThanksList}} + +# Future Plans +1. Upstream all changes of forked linters. +2. Fully integrate all used linters: make a common interface and reuse 100% of what can be reused: AST traversal, packages preparation etc. +3. Make it easy to write own linter/checker: it should take a minimum code, have perfect documentation, debugging and testing tooling. +4. Speedup packages loading (dig into [loader](golang.org/x/tools/go/loader)): on-disk cache and existing code profiling-optimizing. +5. Analyze (don't only filter) only new code: analyze only changed files and dependencies, make incremental analysis, caches. +6. Smart new issues detector: don't print existing issues on changed lines. +7. Integration with Text Editors. On-the-fly code analysis for text editors: it should be super-fast. +8. Minimize false-positives by fixing linters and improving testing tooling. +9. Automatic issues fixing (code rewrite, refactoring) where it's possible. +10. Documentation for every issue type. + +# Contact Information +You can contact the author of GolangCI-Lint by [denis@golangci.com](mailto:denis@golangci.com). diff --git a/pkg/commands/linters.go b/pkg/commands/linters.go index 7adc4d9a..032db3c4 100644 --- a/pkg/commands/linters.go +++ b/pkg/commands/linters.go @@ -6,7 +6,8 @@ import ( "strings" "github.com/fatih/color" - "github.com/golangci/golangci-lint/pkg" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/lint/lintersdb" "github.com/golangci/golangci-lint/pkg/printers" "github.com/spf13/cobra" ) @@ -20,7 +21,7 @@ func (e *Executor) initLinters() { e.rootCmd.AddCommand(lintersCmd) } -func printLinterConfigs(lcs []pkg.LinterConfig) { +func printLinterConfigs(lcs []linter.Config) { for _, lc := range lcs { fmt.Fprintf(printers.StdOut, "%s: %s [fast: %t]\n", color.YellowString(lc.Linter.Name()), lc.Linter.Desc(), !lc.DoesFullImport) @@ -28,8 +29,8 @@ func printLinterConfigs(lcs []pkg.LinterConfig) { } func (e Executor) executeLinters(cmd *cobra.Command, args []string) { - var enabledLCs, disabledLCs []pkg.LinterConfig - for _, lc := range pkg.GetAllSupportedLinterConfigs() { + var enabledLCs, disabledLCs []linter.Config + for _, lc := range lintersdb.GetAllSupportedLinterConfigs() { if lc.EnabledByDefault { enabledLCs = append(enabledLCs, lc) } else { @@ -43,8 +44,8 @@ func (e Executor) executeLinters(cmd *cobra.Command, args []string) { printLinterConfigs(disabledLCs) color.Green("\nLinters presets:") - for _, p := range pkg.AllPresets() { - linters := pkg.GetAllLinterConfigsForPreset(p) + for _, p := range lintersdb.AllPresets() { + linters := lintersdb.GetAllLinterConfigsForPreset(p) linterNames := []string{} for _, lc := range linters { linterNames = append(linterNames, lc.Linter.Name()) diff --git a/pkg/commands/run.go b/pkg/commands/run.go index 151e2dca..c3f3a895 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -12,9 +12,9 @@ import ( "strings" "time" - "github.com/golangci/golangci-lint/pkg" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/lintersdb" "github.com/golangci/golangci-lint/pkg/printers" "github.com/golangci/golangci-lint/pkg/result" "github.com/golangci/golangci-lint/pkg/result/processors" @@ -100,7 +100,7 @@ func (e *Executor) initRun() { runCmd.Flags().BoolVar(&lc.EnableAll, "enable-all", false, "Enable all linters") runCmd.Flags().BoolVar(&lc.DisableAll, "disable-all", false, "Disable all linters") runCmd.Flags().StringSliceVarP(&lc.Presets, "presets", "p", []string{}, - fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see them. This option implies option --disable-all", strings.Join(pkg.AllPresets(), "|"))) + fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see them. This option implies option --disable-all", strings.Join(lintersdb.AllPresets(), "|"))) runCmd.Flags().BoolVar(&lc.Fast, "fast", false, "Run only fast linters from enabled linters set") // Issues config @@ -122,16 +122,12 @@ func (e *Executor) initRun() { func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) { e.cfg.Run.Args = args - linters, err := pkg.GetEnabledLinters(e.cfg) + linters, err := lintersdb.GetEnabledLinters(e.cfg) if err != nil { return nil, err } - ctxLinters := make([]lint.LinterConfig, 0, len(linters)) - for _, lc := range linters { - ctxLinters = append(ctxLinters, lint.LinterConfig(lc)) - } - lintCtx, err := lint.BuildContext(ctx, ctxLinters, e.cfg) + lintCtx, err := lint.LoadContext(ctx, linters, e.cfg) if err != nil { return nil, err } @@ -162,11 +158,7 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan resul }, } - runLinters := make([]lint.RunnerLinterConfig, 0, len(linters)) - for _, lc := range linters { - runLinters = append(runLinters, lint.RunnerLinterConfig(lc)) - } - return runner.Run(ctx, runLinters, lintCtx), nil + return runner.Run(ctx, linters, lintCtx), nil } func setOutputToDevNull() (savedStdout, savedStderr *os.File) { diff --git a/pkg/golinters/deadcode.go b/pkg/golinters/deadcode.go index bb6e89c2..4f7d74be 100644 --- a/pkg/golinters/deadcode.go +++ b/pkg/golinters/deadcode.go @@ -5,7 +5,7 @@ import ( "fmt" deadcodeAPI "github.com/golangci/go-misc/deadcode" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) @@ -19,7 +19,7 @@ func (Deadcode) Desc() string { return "Finds unused code" } -func (d Deadcode) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (d Deadcode) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { issues, err := deadcodeAPI.Run(lintCtx.Program) if err != nil { return nil, err diff --git a/pkg/golinters/depguard.go b/pkg/golinters/depguard.go index 3c3987ea..72f5ed02 100644 --- a/pkg/golinters/depguard.go +++ b/pkg/golinters/depguard.go @@ -6,7 +6,7 @@ import ( "strings" depguardAPI "github.com/OpenPeeDeeP/depguard" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) @@ -20,7 +20,7 @@ func (Depguard) Desc() string { return "Go linter that checks if package imports are in a list of acceptable packages" } -func (d Depguard) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (d Depguard) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { dg := &depguardAPI.Depguard{ Packages: lintCtx.Settings().Depguard.Packages, IncludeGoRoot: lintCtx.Settings().Depguard.IncludeGoRoot, diff --git a/pkg/golinters/dupl.go b/pkg/golinters/dupl.go index d9963620..2784f344 100644 --- a/pkg/golinters/dupl.go +++ b/pkg/golinters/dupl.go @@ -5,7 +5,7 @@ import ( "fmt" "go/token" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" duplAPI "github.com/mibk/dupl" ) @@ -20,7 +20,7 @@ func (Dupl) Desc() string { return "Tool for code clone detection" } -func (d Dupl) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (d Dupl) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { issues, err := duplAPI.Run(lintCtx.Paths.Files, lintCtx.Settings().Dupl.Threshold) if err != nil { return nil, err diff --git a/pkg/golinters/errcheck.go b/pkg/golinters/errcheck.go index fbd59afd..5c5bd00e 100644 --- a/pkg/golinters/errcheck.go +++ b/pkg/golinters/errcheck.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" errcheckAPI "github.com/kisielk/errcheck/golangci" ) @@ -19,7 +19,7 @@ func (Errcheck) Desc() string { return "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases" } -func (e Errcheck) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (e Errcheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { errCfg := &lintCtx.Settings().Errcheck issues, err := errcheckAPI.Run(lintCtx.Program, errCfg.CheckAssignToBlank, errCfg.CheckTypeAssertions) if err != nil { diff --git a/pkg/golinters/gas.go b/pkg/golinters/gas.go index 58c5c2ee..c5ecd033 100644 --- a/pkg/golinters/gas.go +++ b/pkg/golinters/gas.go @@ -10,7 +10,7 @@ import ( "github.com/GoASTScanner/gas" "github.com/GoASTScanner/gas/rules" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" "github.com/sirupsen/logrus" ) @@ -25,7 +25,7 @@ func (Gas) Desc() string { return "Inspects source code for security problems" } -func (lint Gas) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (lint Gas) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { gasConfig := gas.NewConfig() enabledRules := rules.Generate() logger := log.New(ioutil.Discard, "", 0) diff --git a/pkg/golinters/goconst.go b/pkg/golinters/goconst.go index 09ee3628..653561eb 100644 --- a/pkg/golinters/goconst.go +++ b/pkg/golinters/goconst.go @@ -5,7 +5,7 @@ import ( "fmt" goconstAPI "github.com/golangci/goconst" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) @@ -19,7 +19,7 @@ func (Goconst) Desc() string { return "Finds repeated strings that could be replaced by a constant" } -func (lint Goconst) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (lint Goconst) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { var goconstIssues []goconstAPI.Issue // TODO: make it cross-package: pass package names inside goconst for _, files := range lintCtx.Paths.FilesGrouppedByDirs() { diff --git a/pkg/golinters/gocyclo.go b/pkg/golinters/gocyclo.go index 8945abea..d9b43d87 100644 --- a/pkg/golinters/gocyclo.go +++ b/pkg/golinters/gocyclo.go @@ -6,7 +6,7 @@ import ( "sort" gocycloAPI "github.com/golangci/gocyclo/pkg/gocyclo" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) @@ -20,7 +20,7 @@ func (Gocyclo) Desc() string { return "Computes and checks the cyclomatic complexity of functions" } -func (g Gocyclo) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (g Gocyclo) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { var stats []gocycloAPI.Stat for _, f := range lintCtx.ASTCache.GetAllValidFiles() { stats = gocycloAPI.BuildStats(f.F, f.Fset, stats) diff --git a/pkg/golinters/gofmt.go b/pkg/golinters/gofmt.go index d4cc9cae..773405dc 100644 --- a/pkg/golinters/gofmt.go +++ b/pkg/golinters/gofmt.go @@ -8,7 +8,7 @@ import ( gofmtAPI "github.com/golangci/gofmt/gofmt" goimportsAPI "github.com/golangci/gofmt/goimports" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" "github.com/sirupsen/logrus" "sourcegraph.com/sourcegraph/go-diff/diff" @@ -102,7 +102,7 @@ func (g Gofmt) extractIssuesFromPatch(patch string) ([]result.Issue, error) { return issues, nil } -func (g Gofmt) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (g Gofmt) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { var issues []result.Issue for _, f := range lintCtx.Paths.Files { diff --git a/pkg/golinters/golint.go b/pkg/golinters/golint.go index c26f9fc4..1d5f5476 100644 --- a/pkg/golinters/golint.go +++ b/pkg/golinters/golint.go @@ -6,7 +6,7 @@ import ( "io/ioutil" lintAPI "github.com/golang/lint" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" "github.com/sirupsen/logrus" ) @@ -21,7 +21,7 @@ func (Golint) Desc() string { return "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes" } -func (g Golint) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (g Golint) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { var issues []result.Issue var lintErr error for _, pkgFiles := range lintCtx.Paths.FilesGrouppedByDirs() { diff --git a/pkg/golinters/govet.go b/pkg/golinters/govet.go index c50639c6..5dad22ac 100644 --- a/pkg/golinters/govet.go +++ b/pkg/golinters/govet.go @@ -3,7 +3,7 @@ package golinters import ( "context" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" govetAPI "github.com/golangci/govet" ) @@ -18,7 +18,7 @@ func (Govet) Desc() string { return "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string" } -func (g Govet) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (g Govet) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { // TODO: check .S asm files: govet can do it if pass dirs var govetIssues []govetAPI.Issue for _, files := range lintCtx.Paths.FilesGrouppedByDirs() { diff --git a/pkg/golinters/ineffassign.go b/pkg/golinters/ineffassign.go index cb7a75f1..3d13ddf3 100644 --- a/pkg/golinters/ineffassign.go +++ b/pkg/golinters/ineffassign.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ineffassignAPI "github.com/golangci/ineffassign" ) @@ -19,7 +19,7 @@ func (Ineffassign) Desc() string { return "Detects when assignments to existing variables are not used" } -func (lint Ineffassign) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (lint Ineffassign) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { issues := ineffassignAPI.Run(lintCtx.Paths.Files) if len(issues) == 0 { return nil, nil diff --git a/pkg/golinters/interfacer.go b/pkg/golinters/interfacer.go index becc50ce..f3652e49 100644 --- a/pkg/golinters/interfacer.go +++ b/pkg/golinters/interfacer.go @@ -5,7 +5,7 @@ import ( "mvdan.cc/interfacer/check" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) @@ -19,7 +19,7 @@ func (Interfacer) Desc() string { return "Linter that suggests narrower interface types" } -func (lint Interfacer) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (lint Interfacer) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { c := new(check.Checker) c.Program(lintCtx.Program) c.ProgramSSA(lintCtx.SSAProgram) diff --git a/pkg/golinters/maligned.go b/pkg/golinters/maligned.go index a79d633e..e90bc9a4 100644 --- a/pkg/golinters/maligned.go +++ b/pkg/golinters/maligned.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" malignedAPI "github.com/golangci/maligned" ) @@ -19,7 +19,7 @@ func (Maligned) Desc() string { return "Tool to detect Go structs that would take less memory if their fields were sorted" } -func (m Maligned) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (m Maligned) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { issues := malignedAPI.Run(lintCtx.Program) if len(issues) == 0 { return nil, nil diff --git a/pkg/golinters/megacheck.go b/pkg/golinters/megacheck.go index 45f0392d..f853d1b0 100644 --- a/pkg/golinters/megacheck.go +++ b/pkg/golinters/megacheck.go @@ -6,7 +6,7 @@ import ( "strings" megacheckAPI "github.com/golangci/go-tools/cmd/megacheck" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) @@ -42,15 +42,15 @@ func (m Megacheck) Name() string { func (m Megacheck) Desc() string { descs := map[string]string{ "unused": "Checks Go code for unused constants, variables, functions and types", - "gosimple": "Linter for Go source code that specialises on simplifying code", - "staticcheck": "Staticcheck is go vet on steroids, applying a ton of static analysis checks", + "gosimple": "Linter for Go source code that specializes in simplifying a code", + "staticcheck": "Staticcheck is a go vet on steroids, applying a ton of static analysis checks", "megacheck": "3 sub-linters in one: unused, gosimple and staticcheck", } return descs[m.Name()] } -func (m Megacheck) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (m Megacheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { issues := megacheckAPI.Run(lintCtx.Program, lintCtx.LoaderConfig, lintCtx.SSAProgram, m.StaticcheckEnabled, m.GosimpleEnabled, m.UnusedEnabled) if len(issues) == 0 { diff --git a/pkg/golinters/structcheck.go b/pkg/golinters/structcheck.go index dd87a50c..9fd431fa 100644 --- a/pkg/golinters/structcheck.go +++ b/pkg/golinters/structcheck.go @@ -5,7 +5,7 @@ import ( "fmt" structcheckAPI "github.com/golangci/check/cmd/structcheck" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) @@ -16,10 +16,10 @@ func (Structcheck) Name() string { } func (Structcheck) Desc() string { - return "Finds unused struct fields" + return "Finds an unused struct fields" } -func (s Structcheck) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (s Structcheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { issues := structcheckAPI.Run(lintCtx.Program, lintCtx.Settings().Structcheck.CheckExportedFields) if len(issues) == 0 { return nil, nil diff --git a/pkg/golinters/typecheck.go b/pkg/golinters/typecheck.go index 01ad9862..e76108a5 100644 --- a/pkg/golinters/typecheck.go +++ b/pkg/golinters/typecheck.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) @@ -62,7 +62,7 @@ func (lint TypeCheck) parseError(err error) *result.Issue { } } -func (lint TypeCheck) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (lint TypeCheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { if lintCtx.NotCompilingPackages == nil { return nil, nil } diff --git a/pkg/golinters/unconvert.go b/pkg/golinters/unconvert.go index 7f38f8c4..22ca8082 100644 --- a/pkg/golinters/unconvert.go +++ b/pkg/golinters/unconvert.go @@ -3,7 +3,7 @@ package golinters import ( "context" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" unconvertAPI "github.com/golangci/unconvert" ) @@ -18,7 +18,7 @@ func (Unconvert) Desc() string { return "Remove unnecessary type conversions" } -func (lint Unconvert) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (lint Unconvert) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { positions := unconvertAPI.Run(lintCtx.Program) if len(positions) == 0 { return nil, nil diff --git a/pkg/golinters/varcheck.go b/pkg/golinters/varcheck.go index 05aa9c1d..8e506e98 100644 --- a/pkg/golinters/varcheck.go +++ b/pkg/golinters/varcheck.go @@ -5,7 +5,7 @@ import ( "fmt" varcheckAPI "github.com/golangci/check/cmd/varcheck" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) @@ -19,7 +19,7 @@ func (Varcheck) Desc() string { return "Finds unused global variables and constants" } -func (v Varcheck) Run(ctx context.Context, lintCtx *lint.Context) ([]result.Issue, error) { +func (v Varcheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { issues := varcheckAPI.Run(lintCtx.Program, lintCtx.Settings().Varcheck.CheckExportedFields) if len(issues) == 0 { return nil, nil diff --git a/pkg/lint/linter/config.go b/pkg/lint/linter/config.go new file mode 100644 index 00000000..b50c266b --- /dev/null +++ b/pkg/lint/linter/config.go @@ -0,0 +1,69 @@ +package linter + +const ( + PresetFormatting = "format" + PresetComplexity = "complexity" + PresetStyle = "style" + PresetBugs = "bugs" + PresetUnused = "unused" + PresetPerformance = "performance" +) + +type Config struct { + Linter Linter + EnabledByDefault bool + DoesFullImport bool + NeedsSSARepr bool + InPresets []string + Speed int // more value means faster execution of linter + + OriginalURL string // URL of original (not forked) repo, needed for autogenerated README +} + +func (lc Config) WithFullImport() Config { + lc.DoesFullImport = true + return lc +} + +func (lc Config) WithSSA() Config { + lc.DoesFullImport = true + lc.NeedsSSARepr = true + return lc +} + +func (lc Config) WithPresets(presets ...string) Config { + lc.InPresets = presets + return lc +} + +func (lc Config) WithSpeed(speed int) Config { + lc.Speed = speed + return lc +} + +func (lc Config) WithURL(url string) Config { + lc.OriginalURL = url + return lc +} + +func (lc Config) NeedsProgramLoading() bool { + return lc.DoesFullImport +} + +func (lc Config) NeedsSSARepresentation() bool { + return lc.NeedsSSARepr +} + +func (lc Config) GetSpeed() int { + return lc.Speed +} + +func (lc Config) GetLinter() Linter { + return lc.Linter +} + +func NewConfig(linter Linter) *Config { + return &Config{ + Linter: linter, + } +} diff --git a/pkg/lint/linter/context.go b/pkg/lint/linter/context.go new file mode 100644 index 00000000..173b2536 --- /dev/null +++ b/pkg/lint/linter/context.go @@ -0,0 +1,23 @@ +package linter + +import ( + "github.com/golangci/go-tools/ssa" + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/fsutils" + "github.com/golangci/golangci-lint/pkg/lint/astcache" + "golang.org/x/tools/go/loader" +) + +type Context struct { + Paths *fsutils.ProjectPaths + Cfg *config.Config + Program *loader.Program + SSAProgram *ssa.Program + LoaderConfig *loader.Config + ASTCache *astcache.Cache + NotCompilingPackages []*loader.PackageInfo +} + +func (c *Context) Settings() *config.LintersSettings { + return &c.Cfg.LintersSettings +} diff --git a/pkg/lint/linter.go b/pkg/lint/linter/linter.go similarity index 92% rename from pkg/lint/linter.go rename to pkg/lint/linter/linter.go index 1dd58017..cfe9ec02 100644 --- a/pkg/lint/linter.go +++ b/pkg/lint/linter/linter.go @@ -1,4 +1,4 @@ -package lint +package linter import ( "context" diff --git a/pkg/enabled_linters.go b/pkg/lint/lintersdb/lintersdb.go similarity index 59% rename from pkg/enabled_linters.go rename to pkg/lint/lintersdb/lintersdb.go index 814d3279..fbc37253 100644 --- a/pkg/enabled_linters.go +++ b/pkg/lint/lintersdb/lintersdb.go @@ -1,4 +1,4 @@ -package pkg +package lintersdb import ( "fmt" @@ -8,21 +8,12 @@ import ( "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters" - "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/sirupsen/logrus" ) -const ( - PresetFormatting = "format" - PresetComplexity = "complexity" - PresetStyle = "style" - PresetBugs = "bugs" - PresetUnused = "unused" - PresetPerformance = "performance" -) - func AllPresets() []string { - return []string{PresetBugs, PresetUnused, PresetFormatting, PresetStyle, PresetComplexity, PresetPerformance} + return []string{linter.PresetBugs, linter.PresetUnused, linter.PresetFormatting, linter.PresetStyle, linter.PresetComplexity, linter.PresetPerformance} } func allPresetsSet() map[string]bool { @@ -33,64 +24,12 @@ func allPresetsSet() map[string]bool { return ret } -type LinterConfig struct { - Linter lint.Linter - EnabledByDefault bool - DoesFullImport bool - NeedsSSARepr bool - InPresets []string - Speed int // more value means faster execution of linter -} - -func (lc LinterConfig) WithFullImport() LinterConfig { - lc.DoesFullImport = true - return lc -} - -func (lc LinterConfig) WithSSA() LinterConfig { - lc.DoesFullImport = true - lc.NeedsSSARepr = true - return lc -} - -func (lc LinterConfig) WithPresets(presets ...string) LinterConfig { - lc.InPresets = presets - return lc -} - -func (lc LinterConfig) WithSpeed(speed int) LinterConfig { - lc.Speed = speed - return lc -} - -func (lc LinterConfig) NeedsProgramLoading() bool { - return lc.DoesFullImport -} - -func (lc LinterConfig) NeedsSSARepresentation() bool { - return lc.NeedsSSARepr -} - -func (lc LinterConfig) GetSpeed() int { - return lc.Speed -} - -func (lc LinterConfig) GetLinter() lint.Linter { - return lc.Linter -} - -func newLinterConfig(linter lint.Linter) LinterConfig { - return LinterConfig{ - Linter: linter, - } -} - -var nameToLC map[string]LinterConfig +var nameToLC map[string]linter.Config var nameToLCOnce sync.Once -func getLinterConfig(name string) *LinterConfig { +func getLinterConfig(name string) *linter.Config { nameToLCOnce.Do(func() { - nameToLC = make(map[string]LinterConfig) + nameToLC = make(map[string]linter.Config) for _, lc := range GetAllSupportedLinterConfigs() { nameToLC[lc.Linter.Name()] = lc } @@ -104,8 +43,8 @@ func getLinterConfig(name string) *LinterConfig { return &lc } -func enableLinterConfigs(lcs []LinterConfig, isEnabled func(lc *LinterConfig) bool) []LinterConfig { - var ret []LinterConfig +func enableLinterConfigs(lcs []linter.Config, isEnabled func(lc *linter.Config) bool) []linter.Config { + var ret []linter.Config for _, lc := range lcs { lc.EnabledByDefault = isEnabled(&lc) ret = append(ret, lc) @@ -114,35 +53,113 @@ func enableLinterConfigs(lcs []LinterConfig, isEnabled func(lc *LinterConfig) bo return ret } -func GetAllSupportedLinterConfigs() []LinterConfig { - lcs := []LinterConfig{ - newLinterConfig(golinters.Govet{}).WithPresets(PresetBugs).WithSpeed(4), - newLinterConfig(golinters.Errcheck{}).WithFullImport().WithPresets(PresetBugs).WithSpeed(10), - newLinterConfig(golinters.Golint{}).WithPresets(PresetStyle).WithSpeed(3), +func GetAllSupportedLinterConfigs() []linter.Config { + lcs := []linter.Config{ + linter.NewConfig(golinters.Govet{}). + WithPresets(linter.PresetBugs). + WithSpeed(4). + WithURL("https://golang.org/cmd/vet/"), + linter.NewConfig(golinters.Errcheck{}). + WithFullImport(). + WithPresets(linter.PresetBugs). + WithSpeed(10). + WithURL("https://github.com/kisielk/errcheck"), + linter.NewConfig(golinters.Golint{}). + WithPresets(linter.PresetStyle). + WithSpeed(3). + WithURL("https://github.com/golang/lint"), - newLinterConfig(golinters.Megacheck{StaticcheckEnabled: true}).WithSSA(). - WithPresets(PresetBugs).WithSpeed(2), - newLinterConfig(golinters.Megacheck{UnusedEnabled: true}).WithSSA().WithPresets(PresetUnused).WithSpeed(5), - newLinterConfig(golinters.Megacheck{GosimpleEnabled: true}).WithSSA().WithPresets(PresetStyle).WithSpeed(5), + linter.NewConfig(golinters.Megacheck{StaticcheckEnabled: true}). + WithSSA(). + WithPresets(linter.PresetBugs). + WithSpeed(2). + WithURL("https://staticcheck.io/"), + linter.NewConfig(golinters.Megacheck{UnusedEnabled: true}). + WithSSA(). + WithPresets(linter.PresetUnused). + WithSpeed(5). + WithURL("https://github.com/dominikh/go-tools/tree/master/cmd/unused"), + linter.NewConfig(golinters.Megacheck{GosimpleEnabled: true}). + WithSSA(). + WithPresets(linter.PresetStyle). + WithSpeed(5). + WithURL("https://github.com/dominikh/go-tools/tree/master/cmd/gosimple"), - newLinterConfig(golinters.Gas{}).WithFullImport().WithPresets(PresetBugs).WithSpeed(8), - newLinterConfig(golinters.Structcheck{}).WithFullImport().WithPresets(PresetUnused).WithSpeed(10), - newLinterConfig(golinters.Varcheck{}).WithFullImport().WithPresets(PresetUnused).WithSpeed(10), - newLinterConfig(golinters.Interfacer{}).WithSSA().WithPresets(PresetStyle).WithSpeed(6), - newLinterConfig(golinters.Unconvert{}).WithFullImport().WithPresets(PresetStyle).WithSpeed(10), - newLinterConfig(golinters.Ineffassign{}).WithPresets(PresetUnused).WithSpeed(9), - newLinterConfig(golinters.Dupl{}).WithPresets(PresetStyle).WithSpeed(7), - newLinterConfig(golinters.Goconst{}).WithPresets(PresetStyle).WithSpeed(9), - newLinterConfig(golinters.Deadcode{}).WithFullImport().WithPresets(PresetUnused).WithSpeed(10), - newLinterConfig(golinters.Gocyclo{}).WithPresets(PresetComplexity).WithSpeed(8), - newLinterConfig(golinters.TypeCheck{}).WithFullImport().WithPresets(PresetBugs).WithSpeed(10), + linter.NewConfig(golinters.Gas{}). + WithFullImport(). + WithPresets(linter.PresetBugs). + WithSpeed(8). + WithURL("https://github.com/GoASTScanner/gas"), + linter.NewConfig(golinters.Structcheck{}). + WithFullImport(). + WithPresets(linter.PresetUnused). + WithSpeed(10). + WithURL("https://github.com/opennota/check"), + linter.NewConfig(golinters.Varcheck{}). + WithFullImport(). + WithPresets(linter.PresetUnused). + WithSpeed(10). + WithURL("https://github.com/opennota/check"), + linter.NewConfig(golinters.Interfacer{}). + WithSSA(). + WithPresets(linter.PresetStyle). + WithSpeed(6). + WithURL("https://github.com/mvdan/interfacer"), + linter.NewConfig(golinters.Unconvert{}). + WithFullImport(). + WithPresets(linter.PresetStyle). + WithSpeed(10). + WithURL("https://github.com/mdempsky/unconvert"), + linter.NewConfig(golinters.Ineffassign{}). + WithPresets(linter.PresetUnused). + WithSpeed(9). + WithURL("https://github.com/gordonklaus/ineffassign"), + linter.NewConfig(golinters.Dupl{}). + WithPresets(linter.PresetStyle). + WithSpeed(7). + WithURL("https://github.com/mibk/dupl"), + linter.NewConfig(golinters.Goconst{}). + WithPresets(linter.PresetStyle). + WithSpeed(9). + WithURL("https://github.com/jgautheron/goconst"), + linter.NewConfig(golinters.Deadcode{}). + WithFullImport(). + WithPresets(linter.PresetUnused). + WithSpeed(10). + WithURL("https://github.com/remyoudompheng/go-misc/tree/master/deadcode"), + linter.NewConfig(golinters.Gocyclo{}). + WithPresets(linter.PresetComplexity). + WithSpeed(8). + WithURL("https://github.com/alecthomas/gocyclo"), + linter.NewConfig(golinters.TypeCheck{}). + WithFullImport(). + WithPresets(linter.PresetBugs). + WithSpeed(10). + WithURL(""), - newLinterConfig(golinters.Gofmt{}).WithPresets(PresetFormatting).WithSpeed(7), - newLinterConfig(golinters.Gofmt{UseGoimports: true}).WithPresets(PresetFormatting).WithSpeed(5), - newLinterConfig(golinters.Maligned{}).WithFullImport().WithPresets(PresetPerformance).WithSpeed(10), - newLinterConfig(golinters.Megacheck{GosimpleEnabled: true, UnusedEnabled: true, StaticcheckEnabled: true}). - WithSSA().WithPresets(PresetStyle, PresetBugs, PresetUnused).WithSpeed(1), - newLinterConfig(golinters.Depguard{}).WithFullImport().WithPresets(PresetStyle).WithSpeed(6), + linter.NewConfig(golinters.Gofmt{}). + WithPresets(linter.PresetFormatting). + WithSpeed(7). + WithURL("https://golang.org/cmd/gofmt/"), + linter.NewConfig(golinters.Gofmt{UseGoimports: true}). + WithPresets(linter.PresetFormatting). + WithSpeed(5). + WithURL("https://godoc.org/golang.org/x/tools/cmd/goimports"), + linter.NewConfig(golinters.Maligned{}). + WithFullImport(). + WithPresets(linter.PresetPerformance). + WithSpeed(10). + WithURL("https://github.com/mdempsky/maligned"), + linter.NewConfig(golinters.Megacheck{GosimpleEnabled: true, UnusedEnabled: true, StaticcheckEnabled: true}). + WithSSA(). + WithPresets(linter.PresetStyle, linter.PresetBugs, linter.PresetUnused). + WithSpeed(1). + WithURL("https://github.com/dominikh/go-tools/tree/master/cmd/megacheck"), + linter.NewConfig(golinters.Depguard{}). + WithFullImport(). + WithPresets(linter.PresetStyle). + WithSpeed(6). + WithURL("https://github.com/OpenPeeDeeP/depguard"), } if os.Getenv("GOLANGCI_COM_RUN") == "1" { @@ -152,7 +169,7 @@ func GetAllSupportedLinterConfigs() []LinterConfig { golinters.Maligned{}.Name(): true, // rarely usable golinters.TypeCheck{}.Name(): true, // annoying because of different building envs } - return enableLinterConfigs(lcs, func(lc *LinterConfig) bool { + return enableLinterConfigs(lcs, func(lc *linter.Config) bool { return !disabled[lc.Linter.Name()] }) } @@ -170,13 +187,13 @@ func GetAllSupportedLinterConfigs() []LinterConfig { golinters.Deadcode{}.Name(): true, golinters.TypeCheck{}.Name(): true, } - return enableLinterConfigs(lcs, func(lc *LinterConfig) bool { + return enableLinterConfigs(lcs, func(lc *linter.Config) bool { return enabled[lc.Linter.Name()] }) } -func getAllEnabledByDefaultLinters() []LinterConfig { - var ret []LinterConfig +func getAllEnabledByDefaultLinters() []linter.Config { + var ret []linter.Config for _, lc := range GetAllSupportedLinterConfigs() { if lc.EnabledByDefault { ret = append(ret, lc) @@ -186,8 +203,8 @@ func getAllEnabledByDefaultLinters() []LinterConfig { return ret } -func linterConfigsToMap(lcs []LinterConfig) map[string]*LinterConfig { - ret := map[string]*LinterConfig{} +func linterConfigsToMap(lcs []linter.Config) map[string]*linter.Config { + ret := map[string]*linter.Config{} for _, lc := range lcs { lc := lc // local copy ret[lc.Linter.Name()] = &lc @@ -276,8 +293,8 @@ func validateEnabledDisabledLintersConfig(cfg *config.Linters) error { return nil } -func GetAllLinterConfigsForPreset(p string) []LinterConfig { - ret := []LinterConfig{} +func GetAllLinterConfigsForPreset(p string) []linter.Config { + ret := []linter.Config{} for _, lc := range GetAllSupportedLinterConfigs() { for _, ip := range lc.InPresets { if p == ip { @@ -290,8 +307,8 @@ func GetAllLinterConfigsForPreset(p string) []LinterConfig { return ret } -func getEnabledLintersSet(lcfg *config.Linters, enabledByDefaultLinters []LinterConfig) map[string]*LinterConfig { // nolint:gocyclo - resultLintersSet := map[string]*LinterConfig{} +func getEnabledLintersSet(lcfg *config.Linters, enabledByDefaultLinters []linter.Config) map[string]*linter.Config { // nolint:gocyclo + resultLintersSet := map[string]*linter.Config{} switch { case len(lcfg.Presets) != 0: break // imply --disable-all @@ -345,7 +362,7 @@ func getAllMegacheckSubLinterNames() []string { return []string{unusedName, gosimpleName, staticcheckName} } -func optimizeLintersSet(linters map[string]*LinterConfig) { +func optimizeLintersSet(linters map[string]*linter.Config) { unusedName := golinters.Megacheck{UnusedEnabled: true}.Name() gosimpleName := golinters.Megacheck{GosimpleEnabled: true}.Name() staticcheckName := golinters.Megacheck{StaticcheckEnabled: true}.Name() @@ -379,14 +396,14 @@ func optimizeLintersSet(linters map[string]*LinterConfig) { linters[m.Name()] = &lc } -func GetEnabledLinters(cfg *config.Config) ([]LinterConfig, error) { +func GetEnabledLinters(cfg *config.Config) ([]linter.Config, error) { if err := validateEnabledDisabledLintersConfig(&cfg.Linters); err != nil { return nil, err } resultLintersSet := getEnabledLintersSet(&cfg.Linters, getAllEnabledByDefaultLinters()) - var resultLinters []LinterConfig + var resultLinters []linter.Config for _, lc := range resultLintersSet { resultLinters = append(resultLinters, *lc) } @@ -410,7 +427,7 @@ func uniqStrings(ss []string) []string { return ret } -func verbosePrintLintersStatus(cfg *config.Config, lcs []LinterConfig) { +func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config) { var linterNames []string for _, lc := range lcs { linterNames = append(linterNames, lc.Linter.Name()) diff --git a/pkg/enabled_linters_test.go b/pkg/lint/lintersdb/lintersdb_test.go similarity index 92% rename from pkg/enabled_linters_test.go rename to pkg/lint/lintersdb/lintersdb_test.go index cf795c60..7a1205aa 100644 --- a/pkg/enabled_linters_test.go +++ b/pkg/lint/lintersdb/lintersdb_test.go @@ -1,10 +1,11 @@ -package pkg +package lintersdb import ( "sort" "testing" "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/stretchr/testify/assert" ) @@ -45,7 +46,7 @@ func TestGetEnabledLintersSet(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { - defaultLinters := []LinterConfig{} + defaultLinters := []linter.Config{} for _, ln := range c.def { defaultLinters = append(defaultLinters, *getLinterConfig(ln)) } diff --git a/pkg/lint/context.go b/pkg/lint/load.go similarity index 89% rename from pkg/lint/context.go rename to pkg/lint/load.go index b75369b0..8ca3da13 100644 --- a/pkg/lint/context.go +++ b/pkg/lint/load.go @@ -14,6 +14,7 @@ import ( "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/fsutils" "github.com/golangci/golangci-lint/pkg/lint/astcache" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/sirupsen/logrus" "golang.org/x/tools/go/loader" ) @@ -32,12 +33,7 @@ func (c *Context) Settings() *config.LintersSettings { return &c.Cfg.LintersSettings } -type LinterConfig interface { - NeedsProgramLoading() bool - NeedsSSARepresentation() bool -} - -func isFullImportNeeded(linters []LinterConfig) bool { +func isFullImportNeeded(linters []linter.Config) bool { for _, linter := range linters { if linter.NeedsProgramLoading() { return true @@ -47,7 +43,7 @@ func isFullImportNeeded(linters []LinterConfig) bool { return false } -func isSSAReprNeeded(linters []LinterConfig) bool { +func isSSAReprNeeded(linters []linter.Config) bool { for _, linter := range linters { if linter.NeedsSSARepresentation() { return true @@ -57,7 +53,7 @@ func isSSAReprNeeded(linters []LinterConfig) bool { return false } -func loadWholeAppIfNeeded(ctx context.Context, linters []LinterConfig, cfg *config.Run, paths *fsutils.ProjectPaths) (*loader.Program, *loader.Config, error) { +func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Run, paths *fsutils.ProjectPaths) (*loader.Program, *loader.Config, error) { if !isFullImportNeeded(linters) { return nil, nil, nil } @@ -117,7 +113,7 @@ func discoverGoRoot() (string, error) { // separateNotCompilingPackages moves not compiling packages into separate slices: // a lot of linters crash on such packages. Leave them only for those linters // which can work with them. -func separateNotCompilingPackages(lintCtx *Context) { +func separateNotCompilingPackages(lintCtx *linter.Context) { prog := lintCtx.Program if prog.Created != nil { @@ -142,7 +138,7 @@ func separateNotCompilingPackages(lintCtx *Context) { } } -func BuildContext(ctx context.Context, linters []LinterConfig, cfg *config.Config) (*Context, error) { +func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Config) (*linter.Context, error) { // Set GOROOT to have working cross-compilation: cross-compiled binaries // have invalid GOROOT. XXX: can't use runtime.GOROOT(). goroot, err := discoverGoRoot() @@ -183,7 +179,7 @@ func BuildContext(ctx context.Context, linters []LinterConfig, cfg *config.Confi astCache = astcache.LoadFromFiles(paths.Files) } - ret := &Context{ + ret := &linter.Context{ Paths: paths, Cfg: cfg, Program: prog, diff --git a/pkg/lint/context_test.go b/pkg/lint/load_test.go similarity index 81% rename from pkg/lint/context_test.go rename to pkg/lint/load_test.go index a236c744..edd721d1 100644 --- a/pkg/lint/context_test.go +++ b/pkg/lint/load_test.go @@ -7,24 +7,17 @@ import ( "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/fsutils" "github.com/golangci/golangci-lint/pkg/lint/astcache" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/stretchr/testify/assert" ) -type testLinterConfig struct{} - -func (t testLinterConfig) NeedsProgramLoading() bool { - return true -} - -func (t testLinterConfig) NeedsSSARepresentation() bool { - return true -} - func TestASTCacheLoading(t *testing.T) { ctx := context.Background() - linters := []LinterConfig{testLinterConfig{}} + linters := []linter.Config{ + linter.NewConfig(nil).WithFullImport(), + } - inputPaths := []string{"./...", "./", "./context.go", "context.go"} + inputPaths := []string{"./...", "./", "./load.go", "load.go"} for _, inputPath := range inputPaths { paths, err := fsutils.GetPathsForAnalysis(ctx, []string{inputPath}, true) assert.NoError(t, err) diff --git a/pkg/lint/runner.go b/pkg/lint/runner.go index 09038027..594cff4b 100644 --- a/pkg/lint/runner.go +++ b/pkg/lint/runner.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" "github.com/golangci/golangci-lint/pkg/result/processors" "github.com/golangci/golangci-lint/pkg/timeutils" @@ -20,12 +21,12 @@ type SimpleRunner struct { } type lintRes struct { - linter Linter + linter linter.Config err error issues []result.Issue } -func runLinterSafe(ctx context.Context, lintCtx *Context, linter Linter) (ret []result.Issue, err error) { +func runLinterSafe(ctx context.Context, lintCtx *linter.Context, lc linter.Config) (ret []result.Issue, err error) { defer func() { if panicData := recover(); panicData != nil { err = fmt.Errorf("panic occured: %s", panicData) @@ -33,10 +34,19 @@ func runLinterSafe(ctx context.Context, lintCtx *Context, linter Linter) (ret [] } }() - return linter.Run(ctx, lintCtx) + issues, err := lc.Linter.Run(ctx, lintCtx) + if err != nil { + return nil, err + } + + for _, i := range issues { + i.FromLinter = lc.Linter.Name() + } + + return issues, nil } -func runWorker(ctx context.Context, lintCtx *Context, tasksCh <-chan Linter, lintResultsCh chan<- lintRes, name string) { +func runWorker(ctx context.Context, lintCtx *linter.Context, tasksCh <-chan linter.Config, lintResultsCh chan<- lintRes, name string) { sw := timeutils.NewStopwatch(name) defer sw.Print() @@ -44,7 +54,7 @@ func runWorker(ctx context.Context, lintCtx *Context, tasksCh <-chan Linter, lin select { case <-ctx.Done(): return - case linter, ok := <-tasksCh: + case lc, ok := <-tasksCh: if !ok { return } @@ -55,11 +65,11 @@ func runWorker(ctx context.Context, lintCtx *Context, tasksCh <-chan Linter, lin } var issues []result.Issue var err error - sw.TrackStage(linter.Name(), func() { - issues, err = runLinterSafe(ctx, lintCtx, linter) + sw.TrackStage(lc.Linter.Name(), func() { + issues, err = runLinterSafe(ctx, lintCtx, lc) }) lintResultsCh <- lintRes{ - linter: linter, + linter: lc, err: err, issues: issues, } @@ -87,13 +97,8 @@ func logWorkersStat(workersFinishTimes []time.Time) { logrus.Infof("Workers idle times: %s", strings.Join(logStrings, ", ")) } -type RunnerLinterConfig interface { - GetSpeed() int - GetLinter() Linter -} - -func getSortedLintersConfigs(linters []RunnerLinterConfig) []RunnerLinterConfig { - ret := make([]RunnerLinterConfig, len(linters)) +func getSortedLintersConfigs(linters []linter.Config) []linter.Config { + ret := make([]linter.Config, len(linters)) copy(ret, linters) sort.Slice(ret, func(i, j int) bool { @@ -103,8 +108,8 @@ func getSortedLintersConfigs(linters []RunnerLinterConfig) []RunnerLinterConfig return ret } -func (r *SimpleRunner) runWorkers(ctx context.Context, lintCtx *Context, linters []RunnerLinterConfig) <-chan lintRes { - tasksCh := make(chan Linter, len(linters)) +func (r *SimpleRunner) runWorkers(ctx context.Context, lintCtx *linter.Context, linters []linter.Config) <-chan lintRes { + tasksCh := make(chan linter.Config, len(linters)) lintResultsCh := make(chan lintRes, len(linters)) var wg sync.WaitGroup @@ -122,7 +127,7 @@ func (r *SimpleRunner) runWorkers(ctx context.Context, lintCtx *Context, linters lcs := getSortedLintersConfigs(linters) for _, lc := range lcs { - tasksCh <- lc.GetLinter() + tasksCh <- lc } close(tasksCh) @@ -146,7 +151,7 @@ func (r SimpleRunner) processLintResults(ctx context.Context, inCh <-chan lintRe for res := range inCh { if res.err != nil { - logrus.Infof("Can't run linter %s: %s", res.linter.Name(), res.err) + logrus.Infof("Can't run linter %s: %s", res.linter.Linter.Name(), res.err) continue } @@ -189,7 +194,7 @@ func collectIssues(ctx context.Context, resCh <-chan lintRes) <-chan result.Issu return retIssues } -func (r SimpleRunner) Run(ctx context.Context, linters []RunnerLinterConfig, lintCtx *Context) <-chan result.Issue { +func (r SimpleRunner) Run(ctx context.Context, linters []linter.Config, lintCtx *linter.Context) <-chan result.Issue { lintResultsCh := r.runWorkers(ctx, lintCtx, linters) processedLintResultsCh := r.processLintResults(ctx, lintResultsCh) if ctx.Err() != nil { diff --git a/scripts/gen_readme/main.go b/scripts/gen_readme/main.go new file mode 100644 index 00000000..90c89d69 --- /dev/null +++ b/scripts/gen_readme/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" + "text/template" + + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/lint/lintersdb" +) + +func main() { + const ( + tmplPath = "README.md.tmpl" + outPath = "README.md" + ) + + if err := genReadme(tmplPath, outPath); err != nil { + log.Fatalf("failed: %s", err) + } + log.Printf("Successfully generated %s", outPath) +} + +func genReadme(tmplPath, outPath string) error { + ctx, err := buildTemplateContext() + if err != nil { + return err + } + + out, err := os.Create(outPath) + if err != nil { + return err + } + defer out.Close() + + tmpl := template.Must(template.ParseFiles(tmplPath)) + return tmpl.Execute(out, ctx) +} + +func buildTemplateContext() (map[string]interface{}, error) { + golangciYaml, err := ioutil.ReadFile(".golangci.yml") + if err != nil { + return nil, fmt.Errorf("can't read .golangci.yml: %s", err) + } + + if err = exec.Command("go", "install", "./cmd/...").Run(); err != nil { + return nil, fmt.Errorf("can't run go install: %s", err) + } + + lintersOut, err := exec.Command("golangci-lint", "linters").Output() + if err != nil { + return nil, fmt.Errorf("can't run linters cmd: %s", err) + } + + lintersOutParts := bytes.Split(lintersOut, []byte("\n\n")) + + return map[string]interface{}{ + "GolangciYaml": string(golangciYaml), + "LintersCommandOutputEnabledOnly": string(lintersOutParts[0]), + "LintersCommandOutputDisabledOnly": string(lintersOutParts[1]), + "EnabledByDefaultLinters": getLintersListMarkdown(true), + "DisabledByDefaultLinters": getLintersListMarkdown(false), + "ThanksList": getThanksList(), + }, nil +} + +func getLintersListMarkdown(enabled bool) string { + var neededLcs []linter.Config + lcs := lintersdb.GetAllSupportedLinterConfigs() + for _, lc := range lcs { + if lc.EnabledByDefault == enabled { + neededLcs = append(neededLcs, lc) + } + } + + var lines []string + for _, lc := range neededLcs { + var link string + if lc.OriginalURL != "" { + link = fmt.Sprintf("[%s](%s)", lc.Linter.Name(), lc.OriginalURL) + } else { + link = lc.Linter.Name() + } + line := fmt.Sprintf("- %s - %s", link, lc.Linter.Desc()) + lines = append(lines, line) + } + + return strings.Join(lines, "\n") +} + +func getThanksList() string { + var lines []string + addedAuthors := map[string]bool{} + for _, lc := range lintersdb.GetAllSupportedLinterConfigs() { + if lc.OriginalURL == "" { + continue + } + + const githubPrefix = "https://github.com/" + if !strings.HasPrefix(lc.OriginalURL, githubPrefix) { + continue + } + + githubSuffix := strings.TrimPrefix(lc.OriginalURL, githubPrefix) + githubAuthor := strings.Split(githubSuffix, "/")[0] + if addedAuthors[githubAuthor] { + continue + } + addedAuthors[githubAuthor] = true + + line := fmt.Sprintf("- [%s](https://github.com/%s)", + githubAuthor, githubAuthor) + lines = append(lines, line) + } + + return strings.Join(lines, "\n") +}