diff --git a/.golangci.reference.yml b/.golangci.reference.yml index 33a9a0373f4b..2ceb0889283b 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -1799,6 +1799,20 @@ linters-settings: packages: - github.com/jmoiron/sqlx + sloglint: + # Enforce using key-value pairs only (incompatible with attr-only). + # Default: false + kv-only: true + # Enforce using attributes only (incompatible with kv-only). + # Default: false + attr-only: true + # Enforce using constants instead of raw keys. + # Default: false + no-raw-keys: true + # Enforce putting arguments on separate lines. + # Default: false + args-on-sep-lines: true + staticcheck: # Deprecated: use the global `run.go` instead. go: "1.15" @@ -2295,6 +2309,7 @@ linters: - revive - rowserrcheck - scopelint + - sloglint - sqlclosecheck - staticcheck - structcheck @@ -2413,6 +2428,7 @@ linters: - revive - rowserrcheck - scopelint + - sloglint - sqlclosecheck - staticcheck - structcheck diff --git a/go.mod b/go.mod index c3d9fc8ed637..4aaf35b4aa8e 100644 --- a/go.mod +++ b/go.mod @@ -119,6 +119,7 @@ require ( github.com/yeya24/promlinter v0.2.0 github.com/ykadowak/zerologlint v0.1.3 gitlab.com/bosi/decorder v0.4.1 + go-simpler.org/sloglint v0.1.2 go.tmz.dev/musttag v0.7.2 golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea golang.org/x/tools v0.14.0 diff --git a/go.sum b/go.sum index 7f67a6bc65c7..859429167fa4 100644 --- a/go.sum +++ b/go.sum @@ -587,6 +587,8 @@ github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= go-simpler.org/assert v0.6.0 h1:QxSrXa4oRuo/1eHMXSBFHKvJIpWABayzKldqZyugG7E= +go-simpler.org/sloglint v0.1.2 h1:IjdhF8NPxyn0Ckn2+fuIof7ntSnVUAqBFcQRrnG9AiM= +go-simpler.org/sloglint v0.1.2/go.mod h1:2LL+QImPfTslD5muNPydAEYmpXIj6o/WYcqnJjLi4o4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 004b05256f09..0fee9f81ef53 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -113,6 +113,12 @@ var defaultLintersSettings = LintersSettings{ Ignore: "", Qualified: false, }, + SlogLint: SlogLintSettings{ + KVOnly: false, + AttrOnly: false, + NoRawKeys: false, + ArgsOnSepLines: false, + }, TagAlign: TagAlignSettings{ Align: true, Sort: true, @@ -222,6 +228,7 @@ type LintersSettings struct { Reassign ReassignSettings Revive ReviveSettings RowsErrCheck RowsErrCheckSettings + SlogLint SlogLintSettings Staticcheck StaticCheckSettings Structcheck StructCheckSettings Stylecheck StaticCheckSettings @@ -717,6 +724,13 @@ type RowsErrCheckSettings struct { Packages []string } +type SlogLintSettings struct { + KVOnly bool `mapstructure:"kv-only"` + AttrOnly bool `mapstructure:"attr-only"` + NoRawKeys bool `mapstructure:"no-raw-keys"` + ArgsOnSepLines bool `mapstructure:"args-on-sep-lines"` +} + type StaticCheckSettings struct { // Deprecated: use the global `run.go` instead. GoVersion string `mapstructure:"go"` diff --git a/pkg/golinters/sloglint.go b/pkg/golinters/sloglint.go new file mode 100644 index 000000000000..b506d187fd18 --- /dev/null +++ b/pkg/golinters/sloglint.go @@ -0,0 +1,27 @@ +package golinters + +import ( + "go-simpler.org/sloglint" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewSlogLint(settings *config.SlogLintSettings) *goanalysis.Linter { + var opts *sloglint.Options + if settings != nil { + opts = &sloglint.Options{ + KVOnly: settings.KVOnly, + AttrOnly: settings.AttrOnly, + NoRawKeys: settings.NoRawKeys, + ArgsOnSepLines: settings.ArgsOnSepLines, + } + } + + a := sloglint.New(opts) + + return goanalysis. + NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil). + WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 1247f07934f2..f725de95fa21 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -127,6 +127,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { reassignCfg *config.ReassignSettings reviveCfg *config.ReviveSettings rowserrcheckCfg *config.RowsErrCheckSettings + sloglintCfg *config.SlogLintSettings staticcheckCfg *config.StaticCheckSettings structcheckCfg *config.StructCheckSettings stylecheckCfg *config.StaticCheckSettings @@ -208,6 +209,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { reassignCfg = &m.cfg.LintersSettings.Reassign reviveCfg = &m.cfg.LintersSettings.Revive rowserrcheckCfg = &m.cfg.LintersSettings.RowsErrCheck + sloglintCfg = &m.cfg.LintersSettings.SlogLint staticcheckCfg = &m.cfg.LintersSettings.Staticcheck structcheckCfg = &m.cfg.LintersSettings.Structcheck stylecheckCfg = &m.cfg.LintersSettings.Stylecheck @@ -750,6 +752,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetBugs, linter.PresetSQL). WithURL("https://github.com/jingyugao/rowserrcheck"), + linter.NewConfig(golinters.NewSlogLint(sloglintCfg)). + WithSince("v1.55.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle, linter.PresetFormatting). + WithURL("https://github.com/go-simpler/sloglint"), + linter.NewConfig(golinters.NewScopelint()). WithSince("v1.12.0"). WithPresets(linter.PresetBugs). diff --git a/test/testdata/configs/sloglint_args_on_sep_lines.yml b/test/testdata/configs/sloglint_args_on_sep_lines.yml new file mode 100644 index 000000000000..6f544b0239ee --- /dev/null +++ b/test/testdata/configs/sloglint_args_on_sep_lines.yml @@ -0,0 +1,3 @@ +linters-settings: + sloglint: + args-on-sep-lines: true diff --git a/test/testdata/configs/sloglint_attr_only.yml b/test/testdata/configs/sloglint_attr_only.yml new file mode 100644 index 000000000000..a61f2ab4185a --- /dev/null +++ b/test/testdata/configs/sloglint_attr_only.yml @@ -0,0 +1,3 @@ +linters-settings: + sloglint: + attr-only: true diff --git a/test/testdata/configs/sloglint_kv_only.yml b/test/testdata/configs/sloglint_kv_only.yml new file mode 100644 index 000000000000..b482546c64db --- /dev/null +++ b/test/testdata/configs/sloglint_kv_only.yml @@ -0,0 +1,3 @@ +linters-settings: + sloglint: + kv-only: true diff --git a/test/testdata/configs/sloglint_no_raw_keys.yml b/test/testdata/configs/sloglint_no_raw_keys.yml new file mode 100644 index 000000000000..57db6dbaaff6 --- /dev/null +++ b/test/testdata/configs/sloglint_no_raw_keys.yml @@ -0,0 +1,3 @@ +linters-settings: + sloglint: + no-raw-keys: true diff --git a/test/testdata/sloglint.go b/test/testdata/sloglint.go new file mode 100644 index 000000000000..71faeb43000b --- /dev/null +++ b/test/testdata/sloglint.go @@ -0,0 +1,13 @@ +//go:build go1.21 + +//golangcitest:args -Esloglint +package testdata + +import "log/slog" + +func test() { + slog.Info("msg", "foo", 1, "bar", 2) + slog.Info("msg", slog.Int("foo", 1), slog.Int("bar", 2)) + + slog.Info("msg", "foo", 1, slog.Int("bar", 2)) // want `key-value pairs and attributes should not be mixed` +} diff --git a/test/testdata/sloglint_args_on_sep_lines.go b/test/testdata/sloglint_args_on_sep_lines.go new file mode 100644 index 000000000000..9bdd89764cbe --- /dev/null +++ b/test/testdata/sloglint_args_on_sep_lines.go @@ -0,0 +1,17 @@ +//go:build go1.21 + +//golangcitest:args -Esloglint +//golangcitest:config_path testdata/configs/sloglint_args_on_sep_lines.yml +package testdata + +import "log/slog" + +func test() { + slog.Info("msg", "foo", 1) + slog.Info("msg", + "foo", 1, + "bar", 2, + ) + + slog.Info("msg", "foo", 1, "bar", 2) // want `arguments should be put on separate lines` +} diff --git a/test/testdata/sloglint_attr_only.go b/test/testdata/sloglint_attr_only.go new file mode 100644 index 000000000000..3153026e4557 --- /dev/null +++ b/test/testdata/sloglint_attr_only.go @@ -0,0 +1,13 @@ +//go:build go1.21 + +//golangcitest:args -Esloglint +//golangcitest:config_path testdata/configs/sloglint_attr_only.yml +package testdata + +import "log/slog" + +func test() { + slog.Info("msg", slog.Int("foo", 1), slog.Int("bar", 2)) + + slog.Info("msg", "foo", 1, "bar", 2) // want `key-value pairs should not be used` +} diff --git a/test/testdata/sloglint_kv_only.go b/test/testdata/sloglint_kv_only.go new file mode 100644 index 000000000000..efbcefcc8591 --- /dev/null +++ b/test/testdata/sloglint_kv_only.go @@ -0,0 +1,13 @@ +//go:build go1.21 + +//golangcitest:args -Esloglint +//golangcitest:config_path testdata/configs/sloglint_kv_only.yml +package testdata + +import "log/slog" + +func test() { + slog.Info("msg", "foo", 1, "bar", 2) + + slog.Info("msg", slog.Int("foo", 1), slog.Int("bar", 2)) // want `attributes should not be used` +} diff --git a/test/testdata/sloglint_no_raw_keys.go b/test/testdata/sloglint_no_raw_keys.go new file mode 100644 index 000000000000..ff248993142f --- /dev/null +++ b/test/testdata/sloglint_no_raw_keys.go @@ -0,0 +1,21 @@ +//go:build go1.21 + +//golangcitest:args -Esloglint +//golangcitest:config_path testdata/configs/sloglint_no_raw_keys.yml +package testdata + +import "log/slog" + +const foo = "foo" + +func Foo(value int) slog.Attr { + return slog.Int("foo", value) +} + +func test() { + slog.Info("msg", foo, 1) + slog.Info("msg", Foo(1)) + + slog.Info("msg", "foo", 1) // want `raw keys should not be used` + slog.Info("msg", slog.Int("foo", 1)) // want `raw keys should not be used` +}