diff --git a/.golangci.reference.yml b/.golangci.reference.yml index 1f6b8a26a768..236c842ee93e 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -2567,6 +2567,7 @@ linters: - noctx - nolintlint - nonamedreturns + - noniljson - nosnakecase - nosprintfhostport - paralleltest @@ -2690,6 +2691,7 @@ linters: - noctx - nolintlint - nonamedreturns + - noniljson - nosnakecase - nosprintfhostport - paralleltest diff --git a/go.mod b/go.mod index a0d615f86080..da236ed30fbb 100644 --- a/go.mod +++ b/go.mod @@ -87,6 +87,7 @@ require ( github.com/nunnatsa/ginkgolinter v0.15.2 github.com/polyfloyd/go-errorlint v1.4.8 github.com/quasilyte/go-ruleguard/dsl v0.3.22 + github.com/rezkam/noniljson v1.0.2 github.com/ryancurrah/gomodguard v1.3.0 github.com/ryanrolds/sqlclosecheck v0.5.1 github.com/sanposhiho/wastedassign/v2 v2.0.7 diff --git a/go.sum b/go.sum index 38e5806e1cad..e0add170775a 100644 --- a/go.sum +++ b/go.sum @@ -465,6 +465,8 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/rezkam/noniljson v1.0.2 h1:Ftv0ANpDud7D+sQdIhNq6V3BF7WwiIPbf2RrZFs7VZo= +github.com/rezkam/noniljson v1.0.2/go.mod h1:uJL++ig0CSE6XvE16AoxteZXl4AD2Ef2LMMoPjuoSXw= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= diff --git a/pkg/golinters/noniljson.go b/pkg/golinters/noniljson.go new file mode 100644 index 000000000000..a2c3dd1d5452 --- /dev/null +++ b/pkg/golinters/noniljson.go @@ -0,0 +1,19 @@ +package golinters + +import ( + "github.com/rezkam/noniljson" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewNoNilJSON() *goanalysis.Linter { + a := noniljson.Analyzer + + return goanalysis.NewLinter( + a.Name, + "checks that nullable fields in structs used for JSON marshaling use 'omitempty'", + []*analysis.Analyzer{a}, + nil, + ) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index e6a171f1ff5d..40f7d0b3f1d5 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -713,6 +713,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/firefart/nonamedreturns"), + linter.NewConfig(golinters.NewNoNilJSON()). + WithSince("v1.57.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/rezkam/noniljson"), + linter.NewConfig(golinters.NewNoSnakeCase()). WithSince("v1.47.0"). WithPresets(linter.PresetStyle). diff --git a/test/testdata/noniljson.go b/test/testdata/noniljson.go new file mode 100644 index 000000000000..2a4f9ee22f5c --- /dev/null +++ b/test/testdata/noniljson.go @@ -0,0 +1,22 @@ +//golangcitest:args -Enoniljson +package testdata + +type userData struct { + Name *string `json:"name"` // want `nullable field 'Name' in struct 'userData' must include 'omitempty' in its json tag to avoid marshaling as null` + Email string `json:"email,omitempty"` + Address *string `json:"address,omitempty"` + Friends []string `json:"friends"` // want `nullable field 'Friends' in struct 'userData' must include 'omitempty' in its json tag to avoid marshaling as null` + Metadata map[string]string `json:"metadata"` // want `nullable field 'Metadata' in struct 'userData' must include 'omitempty' in its json tag to avoid marshaling as null` +} + +type dynamicData struct { + Data interface{} `json:"data"` // want `nullable field 'Data' in struct 'dynamicData' must include 'omitempty' in its json tag to avoid marshaling as null` +} + +type productData struct { + ID int `json:"id"` + Description *string `json:"description"` // want `nullable field 'Description' in struct 'productData' must include 'omitempty' in its json tag to avoid marshaling as null` + Price float64 `json:"price,omitempty"` + Availability []int `json:"availability"` // want `nullable field 'Availability' in struct 'productData' must include 'omitempty' in its json tag to avoid marshaling as null` + Attributes map[string]interface{} `json:"attributes"` // want `nullable field 'Attributes' in struct 'productData' must include 'omitempty' in its json tag to avoid marshaling as null` +}