From fcb3023f9766f99c5712dbdbc1f35222d1965c98 Mon Sep 17 00:00:00 2001 From: alex <53851759+alxxjohn@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:10:59 -0400 Subject: [PATCH] fix: scope nullable block guards to their bodies --- .../checks/quality/quality_defensive.go | 18 +++++--- .../checks/quality/quality_defensive_test.go | 44 +++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 internal/codeguard/checks/quality/quality_defensive_test.go diff --git a/internal/codeguard/checks/quality/quality_defensive.go b/internal/codeguard/checks/quality/quality_defensive.go index 827303a..b5c1dbb 100644 --- a/internal/codeguard/checks/quality/quality_defensive.go +++ b/internal/codeguard/checks/quality/quality_defensive.go @@ -334,12 +334,20 @@ func nullableParamHasBlockExitGuard(loweredBody string, quotedName string) bool if guardStart == nil { return false } - windowStart := guardStart[1] - windowEnd := windowStart + 3000 - if windowEnd > len(loweredBody) { - windowEnd = len(loweredBody) + blockStart := guardStart[1] - 1 + depth := 1 + for blockEnd := blockStart + 1; blockEnd < len(loweredBody); blockEnd++ { + switch loweredBody[blockEnd] { + case '{': + depth++ + case '}': + depth-- + if depth == 0 { + return regexp.MustCompile(`\b(?:return|throw|continue|break)\b`).MatchString(loweredBody[blockStart+1 : blockEnd]) + } + } } - return regexp.MustCompile(`\b(?:return|throw|continue|break)\b`).MatchString(loweredBody[windowStart:windowEnd]) + return false } func nullableUseLine(fn precisionFunction, name string) int { diff --git a/internal/codeguard/checks/quality/quality_defensive_test.go b/internal/codeguard/checks/quality/quality_defensive_test.go new file mode 100644 index 0000000..aab2606 --- /dev/null +++ b/internal/codeguard/checks/quality/quality_defensive_test.go @@ -0,0 +1,44 @@ +package quality + +import ( + "regexp" + "testing" +) + +func TestNullableParamHasBlockExitGuardRequiresExitInsideGuard(t *testing.T) { + tests := []struct { + name string + body string + want bool + }{ + { + name: "return in guard", + body: `if (!user) { return missingUser(); } return user.email;`, + want: true, + }, + { + name: "return after guard", + body: `if (!user) { logMissing(); } return user.email;`, + want: false, + }, + { + name: "return in nested guard scope", + body: `if (!user) { if (missing()) { return fallback; } throw err; } use(user);`, + want: true, + }, + { + name: "unclosed guard", + body: `if (!user) { logMissing(); return fallback;`, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := nullableParamHasBlockExitGuard(tt.body, regexp.QuoteMeta("user")) + if got != tt.want { + t.Fatalf("nullableParamHasBlockExitGuard() = %v, want %v", got, tt.want) + } + }) + } +}