From bd6051160f747d6da74e99ea298e85ecf06ca74a Mon Sep 17 00:00:00 2001
From: Leon Klingele <git@leonklingele.de>
Date: Tue, 31 Jan 2023 23:44:16 +0100
Subject: [PATCH] feat: add zero linter

Implements https://github.com/golangci/golangci-lint/issues/3491.
---
 .golangci.reference.yml       |  2 ++
 go.mod                        |  1 +
 go.sum                        |  2 ++
 pkg/golinters/zero.go         | 19 +++++++++++++++++++
 pkg/lint/lintersdb/manager.go |  5 +++++
 test/testdata/zero.go         | 35 +++++++++++++++++++++++++++++++++++
 6 files changed, 64 insertions(+)
 create mode 100644 pkg/golinters/zero.go
 create mode 100644 test/testdata/zero.go

diff --git a/.golangci.reference.yml b/.golangci.reference.yml
index 1105594e0595..cc6f096bb118 100644
--- a/.golangci.reference.yml
+++ b/.golangci.reference.yml
@@ -2096,6 +2096,7 @@ linters:
     - whitespace
     - wrapcheck
     - wsl
+    - zero
 
   # Enable all available linters.
   # Default: false
@@ -2206,6 +2207,7 @@ linters:
     - whitespace
     - wrapcheck
     - wsl
+    - zero
 
   # Enable presets.
   # https://golangci-lint.run/usage/linters
diff --git a/go.mod b/go.mod
index ecd772a245a6..939098bc9e1c 100644
--- a/go.mod
+++ b/go.mod
@@ -45,6 +45,7 @@ require (
 	github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8
 	github.com/gostaticanalysis/forcetypeassert v0.1.0
 	github.com/gostaticanalysis/nilerr v0.1.1
+	github.com/gostaticanalysis/zero v0.1.0
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/hashicorp/go-version v1.6.0
 	github.com/hexops/gotextdiff v1.0.3
diff --git a/go.sum b/go.sum
index 10aa93835407..eaeca40ecaa0 100644
--- a/go.sum
+++ b/go.sum
@@ -275,6 +275,8 @@ github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3
 github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A=
 github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
 github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY=
+github.com/gostaticanalysis/zero v0.1.0 h1:yUXv6uwbixlqN6FpJbHTD3XMIu8/LAcQ1Z31yTK2OlY=
+github.com/gostaticanalysis/zero v0.1.0/go.mod h1:CAeyV3lp/uWVyd7aRh8+frtn9bUD3vvkC2YCk6EDTaU=
 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
diff --git a/pkg/golinters/zero.go b/pkg/golinters/zero.go
new file mode 100644
index 000000000000..08896efb0c3d
--- /dev/null
+++ b/pkg/golinters/zero.go
@@ -0,0 +1,19 @@
+package golinters
+
+import (
+	"github.com/gostaticanalysis/zero"
+	"golang.org/x/tools/go/analysis"
+
+	"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
+)
+
+func NewZero() *goanalysis.Linter {
+	a := zero.Analyzer
+
+	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 3bfbbdc26943..476b9be7615f 100644
--- a/pkg/lint/lintersdb/manager.go
+++ b/pkg/lint/lintersdb/manager.go
@@ -872,6 +872,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
 			WithPresets(linter.PresetStyle).
 			WithURL("https://github.com/bombsimon/wsl"),
 
+		linter.NewConfig(golinters.NewZero()).
+			WithSince("v1.51.0").
+			WithPresets(linter.PresetBugs).
+			WithURL("https://github.com/gostaticanalysis/zero"),
+
 		// nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives
 		linter.NewConfig(golinters.NewNoLintLint(noLintLintCfg)).
 			WithSince("v1.26.0").
diff --git a/test/testdata/zero.go b/test/testdata/zero.go
new file mode 100644
index 000000000000..34188f6c6e80
--- /dev/null
+++ b/test/testdata/zero.go
@@ -0,0 +1,35 @@
+//golangcitest:args -Ezero
+package testdata
+
+var _ string = "" // want "shoud not assign zero value"
+
+func _() {
+	n := 0 // want "shoud not assign zero value"
+	_ = n
+
+	var _ []int = nil     // want "shoud not assign zero value"
+	var _ []int = []int{} // OK
+	m := int32(0)         // OK
+	_ = m
+	var _ *int = nil            // want "shoud not assign zero value"
+	var _ struct{} = struct{}{} // want "shoud not assign zero value"
+	var _, _ int                // OK
+	var _, _ int = 0, 1         // want "shoud not assign zero value"
+	var _, _ int = 1, 2         // OK
+	var _, _ int = 1 - 1, 2 - 2 // want "shoud not assign zero value"
+	var _ bool = false          // want "shoud not assign zero value"
+	var _ bool = true           // OK
+
+	type T struct{ N int }
+	var _ T = T{} // want "shoud not assign zero value"
+
+	{
+		n, _ := func() (int, int) { return 0, 0 }() // OK
+		_ = n
+	}
+
+	{
+		var n, _ = func() (int, int) { return 0, 0 }() // OK
+		_ = n
+	}
+}