From 796a95880598d8fd0a8b6b638d2419d84418e59c Mon Sep 17 00:00:00 2001 From: Vladimir Evgrafov Date: Fri, 9 Oct 2020 15:42:48 +0300 Subject: [PATCH] Add go-errorlint (#1420) * Add errorlint * Add errorlint config example --- .golangci.example.yml | 3 ++ go.mod | 1 + go.sum | 8 ++- pkg/config/config.go | 8 +++ pkg/golinters/errorlint.go | 27 ++++++++++ pkg/lint/lintersdb/manager.go | 6 +++ test/testdata/errorlint.go | 90 +++++++++++++++++++++++++++++++ test/testdata/errorlint_errorf.go | 26 +++++++++ 8 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 pkg/golinters/errorlint.go create mode 100644 test/testdata/errorlint.go create mode 100644 test/testdata/errorlint_errorf.go diff --git a/.golangci.example.yml b/.golangci.example.yml index 24ae7e5a..61190bfe 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -331,6 +331,9 @@ linters-settings: # Choose whether or not to use the extra rules that are disabled # by default extra-rules: false + errorlint: + # Report non-wrapping error creation using fmt.Errorf + errorf: true # The custom section can be used to define linter plugins to be loaded at runtime. See README doc # for more info. diff --git a/go.mod b/go.mod index 5cae7c8b..0e8f9a1e 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/nakabonne/nestif v0.3.0 github.com/nishanths/exhaustive v0.0.0-20200811152831-6cf413ae40e0 github.com/pkg/errors v0.9.1 + github.com/polyfloyd/go-errorlint v0.0.0-20201006195004-351e25ade6e3 github.com/ryancurrah/gomodguard v1.1.0 github.com/ryanrolds/sqlclosecheck v0.3.0 github.com/securego/gosec/v2 v2.4.0 diff --git a/go.sum b/go.sum index cc08ad64..e35d53b6 100644 --- a/go.sum +++ b/go.sum @@ -223,6 +223,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kyoh86/exportloopref v0.1.7 h1:u+iHuTbkbTS2D/JP7fCuZDo/t3rBVGo3Hf58Rc+lQVY= github.com/kyoh86/exportloopref v0.1.7/go.mod h1:h1rDl2Kdj97+Kwh4gdz3ujE7XHmH51Q0lUiZ1z4NLj8= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= @@ -233,8 +234,6 @@ github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUC github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -242,6 +241,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -294,6 +294,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v0.0.0-20201006195004-351e25ade6e3 h1:Amgs0nbayPhBNGh1qPqqr2e7B2qNAcBgRjnBH/lmn8k= +github.com/polyfloyd/go-errorlint v0.0.0-20201006195004-351e25ade6e3/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -457,6 +459,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -520,6 +523,7 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/pkg/config/config.go b/pkg/config/config.go index 9689ea09..3f42c604 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -249,6 +249,7 @@ type LintersSettings struct { NoLintLint NoLintLintSettings Exhaustive ExhaustiveSettings Gofumpt GofumptSettings + ErrorLint ErrorLintSettings Custom map[string]CustomLinterSettings } @@ -360,6 +361,10 @@ type GofumptSettings struct { ExtraRules bool `mapstructure:"extra-rules"` } +type ErrorLintSettings struct { + Errorf bool `mapstructure:"errorf"` +} + var defaultLintersSettings = LintersSettings{ Lll: LllSettings{ LineLength: 120, @@ -416,6 +421,9 @@ var defaultLintersSettings = LintersSettings{ Gofumpt: GofumptSettings{ ExtraRules: false, }, + ErrorLint: ErrorLintSettings{ + Errorf: true, + }, } type CustomLinterSettings struct { diff --git a/pkg/golinters/errorlint.go b/pkg/golinters/errorlint.go new file mode 100644 index 00000000..5b656d14 --- /dev/null +++ b/pkg/golinters/errorlint.go @@ -0,0 +1,27 @@ +package golinters + +import ( + "github.com/polyfloyd/go-errorlint/errorlint" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewErrorLint(cfg *config.ErrorLintSettings) *goanalysis.Linter { + a := errorlint.NewAnalyzer() + cfgMap := map[string]map[string]interface{}{} + if cfg != nil { + cfgMap[a.Name] = map[string]interface{}{ + "errorf": cfg.Errorf, + } + } + return goanalysis.NewLinter( + "errorlint", + "go-errorlint is a source code linter for Go software "+ + "that can be used to find code that will cause problems"+ + "with the error wrapping scheme introduced in Go 1.13.", + []*analysis.Analyzer{a}, + cfgMap, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 767ab870..aec8474e 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -90,10 +90,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { var govetCfg *config.GovetSettings var testpackageCfg *config.TestpackageSettings var exhaustiveCfg *config.ExhaustiveSettings + var errorlintCfg *config.ErrorLintSettings if m.cfg != nil { govetCfg = &m.cfg.LintersSettings.Govet testpackageCfg = &m.cfg.LintersSettings.Testpackage exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive + errorlintCfg = &m.cfg.LintersSettings.ErrorLint } const megacheckName = "megacheck" lcs := []*linter.Config{ @@ -315,6 +317,10 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithLoadForGoAnalysis(). WithURL("https://github.com/moricho/tparallel"), + linter.NewConfig(golinters.NewErrorLint(errorlintCfg)). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/polyfloyd/go-errorlint"), // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives linter.NewConfig(golinters.NewNoLintLint()). diff --git a/test/testdata/errorlint.go b/test/testdata/errorlint.go new file mode 100644 index 00000000..a61931dc --- /dev/null +++ b/test/testdata/errorlint.go @@ -0,0 +1,90 @@ +//args: -Eerrorlint +package testdata + +import ( + "errors" + "log" +) + +var errFoo = errors.New("foo") + +func doThing() error { + return errFoo +} + +func compare() { + err := doThing() + if errors.Is(err, errFoo) { + log.Println("ErrFoo") + } + if err == nil { + log.Println("nil") + } + if err != nil { + log.Println("nil") + } + if nil == err { + log.Println("nil") + } + if nil != err { + log.Println("nil") + } + if err == errFoo { // ERROR "comparing with == will fail on wrapped errors. Use errors.Is to check for a specific error" + log.Println("errFoo") + } + if err != errFoo { // ERROR "comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error" + log.Println("not errFoo") + } + if errFoo == err { // ERROR "comparing with == will fail on wrapped errors. Use errors.Is to check for a specific error" + log.Println("errFoo") + } + if errFoo != err { // ERROR "comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error" + log.Println("not errFoo") + } + switch err { // ERROR "switch on an error will fail on wrapped errors. Use errors.Is to check for specific errors" + case errFoo: + log.Println("errFoo") + } + switch doThing() { // ERROR "switch on an error will fail on wrapped errors. Use errors.Is to check for specific errors" + case errFoo: + log.Println("errFoo") + } +} + +type myError struct{} + +func (*myError) Error() string { + return "foo" +} + +func doAnotherThing() error { + return &myError{} +} + +func typeCheck() { + err := doAnotherThing() + var me *myError + if errors.As(err, &me) { + log.Println("myError") + } + _, ok := err.(*myError) // ERROR "type assertion on error will fail on wrapped errors. Use errors.As to check for specific errors" + if ok { + log.Println("myError") + } + switch err.(type) { // ERROR "type switch on error will fail on wrapped errors. Use errors.As to check for specific errors" + case *myError: + log.Println("myError") + } + switch doAnotherThing().(type) { // ERROR "type switch on error will fail on wrapped errors. Use errors.As to check for specific errors" + case *myError: + log.Println("myError") + } + switch t := err.(type) { // ERROR "type switch on error will fail on wrapped errors. Use errors.As to check for specific errors" + case *myError: + log.Println("myError", t) + } + switch t := doAnotherThing().(type) { // ERROR "type switch on error will fail on wrapped errors. Use errors.As to check for specific errors" + case *myError: + log.Println("myError", t) + } +} diff --git a/test/testdata/errorlint_errorf.go b/test/testdata/errorlint_errorf.go new file mode 100644 index 00000000..8bd3f419 --- /dev/null +++ b/test/testdata/errorlint_errorf.go @@ -0,0 +1,26 @@ +//args: -Eerrorlint +//config: linters-settings.errorlint.errorf=true +package testdata + +import ( + "errors" + "fmt" +) + +type customError struct{} + +func (customError) Error() string { + return "oops" +} + +func wraps() { + err := errors.New("oops") + fmt.Errorf("error: %w", err) + fmt.Errorf("error: %v", err) // ERROR "non-wrapping format verb for fmt.Errorf. Use `%w` to format errors" + fmt.Errorf("%v %v", err, err) // ERROR "non-wrapping format verb for fmt.Errorf. Use `%w` to format errors" + fmt.Errorf("error: %s", err.Error()) // ERROR "non-wrapping format verb for fmt.Errorf. Use `%w` to format errors" + customError := customError{} + fmt.Errorf("error: %s", customError.Error()) // ERROR "non-wrapping format verb for fmt.Errorf. Use `%w` to format errors" + strErr := "oops" + fmt.Errorf("%v", strErr) +}