diff --git a/.golangci.example.yml b/.golangci.example.yml index e15a300e60dc..0933c52881a9 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -285,6 +285,11 @@ linters-settings: - '*.Test' - 'example.com/package.ExampleStruct' + floatcompare: + # Search only for == and != no other comparisons + # Default: false + equal-only: true + forbidigo: # Forbid the following identifiers (list of regexp). forbid: @@ -1636,6 +1641,7 @@ linters: - exhaustive - exhaustivestruct - exportloopref + - floatcompare - forbidigo - forcetypeassert - funlen @@ -1726,6 +1732,7 @@ linters: - exhaustive - exhaustivestruct - exportloopref + - floatcompare - forbidigo - forcetypeassert - funlen diff --git a/go.mod b/go.mod index 50147f8c3814..270b2b866a26 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-ps v1.0.0 github.com/moricho/tparallel v0.2.1 + github.com/mweb/floatcompare v1.0.4 github.com/nakabonne/nestif v0.3.1 github.com/nishanths/exhaustive v0.7.11 github.com/nishanths/predeclared v0.2.1 @@ -162,7 +163,7 @@ require ( golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect + golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/go.sum b/go.sum index 621d32160335..e5887ff538c8 100644 --- a/go.sum +++ b/go.sum @@ -530,6 +530,8 @@ github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EH github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8= github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= +github.com/mweb/floatcompare v1.0.4 h1:Fgwv7o9EwK2HQwPNsaAZLaa88y5c8V3qwHegv7my/OY= +github.com/mweb/floatcompare v1.0.4/go.mod h1:Tl63NgzalWNFF3c+Lsml0oocmFkJE6pbPYyCULQl36M= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= @@ -1016,8 +1018,8 @@ golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 h1:D1v9ucDTYBtbz5vNuBbAhIMAGhQhJ6Ym5ah3maMVNX4= +golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 97c2065d4bf5..557b0821aad6 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -123,6 +123,7 @@ type LintersSettings struct { ErrorLint ErrorLintSettings Exhaustive ExhaustiveSettings ExhaustiveStruct ExhaustiveStructSettings + FloatCompare FloatCompareSettings Forbidigo ForbidigoSettings Funlen FunlenSettings Gci GciSettings @@ -255,6 +256,10 @@ type ExhaustiveStructSettings struct { StructPatterns []string `mapstructure:"struct-patterns"` } +type FloatCompareSettings struct { + EqualOnly bool `mapstructure:"equal-only"` +} + type ForbidigoSettings struct { Forbid []string `mapstructure:"forbid"` ExcludeGodocExamples bool `mapstructure:"exclude-godoc-examples"` diff --git a/pkg/golinters/floatcompare.go b/pkg/golinters/floatcompare.go new file mode 100644 index 000000000000..35447725fbb4 --- /dev/null +++ b/pkg/golinters/floatcompare.go @@ -0,0 +1,29 @@ +package golinters + +import ( + "github.com/mweb/floatcompare" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewFloatCompare(settings *config.FloatCompareSettings) *goanalysis.Linter { + a := floatcompare.NewAnalyzer() + + var cfg map[string]map[string]interface{} + if settings != nil { + d := map[string]interface{}{ + "equalOnly": settings.EqualOnly, + } + + cfg = map[string]map[string]interface{}{a.Name: d} + } + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + cfg, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index adf91cbc601d..29658f44c866 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -107,6 +107,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { var errorlintCfg *config.ErrorLintSettings var exhaustiveCfg *config.ExhaustiveSettings var exhaustiveStructCfg *config.ExhaustiveStructSettings + var floatCompareStructCfg *config.FloatCompareSettings var gciCfg *config.GciSettings var goModDirectivesCfg *config.GoModDirectivesSettings var goMndCfg *config.GoMndSettings @@ -140,6 +141,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { errorlintCfg = &m.cfg.LintersSettings.ErrorLint exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive exhaustiveStructCfg = &m.cfg.LintersSettings.ExhaustiveStruct + floatCompareStructCfg = &m.cfg.LintersSettings.FloatCompare gciCfg = &m.cfg.LintersSettings.Gci goModDirectivesCfg = &m.cfg.LintersSettings.GoModDirectives goMndCfg = &m.cfg.LintersSettings.Gomnd @@ -289,6 +291,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/kyoh86/exportloopref"), + linter.NewConfig(golinters.NewFloatCompare(floatCompareStructCfg)). + WithSince("v1.46.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/mweb/floatcompare"), + linter.NewConfig(golinters.NewForbidigo()). WithSince("v1.34.0"). WithPresets(linter.PresetStyle). diff --git a/test/testdata/floatcompare.go b/test/testdata/floatcompare.go new file mode 100644 index 000000000000..214a863dfe7e --- /dev/null +++ b/test/testdata/floatcompare.go @@ -0,0 +1,71 @@ +// args: -Efloatcompare +package testdata + +import "fmt" + +func EqualCompareIfFloats() { + x, y := 400., 500. + if 300. == 100. { // ERROR `float comparison found "300. == 100."` + dummy() + } + if x == y { // ERROR `float comparison found "x == y"` + dummy() + } + if 300.+200. == 10. { // ERROR `float comparison found "300.+200. == 10."` + dummy() + } + if 300 == 200 { + dummy() + } +} + +func NotEqualCompareIfFloats() { + x, y := 400., 500. + if 300. != 100. { // ERROR `float comparison found "300. != 100."` + + dummy() + } + if x != y { // ERROR `float comparison found "x != y"` + dummy() + } +} + +func EqualCompareIfCustomType() { + type number float64 + var x, y number = 300., 400. + if x == y { // ERROR `float comparison found "x == y"` + dummy() + } +} + +func GreaterLessCompareIfFloats() { + if 300. >= 100. { // ERROR `float comparison found "300. >= 100."` + dummy() + } + if 300. <= 100. { // ERROR `float comparison found "300. <= 100."` + dummy() + } + if 300. < 100. { // ERROR `float comparison found "300. < 100."` + dummy() + } + if 300. > 100. { // ERROR `float comparison found "300. > 100."` + dummy() + } +} + +func SwitchStmtWithFloat() { + switch 300. { // ERROR "float comparison with switch statement" + case 100.: + case 200: + } +} + +func EqualCompareSwitchFloats() { + switch { + case 100. == 200.: // ERROR `float comparison found "100. == 200."` + } +} + +func dummy() { + fmt.Println("dummy()") +}