Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Fix reflect DeepEqual linter CI integration
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
  • Loading branch information
Copilot and pelikhan authored Oct 6, 2026
commit 36ee3598738e4553ce9be2cb3ca60ad42fd66835
4 changes: 3 additions & 1 deletion pkg/linters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ This package currently provides custom Go analyzers in the following subpackages
- `packagelevelmutableslicemap` — reports package-level (file/package-scope) `var` slice/map declarations mutated from inside a function body via `append()` re-assignment, index assignment, or `delete()`. Mutations inside a top-level `init()` are exempt.
- `panic-in-library-code` — reports `panic()` calls in library packages (`pkg/*`) where errors should be returned instead.
- `rawloginlib` — reports direct usage of the standard `log` package in library packages, where `pkg/logger` should be used.
- `reflectdeepequalusage` — reports `reflect.DeepEqual()` calls that should use typed equality operators or type-specific comparison functions.
- `regexpcompileinfunction` — reports `regexp.Compile` / `regexp.MustCompile` and their POSIX variants called inside functions that should be package-level.
- `regexpdynamicpattern` — reports regexp compile calls whose pattern is not a compile-time constant string.
- `seenmapbool` — reports `map[string]bool` used as a set (values always `true`) that should use `map[string]struct{}` instead.
Expand Down Expand Up @@ -87,7 +88,7 @@ This package currently provides custom Go analyzers in the following subpackages

