From 11ddf2b49ed1c1a341290fa2eff2e80607acac60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Jun 2026 05:44:33 +0000 Subject: [PATCH 1/4] Initial plan From 45421698821afc8ecf119864d943103ec81e498d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Jun 2026 06:01:59 +0000 Subject: [PATCH 2/4] fix(httpstatuscode): replace spelling-based gating with type-aware detection - Identifier path: resolve type via TypesInfo; for named integer types require both 'http' and 'status' in the type name to avoid false positives (e.g. type JobState int); for plain integers fall back to broadened name list (status, statusCode, httpStatus). - Selector path: accept Status and HTTPStatus field names in addition to StatusCode, fixing false negatives for entry.Status and .HTTPStatus fields. - Adds isHTTPStatusVarName, isHTTPStatusFieldName, isHTTPStatusTypeName helpers. - Extends testdata with: Status/HTTPStatus field cases (flagged), JobState named-enum case (not flagged), and a documented false-negative for non-status-named locals. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/linters/httpstatuscode/httpstatuscode.go | 49 ++++++++++- .../src/httpstatuscode/httpstatuscode.go | 82 +++++++++++++++++++ 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/pkg/linters/httpstatuscode/httpstatuscode.go b/pkg/linters/httpstatuscode/httpstatuscode.go index 8666e817ed1..702e3a1584b 100644 --- a/pkg/linters/httpstatuscode/httpstatuscode.go +++ b/pkg/linters/httpstatuscode/httpstatuscode.go @@ -8,6 +8,7 @@ import ( "go/token" "go/types" "strconv" + "strings" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" @@ -173,9 +174,24 @@ func extractStatusLiteral(expr *ast.BinaryExpr) (*ast.BasicLit, ast.Expr) { func isHTTPStatusContext(pass *analysis.Pass, expr ast.Expr) bool { switch e := expr.(type) { case *ast.Ident: - return e.Name == "status" || e.Name == "statusCode" + obj, ok := pass.TypesInfo.Uses[e] + if !ok { + return false + } + t := obj.Type() + if !isIntegerType(t) { + return false + } + // For named integer types (custom enums/aliases), check whether the type + // name itself indicates HTTP status to avoid false positives on non-HTTP + // integer types (e.g. type JobState int). + if named, isNamed := t.(*types.Named); isNamed { + return isHTTPStatusTypeName(named.Obj().Name()) + } + // For plain integer types, fall back to variable name heuristic. + return isHTTPStatusVarName(e.Name) case *ast.SelectorExpr: - if e.Sel.Name != "StatusCode" { + if !isHTTPStatusFieldName(e.Sel.Name) { return false } if sel, ok := pass.TypesInfo.Selections[e]; ok { @@ -194,6 +210,35 @@ func isHTTPStatusContext(pass *analysis.Pass, expr ast.Expr) bool { return false } +// isHTTPStatusVarName returns true if a plain-integer variable/parameter name +// suggests it holds an HTTP status code. +func isHTTPStatusVarName(name string) bool { + switch name { + case "status", "statusCode", "httpStatus": + return true + } + return false +} + +// isHTTPStatusFieldName returns true if a struct field name suggests HTTP status. +// Accepts StatusCode, Status, and HTTPStatus to cover common response field spellings. +func isHTTPStatusFieldName(name string) bool { + switch name { + case "StatusCode", "Status", "HTTPStatus": + return true + } + return false +} + +// isHTTPStatusTypeName returns true if a named integer type's name indicates that +// it represents an HTTP status code (e.g. HTTPStatusCode, HTTPStatus). +// Both "http" and "status" must appear in the name (case-insensitive) to avoid +// matching unrelated HTTP types such as HTTPVersion or HTTPMethod. +func isHTTPStatusTypeName(name string) bool { + lower := strings.ToLower(name) + return strings.Contains(lower, "http") && strings.Contains(lower, "status") +} + func isIntegerType(t types.Type) bool { basic, ok := t.Underlying().(*types.Basic) return ok && basic.Info()&types.IsInteger != 0 diff --git a/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go b/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go index cc67edc907d..6d2d3c0dbeb 100644 --- a/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go +++ b/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go @@ -32,6 +32,13 @@ func compareStatusCode(statusCode int) { } } +func compareHTTPStatus(httpStatus int) { + if httpStatus == 200 { // want `use http\.StatusOK instead of magic HTTP status code 200` + } + if httpStatus == 404 { // want `use http\.StatusNotFound instead of magic HTTP status code 404` + } +} + func compareResponse(resp *http.Response) { if resp.StatusCode == 200 { // want `use http\.StatusOK instead of magic HTTP status code 200` } @@ -118,3 +125,78 @@ func compareCustomIntStatusCode(r customResponse) { if r.StatusCode == 418 { // want `use http\.StatusTeapot instead of magic HTTP status code 418` } } + +// httpEntry is a response type with a field named Status (not StatusCode). +type httpEntry struct { + Status int +} + +func compareFieldStatus(entry httpEntry) { + if entry.Status == 200 { // want `use http\.StatusOK instead of magic HTTP status code 200` + } + if entry.Status == 404 { // want `use http\.StatusNotFound instead of magic HTTP status code 404` + } +} + +func compareSwitchFieldStatus(entry httpEntry) { + switch entry.Status { + case 200: // want `use http\.StatusOK instead of magic HTTP status code 200` + case 500: // want `use http\.StatusInternalServerError instead of magic HTTP status code 500` + } +} + +// httpClientInfo is a type with a field named HTTPStatus. +type httpClientInfo struct { + HTTPStatus int +} + +func compareFieldHTTPStatus(c httpClientInfo) { + if c.HTTPStatus == 404 { // want `use http\.StatusNotFound instead of magic HTTP status code 404` + } + if c.HTTPStatus == 500 { // want `use http\.StatusInternalServerError instead of magic HTTP status code 500` + } +} + +func compareSwitchFieldHTTPStatus(c httpClientInfo) { + switch c.HTTPStatus { + case 200: // want `use http\.StatusOK instead of magic HTTP status code 200` + case 404: // want `use http\.StatusNotFound instead of magic HTTP status code 404` + } +} + +// JobState is a non-HTTP integer enum (state machine). Comparisons against +// HTTP-range integers must not be flagged because the type name contains no +// HTTP indicator. +type JobState int + +const ( + JobPending JobState = iota + JobRunning + JobDone +) + +func compareNonHTTPJobState(state JobState) { + if state == 200 { + } + if state == 404 { + } +} + +func compareSwitchNonHTTPJobState(state JobState) { + switch state { + case 200: + case 404: + } +} + +func compareNonStatusNamedLocal(resp *http.Response) { + // False negative: "code" is a plain int whose name is not in the HTTP-status + // name list. Flow-based analysis would be required to detect this pattern. + // The absence of a "want" comment below is the test assertion — analysistest + // fails if an unexpected diagnostic is produced, so this line also guards + // against regressions that would cause the linter to start flagging it. + code := resp.StatusCode + if code == 404 { + } + _ = code +} From 7081a849cce6478d8c835602593d0f422af6a1e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Jun 2026 06:03:48 +0000 Subject: [PATCH 3/4] refine: clarify type-name detection comments in testdata Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../testdata/src/httpstatuscode/httpstatuscode.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go b/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go index 6d2d3c0dbeb..f586b661dcd 100644 --- a/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go +++ b/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go @@ -165,8 +165,8 @@ func compareSwitchFieldHTTPStatus(c httpClientInfo) { } // JobState is a non-HTTP integer enum (state machine). Comparisons against -// HTTP-range integers must not be flagged because the type name contains no -// HTTP indicator. +// HTTP-range integers must not be flagged: the type name lacks both "http" +// and "status", so isHTTPStatusTypeName returns false. type JobState int const ( @@ -190,11 +190,8 @@ func compareSwitchNonHTTPJobState(state JobState) { } func compareNonStatusNamedLocal(resp *http.Response) { - // False negative: "code" is a plain int whose name is not in the HTTP-status - // name list. Flow-based analysis would be required to detect this pattern. - // The absence of a "want" comment below is the test assertion — analysistest - // fails if an unexpected diagnostic is produced, so this line also guards - // against regressions that would cause the linter to start flagging it. + // False negative: plain int local with non-status name requires flow analysis. + // No want comment = analysistest ensures this remains unflagged. code := resp.StatusCode if code == 404 { } From 05ae43cf77e73a70c39f182b688c88db9dd385bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Jun 2026 06:04:26 +0000 Subject: [PATCH 4/4] refine: further clarify testdata comments for JobState and false-negative cases Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../testdata/src/httpstatuscode/httpstatuscode.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go b/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go index f586b661dcd..234ac578127 100644 --- a/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go +++ b/pkg/linters/httpstatuscode/testdata/src/httpstatuscode/httpstatuscode.go @@ -164,9 +164,10 @@ func compareSwitchFieldHTTPStatus(c httpClientInfo) { } } -// JobState is a non-HTTP integer enum (state machine). Comparisons against -// HTTP-range integers must not be flagged: the type name lacks both "http" -// and "status", so isHTTPStatusTypeName returns false. +// JobState is a non-HTTP integer enum (state machine). Integer literals that +// happen to fall in the HTTP status-code range (100-599) must not be flagged: +// the type name lacks both "http" and "status", so isHTTPStatusTypeName returns +// false regardless of the variable name. type JobState int const ( @@ -190,8 +191,12 @@ func compareSwitchNonHTTPJobState(state JobState) { } func compareNonStatusNamedLocal(resp *http.Response) { - // False negative: plain int local with non-status name requires flow analysis. - // No want comment = analysistest ensures this remains unflagged. + // False negative: plain int local with non-status name requires flow analysis + // to detect, which is out of scope for this linter (tracking value origins + // across assignments would require SSA/dataflow infrastructure). The trade-off + // is documented here intentionally. No want comment = analysistest ensures + // this remains unflagged (any future regression that starts flagging it + // would fail the test). code := resp.StatusCode if code == 404 { }