From 58809c3bf5d4e94500f7880978b6eb5e204f0f96 Mon Sep 17 00:00:00 2001
From: Choko <choko@curioswitch.org>
Date: Tue, 23 Aug 2022 20:19:26 +0900
Subject: [PATCH] feat: add reassign linter (#3064)

---
 .golangci.reference.yml                     |  9 ++++++
 go.mod                                      |  1 +
 go.sum                                      |  2 ++
 pkg/config/linters_settings.go              |  5 ++++
 pkg/golinters/reassign.go                   | 32 +++++++++++++++++++++
 pkg/lint/lintersdb/manager.go               |  7 +++++
 test/testdata/configs/reassign_patterns.yml |  5 ++++
 test/testdata/reassign.go                   | 13 +++++++++
 test/testdata/reassign_patterns.go          | 14 +++++++++
 9 files changed, 88 insertions(+)
 create mode 100644 pkg/golinters/reassign.go
 create mode 100644 test/testdata/configs/reassign_patterns.yml
 create mode 100644 test/testdata/reassign.go
 create mode 100644 test/testdata/reassign_patterns.go

diff --git a/.golangci.reference.yml b/.golangci.reference.yml
index e0289724..2baf9f7f 100644
--- a/.golangci.reference.yml
+++ b/.golangci.reference.yml
@@ -1225,6 +1225,13 @@ linters-settings:
      - CamelCase
      - UnitAbbreviations
 
+  reassign:
+    # Patterns for global variable names that are checked for reassignment.
+    # See https://github.com/curioswitch/go-reassign#usage
+    # Default: ["EOF", "Err.*"]
+    patterns:
+     - ".*"
+
   revive:
     # Maximum number of open files at the same time.
     # See https://github.com/mgechev/revive#command-line-flags
@@ -1951,6 +1958,7 @@ linters:
     - prealloc
     - predeclared
     - promlinter
+    - reassign
     - revive
     - rowserrcheck
     - scopelint
@@ -2055,6 +2063,7 @@ linters:
     - prealloc
     - predeclared
     - promlinter
+    - reassign
     - revive
     - rowserrcheck
     - scopelint
diff --git a/go.mod b/go.mod
index 4069dee6..46d111d9 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@ require (
 	github.com/breml/errchkjson v0.3.0
 	github.com/butuzov/ireturn v0.1.1
 	github.com/charithe/durationcheck v0.0.9
+	github.com/curioswitch/go-reassign v0.1.2
 	github.com/daixiang0/gci v0.6.3
 	github.com/denis-tingaikin/go-header v0.4.3
 	github.com/esimonov/ifshort v1.0.4
diff --git a/go.sum b/go.sum
index d615d7da..e2e7ec2f 100644
--- a/go.sum
+++ b/go.sum
@@ -103,6 +103,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/cristalhq/acmd v0.7.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ=
+github.com/curioswitch/go-reassign v0.1.2 h1:ekM07+z+VFT560Exz4mTv0/s1yU9gem6CJc/tlYpkmI=
+github.com/curioswitch/go-reassign v0.1.2/go.mod h1:bFJIHgtTM3hRm2sKXSPkbwNjSFyGURQXyn4IXD2qwfQ=
 github.com/daixiang0/gci v0.6.3 h1:wUAqXChk8HbwXn8AfxD9DYSCp9Bpz1L3e6Q4Roe+q9E=
 github.com/daixiang0/gci v0.6.3/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+MB0V0c=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go
index 242a1de1..f1c36c38 100644
--- a/pkg/config/linters_settings.go
+++ b/pkg/config/linters_settings.go
@@ -168,6 +168,7 @@ type LintersSettings struct {
 	Prealloc         PreallocSettings
 	Predeclared      PredeclaredSettings
 	Promlinter       PromlinterSettings
+	Reassign         ReassignSettings
 	Revive           ReviveSettings
 	RowsErrCheck     RowsErrCheckSettings
 	Staticcheck      StaticCheckSettings
@@ -532,6 +533,10 @@ type PromlinterSettings struct {
 	DisabledLinters []string `mapstructure:"disabled-linters"`
 }
 
+type ReassignSettings struct {
+	Patterns []string `mapstructure:"patterns"`
+}
+
 type ReviveSettings struct {
 	MaxOpenFiles          int  `mapstructure:"max-open-files"`
 	IgnoreGeneratedHeader bool `mapstructure:"ignore-generated-header"`
diff --git a/pkg/golinters/reassign.go b/pkg/golinters/reassign.go
new file mode 100644
index 00000000..a9ff67ee
--- /dev/null
+++ b/pkg/golinters/reassign.go
@@ -0,0 +1,32 @@
+package golinters
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/curioswitch/go-reassign"
+	"golang.org/x/tools/go/analysis"
+
+	"github.com/golangci/golangci-lint/pkg/config"
+	"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
+)
+
+func NewReassign(settings *config.ReassignSettings) *goanalysis.Linter {
+	a := reassign.NewAnalyzer()
+
+	var cfg map[string]map[string]interface{}
+	if settings != nil && len(settings.Patterns) > 0 {
+		cfg = map[string]map[string]interface{}{
+			a.Name: {
+				reassign.FlagPattern: fmt.Sprintf("^(%s)$", strings.Join(settings.Patterns, "|")),
+			},
+		}
+	}
+
+	return goanalysis.NewLinter(
+		a.Name,
+		a.Doc,
+		[]*analysis.Analyzer{a},
+		cfg,
+	).WithLoadMode(goanalysis.LoadModeSyntax)
+}
diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go
index 9a1f1fa4..8eb11943 100644
--- a/pkg/lint/lintersdb/manager.go
+++ b/pkg/lint/lintersdb/manager.go
@@ -154,6 +154,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
 		preallocCfg         *config.PreallocSettings
 		predeclaredCfg      *config.PredeclaredSettings
 		promlinterCfg       *config.PromlinterSettings
+		reassignCfg         *config.ReassignSettings
 		reviveCfg           *config.ReviveSettings
 		rowserrcheckCfg     *config.RowsErrCheckSettings
 		staticcheckCfg      *config.StaticCheckSettings
@@ -227,6 +228,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
 		parallelTestCfg = &m.cfg.LintersSettings.ParallelTest
 		predeclaredCfg = &m.cfg.LintersSettings.Predeclared
 		promlinterCfg = &m.cfg.LintersSettings.Promlinter
+		reassignCfg = &m.cfg.LintersSettings.Reassign
 		reviveCfg = &m.cfg.LintersSettings.Revive
 		rowserrcheckCfg = &m.cfg.LintersSettings.RowsErrCheck
 		staticcheckCfg = &m.cfg.LintersSettings.Staticcheck
@@ -683,6 +685,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
 			WithPresets(linter.PresetStyle).
 			WithURL("https://github.com/yeya24/promlinter"),
 
+		linter.NewConfig(golinters.NewReassign(reassignCfg)).
+			WithSince("1.49.0").
+			WithPresets(linter.PresetBugs).
+			WithURL("https://github.com/curioswitch/go-reassign"),
+
 		linter.NewConfig(golinters.NewRevive(reviveCfg)).
 			WithSince("v1.37.0").
 			WithPresets(linter.PresetStyle, linter.PresetMetaLinter).
diff --git a/test/testdata/configs/reassign_patterns.yml b/test/testdata/configs/reassign_patterns.yml
new file mode 100644
index 00000000..5af124db
--- /dev/null
+++ b/test/testdata/configs/reassign_patterns.yml
@@ -0,0 +1,5 @@
+linters-settings:
+  reassign:
+    patterns:
+      - DefaultClient
+      - DefaultTransport
diff --git a/test/testdata/reassign.go b/test/testdata/reassign.go
new file mode 100644
index 00000000..27a7baa5
--- /dev/null
+++ b/test/testdata/reassign.go
@@ -0,0 +1,13 @@
+//golangcitest:args -Ereassign
+package testdata
+
+import (
+	"io"
+	"net/http"
+)
+
+func reassignTest() {
+	http.DefaultClient = nil
+	http.DefaultTransport = nil
+	io.EOF = nil // want `reassigning variable EOF in other package io`
+}
diff --git a/test/testdata/reassign_patterns.go b/test/testdata/reassign_patterns.go
new file mode 100644
index 00000000..dc83cd71
--- /dev/null
+++ b/test/testdata/reassign_patterns.go
@@ -0,0 +1,14 @@
+//golangcitest:args -Ereassign
+//golangcitest:config_path testdata/configs/reassign_patterns.yml
+package testdata
+
+import (
+	"io"
+	"net/http"
+)
+
+func reassignTestPatterns() {
+	http.DefaultClient = nil    // want `reassigning variable DefaultClient in other package http`
+	http.DefaultTransport = nil // want `reassigning variable DefaultTransport in other package http`
+	io.EOF = nil
+}