diff --git a/.travis.yml b/.travis.yml
index b455d67..52aad3f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,6 @@
language: go
go: 1.11.x
+node_js: 11.15.0
install: make install
script:
- make build
diff --git a/README.md b/README.md
index 6301a34..4bc968b 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,13 @@ Here's how `revive` is different from `golint`:
- [`excelize`](https://github.com/360EntSecGroup-Skylar/excelize) - Go library for reading and writing Microsoft Excel™ (XLSX) files
- [`aurora`](https://github.com/xuri/aurora) - aurora is a web-based Beanstalk queue server console written in Go
- [`soar`](https://github.com/XiaoMi/soar) - SQL Optimizer And Rewriter
-- [`gorush`](https://github.com/appleboy/gorush) - A push notification server written in Go (Golang)
+- [`gorush`](https://github.com/appleboy/gorush) - A push notification server written in Go (Golang)a
+- [`go-echarts`](https://github.com/chenjiandongx/go-echarts) - The adorable charts library for Golang
+- [`reviewdog`](https://github.com/reviewdog/reviewdog) - Automated code review tool integrated with any code analysis tools regardless of programming language
+- [`sklearn`](https://github.com/pa-m/sklearn) - A partial port of scikit-learn written in Go
+- [`lorawan-stack`](https://github.com/TheThingsNetwork/lorawan-stack) - The Things Network Stack for LoRaWAN V3
+- [`gofight`](https://github.com/appleboy/gofight) - Testing API Handler written in Golang.
+- [`ggz`](https://github.com/go-ggz/ggz) - An URL shortener service written in Golang
*Open a PR to add your project*.
@@ -47,6 +53,7 @@ Here's how `revive` is different from `golint`:
- [revive](#revive)
- [Usage](#usage)
- [Text Editors](#text-editors)
+ - [Bazel](#bazel)
- [Installation](#installation)
- [Command Line Flags](#command-line-flags)
- [Sample Invocations](#sample-invocations)
@@ -80,6 +87,10 @@ Here's how `revive` is different from `golint`:
Since the default behavior of `revive` is compatible with `golint`, without providing any additional flags, the only difference you'd notice is faster execution.
+### Bazel
+
+If you want to use revive with Bazel, take a look at the [rules](https://github.com/atlassian/bazel-tools/tree/master/gorevive) that Atlassian maintains.
+
### Text Editors
- Support for VSCode in [vscode-go](https://github.com/Microsoft/vscode-go/pull/1699).
@@ -129,7 +140,7 @@ revive -config revive.toml -exclude file1.go -exclude file2.go -formatter friend
- The output will be formatted with the `friendly` formatter
- The linter will analyze `github.com/mgechev/revive` and the files in `package`
-### Comment Annotations
+### Comment Directives
Using comments, you can disable the linter for the entire file or only range of lines:
@@ -156,6 +167,28 @@ func Public() private {
This way, `revive` will not warn you for that you're returning an object of an unexported type, from an exported function.
+You can document why you disable the linter by adding a trailing text in the directive, for example
+
+```go
+//revive:disable Until the code is stable
+```
+```go
+//revive:disable:cyclomatic High complexity score but easy to understand
+```
+
+You can also configure `revive` to enforce documenting linter disabling directives by adding
+
+```toml
+[directive.specify-disable-reason]
+```
+
+in the configuration. You can set the severity (defaults to _warning_) of the violation of this directive
+
+```toml
+[directive.specify-disable-reason]
+ severity = "error"
+```
+
### Configuration
`revive` can be configured with a TOML file. Here's a sample configuration with explanation for the individual properties:
@@ -296,10 +329,11 @@ List of all available rules. The rules ported from `golint` are left unchanged a
| [`empty-lines`](./RULES_DESCRIPTIONS.md#empty-lines) | n/a | Warns when there are heading or trailing newlines in a block | no | no |
| [`line-length-limit`](./RULES_DESCRIPTIONS.md#line-length-limit) | int | Specifies the maximum number of characters in a line | no | no |
| [`call-to-gc`](./RULES_DESCRIPTIONS.md#call-to-gc) | n/a | Warns on explicit call to the garbage collector | no | no |
-| [`duplicated-imports`](./RULES_DESCRIPTIONS#duplicated-imports) | n/a | Looks for packages that are imported two or more times | no | no |
+| [`duplicated-imports`](./RULES_DESCRIPTIONS.md#duplicated-imports) | n/a | Looks for packages that are imported two or more times | no | no |
| [`import-shadowing`](./RULES_DESCRIPTIONS.md#import-shadowing) | n/a | Spots identifiers that shadow an import | no | no |
-| [`bare-return`](./RULES_DESCRIPTIONS#bare-return) | n/a | Warns on bare returns | no | no |
+| [`bare-return`](./RULES_DESCRIPTIONS.md#bare-return) | n/a | Warns on bare returns | no | no |
| [`unused-receiver`](./RULES_DESCRIPTIONS.md#unused-receiver) | n/a | Suggests to rename or remove unused method receivers | no | no |
+| [`unhandled-error`](./RULES_DESCRIPTIONS.md#unhandled-error) | []string | Warns on unhandled errors returned by funcion calls | no | yes |
## Configurable rules
@@ -387,7 +421,7 @@ Each formatter needs to implement the following interface:
```go
type Formatter interface {
- Format(<-chan Failure, RulesConfig) (string, error)
+ Format(<-chan Failure, Config) (string, error)
Name() string
}
```
@@ -440,13 +474,13 @@ Currently, type checking is enabled by default. If you want to run the linter wi
:---: |:---: |:---: |:---: |:---: |:---: |
[mgechev](https://github.com/mgechev) |[chavacava](https://github.com/chavacava) |[xuri](https://github.com/xuri) |[gsamokovarov](https://github.com/gsamokovarov) |[morphy2k](https://github.com/morphy2k) |[tamird](https://github.com/tamird) |
-[](https://github.com/AragurDEV) |[](https://github.com/yangdiangzb) |[](https://github.com/jamesmaidment) |[](https://github.com/mapreal19) |[](https://github.com/markelog) |[](https://github.com/paul-at-start) |
+[](https://github.com/AragurDEV) |[](https://github.com/yangdiangzb) |[](https://github.com/jamesmaidment) |[](https://github.com/mapreal19) |[](https://github.com/markelog) |[](https://github.com/pa-m) |
:---: |:---: |:---: |:---: |:---: |:---: |
-[AragurDEV](https://github.com/AragurDEV) |[yangdiangzb](https://github.com/yangdiangzb) |[jamesmaidment](https://github.com/jamesmaidment) |[mapreal19](https://github.com/mapreal19) |[markelog](https://github.com/markelog) |[paul-at-start](https://github.com/paul-at-start) |
+[AragurDEV](https://github.com/AragurDEV) |[yangdiangzb](https://github.com/yangdiangzb) |[jamesmaidment](https://github.com/jamesmaidment) |[mapreal19](https://github.com/mapreal19) |[markelog](https://github.com/markelog) |[pa-m](https://github.com/pa-m) |
-[](https://github.com/psapezhko) |[](https://github.com/ridvansumset) |[](https://github.com/Jarema) |[](https://github.com/vkrol) |[](https://github.com/haya14busa) |
-:---: |:---: |:---: |:---: |:---: |
-[psapezhko](https://github.com/psapezhko) |[ridvansumset](https://github.com/ridvansumset) |[Jarema](https://github.com/Jarema) |[vkrol](https://github.com/vkrol) |[haya14busa](https://github.com/haya14busa) |
+[](https://github.com/paul-at-start) |[](https://github.com/psapezhko) |[](https://github.com/ridvansumset) |[](https://github.com/Jarema) |[](https://github.com/vkrol) |[](https://github.com/haya14busa) |
+:---: |:---: |:---: |:---: |:---: |:---: |
+[paul-at-start](https://github.com/paul-at-start) |[psapezhko](https://github.com/psapezhko) |[ridvansumset](https://github.com/ridvansumset) |[Jarema](https://github.com/Jarema) |[vkrol](https://github.com/vkrol) |[haya14busa](https://github.com/haya14busa) |
## License
diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md
index e4d6225..8121ac1 100644
--- a/RULES_DESCRIPTIONS.md
+++ b/RULES_DESCRIPTIONS.md
@@ -51,6 +51,7 @@ List of all available rules.
- [var-naming](#var-naming)
- [var-declaration](#var-declaration)
- [unexported-return](#unexported-return)
+ - [unhandled-error](#unhandled-error)
- [unnecessary-stmt](#unnecessary-stmt)
- [unreachable-code](#unreachable-code)
- [unused-parameter](#unused-parameter)
@@ -434,6 +435,18 @@ _Description_: This rule warns when an exported function or method returns a val
_Configuration_: N/A
+## unhandled-error
+
+_Description_: This rule warns when errors returned by a function are not explicitly handled on the caller side.
+
+_Configuration_: function names to ignore
+
+Example:
+
+```toml
+[unhandled-error]
+ arguments =["fmt.Printf", "myFunction"]
+```
## unnecessary-stmt
_Description_: This rule suggests to remove redundant statements like a `break` at the end of a case block, for improving the code's readability.
diff --git a/config.go b/config.go
index f874326..e6bb1af 100644
--- a/config.go
+++ b/config.go
@@ -77,6 +77,7 @@ var allRules = append([]lint.Rule{
&rule.ImportShadowingRule{},
&rule.BareReturnRule{},
&rule.UnusedReceiverRule{},
+ &rule.UnhandledErrorRule{},
}, defaultRules...)
var allFormatters = []lint.Formatter{
@@ -141,6 +142,12 @@ func normalizeConfig(config *lint.Config) {
}
config.Rules[k] = v
}
+ for k, v := range config.Directives {
+ if v.Severity == "" {
+ v.Severity = severity
+ }
+ config.Directives[k] = v
+ }
}
}
diff --git a/fixtures/disable-annotations2.go b/fixtures/disable-annotations2.go
index 36cd65b..d82a78f 100644
--- a/fixtures/disable-annotations2.go
+++ b/fixtures/disable-annotations2.go
@@ -6,6 +6,13 @@ func foo1() {
var invalid_name2 = 1 //revive:disable-line:var-naming
}
+func foo11() {
+ //revive:disable-next-line:var-naming I'm an Eiffel programmer thus I like underscores
+ var invalid_name = 0
+ var invalid_name2 = 1 //revive:disable-line:var-naming I'm an Eiffel programmer thus I like underscores
+}
+
+
func foo2() {
// revive:disable-next-line:var-naming
//revive:disable
@@ -22,3 +29,10 @@ func foo3() {
/* revive:disable-next-line:var-naming */
var invalid_name3 = 0 // MATCH /don't use underscores in Go names; var invalid_name3 should be invalidName3/
}
+
+func foo2p1() {
+ //revive:disable Underscores are fine
+ var invalid_name = 0
+ //revive:enable No! Underscores are not nice!
+ var invalid_name2 = 1 // MATCH /don't use underscores in Go names; var invalid_name2 should be invalidName2/
+}
diff --git a/fixtures/struct-tag.go b/fixtures/struct-tag.go
index 86f2447..b53c883 100644
--- a/fixtures/struct-tag.go
+++ b/fixtures/struct-tag.go
@@ -1,5 +1,7 @@
package fixtures
+import "time"
+
type decodeAndValidateRequest struct {
// BEAWRE : the flag of URLParam should match the const string URLParam
URLParam string `json:"-" path:"url_param" validate:"numeric"`
@@ -17,6 +19,8 @@ type decodeAndValidateRequest struct {
MandatoryStruct4 mandatoryStruct `json:"mandatoryStruct" required:"false"`
OptionalStruct *optionalStruct `json:"optionalStruct,omitempty"`
OptionalQuery string `json:"-" querystring:"queryfoo"`
+ optionalQuery string `json:"-" querystring:"queryfoo"` // MATCH /tag on not-exported field optionalQuery/
+
}
type RangeAllocation struct {
diff --git a/fixtures/unhandled-error-w-ignorelist.go b/fixtures/unhandled-error-w-ignorelist.go
new file mode 100644
index 0000000..e157a83
--- /dev/null
+++ b/fixtures/unhandled-error-w-ignorelist.go
@@ -0,0 +1,19 @@
+package fixtures
+
+import (
+ "fmt"
+ "os"
+)
+
+func unhandledError1(a int) (int, error) {
+ return a, nil
+}
+
+func unhandledError2() error {
+ _, err := unhandledError1(1)
+ unhandledError1(1)
+ fmt.Fprintf(nil, "") // MATCH /Unhandled error in call to function fmt.Fprintf/
+ os.Chdir("..")
+ _ = os.Chdir("..")
+ return err
+}
diff --git a/fixtures/unhandled-error.go b/fixtures/unhandled-error.go
new file mode 100644
index 0000000..d3e9a45
--- /dev/null
+++ b/fixtures/unhandled-error.go
@@ -0,0 +1,19 @@
+package fixtures
+
+import (
+ "fmt"
+ "os"
+)
+
+func unhandledError1(a int) (int, error) {
+ return a, nil
+}
+
+func unhandledError2() error {
+ _, err := unhandledError1(1)
+ unhandledError1(1) // MATCH /Unhandled error in call to function unhandledError1/
+ fmt.Fprintf(nil, "") // MATCH /Unhandled error in call to function fmt.Fprintf/
+ os.Chdir("..") // MATCH /Unhandled error in call to function os.Chdir/
+ _ = os.Chdir("..")
+ return err
+}
diff --git a/formatter/checkstyle.go b/formatter/checkstyle.go
index 6f7e20a..bd20da8 100644
--- a/formatter/checkstyle.go
+++ b/formatter/checkstyle.go
@@ -28,7 +28,7 @@ type issue struct {
}
// Format formats the failures gotten from the lint.
-func (f *Checkstyle) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
+func (f *Checkstyle) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
var issues = map[string][]issue{}
for failure := range failures {
buf := new(bytes.Buffer)
diff --git a/formatter/default.go b/formatter/default.go
index 415c95c..145e6d5 100644
--- a/formatter/default.go
+++ b/formatter/default.go
@@ -18,7 +18,7 @@ func (f *Default) Name() string {
}
// Format formats the failures gotten from the lint.
-func (f *Default) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
+func (f *Default) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) {
for failure := range failures {
fmt.Printf("%v: %s\n", failure.Position.Start, failure.Failure)
}
diff --git a/formatter/friendly.go b/formatter/friendly.go
index 01f22c8..a543eeb 100644
--- a/formatter/friendly.go
+++ b/formatter/friendly.go
@@ -37,7 +37,7 @@ func (f *Friendly) Name() string {
}
// Format formats the failures gotten from the lint.
-func (f *Friendly) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
+func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
errorMap := map[string]int{}
warningMap := map[string]int{}
totalErrors := 0
diff --git a/formatter/json.go b/formatter/json.go
index 74ed585..9c939fa 100644
--- a/formatter/json.go
+++ b/formatter/json.go
@@ -24,7 +24,7 @@ type jsonObject struct {
}
// Format formats the failures gotten from the lint.
-func (f *JSON) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
+func (f *JSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
var slice []jsonObject
for failure := range failures {
obj := jsonObject{}
diff --git a/formatter/ndjson.go b/formatter/ndjson.go
index 7b5ba61..aa2b1d6 100644
--- a/formatter/ndjson.go
+++ b/formatter/ndjson.go
@@ -19,7 +19,7 @@ func (f *NDJSON) Name() string {
}
// Format formats the failures gotten from the lint.
-func (f *NDJSON) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
+func (f *NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
enc := json.NewEncoder(os.Stdout)
for failure := range failures {
obj := jsonObject{}
diff --git a/formatter/plain.go b/formatter/plain.go
index aef9a0c..a854d25 100644
--- a/formatter/plain.go
+++ b/formatter/plain.go
@@ -18,7 +18,7 @@ func (f *Plain) Name() string {
}
// Format formats the failures gotten from the lint.
-func (f *Plain) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
+func (f *Plain) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) {
for failure := range failures {
fmt.Printf("%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName)
}
diff --git a/formatter/severity.go b/formatter/severity.go
index 06ba082..a43bf31 100644
--- a/formatter/severity.go
+++ b/formatter/severity.go
@@ -2,8 +2,11 @@ package formatter
import "github.com/mgechev/revive/lint"
-func severity(config lint.RulesConfig, failure lint.Failure) lint.Severity {
- if config, ok := config[failure.RuleName]; ok && config.Severity == lint.SeverityError {
+func severity(config lint.Config, failure lint.Failure) lint.Severity {
+ if config, ok := config.Rules[failure.RuleName]; ok && config.Severity == lint.SeverityError {
+ return lint.SeverityError
+ }
+ if config, ok := config.Directives[failure.RuleName]; ok && config.Severity == lint.SeverityError {
return lint.SeverityError
}
return lint.SeverityWarning
diff --git a/formatter/stylish.go b/formatter/stylish.go
index 9d3286c..cd81fda 100644
--- a/formatter/stylish.go
+++ b/formatter/stylish.go
@@ -32,7 +32,7 @@ func formatFailure(failure lint.Failure, severity lint.Severity) []string {
}
// Format formats the failures gotten from the lint.
-func (f *Stylish) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
+func (f *Stylish) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
var result [][]string
var totalErrors = 0
var total = 0
@@ -82,7 +82,8 @@ func (f *Stylish) Format(failures <-chan lint.Failure, config lint.RulesConfig)
} else if total > 0 && totalErrors == 0 {
suffix = color.YellowString("\n ✖" + suffix)
} else {
- suffix = color.GreenString("\n" + suffix)
+ suffix, output = "", ""
}
+
return output + suffix, nil
}
diff --git a/formatter/unix.go b/formatter/unix.go
index 849866f..b9ae62d 100644
--- a/formatter/unix.go
+++ b/formatter/unix.go
@@ -19,7 +19,7 @@ func (f *Unix) Name() string {
}
// Format formats the failures gotten from the lint.
-func (f *Unix) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
+func (f *Unix) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) {
for failure := range failures {
fmt.Printf("%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure)
}
diff --git a/go.mod b/go.mod
index 48b56cf..a069d28 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,14 @@
module github.com/mgechev/revive
require (
- github.com/BurntSushi/toml v0.3.0
+ github.com/BurntSushi/toml v0.3.1
github.com/fatih/color v1.7.0
github.com/fatih/structtag v1.0.0
- github.com/mattn/go-colorable v0.0.9 // indirect
- github.com/mattn/go-isatty v0.0.4 // indirect
- github.com/mattn/go-runewidth v0.0.3 // indirect
- github.com/mgechev/dots v0.0.0-20180605013149-8e09d8ea2757
- github.com/olekukonko/tablewriter v0.0.0-20180912035003-be2c049b30cc
- github.com/pkg/errors v0.8.0
- golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect
- golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1
+ github.com/mattn/go-colorable v0.1.2 // indirect
+ github.com/mattn/go-runewidth v0.0.4 // indirect
+ github.com/mgechev/dots v0.0.0-20190603122614-18fa4c4b71cc
+ github.com/olekukonko/tablewriter v0.0.1
+ github.com/pkg/errors v0.8.1
+ golang.org/x/sys v0.0.0-20190801053355-cbf593c0f2f3 // indirect
+ golang.org/x/tools v0.0.0-20190802005412-e9bb7d36c060
)
diff --git a/go.sum b/go.sum
index 3f3de89..9519d8b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,22 +1,152 @@
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structtag v1.0.0 h1:pTHj65+u3RKWYPSGaU290FpI/dXxTaHdVwVwbcPKmEc=
github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mgechev/dots v0.0.0-20180605013149-8e09d8ea2757 h1:KTwJ7Lo3KDKMknRYN5JEFRGIM4IkG59QjFFM2mxsMEU=
github.com/mgechev/dots v0.0.0-20180605013149-8e09d8ea2757/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=
+github.com/mgechev/dots v0.0.0-20190603122614-18fa4c4b71cc h1:ErGdrZWM/CrAz0FVwcznlAScsmr2pdSMMPMwFL9TNmw=
+github.com/mgechev/dots v0.0.0-20190603122614-18fa4c4b71cc/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=
github.com/olekukonko/tablewriter v0.0.0-20180912035003-be2c049b30cc h1:rQ1O4ZLYR2xXHXgBCCfIIGnuZ0lidMQw2S5n1oOv+Wg=
github.com/olekukonko/tablewriter v0.0.0-20180912035003-be2c049b30cc/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
+github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190603122648-4c4f7f33c9ed h1:Lf5SX+bXEwoj3Y6Nfu5qtffzOXhPQA9REb2R5W4nGP8=
+golang.org/x/sys v0.0.0-20190603122648-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606125941-79a91cf218c4 h1:HWs74PINelUuEfUbwnBO+1N52oVhkjuLqpbUmyrFA1s=
+golang.org/x/sys v0.0.0-20190606125941-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606203914-7fc4e5ec1444 h1:U3H/lfsEYy6ld4rCevWA3QgxHlZLiFUfuBFIEc/Ifyo=
+golang.org/x/sys v0.0.0-20190606203914-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190608055321-5b15430b70e3 h1:vzXBGBUiVyR0q7D4t89HsaY0TYhmviZBVOVUyvyMBjU=
+golang.org/x/sys v0.0.0-20190608055321-5b15430b70e3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190614215434-b47fdc937951 h1:pGVEuw9fHbMpaZlMbLwssX14J35+8blxkNxbyMcO/qE=
+golang.org/x/sys v0.0.0-20190614215434-b47fdc937951/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190616132722-15dcb6c0061f h1:bWC5mWiwVGXbr7P6ugM+hu6QytMFEjbwt+SO2r1M2+o=
+golang.org/x/sys v0.0.0-20190616132722-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190619193031-17bc6164aac4 h1:wlKUlj5/boPTVWT2ysl97FAXePcoaV9FNqFOzsNuwpk=
+golang.org/x/sys v0.0.0-20190619193031-17bc6164aac4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190621215844-d432491b9138 h1:mOtS9UBYWoZzbGWv3/XJz0cxMw8MTBbNKy3pRvP2Mjk=
+golang.org/x/sys v0.0.0-20190621215844-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624145334-c5567b49c5d0 h1:z1lZzI4A4GjnM80wHx8Yrjtcr56DRJ6VJnipc0bbG3o=
+golang.org/x/sys v0.0.0-20190624145334-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190629205408-04f50cda93cb h1:IeU57h/r0+/v829dDR8bskefuF1Cx4KAxce1Cn0LFvE=
+golang.org/x/sys v0.0.0-20190629205408-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190710165549-6ec70d6a5542 h1:PNYRbnC8XRk0bAINQCfprVc169PXbLRG2HI1HXbUEg8=
+golang.org/x/sys v0.0.0-20190710165549-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190712063909-fae7ac547cb7 h1:kj9oKJYzWQH5MvoBhxb9WQn/LqYor42sKTvsrUCCu7g=
+golang.org/x/sys v0.0.0-20190712063909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726004620-94b544f455ef h1:pKfIZaaN+ipVf4Vz//s+8MRZkgZvBsds3BOAAYmXV/Y=
+golang.org/x/sys v0.0.0-20190726004620-94b544f455ef/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726093751-fc99dfbffb4e h1:AhhcJML1FGXN10f3s4/WyoO5/8u7Rfb1ZyB47O8qp+s=
+golang.org/x/sys v0.0.0-20190726093751-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190731002446-1393eb018365 h1:skmasgLtLOsII4VBh/TRzNotE2cn3rMyPB9/A67wLDU=
+golang.org/x/sys v0.0.0-20190731002446-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190801053355-cbf593c0f2f3 h1:sL449jA3eITt86CGRvhic5g9AuQEnaqaasKTT+ieDng=
+golang.org/x/sys v0.0.0-20190801053355-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1 h1:dzEuQYa6+a3gROnSlgly5ERUm4SZKJt+dh+4iSbO+bI=
golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190604003109-8aaa1484dc10 h1:VULJh5DQg4Inr1Xiypf4pj72xB4lPJdiUgV2Ra5M8og=
+golang.org/x/tools v0.0.0-20190604003109-8aaa1484dc10/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190606052759-4d9ae51c2468 h1:ousb8Vkhj77qYml3n9BuPuYaR6LnZyLh5LXB2qq8wzI=
+golang.org/x/tools v0.0.0-20190606052759-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190614153353-1edc8e83c897 h1:Tcg4xXCU7bSKi8WaBg3N7lJTHwLv45zG+LXHG6WxorM=
+golang.org/x/tools v0.0.0-20190614153353-1edc8e83c897/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190614185444-d1d6cdd8a67e h1:bZRjbS2OsoCz4Whw4o7b1Il1obBuaKSM0FXMfFrRut4=
+golang.org/x/tools v0.0.0-20190614185444-d1d6cdd8a67e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190614215004-5aca471b1d59 h1:rBGSbLsDaYExWFDBn79o7jc1xpwfxSofsWJNihAfWto=
+golang.org/x/tools v0.0.0-20190614215004-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190617174016-6fea9ef05e7a h1:gdHr3XuwwcWiVcNwFFdaMAXkLC9SGW3+yn63Pol+wL0=
+golang.org/x/tools v0.0.0-20190617174016-6fea9ef05e7a/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190617192825-da514acc4774 h1:hL3a3nRZsZm7FQGehfRB1w5y8mbrLyMSAgki4j0z3I0=
+golang.org/x/tools v0.0.0-20190617192825-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190618173119-fdf1049a943a h1:UYXkp2zG6yfcsx4WdMshPebv30sUsSXURy1nWiwy0oc=
+golang.org/x/tools v0.0.0-20190618173119-fdf1049a943a/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190623014052-6e04913cbbac h1:p4rf0zoHymrHSlKS+xG2PhJOtxOEWC9nu4NamWffoY0=
+golang.org/x/tools v0.0.0-20190623014052-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190624225754-a101b041ded4 h1:3Yme/SSFE6mHkvIIhjjiTrlmOsy6m0aNnyTDZPYJIy4=
+golang.org/x/tools v0.0.0-20190624225754-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190625183255-252024b82959 h1:icB3Kx/bolKriEovlxmFvvEBA1b24q6lQkwGLiO7Kgo=
+golang.org/x/tools v0.0.0-20190625183255-252024b82959/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628234000-fb37f6ba8261 h1:D/pylchHpGxucPHRiQsXXfyk9R4ywTUoH6FaZx0R0Q0=
+golang.org/x/tools v0.0.0-20190628234000-fb37f6ba8261/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190701204812-38ae2c8f6412 h1:eiOZ97iLROr9dsoElIcgpaBTnypaUM6FklHaTH1WCUQ=
+golang.org/x/tools v0.0.0-20190701204812-38ae2c8f6412/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190702152726-7e72c71c505f h1:XEee/4s6OPmxKNwnku3HeR9yvHS+DfiMUBnZRvGYKsc=
+golang.org/x/tools v0.0.0-20190702152726-7e72c71c505f/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190702203059-44aeb8b7c377 h1:ImzDLDLqRaKKO5JeKsrJF73YTtC/UGJYXQfnhCykd1U=
+golang.org/x/tools v0.0.0-20190702203059-44aeb8b7c377/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190703183741-063514c48b26 h1:RSQtTb58BibjAu/zvYqkvmSd3hCVrxUHtxtJgdmlo8U=
+golang.org/x/tools v0.0.0-20190703183741-063514c48b26/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190708153032-60762fc531e6 h1:+09ssZLX5m87DAU4ovA5R2dT8+rxQJOSrnvJ08dNOvI=
+golang.org/x/tools v0.0.0-20190708153032-60762fc531e6/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190708212629-c8855242db9c h1:61VlnXlxJ8ZQ0ScNJLbv0lMvy2zxMOMtmqgczqTjoSw=
+golang.org/x/tools v0.0.0-20190708212629-c8855242db9c/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190709205837-f82b303b69d7 h1:POOqS8nIAlVjiggJspEagLTIm7FecjZ3zjL+UXqxaeo=
+golang.org/x/tools v0.0.0-20190709205837-f82b303b69d7/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190709213823-7b25e351ac0e h1:KLE9RrAWtdmIWsb/E0ljAvXMM5lZf4Gtr3HYCEE4FIE=
+golang.org/x/tools v0.0.0-20190709213823-7b25e351ac0e/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190710195507-286818132824 h1:2492BvYnJkin59SmTIFBH3mlkrNxeIKPJuM3GrK/gYo=
+golang.org/x/tools v0.0.0-20190710195507-286818132824/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190712214651-8b927904ee0d h1:KlQeLUDWg+cWfoNyo2obro62ANg9Ain//HXb8aKT2jg=
+golang.org/x/tools v0.0.0-20190712214651-8b927904ee0d/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190715053316-607ca053a137 h1:JD/hG/8Cy1uTpUou5cTdh88WzXO985+ykk+/kD2B6Og=
+golang.org/x/tools v0.0.0-20190715053316-607ca053a137/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190715174000-9e48ab1d90cd h1:WtY90UqL9QgrhtGZXkSuv3VgN9O2zchNuw7mCsy5EAo=
+golang.org/x/tools v0.0.0-20190715174000-9e48ab1d90cd/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190715222530-9b2cb0e5f602 h1:aPQ4URfBa5qpEffv7JQWB643/X9MIooR7AmSM4PhjbQ=
+golang.org/x/tools v0.0.0-20190715222530-9b2cb0e5f602/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190716023037-fefcef05abb1 h1:XTh+ATqTLtxlslz1/eewT2lc16Tn566b6TuHLllcamo=
+golang.org/x/tools v0.0.0-20190716023037-fefcef05abb1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190716155013-919acb9f1ffd h1:awSISWvdxEIWPMBRrxodeas3r6hNs3R7fSCAKsN+2eg=
+golang.org/x/tools v0.0.0-20190716155013-919acb9f1ffd/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190716192744-0b5a7f81db50 h1:MjNb67gYViBJS2G0qlnUyUvz01HpvT0hWbRK6+CVeB4=
+golang.org/x/tools v0.0.0-20190716192744-0b5a7f81db50/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190719014327-e377ae9d6386 h1:XYHge1yfBqxAoAUYFNfgQBUM/pLA4cVgBCxSVP4SFM8=
+golang.org/x/tools v0.0.0-20190719014327-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190723023520-8bb11ff117ca h1:jxUITcp9Zck5mo94NnK9nxKhcrCHHu3Uxn3zT/Ab6GQ=
+golang.org/x/tools v0.0.0-20190723023520-8bb11ff117ca/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190724192812-8aa4eac1a7c1 h1:fUhbXHMxzVzne8fm8LoGbgb29YxdSR6sXVOA6M1+79c=
+golang.org/x/tools v0.0.0-20190724192812-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190725162026-2e34cfcb95cb h1:nCidYN5rpBq6eOoKgsg8OPyARFROWoopPyENOA1AADU=
+golang.org/x/tools v0.0.0-20190725162026-2e34cfcb95cb/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190727014933-1bd56024c620 h1:V0R1y3ny4Qty3oJpgixfc7BF1GqcBtG492UJDH4XNi4=
+golang.org/x/tools v0.0.0-20190727014933-1bd56024c620/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190727174842-db2fa46ec33c h1:jOixaG4f/xPYap0HSnwlewCOTxw7qq5hqxvCyw5Kfvg=
+golang.org/x/tools v0.0.0-20190727174842-db2fa46ec33c/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190728085240-fc6e2057e7f6 h1:HwL5zE73Lj6ca2l3QvmH+z+mMQJp6w6/7zcxRJcGNRw=
+golang.org/x/tools v0.0.0-20190728085240-fc6e2057e7f6/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190729094940-ff9f1409240a h1:s5iVUvQeQogtT5N2UsrWTExwB/vujdgo2oRIoKqGHP0=
+golang.org/x/tools v0.0.0-20190729094940-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190730215743-ed3277de2799 h1:QDcqPiPMwhOSeNS/65AYJQxAYb2HIYLFw6NRbGQUtSU=
+golang.org/x/tools v0.0.0-20190730215743-ed3277de2799/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190731224408-1e85ed8060aa h1:crSxYKs/4ckOznRS/ORDRJikUnqR5pcExM2Q9fNghbw=
+golang.org/x/tools v0.0.0-20190731224408-1e85ed8060aa/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190802005412-e9bb7d36c060 h1:ixdNsWPlaysKGPShUO+OF5eF/dSQOGTB86mRds+STrY=
+golang.org/x/tools v0.0.0-20190802005412-e9bb7d36c060/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
diff --git a/lint/config.go b/lint/config.go
index 05d8fae..fe65ace 100644
--- a/lint/config.go
+++ b/lint/config.go
@@ -12,12 +12,21 @@ type RuleConfig struct {
// RulesConfig defines the config for all rules.
type RulesConfig = map[string]RuleConfig
+// DirectiveConfig is type used for the linter directive configuration.
+type DirectiveConfig struct {
+ Severity Severity
+}
+
+// DirectivesConfig defines the config for all directives.
+type DirectivesConfig = map[string]DirectiveConfig
+
// Config defines the config of the linter.
type Config struct {
IgnoreGeneratedHeader bool `toml:"ignoreGeneratedHeader"`
Confidence float64
Severity Severity
- Rules RulesConfig `toml:"rule"`
- ErrorCode int `toml:"errorCode"`
- WarningCode int `toml:"warningCode"`
+ Rules RulesConfig `toml:"rule"`
+ ErrorCode int `toml:"errorCode"`
+ WarningCode int `toml:"warningCode"`
+ Directives DirectivesConfig `toml:"directive"`
}
diff --git a/lint/file.go b/lint/file.go
index 99dd69c..8bef9c2 100644
--- a/lint/file.go
+++ b/lint/file.go
@@ -97,9 +97,12 @@ func (f *File) isMain() bool {
return false
}
+const directiveSpecifyDisableReason = "specify-disable-reason"
+
func (f *File) lint(rules []Rule, config Config, failures chan Failure) {
rulesConfig := config.Rules
- disabledIntervals := f.disabledIntervals(rules)
+ _, mustSpecifyDisableReason := config.Directives[directiveSpecifyDisableReason]
+ disabledIntervals := f.disabledIntervals(rules, mustSpecifyDisableReason, failures)
for _, currentRule := range rules {
ruleConfig := rulesConfig[currentRule.Name()]
currentFailures := currentRule.Apply(f, ruleConfig.Arguments)
@@ -126,9 +129,15 @@ type enableDisableConfig struct {
position int
}
-func (f *File) disabledIntervals(rules []Rule) disabledIntervalsMap {
- re := regexp.MustCompile(`^//[\s]*revive:(enable|disable)(?:-(line|next-line))?(?::([^\s]+))?[\s]*$`)
+const directiveRE = `^//[\s]*revive:(enable|disable)(?:-(line|next-line))?(?::([^\s]+))?[\s]*(?: (.+))?$`
+const directivePos = 1
+const modifierPos = 2
+const rulesPos = 3
+const reasonPos = 4
+var re = regexp.MustCompile(directiveRE)
+
+func (f *File) disabledIntervals(rules []Rule, mustSpecifyDisableReason bool, failures chan Failure) disabledIntervalsMap {
enabledDisabledRulesMap := make(map[string][]enableDisableConfig)
getEnabledDisabledIntervals := func() disabledIntervalsMap {
@@ -202,16 +211,26 @@ func (f *File) disabledIntervals(rules []Rule) disabledIntervalsMap {
}
ruleNames := []string{}
- if len(match) > 2 {
- tempNames := strings.Split(match[3], ",")
- for _, name := range tempNames {
- name = strings.Trim(name, "\n")
- if len(name) > 0 {
- ruleNames = append(ruleNames, name)
- }
+ tempNames := strings.Split(match[rulesPos], ",")
+ for _, name := range tempNames {
+ name = strings.Trim(name, "\n")
+ if len(name) > 0 {
+ ruleNames = append(ruleNames, name)
}
}
+ mustCheckDisablingReason := mustSpecifyDisableReason && match[directivePos] == "disable"
+ if mustCheckDisablingReason && strings.Trim(match[reasonPos], " ") == "" {
+ failures <- Failure{
+ Confidence: 1,
+ RuleName: directiveSpecifyDisableReason,
+ Failure: "reason of lint disabling not found",
+ Position: ToFailurePosition(c.Pos(), c.End(), f),
+ Node: c,
+ }
+ continue // skip this linter disabling directive
+ }
+
// TODO: optimize
if len(ruleNames) == 0 {
for _, rule := range rules {
@@ -219,7 +238,7 @@ func (f *File) disabledIntervals(rules []Rule) disabledIntervalsMap {
}
}
- handleRules(filename, match[2], match[1] == "enable", line, ruleNames)
+ handleRules(filename, match[modifierPos], match[directivePos] == "enable", line, ruleNames)
}
}
diff --git a/lint/formatter.go b/lint/formatter.go
index 3d87353..7c19af2 100644
--- a/lint/formatter.go
+++ b/lint/formatter.go
@@ -9,6 +9,6 @@ type FormatterMetadata struct {
// Formatter defines an interface for failure formatters
type Formatter interface {
- Format(<-chan Failure, RulesConfig) (string, error)
+ Format(<-chan Failure, Config) (string, error)
Name() string
}
diff --git a/main.go b/main.go
index 8498b55..6851c98 100644
--- a/main.go
+++ b/main.go
@@ -44,7 +44,7 @@ func main() {
var output string
go (func() {
- output, err = formatter.Format(formatChan, config.Rules)
+ output, err = formatter.Format(formatChan, *config)
if err != nil {
fail(err.Error())
}
@@ -62,6 +62,10 @@ func main() {
if c, ok := config.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError {
exitCode = config.ErrorCode
}
+ if c, ok := config.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError {
+ exitCode = config.ErrorCode
+ }
+
formatChan <- f
}
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..f45d8f1
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,5 @@
+{
+ "extends": [
+ "config:base"
+ ]
+}
diff --git a/rule/imports-blacklist.go b/rule/imports-blacklist.go
index 3732083..31ef901 100644
--- a/rule/imports-blacklist.go
+++ b/rule/imports-blacklist.go
@@ -2,7 +2,6 @@ package rule
import (
"fmt"
- "go/ast"
"github.com/mgechev/revive/lint"
)
@@ -13,6 +12,11 @@ type ImportsBlacklistRule struct{}
// Apply applies the rule to given file.
func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
var failures []lint.Failure
+
+ if file.IsTest() {
+ return failures // skip, test file
+ }
+
blacklist := make(map[string]bool, len(arguments))
for _, arg := range arguments {
@@ -20,25 +24,25 @@ func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments)
if !ok {
panic(fmt.Sprintf("Invalid argument to the imports-blacklist rule. Expecting a string, got %T", arg))
}
- // we add quotes if nt present, because when parsed, the value of the AST node, will be quoted
+ // we add quotes if not present, because when parsed, the value of the AST node, will be quoted
if len(argStr) > 2 && argStr[0] != '"' && argStr[len(argStr)-1] != '"' {
argStr = fmt.Sprintf(`"%s"`, argStr)
}
blacklist[argStr] = true
}
- fileAst := file.AST
- walker := blacklistedImports{
- file: file,
- fileAst: fileAst,
- onFailure: func(failure lint.Failure) {
- failures = append(failures, failure)
- },
- blacklist: blacklist,
+ for _, is := range file.AST.Imports {
+ path := is.Path
+ if path != nil && blacklist[path.Value] {
+ failures = append(failures, lint.Failure{
+ Confidence: 1,
+ Failure: "should not use the following blacklisted import: " + path.Value,
+ Node: is,
+ Category: "imports",
+ })
+ }
}
- ast.Walk(walker, fileAst)
-
return failures
}
@@ -46,24 +50,3 @@ func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments)
func (r *ImportsBlacklistRule) Name() string {
return "imports-blacklist"
}
-
-type blacklistedImports struct {
- file *lint.File
- fileAst *ast.File
- onFailure func(lint.Failure)
- blacklist map[string]bool
-}
-
-func (w blacklistedImports) Visit(_ ast.Node) ast.Visitor {
- for _, is := range w.fileAst.Imports {
- if is.Path != nil && !w.file.IsTest() && w.blacklist[is.Path.Value] {
- w.onFailure(lint.Failure{
- Confidence: 1,
- Failure: fmt.Sprintf("should not use the following blacklisted import: %s", is.Path.Value),
- Node: is,
- Category: "imports",
- })
- }
- }
- return nil
-}
diff --git a/rule/struct-tag.go b/rule/struct-tag.go
index 2ed1410..8335e0d 100644
--- a/rule/struct-tag.go
+++ b/rule/struct-tag.go
@@ -2,11 +2,12 @@ package rule
import (
"fmt"
- "github.com/fatih/structtag"
- "github.com/mgechev/revive/lint"
"go/ast"
"strconv"
"strings"
+
+ "github.com/fatih/structtag"
+ "github.com/mgechev/revive/lint"
)
// StructTagRule lints struct tags.
@@ -58,6 +59,10 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
// checkTaggedField checks the tag of the given field.
// precondition: the field has a tag
func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
+ if len(f.Names) > 0 && !f.Names[0].IsExported() {
+ w.addFailure(f, "tag on not-exported field "+f.Names[0].Name)
+ }
+
tags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`"))
if err != nil || tags == nil {
w.addFailure(f.Tag, "malformed tag")
diff --git a/rule/unhandled-error.go b/rule/unhandled-error.go
new file mode 100644
index 0000000..0e2f628
--- /dev/null
+++ b/rule/unhandled-error.go
@@ -0,0 +1,120 @@
+package rule
+
+import (
+ "fmt"
+ "go/ast"
+ "go/types"
+
+ "github.com/mgechev/revive/lint"
+)
+
+// UnhandledErrorRule lints given else constructs.
+type UnhandledErrorRule struct{}
+
+type ignoreListType map[string]struct{}
+
+// Apply applies the rule to given file.
+func (r *UnhandledErrorRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
+ var failures []lint.Failure
+
+ ignoreList := make(ignoreListType, len(args))
+
+ for _, arg := range args {
+ argStr, ok := arg.(string)
+ if !ok {
+ panic(fmt.Sprintf("Invalid argument to the unhandled-error rule. Expecting a string, got %T", arg))
+ }
+
+ ignoreList[argStr] = struct{}{}
+ }
+
+ walker := &lintUnhandledErrors{
+ ignoreList: ignoreList,
+ pkg: file.Pkg,
+ onFailure: func(failure lint.Failure) {
+ failures = append(failures, failure)
+ },
+ }
+
+ file.Pkg.TypeCheck()
+ ast.Walk(walker, file.AST)
+
+ return failures
+}
+
+// Name returns the rule name.
+func (r *UnhandledErrorRule) Name() string {
+ return "unhandled-error"
+}
+
+type lintUnhandledErrors struct {
+ ignoreList ignoreListType
+ pkg *lint.Package
+ onFailure func(lint.Failure)
+}
+
+// Visit looks for statements that are function calls.
+// If the called function returns a value of type error a failure will be created.
+func (w *lintUnhandledErrors) Visit(node ast.Node) ast.Visitor {
+ switch n := node.(type) {
+ case *ast.ExprStmt:
+ fCall, ok := n.X.(*ast.CallExpr)
+ if !ok {
+ return nil // not a function call
+ }
+
+ funcType := w.pkg.TypeOf(fCall)
+ if funcType == nil {
+ return nil // skip, type info not available
+ }
+
+ switch t := funcType.(type) {
+ case *types.Named:
+ if !w.isTypeError(t) {
+ return nil // func call does not return an error
+ }
+
+ w.addFailure(fCall)
+ default:
+ retTypes, ok := funcType.Underlying().(*types.Tuple)
+ if !ok {
+ return nil // skip, unable to retrieve return type of the called function
+ }
+
+ if w.returnsAnError(retTypes) {
+ w.addFailure(fCall)
+ }
+ }
+ }
+ return w
+}
+
+func (w *lintUnhandledErrors) addFailure(n *ast.CallExpr) {
+ funcName := gofmt(n.Fun)
+ if _, mustIgnore := w.ignoreList[funcName]; mustIgnore {
+ return
+ }
+
+ w.onFailure(lint.Failure{
+ Category: "bad practice",
+ Confidence: 1,
+ Node: n,
+ Failure: fmt.Sprintf("Unhandled error in call to function %v", funcName),
+ })
+}
+
+func (*lintUnhandledErrors) isTypeError(t *types.Named) bool {
+ const errorTypeName = "_.error"
+
+ return t.Obj().Id() == errorTypeName
+}
+
+func (w *lintUnhandledErrors) returnsAnError(tt *types.Tuple) bool {
+ for i := 0; i < tt.Len(); i++ {
+ nt, ok := tt.At(i).Type().(*types.Named)
+ if ok && w.isTypeError(nt) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/rule/unused-param.go b/rule/unused-param.go
index f4de228..60df908 100644
--- a/rule/unused-param.go
+++ b/rule/unused-param.go
@@ -3,7 +3,6 @@ package rule
import (
"fmt"
"go/ast"
- "go/token"
"github.com/mgechev/revive/lint"
)
@@ -38,205 +37,66 @@ type lintUnusedParamRule struct {
func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.FuncDecl:
- fv := newFuncVisitor(retrieveNamedParams(n.Type.Params.List))
- if n.Body != nil {
- ast.Walk(fv, n.Body)
- checkUnusedParams(w, fv.params, n)
+ params := retrieveNamedParams(n.Type.Params)
+ if len(params) < 1 {
+ return nil // skip, func without parameters
}
- return nil
+
+ if n.Body == nil {
+ return nil // skip, is a function prototype
+ }
+
+ // inspect the func body looking for references to parameters
+ fselect := func(n ast.Node) bool {
+ ident, isAnID := n.(*ast.Ident)
+
+ if !isAnID {
+ return false
+ }
+
+ _, isAParam := params[ident.Obj]
+ if isAParam {
+ params[ident.Obj] = false // mark as used
+ }
+
+ return false
+ }
+ _ = pick(n.Body, fselect, nil)
+
+ for _, p := range n.Type.Params.List {
+ for _, n := range p.Names {
+ if params[n.Obj] {
+ w.onFailure(lint.Failure{
+ Confidence: 1,
+ Node: n,
+ Category: "bad practice",
+ Failure: fmt.Sprintf("parameter '%s' seems to be unused, consider removing or renaming it as _", n.Name),
+ })
+ }
+ }
+ }
+
+ return nil // full method body already inspected
}
return w
}
-type scope struct {
- vars map[string]bool
-}
-
-func newScope() scope {
- return scope{make(map[string]bool, 0)}
-}
-
-func (s *scope) addVars(exps []ast.Expr) {
- for _, e := range exps {
- if id, ok := e.(*ast.Ident); ok {
- s.vars[id.Name] = true
- }
- }
-}
-
-type scopeStack struct {
- stk []scope
-}
-
-func (s *scopeStack) openScope() {
- s.stk = append(s.stk, newScope())
-}
-
-func (s *scopeStack) closeScope() {
- if len(s.stk) > 0 {
- s.stk = s.stk[:len(s.stk)-1]
- }
-}
-
-func (s *scopeStack) currentScope() scope {
- if len(s.stk) > 0 {
- return s.stk[len(s.stk)-1]
+func retrieveNamedParams(params *ast.FieldList) map[*ast.Object]bool {
+ result := map[*ast.Object]bool{}
+ if params.List == nil {
+ return result
}
- panic("no current scope")
-}
-
-func newScopeStack() scopeStack {
- return scopeStack{make([]scope, 0)}
-}
-
-type funcVisitor struct {
- sStk scopeStack
- params map[string]bool
-}
-
-func newFuncVisitor(params map[string]bool) funcVisitor {
- return funcVisitor{sStk: newScopeStack(), params: params}
-}
-
-func walkStmtList(v ast.Visitor, list []ast.Stmt) {
- for _, s := range list {
- ast.Walk(v, s)
- }
-}
-
-func (v funcVisitor) Visit(node ast.Node) ast.Visitor {
- varSelector := func(n ast.Node) bool {
- id, ok := n.(*ast.Ident)
- return ok && id.Obj != nil && id.Obj.Kind.String() == "var"
- }
- switch n := node.(type) {
- case *ast.BlockStmt:
- v.sStk.openScope()
- walkStmtList(v, n.List)
- v.sStk.closeScope()
- return nil
- case *ast.AssignStmt:
- var uses []ast.Node
- if isOpAssign(n.Tok) { // Case of id += expr
- uses = append(uses, pickFromExpList(n.Lhs, varSelector, nil)...)
- } else { // Case of id[expr] = expr
- indexSelector := func(n ast.Node) bool {
- _, ok := n.(*ast.IndexExpr)
- return ok
- }
- f := func(n ast.Node) []ast.Node {
- ie, ok := n.(*ast.IndexExpr)
- if !ok { // not possible
- return nil
- }
-
- return pick(ie.Index, varSelector, nil)
- }
-
- uses = append(uses, pickFromExpList(n.Lhs, indexSelector, f)...)
- }
-
- uses = append(uses, pickFromExpList(n.Rhs, varSelector, nil)...)
-
- markParamListAsUsed(uses, v)
- cs := v.sStk.currentScope()
- cs.addVars(n.Lhs)
- case *ast.Ident:
- if n.Obj != nil {
- if n.Obj.Kind.String() == "var" {
- markParamAsUsed(n, v)
- }
- }
- case *ast.ForStmt:
- v.sStk.openScope()
- if n.Init != nil {
- ast.Walk(v, n.Init)
- }
- uses := pickFromExpList([]ast.Expr{n.Cond}, varSelector, nil)
- markParamListAsUsed(uses, v)
- ast.Walk(v, n.Body)
- v.sStk.closeScope()
- return nil
- case *ast.SwitchStmt:
- v.sStk.openScope()
- if n.Init != nil {
- ast.Walk(v, n.Init)
- }
- uses := pickFromExpList([]ast.Expr{n.Tag}, varSelector, nil)
- markParamListAsUsed(uses, v)
- // Analyze cases (they are not BlockStmt but a list of Stmt)
- cases := n.Body.List
- for _, c := range cases {
- cc, ok := c.(*ast.CaseClause)
- if !ok {
+ for _, p := range params.List {
+ for _, n := range p.Names {
+ if n.Name == "_" {
continue
}
- uses := pickFromExpList(cc.List, varSelector, nil)
- markParamListAsUsed(uses, v)
- v.sStk.openScope()
- for _, stmt := range cc.Body {
- ast.Walk(v, stmt)
- }
- v.sStk.closeScope()
- }
- v.sStk.closeScope()
- return nil
- }
-
- return v
-}
-
-func retrieveNamedParams(pl []*ast.Field) map[string]bool {
- result := make(map[string]bool, len(pl))
- for _, p := range pl {
- for _, n := range p.Names {
- if n.Name != "_" {
- result[n.Name] = true
- }
+ result[n.Obj] = true
}
}
+
return result
}
-
-func checkUnusedParams(w lintUnusedParamRule, params map[string]bool, n *ast.FuncDecl) {
- for k, v := range params {
- if v {
- w.onFailure(lint.Failure{
- Confidence: 0.8, // confidence is not 1.0 because of shadow variables
- Node: n,
- Category: "bad practice",
- Failure: fmt.Sprintf("parameter '%s' seems to be unused, consider removing or renaming it as _", k),
- })
- }
- }
-
-}
-
-func markParamListAsUsed(ids []ast.Node, v funcVisitor) {
- for _, id := range ids {
- markParamAsUsed(id.(*ast.Ident), v)
- }
-}
-
-func markParamAsUsed(id *ast.Ident, v funcVisitor) { // TODO: constraint parameters to receive just a list of params and a scope stack
- for _, s := range v.sStk.stk {
- if s.vars[id.Name] {
- return
- }
- }
-
- if v.params[id.Name] {
- v.params[id.Name] = false
- }
-}
-
-func isOpAssign(aTok token.Token) bool {
- return aTok == token.ADD_ASSIGN || aTok == token.AND_ASSIGN ||
- aTok == token.MUL_ASSIGN || aTok == token.OR_ASSIGN ||
- aTok == token.QUO_ASSIGN || aTok == token.REM_ASSIGN ||
- aTok == token.SHL_ASSIGN || aTok == token.SHR_ASSIGN ||
- aTok == token.SUB_ASSIGN || aTok == token.XOR_ASSIGN
-}
diff --git a/test/import-blacklist_test.go b/test/import-blacklist_test.go
index 6132b7b..d427f0b 100644
--- a/test/import-blacklist_test.go
+++ b/test/import-blacklist_test.go
@@ -14,3 +14,13 @@ func TestImportsBlacklist(t *testing.T) {
Arguments: args,
})
}
+
+func BenchmarkImportsBlacklist(b *testing.B) {
+ args := []interface{}{"crypto/md5", "crypto/sha1"}
+ var t *testing.T
+ for i := 0; i <= b.N; i++ {
+ testRule(t, "imports-blacklist", &rule.ImportsBlacklistRule{}, &lint.RuleConfig{
+ Arguments: args,
+ })
+ }
+}
diff --git a/test/unhandled-error_test.go b/test/unhandled-error_test.go
new file mode 100644
index 0000000..a3a5cc3
--- /dev/null
+++ b/test/unhandled-error_test.go
@@ -0,0 +1,18 @@
+package test
+
+import (
+ "testing"
+
+ "github.com/mgechev/revive/lint"
+ "github.com/mgechev/revive/rule"
+)
+
+func TestUnhandledError(t *testing.T) {
+ testRule(t, "unhandled-error", &rule.UnhandledErrorRule{})
+}
+
+func TestUnhandledErrorWithBlacklist(t *testing.T) {
+ args := []interface{}{"os.Chdir", "unhandledError1"}
+
+ testRule(t, "unhandled-error-w-ignorelist", &rule.UnhandledErrorRule{}, &lint.RuleConfig{Arguments: args})
+}
diff --git a/test/unused-param_test.go b/test/unused-param_test.go
index c8b706b..7b6472c 100644
--- a/test/unused-param_test.go
+++ b/test/unused-param_test.go
@@ -9,3 +9,10 @@ import (
func TestUnusedParam(t *testing.T) {
testRule(t, "unused-param", &rule.UnusedParamRule{})
}
+
+func BenchmarkUnusedParam(b *testing.B) {
+ var t *testing.T
+ for i := 0; i <= b.N; i++ {
+ testRule(t, "unused-param", &rule.UnusedParamRule{})
+ }
+}