From c00c1a56115aba5d06dad97a4491c87975297091 Mon Sep 17 00:00:00 2001
From: Gabriel Augendre <gabriel@augendre.info>
Date: Mon, 1 Apr 2024 16:30:39 +0200
Subject: [PATCH] feat: add fatcontext linter (#4583)

---
 .golangci.next.reference.yml             |  2 ++
 go.mod                                   |  1 +
 go.sum                                   |  2 ++
 jsonschema/golangci.next.jsonschema.json |  1 +
 pkg/golinters/fatcontext.go              | 19 ++++++++++++++
 pkg/lint/lintersdb/builder_linter.go     |  6 +++++
 test/testdata/fatcontext.go              | 33 ++++++++++++++++++++++++
 7 files changed, 64 insertions(+)
 create mode 100644 pkg/golinters/fatcontext.go
 create mode 100644 test/testdata/fatcontext.go

diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml
index ea1dac8b..0bfc25b5 100644
--- a/.golangci.next.reference.yml
+++ b/.golangci.next.reference.yml
@@ -2534,6 +2534,7 @@ linters:
     - exhaustive
     - exhaustruct
     - exportloopref
+    - fatcontext
     - forbidigo
     - forcetypeassert
     - funlen
@@ -2647,6 +2648,7 @@ linters:
     - exhaustive
     - exhaustruct
     - exportloopref
+    - fatcontext
     - forbidigo
     - forcetypeassert
     - funlen
diff --git a/go.mod b/go.mod
index 2e04f9ab..98941d77 100644
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@ require (
 	github.com/Antonboom/nilnil v0.1.7
 	github.com/Antonboom/testifylint v1.2.0
 	github.com/BurntSushi/toml v1.3.2
+	github.com/Crocmagnon/fatcontext v0.2.2
 	github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24
 	github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0
 	github.com/OpenPeeDeeP/depguard/v2 v2.2.0
diff --git a/go.sum b/go.sum
index fdbc3ac6..7e1e9534 100644
--- a/go.sum
+++ b/go.sum
@@ -49,6 +49,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/Crocmagnon/fatcontext v0.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk=
+github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0=
 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM=
 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c=
diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json
index f565cae6..18267d08 100644
--- a/jsonschema/golangci.next.jsonschema.json
+++ b/jsonschema/golangci.next.jsonschema.json
@@ -235,6 +235,7 @@
             "exhaustivestruct",
             "exhaustruct",
             "exportloopref",
+            "fatcontext",
             "forbidigo",
             "forcetypeassert",
             "funlen",
diff --git a/pkg/golinters/fatcontext.go b/pkg/golinters/fatcontext.go
new file mode 100644
index 00000000..8ea31029
--- /dev/null
+++ b/pkg/golinters/fatcontext.go
@@ -0,0 +1,19 @@
+package golinters
+
+import (
+	"github.com/Crocmagnon/fatcontext/pkg/analyzer"
+	"golang.org/x/tools/go/analysis"
+
+	"github.com/golangci/golangci-lint/pkg/goanalysis"
+)
+
+func NewFatContext() *goanalysis.Linter {
+	a := analyzer.Analyzer
+
+	return goanalysis.NewLinter(
+		a.Name,
+		a.Doc,
+		[]*analysis.Analyzer{a},
+		nil,
+	).WithLoadMode(goanalysis.LoadModeTypesInfo)
+}
diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go
index fe52b888..a881eb59 100644
--- a/pkg/lint/lintersdb/builder_linter.go
+++ b/pkg/lint/lintersdb/builder_linter.go
@@ -181,6 +181,12 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) {
 			WithPresets(linter.PresetStyle).
 			WithURL("https://github.com/gostaticanalysis/forcetypeassert"),
 
+		linter.NewConfig(golinters.NewFatContext()).
+			WithSince("1.58.0").
+			WithPresets(linter.PresetPerformance).
+			WithLoadForGoAnalysis().
+			WithURL("https://github.com/Crocmagnon/fatcontext"),
+
 		linter.NewConfig(golinters.NewFunlen(&cfg.LintersSettings.Funlen)).
 			WithSince("v1.18.0").
 			WithPresets(linter.PresetComplexity).
diff --git a/test/testdata/fatcontext.go b/test/testdata/fatcontext.go
new file mode 100644
index 00000000..f504f4d7
--- /dev/null
+++ b/test/testdata/fatcontext.go
@@ -0,0 +1,33 @@
+//golangcitest:args -Efatcontext
+package testdata
+
+import "context"
+
+func example() {
+	ctx := context.Background()
+
+	for i := 0; i < 10; i++ {
+		ctx := context.WithValue(ctx, "key", i)
+		ctx = context.WithValue(ctx, "other", "val")
+	}
+
+	for i := 0; i < 10; i++ {
+		ctx = context.WithValue(ctx, "key", i) // want "nested context in loop"
+		ctx = context.WithValue(ctx, "other", "val")
+	}
+
+	for item := range []string{"one", "two", "three"} {
+		ctx = wrapContext(ctx) // want "nested context in loop"
+		ctx := context.WithValue(ctx, "key", item)
+		ctx = wrapContext(ctx)
+	}
+
+	for {
+		ctx = wrapContext(ctx) // want "nested context in loop"
+		break
+	}
+}
+
+func wrapContext(ctx context.Context) context.Context {
+	return context.WithoutCancel(ctx)
+}