From c8bde90d039f2f5753eff4f6d21704a23ef8d45a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 16 Jul 2026 18:01:36 +0000
Subject: [PATCH 1/6] feat(linters): add mapclearloop linter

Detects range-over-map loops whose sole body statement is delete(m, k)
which can be replaced by the built-in clear(m) introduced in Go 1.21.

The linter:
- Verifies the range expression is a map type via type-info
- Requires the value variable to be absent or blank (_)
- Requires exactly one body statement: the builtin delete(m, k)
- Checks delete targets the same map and key as the range
- Emits a suggested fix rewriting the loop to clear(m)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 cmd/linters/main.go                           |   2 +
 pkg/linters/mapclearloop/mapclearloop.go      | 163 ++++++++++++++++++
 pkg/linters/mapclearloop/mapclearloop_test.go |  16 ++
 .../testdata/src/mapclearloop/mapclearloop.go |  47 +++++
 .../src/mapclearloop/mapclearloop.go.golden   |  43 +++++
 5 files changed, 271 insertions(+)
 create mode 100644 pkg/linters/mapclearloop/mapclearloop.go
 create mode 100644 pkg/linters/mapclearloop/mapclearloop_test.go
 create mode 100644 pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go
 create mode 100644 pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go.golden

diff --git a/cmd/linters/main.go b/cmd/linters/main.go
index 04ee1f48e4f..21ade842b54 100644
--- a/cmd/linters/main.go
+++ b/cmd/linters/main.go
@@ -42,6 +42,7 @@ import (
 	"github.com/github/gh-aw/pkg/linters/lenstringzero"
 	"github.com/github/gh-aw/pkg/linters/logfatallibrary"
 	"github.com/github/gh-aw/pkg/linters/manualmutexunlock"
+	"github.com/github/gh-aw/pkg/linters/mapclearloop"
 	"github.com/github/gh-aw/pkg/linters/mapdeletecheck"
 	"github.com/github/gh-aw/pkg/linters/nilctxpassed"
 	"github.com/github/gh-aw/pkg/linters/osexitinlibrary"
@@ -93,6 +94,7 @@ func main() {
 		largefunc.Analyzer,
 		logfatallibrary.Analyzer,
 		manualmutexunlock.Analyzer,
+		mapclearloop.Analyzer,
 		mapdeletecheck.Analyzer,
 		nilctxpassed.Analyzer,
 		osexitinlibrary.Analyzer,
diff --git a/pkg/linters/mapclearloop/mapclearloop.go b/pkg/linters/mapclearloop/mapclearloop.go
new file mode 100644
index 00000000000..a851707e472
--- /dev/null
+++ b/pkg/linters/mapclearloop/mapclearloop.go
@@ -0,0 +1,163 @@
+// Package mapclearloop implements a Go analysis linter that flags
+// range-over-map loops whose only body statement is delete(m, k),
+// which should be replaced by the built-in clear(m) introduced in Go 1.21.
+package mapclearloop
+
+import (
+	"go/ast"
+	"go/types"
+
+	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/analysis/passes/inspect"
+
+	"github.com/github/gh-aw/pkg/linters/internal/astutil"
+	"github.com/github/gh-aw/pkg/linters/internal/filecheck"
+	"github.com/github/gh-aw/pkg/linters/internal/nolint"
+)
+
+// Analyzer is the map-clear-loop analysis pass.
+var Analyzer = &analysis.Analyzer{
+	Name:     "mapclearloop",
+	Doc:      "reports range-over-map loops that delete every entry and can be replaced with clear(m)",
+	URL:      "https://github.com/github/gh-aw/tree/main/pkg/linters/mapclearloop",
+	Requires: []*analysis.Analyzer{inspect.Analyzer, nolint.Analyzer, filecheck.Analyzer},
+	Run:      run,
+}
+
+func run(pass *analysis.Pass) (any, error) {
+	insp, err := astutil.Inspector(pass)
+	if err != nil {
+		return nil, err
+	}
+	noLintIndex, err := nolint.Index(pass)
+	if err != nil {
+		return nil, err
+	}
+	generatedFiles, err := filecheck.Index(pass)
+	if err != nil {
+		return nil, err
+	}
+
+	nodeFilter := []ast.Node{(*ast.RangeStmt)(nil)}
+
+	insp.Preorder(nodeFilter, func(n ast.Node) {
+		rangeStmt, ok := n.(*ast.RangeStmt)
+		if !ok {
+			return
+		}
+
+		pos := pass.Fset.PositionFor(rangeStmt.Pos(), false)
+		if filecheck.ShouldSkipFilename(pos.Filename, generatedFiles) {
+			return
+		}
+		if nolint.HasDirectiveForLinter(pos, noLintIndex, "mapclearloop") {
+			return
+		}
+
+		// The range expression must be a map type.
+		mapType := pass.TypesInfo.TypeOf(rangeStmt.X)
+		if mapType == nil {
+			return
+		}
+		if _, ok := mapType.Underlying().(*types.Map); !ok {
+			return
+		}
+
+		// The key variable must be present (not blank or absent).
+		keyIdent, ok := rangeStmt.Key.(*ast.Ident)
+		if !ok || keyIdent.Name == "_" {
+			return
+		}
+		keyObj := pass.TypesInfo.Defs[keyIdent]
+		if keyObj == nil {
+			return
+		}
+
+		// The value variable must be absent or blank.
+		if rangeStmt.Value != nil {
+			valueIdent, ok := rangeStmt.Value.(*ast.Ident)
+			if !ok || valueIdent.Name != "_" {
+				return
+			}
+		}
+
+		// The body must contain exactly one statement: delete(m, k).
+		if len(rangeStmt.Body.List) != 1 {
+			return
+		}
+		exprStmt, ok := rangeStmt.Body.List[0].(*ast.ExprStmt)
+		if !ok {
+			return
+		}
+		callExpr, ok := exprStmt.X.(*ast.CallExpr)
+		if !ok {
+			return
+		}
+		delIdent, ok := callExpr.Fun.(*ast.Ident)
+		if !ok || delIdent.Name != "delete" {
+			return
+		}
+		delBuiltin, ok := pass.TypesInfo.Uses[delIdent].(*types.Builtin)
+		if !ok || delBuiltin.Name() != "delete" {
+			return
+		}
+		if len(callExpr.Args) != 2 {
+			return
+		}
+
+		// First arg to delete must be the same map as the range expression.
+		if !sameObject(pass, callExpr.Args[0], rangeStmt.X) {
+			return
+		}
+
+		// Second arg to delete must be the key variable from the range.
+		delKeyIdent, ok := callExpr.Args[1].(*ast.Ident)
+		if !ok {
+			return
+		}
+		delKeyObj := pass.TypesInfo.Uses[delKeyIdent]
+		if delKeyObj == nil || delKeyObj != keyObj {
+			return
+		}
+
+		mText := astutil.NodeText(pass.Fset, rangeStmt.X)
+
+		pass.Report(analysis.Diagnostic{
+			Pos:     rangeStmt.Pos(),
+			End:     rangeStmt.End(),
+			Message: "range-delete loop over map can be replaced with clear(" + mText + ")",
+			SuggestedFixes: []analysis.SuggestedFix{{
+				Message: "Replace range-delete loop with clear",
+				TextEdits: []analysis.TextEdit{{
+					Pos:     rangeStmt.Pos(),
+					End:     rangeStmt.End(),
+					NewText: []byte("clear(" + mText + ")"),
+				}},
+			}},
+		})
+	})
+
+	return nil, nil
+}
+
+// sameObject reports whether expr refers to the same declared object as ref.
+// ref is expected to be an *ast.Ident or *ast.SelectorExpr.
+func sameObject(pass *analysis.Pass, expr, ref ast.Expr) bool {
+	switch r := ref.(type) {
+	case *ast.Ident:
+		e, ok := expr.(*ast.Ident)
+		if !ok {
+			return false
+		}
+		return pass.TypesInfo.Uses[e] == pass.TypesInfo.Uses[r]
+	case *ast.SelectorExpr:
+		e, ok := expr.(*ast.SelectorExpr)
+		if !ok {
+			return false
+		}
+		return pass.TypesInfo.Uses[e.Sel] == pass.TypesInfo.Uses[r.Sel] &&
+			sameObject(pass, e.X, r.X)
+	default:
+		return false
+	}
+}
diff --git a/pkg/linters/mapclearloop/mapclearloop_test.go b/pkg/linters/mapclearloop/mapclearloop_test.go
new file mode 100644
index 00000000000..0633af4be04
--- /dev/null
+++ b/pkg/linters/mapclearloop/mapclearloop_test.go
@@ -0,0 +1,16 @@
+//go:build !integration
+
+package mapclearloop_test
+
+import (
+	"testing"
+
+	"golang.org/x/tools/go/analysis/analysistest"
+
+	"github.com/github/gh-aw/pkg/linters/mapclearloop"
+)
+
+func TestAnalyzer(t *testing.T) {
+	testdata := analysistest.TestData()
+	analysistest.RunWithSuggestedFixes(t, testdata, mapclearloop.Analyzer, "mapclearloop")
+}
diff --git a/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go b/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go
new file mode 100644
index 00000000000..c82c7f53ac8
--- /dev/null
+++ b/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go
@@ -0,0 +1,47 @@
+package mapclearloop
+
+func bad() {
+	m := map[string]int{"a": 1, "b": 2}
+
+	for k := range m { // want `range-delete loop over map can be replaced with clear\(m\)`
+		delete(m, k)
+	}
+
+	m2 := map[int]string{1: "x"}
+	for k := range m2 { // want `range-delete loop over map can be replaced with clear\(m2\)`
+		delete(m2, k)
+	}
+}
+
+func good() {
+	m := map[string]int{"a": 1}
+
+	// Only ranging over value – not flagged.
+	for _, v := range m {
+		_ = v
+	}
+
+	// Body has more than one statement – not flagged.
+	for k := range m {
+		delete(m, k)
+		_ = k
+	}
+
+	// Deleting into a different map – not flagged.
+	m2 := map[string]int{"b": 2}
+	for k := range m {
+		delete(m2, k)
+	}
+
+	// delete is shadowed – not flagged.
+	delete := func(_ map[string]int, _ string) {}
+	for k := range m {
+		delete(m, k)
+	}
+
+	// Ranging over a slice – not flagged.
+	s := []int{1, 2, 3}
+	for i := range s {
+		_ = i
+	}
+}
diff --git a/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go.golden b/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go.golden
new file mode 100644
index 00000000000..55690331d43
--- /dev/null
+++ b/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go.golden
@@ -0,0 +1,43 @@
+package mapclearloop
+
+func bad() {
+	m := map[string]int{"a": 1, "b": 2}
+
+	clear(m)
+
+	m2 := map[int]string{1: "x"}
+	clear(m2)
+}
+
+func good() {
+	m := map[string]int{"a": 1}
+
+	// Only ranging over value – not flagged.
+	for _, v := range m {
+		_ = v
+	}
+
+	// Body has more than one statement – not flagged.
+	for k := range m {
+		delete(m, k)
+		_ = k
+	}
+
+	// Deleting into a different map – not flagged.
+	m2 := map[string]int{"b": 2}
+	for k := range m {
+		delete(m2, k)
+	}
+
+	// delete is shadowed – not flagged.
+	delete := func(_ map[string]int, _ string) {}
+	for k := range m {
+		delete(m, k)
+	}
+
+	// Ranging over a slice – not flagged.
+	s := []int{1, 2, 3}
+	for i := range s {
+		_ = i
+	}
+}

From 8e95b34c8bd62a5623fb75e07b73962eb17e9cc6 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 16 Jul 2026 19:13:24 +0000
Subject: [PATCH 2/6] docs(adr): add draft ADR-46060 for mapclearloop linter

Documents the architectural decision to add the mapclearloop analyzer,
which replaces range-delete loops with clear(m) introduced in Go 1.21.
---
 docs/adr/46060-add-mapclearloop-linter.md | 50 +++++++++++++++++++++++
 1 file changed, 50 insertions(+)
 create mode 100644 docs/adr/46060-add-mapclearloop-linter.md

diff --git a/docs/adr/46060-add-mapclearloop-linter.md b/docs/adr/46060-add-mapclearloop-linter.md
new file mode 100644
index 00000000000..08b889fe02f
--- /dev/null
+++ b/docs/adr/46060-add-mapclearloop-linter.md
@@ -0,0 +1,50 @@
+# ADR-46060: Add mapclearloop Linter to Detect Range-Delete Loops Replaceable with clear(m)
+
+**Date**: 2026-07-16
+**Status**: Draft
+**Deciders**: Unknown (automated PR — pelikhan)
+
+---
+
+### Context
+
+Go 1.21 introduced the built-in `clear(m)` function, which atomically clears all entries from a map in a single call. Before Go 1.21, the idiomatic way to clear a map was a range-over-map loop with a `delete` call per entry: `for k := range m { delete(m, k) }`. Codebases that have been upgraded to Go 1.21+ frequently retain the old loop pattern due to habit or incremental migration. This project maintains a suite of custom static analysis linters (in `pkg/linters/`) that enforce idiomatic Go style across the codebase. A linter for this pattern is a natural addition: it is high-signal (the pattern is unambiguous), auto-fixable, and complements the existing `mapdeletecheck` linter which targets related map/delete idioms.
+
+### Decision
+
+We will add a new `mapclearloop` analysis pass (`pkg/linters/mapclearloop/`) that detects range-over-map loops whose sole body statement is `delete(m, k)` targeting the same map and key variable from the range, and flags them as candidates for replacement with `clear(m)`. The analyzer emits a `SuggestedFix` enabling automatic rewriting. It will be registered in `cmd/linters/main.go` alongside all other linters in this project.
+
+### Alternatives Considered
+
+#### Alternative 1: Extend the existing `mapdeletecheck` linter
+
+The `mapdeletecheck` linter already handles a related idiom. Extending it to also catch the range-delete-entire-map pattern would avoid creating a new package and keep map-related lint rules in one place.
+
+This was not chosen because `mapdeletecheck` addresses a distinct class of problems (likely incorrect individual deletes, e.g., deleting while iterating with possible side effects). Merging two semantically different checks into one package would blur responsibility boundaries, complicate testing, and make the codebase harder to navigate. The project's existing convention is one package per linter.
+
+#### Alternative 2: Rely on an upstream linter (e.g., golangci-lint, staticcheck, revive)
+
+Rather than maintaining a custom analyzer, the team could wait for the broader Go tooling ecosystem to provide this check, or configure an existing tool that already detects it.
+
+This was not chosen because the project requires tight integration with its own linter infrastructure (nolint directives, filecheck for generated file skipping, the shared `astutil` helpers, and registration in the project's own linter runner). Upstream tools may not respect project-local nolint conventions or may have false positives in the project's specific patterns. Additionally, having the linter in-tree allows immediate fixes through `SuggestedFix` and ensures the check is versioned with the codebase.
+
+### Consequences
+
+#### Positive
+- Code that clears maps will be more concise and express intent clearly via `clear(m)`.
+- The linter has a high signal-to-noise ratio: it only fires when the range body is exactly one `delete` call on the same map with the same key, verified at the type level using `go/types` (e.g., shadowed `delete` identifiers are correctly excluded).
+- The `SuggestedFix` enables automatic code rewriting, reducing developer toil when fixing violations.
+- The check skips generated files via `filecheck`, avoiding noisy violations in machine-generated code.
+
+#### Negative
+- Maintainers must maintain another custom linter package indefinitely.
+- Existing code in repositories using this linter suite will gain new lint failures on upgrade and must be updated to use `clear(m)`.
+- The linter only targets Go 1.21+ idiom; it would produce confusing violations in codebases that cannot use Go 1.21 — though in practice this project already requires a modern Go version.
+
+#### Neutral
+- The linter follows the same structural conventions as all other linters in `pkg/linters/`: `Analyzer` var, `run` function, `testdata/` fixture with `.golden` file, `analysistest` test harness.
+- No new external dependencies are introduced; `golang.org/x/tools/go/analysis` is already a project dependency.
+
+---
+
+*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.*

From 4ac17474f3a139b0281cc870ef55c381c9e06896 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 16 Jul 2026 21:39:58 +0000
Subject: [PATCH 3/6] fix(linters): harden mapclearloop suggestion safety

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/linters/mapclearloop/mapclearloop.go      | 49 +++++++++++++++++--
 .../testdata/src/mapclearloop/mapclearloop.go | 24 +++++++++
 .../src/mapclearloop/mapclearloop.go.golden   | 20 ++++++++
 3 files changed, 89 insertions(+), 4 deletions(-)

diff --git a/pkg/linters/mapclearloop/mapclearloop.go b/pkg/linters/mapclearloop/mapclearloop.go
index a851707e472..f048ff8831c 100644
--- a/pkg/linters/mapclearloop/mapclearloop.go
+++ b/pkg/linters/mapclearloop/mapclearloop.go
@@ -5,6 +5,7 @@ package mapclearloop
 
 import (
 	"go/ast"
+	"go/token"
 	"go/types"
 
 	"golang.org/x/tools/go/analysis"
@@ -69,6 +70,9 @@ func run(pass *analysis.Pass) (any, error) {
 			return
 		}
 		keyObj := pass.TypesInfo.Defs[keyIdent]
+		if keyObj == nil {
+			keyObj = pass.TypesInfo.Uses[keyIdent]
+		}
 		if keyObj == nil {
 			return
 		}
@@ -121,25 +125,62 @@ func run(pass *analysis.Pass) (any, error) {
 		}
 
 		mText := astutil.NodeText(pass.Fset, rangeStmt.X)
+		if mText == "" {
+			return
+		}
+		if !builtinVisibleAtPos(pass.Pkg, rangeStmt.Pos(), "clear") {
+			return
+		}
 
-		pass.Report(analysis.Diagnostic{
+		diag := analysis.Diagnostic{
 			Pos:     rangeStmt.Pos(),
 			End:     rangeStmt.End(),
 			Message: "range-delete loop over map can be replaced with clear(" + mText + ")",
-			SuggestedFixes: []analysis.SuggestedFix{{
+		}
+		if !hasOverlappingComment(pass.Files, rangeStmt.Pos(), rangeStmt.End()) {
+			diag.SuggestedFixes = []analysis.SuggestedFix{{
 				Message: "Replace range-delete loop with clear",
 				TextEdits: []analysis.TextEdit{{
 					Pos:     rangeStmt.Pos(),
 					End:     rangeStmt.End(),
 					NewText: []byte("clear(" + mText + ")"),
 				}},
-			}},
-		})
+			}}
+		}
+		pass.Report(diag)
 	})
 
 	return nil, nil
 }
 
+func builtinVisibleAtPos(pkg *types.Package, pos token.Pos, name string) bool {
+	if pkg == nil {
+		return false
+	}
+	scope := pkg.Scope().Innermost(pos)
+	if scope == nil {
+		return false
+	}
+	_, obj := scope.LookupParent(name, pos)
+	builtin, ok := obj.(*types.Builtin)
+	return ok && builtin.Name() == name
+}
+
+func hasOverlappingComment(files []*ast.File, start, end token.Pos) bool {
+	for _, file := range files {
+		if start < file.Pos() || end > file.End() {
+			continue
+		}
+		for _, group := range file.Comments {
+			if group.Pos() < end && start < group.End() {
+				return true
+			}
+		}
+		return false
+	}
+	return false
+}
+
 // sameObject reports whether expr refers to the same declared object as ref.
 // ref is expected to be an *ast.Ident or *ast.SelectorExpr.
 func sameObject(pass *analysis.Pass, expr, ref ast.Expr) bool {
diff --git a/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go b/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go
index c82c7f53ac8..81654e774a2 100644
--- a/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go
+++ b/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go
@@ -11,6 +11,23 @@ func bad() {
 	for k := range m2 { // want `range-delete loop over map can be replaced with clear\(m2\)`
 		delete(m2, k)
 	}
+
+	m3 := map[string]int{"c": 3}
+	for k, _ := range m3 { // want `range-delete loop over map can be replaced with clear\(m3\)`
+		delete(m3, k)
+	}
+
+	var k string
+	for k = range m { // want `range-delete loop over map can be replaced with clear\(m\)`
+		delete(m, k)
+	}
+	_ = k
+
+	m4 := map[string]int{"d": 4}
+	for k := range m4 { // want `range-delete loop over map can be replaced with clear\(m4\)`
+		// keep this comment in place by omitting the suggested fix
+		delete(m4, k)
+	}
 }
 
 func good() {
@@ -39,6 +56,13 @@ func good() {
 		delete(m, k)
 	}
 
+	// clear is shadowed – not flagged.
+	clear := func(_ map[string]int) {}
+	for k := range m {
+		delete(m, k)
+	}
+	clear(m)
+
 	// Ranging over a slice – not flagged.
 	s := []int{1, 2, 3}
 	for i := range s {
diff --git a/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go.golden b/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go.golden
index 55690331d43..77c5f6622b8 100644
--- a/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go.golden
+++ b/pkg/linters/mapclearloop/testdata/src/mapclearloop/mapclearloop.go.golden
@@ -7,6 +7,19 @@ func bad() {
 
 	m2 := map[int]string{1: "x"}
 	clear(m2)
+
+	m3 := map[string]int{"c": 3}
+	clear(m3)
+
+	var k string
+	clear(m)
+	_ = k
+
+	m4 := map[string]int{"d": 4}
+	for k := range m4 {
+		// keep this comment in place by omitting the suggested fix
+		delete(m4, k)
+	}
 }
 
 func good() {
@@ -35,6 +48,13 @@ func good() {
 		delete(m, k)
 	}
 
+	// clear is shadowed – not flagged.
+	clear := func(_ map[string]int) {}
+	for k := range m {
+		delete(m, k)
+	}
+	clear(m)
+
 	// Ranging over a slice – not flagged.
 	s := []int{1, 2, 3}
 	for i := range s {

From 8bd6db969ab24753a812fbd0a79e74a64929b45b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 16 Jul 2026 21:45:11 +0000
Subject: [PATCH 4/6] fix(linters): avoid premature comment scan return

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/linters/mapclearloop/mapclearloop.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/pkg/linters/mapclearloop/mapclearloop.go b/pkg/linters/mapclearloop/mapclearloop.go
index f048ff8831c..c834b4ae496 100644
--- a/pkg/linters/mapclearloop/mapclearloop.go
+++ b/pkg/linters/mapclearloop/mapclearloop.go
@@ -176,7 +176,6 @@ func hasOverlappingComment(files []*ast.File, start, end token.Pos) bool {
 				return true
 			}
 		}
-		return false
 	}
 	return false
 }

From f0e3733a09c53291ca9335e436ddf4a585a83c73 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 16 Jul 2026 21:50:16 +0000
Subject: [PATCH 5/6] fix(linters): detect comment overlap by range
 intersection

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/linters/mapclearloop/mapclearloop.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/linters/mapclearloop/mapclearloop.go b/pkg/linters/mapclearloop/mapclearloop.go
index c834b4ae496..55d00eb4a66 100644
--- a/pkg/linters/mapclearloop/mapclearloop.go
+++ b/pkg/linters/mapclearloop/mapclearloop.go
@@ -168,7 +168,7 @@ func builtinVisibleAtPos(pkg *types.Package, pos token.Pos, name string) bool {
 
 func hasOverlappingComment(files []*ast.File, start, end token.Pos) bool {
 	for _, file := range files {
-		if start < file.Pos() || end > file.End() {
+		if end <= file.Pos() || start >= file.End() {
 			continue
 		}
 		for _, group := range file.Comments {

From 597819b354d133f71673fcc3a50c3344107c017d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 16 Jul 2026 21:55:18 +0000
Subject: [PATCH 6/6] docs(linters): clarify mapclearloop helper guards

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/linters/mapclearloop/mapclearloop.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/pkg/linters/mapclearloop/mapclearloop.go b/pkg/linters/mapclearloop/mapclearloop.go
index 55d00eb4a66..6771d4b4afc 100644
--- a/pkg/linters/mapclearloop/mapclearloop.go
+++ b/pkg/linters/mapclearloop/mapclearloop.go
@@ -153,6 +153,7 @@ func run(pass *analysis.Pass) (any, error) {
 	return nil, nil
 }
 
+// builtinVisibleAtPos reports whether name resolves to a builtin object at pos.
 func builtinVisibleAtPos(pkg *types.Package, pos token.Pos, name string) bool {
 	if pkg == nil {
 		return false
@@ -162,10 +163,14 @@ func builtinVisibleAtPos(pkg *types.Package, pos token.Pos, name string) bool {
 		return false
 	}
 	_, obj := scope.LookupParent(name, pos)
+	if obj == nil {
+		return false
+	}
 	builtin, ok := obj.(*types.Builtin)
 	return ok && builtin.Name() == name
 }
 
+// hasOverlappingComment reports whether any comment group overlaps [start, end).
 func hasOverlappingComment(files []*ast.File, start, end token.Pos) bool {
 	for _, file := range files {
 		if end <= file.Pos() || start >= file.End() {
