From 1baa9aaa67d58e29ae43f8ae2f9039b63788041a Mon Sep 17 00:00:00 2001 From: AdminBenni Date: Sat, 2 Aug 2025 14:41:54 +0000 Subject: [PATCH] feat: add iotamixing linter --- .golangci.next.reference.yml | 7 +++ go.mod | 5 +- go.sum | 2 + jsonschema/golangci.next.jsonschema.json | 12 ++++ pkg/config/linters_settings.go | 5 ++ pkg/golinters/iotamixing/iotamixing.go | 29 ++++++++++ .../iotamixing/iotamixing_integration_test.go | 11 ++++ .../testdata/iotamixing-report-individual.go | 50 +++++++++++++++++ .../testdata/iotamixing-report-individual.yml | 3 + .../iotamixing/testdata/iotamixing.go | 55 +++++++++++++++++++ pkg/lint/lintersdb/builder_linter.go | 6 ++ 11 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 pkg/golinters/iotamixing/iotamixing.go create mode 100644 pkg/golinters/iotamixing/iotamixing_integration_test.go create mode 100644 pkg/golinters/iotamixing/testdata/iotamixing-report-individual.go create mode 100644 pkg/golinters/iotamixing/testdata/iotamixing-report-individual.yml create mode 100644 pkg/golinters/iotamixing/testdata/iotamixing.go diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index fb0895eb3700..cefe183f9867 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -68,6 +68,7 @@ linters: - ineffassign - interfacebloat - intrange + - iotamixing - ireturn - lll - loggercheck @@ -184,6 +185,7 @@ linters: - ineffassign - interfacebloat - intrange + - iotamixing - ireturn - lll - loggercheck @@ -2032,6 +2034,11 @@ linters-settings: # Default: 10 max: 5 + iotamixing: + # Whether to report individual consts rather than just the const block. + # Default: false + report-individual: true + ireturn: # List of interfaces to allow. # Lists of the keywords and regular expressions matched to interface or package names can be used. diff --git a/go.mod b/go.mod index 4755f528fab1..42c548c73f48 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,15 @@ module github.com/golangci/golangci-lint -go 1.23.0 +go 1.23.3 + +toolchain go1.23.11 require ( 4d63.com/gocheckcompilerdirectives v1.3.0 4d63.com/gochecknoglobals v0.2.2 github.com/4meepo/tagalign v1.4.2 github.com/Abirdcfly/dupword v0.1.3 + github.com/AdminBenni/iota-mixing v0.0.5 github.com/Antonboom/errname v1.0.0 github.com/Antonboom/nilnil v1.0.1 github.com/Antonboom/testifylint v1.5.2 diff --git a/go.sum b/go.sum index eb6fe0ef722f..8c15947595a9 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= +github.com/AdminBenni/iota-mixing v0.0.5 h1:BUJ5+puRS0vksO4JFOqsSMDlpuQrU6W6cw4QI3doXAU= +github.com/AdminBenni/iota-mixing v0.0.5/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY= github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= github.com/Antonboom/nilnil v1.0.1 h1:C3Tkm0KUxgfO4Duk3PM+ztPncTFlOf0b2qadmS0s4xs= diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index f80ccf06235a..e58364ac3cee 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -387,6 +387,7 @@ "ineffassign", "interfacebloat", "intrange", + "iotamixing", "ireturn", "lll", "loggercheck", @@ -2084,6 +2085,17 @@ } } }, + "iotamixing": { + "type": "object", + "additionalProperties": false, + "properties": { + "report-individual": { + "description": "Whether to report individual consts rather than just the const block.", + "type": "boolean", + "default": false + } + } + }, "ireturn": { "type": "object", "additionalProperties": false, diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 94650a66de9b..159536057227 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -255,6 +255,7 @@ type LintersSettings struct { ImportAs ImportAsSettings Inamedparam INamedParamSettings InterfaceBloat InterfaceBloatSettings + IotaMixing IotaMixingSettings Ireturn IreturnSettings Lll LllSettings LoggerCheck LoggerCheckSettings @@ -704,6 +705,10 @@ type InterfaceBloatSettings struct { Max int `mapstructure:"max"` } +type IotaMixingSettings struct { + ReportIndividual bool `mapstructure:"report-individual"` +} + type IreturnSettings struct { Allow []string `mapstructure:"allow"` Reject []string `mapstructure:"reject"` diff --git a/pkg/golinters/iotamixing/iotamixing.go b/pkg/golinters/iotamixing/iotamixing.go new file mode 100644 index 000000000000..fda0b1e49828 --- /dev/null +++ b/pkg/golinters/iotamixing/iotamixing.go @@ -0,0 +1,29 @@ +package iotamixing + +import ( + "golang.org/x/tools/go/analysis" + + "github.com/AdminBenni/iota-mixing/pkg/analyzer" + "github.com/AdminBenni/iota-mixing/pkg/analyzer/flags" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goanalysis" +) + +func New(settings *config.IotaMixingSettings) *goanalysis.Linter { + a := analyzer.GetIotaMixingAnalyzer() + + flags.SetupFlags(&a.Flags) + + cfg := map[string]map[string]any{} + if settings != nil { + cfg[a.Name] = map[string]any{flags.ReportIndividualFlagName: settings.ReportIndividual} + } + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + cfg, + ).WithLoadMode(goanalysis.LoadModeSyntax) +} diff --git a/pkg/golinters/iotamixing/iotamixing_integration_test.go b/pkg/golinters/iotamixing/iotamixing_integration_test.go new file mode 100644 index 000000000000..837eadc6b793 --- /dev/null +++ b/pkg/golinters/iotamixing/iotamixing_integration_test.go @@ -0,0 +1,11 @@ +package iotamixing + +import ( + "testing" + + "github.com/golangci/golangci-lint/test/testshared/integration" +) + +func TestFromTestdata(t *testing.T) { + integration.RunTestdata(t) +} diff --git a/pkg/golinters/iotamixing/testdata/iotamixing-report-individual.go b/pkg/golinters/iotamixing/testdata/iotamixing-report-individual.go new file mode 100644 index 000000000000..f23b06675241 --- /dev/null +++ b/pkg/golinters/iotamixing/testdata/iotamixing-report-individual.go @@ -0,0 +1,50 @@ +//golangcitest:args -Eiotamixing +//golangcitest:config_path testdata/iotamixing-report-individual.yml +package testdata + +import "fmt" + +const ( + InvalidPerIndividualIotaDeclAboveAnything = "anything" // want "InvalidPerIndividualIotaDeclAboveAnything is a const with r-val in same const block as iota. keep iotas in separate const blocks" + InvalidPerIndividualIotaDeclAboveNotZero = iota + InvalidPerIndividualIotaDeclAboveNotOne + InvalidPerIndividualIotaDeclAboveNotTwo +) + +const ( + InvalidPerIndividualIotaDeclBelowZero = iota + InvalidPerIndividualIotaDeclBelowOne + InvalidPerIndividualIotaDeclBelowTwo + InvalidPerIndividualIotaDeclBelowAnything = "anything" // want "InvalidPerIndividualIotaDeclBelowAnything is a const with r-val in same const block as iota. keep iotas in separate const blocks" +) + +const ( + InvalidPerIndividualIotaDeclBetweenZero = iota + InvalidPerIndividualIotaDeclBetweenOne + InvalidPerIndividualIotaDeclBetweenAnything = "anything" // want "InvalidPerIndividualIotaDeclBetweenAnything is a const with r-val in same const block as iota. keep iotas in separate const blocks" + InvalidPerIndividualIotaDeclBetweenNotTwo +) + +const ( + InvalidPerIndividualIotaDeclMultipleAbove = "above" // want "InvalidPerIndividualIotaDeclMultipleAbove is a const with r-val in same const block as iota. keep iotas in separate const blocks" + InvalidPerIndividualIotaDeclMultipleNotZero = iota + InvalidPerIndividualIotaDeclMultipleNotOne + InvalidPerIndividualIotaDeclMultipleBetween = "between" // want "InvalidPerIndividualIotaDeclMultipleBetween is a const with r-val in same const block as iota. keep iotas in separate const blocks" + InvalidPerIndividualIotaDeclMultipleNotTwo + InvalidPerIndividualIotaDeclMultipleBelow = "below" // want "InvalidPerIndividualIotaDeclMultipleBelow is a const with r-val in same const block as iota. keep iotas in separate const blocks" +) + +const ( + ValidPerIndividualIotaZero = iota + ValidPerIndividualIotaOne + ValidPerIndividualIotaTwo +) + +const ( + ValidPerIndividualRegularSomething = "something" + ValidPerIndividualRegularAnything = "anything" +) + +func _() { + fmt.Println("using the std import so goland doesn't nuke it") +} diff --git a/pkg/golinters/iotamixing/testdata/iotamixing-report-individual.yml b/pkg/golinters/iotamixing/testdata/iotamixing-report-individual.yml new file mode 100644 index 000000000000..50d0f842ede7 --- /dev/null +++ b/pkg/golinters/iotamixing/testdata/iotamixing-report-individual.yml @@ -0,0 +1,3 @@ +linters-settings: + iotamixing: + report-individual: true \ No newline at end of file diff --git a/pkg/golinters/iotamixing/testdata/iotamixing.go b/pkg/golinters/iotamixing/testdata/iotamixing.go new file mode 100644 index 000000000000..ab537b632dc9 --- /dev/null +++ b/pkg/golinters/iotamixing/testdata/iotamixing.go @@ -0,0 +1,55 @@ +//golangcitest:args -Eiotamixing +package testdata + +import "fmt" + +// iota mixing in const block containing an iota and r-val declared above. +const ( // want "iota mixing. keep iotas in separate blocks to consts with r-val" + InvalidPerBlockIotaDeclAboveAnything = "anything" + InvalidPerBlockIotaDeclAboveNotZero = iota + InvalidPerBlockIotaDeclAboveNotOne + InvalidPerBlockIotaDeclAboveNotTwo +) + +// iota mixing in const block containing an iota and r-val declared below. +const ( // want "iota mixing. keep iotas in separate blocks to consts with r-val" + InvalidPerBlockIotaDeclBelowZero = iota + InvalidPerBlockIotaDeclBelowOne + InvalidPerBlockIotaDeclBelowTwo + InvalidPerBlockIotaDeclBelowAnything = "anything" +) + +// iota mixing in const block containing an iota and r-val declared between consts. +const ( // want "iota mixing. keep iotas in separate blocks to consts with r-val" + InvalidPerBlockIotaDeclBetweenZero = iota + InvalidPerBlockIotaDeclBetweenOne + InvalidPerBlockIotaDeclBetweenAnything = "anything" + InvalidPerBlockIotaDeclBetweenNotTwo +) + +// iota mixing in const block containing an iota and r-vals declared above, between, and below consts. +const ( // want "iota mixing. keep iotas in separate blocks to consts with r-val" + InvalidPerBlockIotaDeclMultipleAbove = "above" + InvalidPerBlockIotaDeclMultipleNotZero = iota + InvalidPerBlockIotaDeclMultipleNotOne + InvalidPerBlockIotaDeclMultipleBetween = "between" + InvalidPerBlockIotaDeclMultipleNotTwo + InvalidPerBlockIotaDeclMultipleBelow = "below" +) + +// no iota mixing in a const block containing an iota and no r-vals. +const ( + ValidPerBlockIotaZero = iota + ValidPerBlockIotaOne + ValidPerBlockIotaTwo +) + +// no iota mixing in a const block containing r-vals and no iota. +const ( + ValidPerBlockRegularSomething = "something" + ValidPerBlockRegularAnything = "anything" +) + +func _() { + fmt.Println("using the std import so goland doesn't nuke it") +} diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index ddeb99e1440d..9b327dfe7a1d 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -60,6 +60,7 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/ineffassign" "github.com/golangci/golangci-lint/pkg/golinters/interfacebloat" "github.com/golangci/golangci-lint/pkg/golinters/intrange" + "github.com/golangci/golangci-lint/pkg/golinters/iotamixing" "github.com/golangci/golangci-lint/pkg/golinters/ireturn" "github.com/golangci/golangci-lint/pkg/golinters/lll" "github.com/golangci/golangci-lint/pkg/golinters/loggercheck" @@ -536,6 +537,11 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithURL("https://github.com/ckaznocha/intrange"). WithNoopFallback(cfg, linter.IsGoLowerThanGo122()), + linter.NewConfig(iotamixing.New(&cfg.LintersSettings.IotaMixing)). + WithSince("v2.4.0"). + WithPresets(linter.PresetBugs). + WithURL("github.com/AdminBenni/iota-mixing"), + linter.NewConfig(ireturn.New(&cfg.LintersSettings.Ireturn)). WithSince("v1.43.0"). WithPresets(linter.PresetStyle).