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) | -[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) | :---: |:---: |:---: |:---: |:---: |:---: | -[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) | -[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) | -:---: |:---: |:---: |:---: |:---: | -[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) | +[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) | +:---: |:---: |:---: |:---: |:---: |:---: | +[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{}) + } +}