Micro-optimizations flagged by allocation/perf linters (e.g. `stringsconcatloop`, `appendoneelement`,
`appendbytestring`, `bytesbufferstring`, `bytescomparestring`, `lenstringsplit`, `mapclearloop`,
`seenmapbool`, `slicemakezerolength`, `sortslice`, `stringbytesroundtrip`, `stringsjoinone`, `tolowerequalfold`, and
`reflectdeepequalusage`, `seenmapbool`, `slicemakezerolength`, `sortslice`, `stringbytesroundtrip`, `stringsjoinone`, `tolowerequalfold`, and
`writebytestring`) only matter on hot paths: applying them to code that tests never execute adds
churn without a measurable benefit. These linters consult the shared
`pkg/linters/internal/coverage` package, which loads a Go coverage profile (produced by
Expand Down Expand Up @@ -155,6 +156,7 @@ environment variable and gates findings on the recorded execution hit count for
| `packagelevelmutableslicemap` | Custom `go/analysis` analyzer that flags package-level slice/map `var` declarations mutated from inside a function body via `append()` re-assignment, index assignment, or `delete()` |
| `panic-in-library-code` | Custom `go/analysis` analyzer that flags `panic()` usage in library packages |
| `rawloginlib` | Custom `go/analysis` analyzer that flags standard-library `log` package calls in library packages |
| `reflectdeepequalusage` | Custom `go/analysis` analyzer that flags `reflect.DeepEqual()` calls that should use typed equality operators or type-specific comparison functions |
| `regexpcompileinfunction` | Custom `go/analysis` analyzer that flags regexp compilation inside function bodies |
| `regexpdynamicpattern` | Custom `go/analysis` analyzer that flags regexp compile calls with non-constant patterns |
| `seenmapbool` | Custom `go/analysis` analyzer that flags `map[string]bool` used as a set that should use `map[string]struct{}` |
Expand Down
3 changes: 2 additions & 1 deletion pkg/linters/doc.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Package linters is a namespace for gh-aw's custom Go analysis linters.
//
// All 76 active analyzers:
// All 77 active analyzers:
//
// - appendbytestring — flags append(b, []byte(s)...) calls where s is a string that can be simplified to append(b, s...)
// - appendoneelement — flags append(s, []T{x}...) calls where a single-element slice literal is spread and can be simplified to append(s, x)
Expand Down Expand Up @@ -47,6 +47,7 @@
// - packagelevelmutableslicemap — reports mutation of package-level slice/map variables from inside function bodies, which risks data races and cross-call state leaks
// - panic-in-library-code — flags panic() calls in library packages
// - rawloginlib — flags direct usage of the standard log package in library packages
// - reflectdeepequalusage — flags reflect.DeepEqual calls that should use typed equality or type-specific comparison functions
// - regexpcompileinfunction — flags regexp.MustCompile/Compile calls inside functions
// - regexpdynamicpattern — flags regexp compile calls whose pattern is not a compile-time constant
// - seenmapbool — flags map[string]bool used as a set that should use map[string]struct{}
Expand Down
1 change: 1 addition & 0 deletions pkg/linters/doc_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var notYetEnforced = map[string]string{
"lenstringzero": "requires an enforcement audit after the diagnostic-message fix (#54717)",
"manualpathconcat": "existing production violations need remediation",
"packagelevelmutableslicemap": "existing production violations need remediation",
"reflectdeepequalusage": "existing reflect.DeepEqual uses need case-by-case review before CI enforcement",
"seenmapbool": "existing production violations need remediation",
"sprintferrdot": "has not yet completed an enforcement-readiness audit",
"ssljson": "has not yet completed an enforcement-readiness audit",
Expand Down
20 changes: 14 additions & 6 deletions pkg/linters/reflect-deepequal-usage/reflect_deepequal_usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
package reflectdeepequalusage

import (
"fmt"
"go/ast"

"golang.org/x/tools/go/analysis"

"github.com/github/gh-aw/pkg/linters/internal/analyzerutil"
"github.com/github/gh-aw/pkg/linters/internal/astutil"
"github.com/github/gh-aw/pkg/linters/internal/coverage"
"github.com/github/gh-aw/pkg/linters/internal/filecheck"
"github.com/github/gh-aw/pkg/linters/internal/nolint"
"github.com/github/gh-aw/pkg/logger"
Expand All @@ -26,6 +26,12 @@ var Analyzer = analyzerutil.New(
run,
)

var hotThreshold *int

func init() {
hotThreshold = coverage.RegisterHotThresholdFlag(Analyzer)
}

// URL points to documentation for this linter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] Don't hand-roll the Analyzer.URL patch with an IIFE; use the existing analyzerutil.NewAtPath helper, which is built exactly for dash-named package directories (see panic-in-library-code.go).

💡 Suggested fix
var Analyzer = analyzerutil.NewAtPath(
	"reflectdeepequalusage",
	"reports reflect.DeepEqual() usage in conditionals or comparisons that should use typed equality operators or type-specific comparison functions for performance and type safety",
	"reflect-deepequal-usage",
	run,
)

This removes the need for the awkward package-scope var _ = func() struct{} { ... }() block entirely and matches the convention panicinlibrarycode.Analyzer already establishes for the same directory-naming mismatch.

@copilot please address this.

var _ = func() struct{} {
Analyzer.URL = "https://github.com/github/gh-aw/tree/main/pkg/linters/reflect-deepequal-usage"
Expand Down Expand Up @@ -68,12 +74,14 @@ func analyzeCall(pass *analysis.Pass, n ast.Node, generatedFiles filecheck.Gener
return
}

if !coverage.ShouldApply(pass, call.Pos(), *hotThreshold) {
return
}

pkgLog.Printf("flagging reflect.DeepEqual() call at %s:%d", pos.Filename, pos.Line)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] fmt.Sprintf is used with a static string and no format arguments — a plain string literal (or pass.Reportf, as sprintferrorsnew.go does) is simpler and avoids an unnecessary import/indirection.

💡 Suggested simplification
pass.Report(analysis.Diagnostic{
	Pos: call.Pos(),
	End: call.End(),
	Message: "reflect.DeepEqual() is inefficient and should not be used in conditionals; prefer typed equality operators or type-specific comparison functions",
})

This also lets you drop the "fmt" import.

@copilot please address this.

pass.Report(analysis.Diagnostic{
Comment on lines +81 to +82
Pos: call.Pos(),
End: call.End(),
Message: fmt.Sprintf(
"reflect.DeepEqual() is inefficient and should not be used in conditionals; prefer typed equality operators or type-specific comparison functions",
),
Pos: call.Pos(),
End: call.End(),
Message: "reflect.DeepEqual() is inefficient and should not be used in conditionals; prefer typed equality operators or type-specific comparison functions",
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

"golang.org/x/tools/go/analysis/analysistest"

"github.com/github/gh-aw/pkg/linters/reflect-deepequal-usage"
reflectdeepequalusage "github.com/github/gh-aw/pkg/linters/reflect-deepequal-usage"
)

func TestReflectDeepEqualUsage(t *testing.T) {
Expand Down
6 changes: 4 additions & 2 deletions pkg/linters/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import (
"github.com/github/gh-aw/pkg/linters/packagelevelmutableslicemap"
panicinlibrarycode "github.com/github/gh-aw/pkg/linters/panic-in-library-code"
"github.com/github/gh-aw/pkg/linters/rawloginlib"
reflectdeepequalusage "github.com/github/gh-aw/pkg/linters/reflect-deepequal-usage"
"github.com/github/gh-aw/pkg/linters/regexpcompileinfunction"
"github.com/github/gh-aw/pkg/linters/regexpdynamicpattern"
"github.com/github/gh-aw/pkg/linters/seenmapbool"
Expand Down Expand Up @@ -102,7 +103,7 @@ type docAnalyzer struct {
}

// documentedAnalyzers returns the analyzer subpackages documented in the README
// "Public API > Subpackages" table. The README documents 76 analyzer
// "Public API > Subpackages" table. The README documents 77 analyzer
// subpackages (the non-analyzer `internal` helper subpackage is excluded because
// it exposes no Analyzer).
//
Expand All @@ -112,7 +113,7 @@ type docAnalyzer struct {
// errortypeassertion, errstringmatch, execcommandwithoutcontext, fileclosenotdeferred, fmterrorfnoverbs, fprintferrorunchecked, fprintlnsprintf,
// generatedyamlheredoc, globwalkignorederror, goroutinemissingrecover, hardcodedfilepath, httpnoctx, httprespbodyclose, httpstatuscode, ioutildeprecated, jsonmarshalignoredeerror, largefunc, lenstringsplit, lenstringzero,
// logfatallibrary, manualmutexunlock, manualpathconcat, mapclearloop, mapdeletecheck, nilctxpassed, osexitinlibrary, osgetenvlibrary, ossetenvlibrary, packagelevelmutableslicemap, panic-in-library-code, rawloginlib,
// regexpcompileinfunction, regexpdynamicpattern, seenmapbool, slicemakezerolength, sortslice, sprintferrdot, sprintferrorsnew, sprintfbool, sprintfint, ssljson,
// reflectdeepequalusage, regexpcompileinfunction, regexpdynamicpattern, seenmapbool, slicemakezerolength, sortslice, sprintferrdot, sprintferrorsnew, sprintfbool, sprintfint, ssljson,
// strconvparseignorederror, stringbytesroundtrip, stringreplaceminusone, stringsconcatloop, stringscountcontains, stringsindexcontains, stringsindexhasprefix, stringsjoinone, timeafterleak, timesleepnocontext, timenowsub,
// tolowerequalfold, trimleftright, typeassertionnil, typeassertionokdiscarded, uncheckedflushreturn, uncheckedsliceindex, uncheckedtypeassertion, walkfuncerrshadow, wgdonenotdeferred, writebytestring
func documentedAnalyzers() []docAnalyzer {
Expand Down Expand Up @@ -162,6 +163,7 @@ func documentedAnalyzers() []docAnalyzer {
{"packagelevelmutableslicemap", packagelevelmutableslicemap.Analyzer},
{"panic-in-library-code", panicinlibrarycode.Analyzer},
{"rawloginlib", rawloginlib.Analyzer},
{"reflectdeepequalusage", reflectdeepequalusage.Analyzer},
{"regexpcompileinfunction", regexpcompileinfunction.Analyzer},
{"regexpdynamicpattern", regexpdynamicpattern.Analyzer},
{"seenmapbool", seenmapbool.Analyzer},
Expand Down
Loading