Resolves the 8 BLOCKER/CRITICAL SonarQube issues in sql-utilities that aren't cognitive-complexity (S3776). Pragmatic mix: fix where the rule reflects a real defect, suppress with justification where it conflicts with deliberate design.
S3875 (BLOCKER) — Suppress on Expression.Operators.cs. The operator== overload returns a Comparison expression (DSL semantics), not a bool. File already has #pragma warning disable CS0660, CS0661 documenting the same intent.
S927 x2 — Rename IVisitor<T>.VisitNotEquals interface parameter from Equal → notEqual. The interface was the wrong one; implementations were already correct.
S4487 — Delete the unread _breakdown field from LinqQueryBreakdownBuilder.cs.
S2696 — Make _parameterIndex instance-scoped in PostgreSql.CommandVisitor. Latent bug fix: the static field was mutated from an instance method, causing every new visitor to inherit the previous instance's counter. Adds CommandVisitorTests.TwoVisitors_HaveIndependentParameterIndices to lock in the contract.
S2365 x3 — Two refactored (CalculationFilterGroup.Filters → plain auto-property + GetValidFilters() method in both Snowflake and SqlServer; callers in QueryConfigExtensions updated). One suppressed (HierarchicalData.AllChildData is an interface contract member tied to JSON shape; suppression with justification is documented in code and PR description).
Behavior changes
CalculationFilterGroup JSON output now serializes the raw filter collection rather than the pre-filtered one. Round-trip is preserved; callers needing the filtered view must call GetValidFilters().
PostgreSql.CommandVisitor parameter indices now restart at $1 per visitor instance instead of accumulating across instances. This is the intended PostgreSQL behavior.
dotnet test Strata.SqlTools.QueryBreakdown.sln -c Release --no-build — 1,180 passed, 7 skipped, 0 failed (baseline + 1 new CommandVisitorTests.TwoVisitors_HaveIndependentParameterIndices)
Run ./scan-sonar.ps1 after merge to confirm the 8 issues clear in SonarQube (S3875 + S2365 on AllChildData will show as suppressed; the other 6 should resolve outright)
## Summary
Resolves the 8 BLOCKER/CRITICAL SonarQube issues in `sql-utilities` that aren't cognitive-complexity (S3776). Pragmatic mix: fix where the rule reflects a real defect, suppress with justification where it conflicts with deliberate design.
- **S3875 (BLOCKER)** — Suppress on `Expression.Operators.cs`. The `operator==` overload returns a `Comparison` expression (DSL semantics), not a `bool`. File already has `#pragma warning disable CS0660, CS0661` documenting the same intent.
- **S927 x2** — Rename `IVisitor<T>.VisitNotEquals` interface parameter from `Equal` → `notEqual`. The interface was the wrong one; implementations were already correct.
- **S4487** — Delete the unread `_breakdown` field from `LinqQueryBreakdownBuilder.cs`.
- **S2696** — Make `_parameterIndex` instance-scoped in `PostgreSql.CommandVisitor`. **Latent bug fix:** the static field was mutated from an instance method, causing every new visitor to inherit the previous instance's counter. Adds `CommandVisitorTests.TwoVisitors_HaveIndependentParameterIndices` to lock in the contract.
- **S2365 x3** — Two refactored (`CalculationFilterGroup.Filters` → plain auto-property + `GetValidFilters()` method in both Snowflake and SqlServer; callers in `QueryConfigExtensions` updated). One suppressed (`HierarchicalData.AllChildData` is an interface contract member tied to JSON shape; suppression with justification is documented in code and PR description).
## Behavior changes
- `CalculationFilterGroup` JSON output now serializes the **raw** filter collection rather than the pre-filtered one. Round-trip is preserved; callers needing the filtered view must call `GetValidFilters()`.
- `PostgreSql.CommandVisitor` parameter indices now restart at `$1` per visitor instance instead of accumulating across instances. This is the intended PostgreSQL behavior.
## Out of scope
- 11 S3776 cognitive-complexity hotspots (separate follow-up plan)
- ~487 NUnit modernization + ~150 style/perf issues in test files
- Coverage reporting (0% in SonarQube — being worked separately via the `.gitea/workflows/sonarqube.yml` iteration)
## Test plan
- [x] `dotnet build Strata.SqlTools.QueryBreakdown.sln -c Release` — clean
- [x] `dotnet test Strata.SqlTools.QueryBreakdown.sln -c Release --no-build` — **1,180 passed, 7 skipped, 0 failed** (baseline + 1 new `CommandVisitorTests.TwoVisitors_HaveIndependentParameterIndices`)
- [ ] Run `./scan-sonar.ps1` after merge to confirm the 8 issues clear in SonarQube (S3875 + S2365 on `AllChildData` will show as suppressed; the other 6 should resolve outright)
## Spec & plan
- Spec: `docs/superpowers/specs/2026-05-19-sonar-tech-debt-design.md`
- Plan: `docs/superpowers/plans/2026-05-19-sonar-tech-debt.md`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Design doc covering the 8 BLOCKER/CRITICAL non-S3776 issues across six
projects. Approach is a pragmatic mix: fix where the rule reflects a
real defect, suppress with justification where the rule conflicts with
deliberate design (DSL operator==).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five-task plan implementing the 2026-05-19 spec: per-rule commits on the
existing fix/Sonarqube-Tech-Debt branch with build+test verification
between each. Notes one spec deviation discovered during plan-time
exploration (HierarchicalData.AllChildData is suppressed rather than
refactored — it's an interface contract member tied to JSON shape).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves SonarQube S927 x2 in ExpressionVisitor.cs.
The interface parameter was named ``Equal`` on a ``VisitNotEquals`` method,
which is semantically wrong and forced implementations to mismatch.
Implementations already used ``notEqual``; rename the interface to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves SonarQube S4487 in LinqQueryBreakdownBuilder.cs.
The field was assigned in the constructor but never read. Grep across
the project confirmed no external references.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves SonarQube S3875 (BLOCKER) in Expression.Operators.cs.
The operator== returns a Comparison expression (DSL semantics), not a
bool. The existing CS0660/CS0661 pragma already documents this design;
the new SuppressMessage attribute makes the Sonar analyzer agree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves SonarQube S2696 in CommandVisitor.cs.
The static _parameterIndex field was mutated from an instance method,
causing every new CommandVisitor to inherit the previous instance's
counter and never reset. Parameter indices now restart at $1 per
visitor, which is the intended PostgreSQL behavior.
Adds CommandVisitorTests.TwoVisitors_HaveIndependentParameterIndices to
lock in the contract via reflection (FormatParameterName is protected).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code review follow-up on acfe29e:
- Switch namespace to Strata.SqlTools.SqlBreakdown.Tests.PostgreSql to
match the dominant convention in the sibling test folder.
- Remove the unreachable SqlServer.CommandVisitor fallback in the
reflection helper. The Assert.That guard alone is enough to surface
a future regression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves SonarQube S2365 x3.
CalculationFilterGroup (Snowflake + SqlServer): the `Filters` getter
ran .Where(...).ToList() on every access. Convert to a plain
auto-property holding the raw collection plus a `GetValidFilters()`
method that does the filtering. Update internal IsValid() and the two
QueryConfigExtensions callers to use the new method.
HierarchicalData.AllChildData: part of the IHierarchicalData interface
and JSON-serialized; the transformation IS the property's contract.
Suppress S2365 with justification rather than refactor -- same pattern
as S3875.
Behavior change: CalculationFilterGroup JSON output now serializes the
raw filter collection rather than the pre-filtered one. Round-trip is
preserved; callers needing the filtered view must call GetValidFilters().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code review follow-up on 6f85358:
- CalculationFilterGroup.IsValid() previously called GetValidFilters().Any(),
which materialized a full filtered List<CalculationFilter> just to check
existence. Reimplement directly as Filters.Any(x => x.IsValid()) so
IsValid() recovers its pre-refactor O(1) early-exit behavior without
allocations. GetValidFilters() remains for callers that need the full
materialized list.
- QueryConfigExtensions.GetAllColumnIds line 16 uses FilterGroup (not
CalculationFilterGroup), which pre-filters in its JsonConstructor and
has no GetValidFilters() method. Add a comment to document the
intentional asymmetry between line 14 and line 16.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Establishes tooling to systematically analyze and address technical debt.
This includes:
- `scan-sonar.ps1`: An orchestration script for local SonarQube scans with coverage.
- `Directory.Build.props`: Integrates SonarAnalyzer.CSharp for static analysis during build.
- `coverlet.runsettings`: Configures code coverage collection using Coverlet.
- `.claude/settings.local.json`: Adds permissions for AI to query SonarQube and local dev status.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Resolves the 8 BLOCKER/CRITICAL SonarQube issues in
sql-utilitiesthat aren't cognitive-complexity (S3776). Pragmatic mix: fix where the rule reflects a real defect, suppress with justification where it conflicts with deliberate design.Expression.Operators.cs. Theoperator==overload returns aComparisonexpression (DSL semantics), not abool. File already has#pragma warning disable CS0660, CS0661documenting the same intent.IVisitor<T>.VisitNotEqualsinterface parameter fromEqual→notEqual. The interface was the wrong one; implementations were already correct._breakdownfield fromLinqQueryBreakdownBuilder.cs._parameterIndexinstance-scoped inPostgreSql.CommandVisitor. Latent bug fix: the static field was mutated from an instance method, causing every new visitor to inherit the previous instance's counter. AddsCommandVisitorTests.TwoVisitors_HaveIndependentParameterIndicesto lock in the contract.CalculationFilterGroup.Filters→ plain auto-property +GetValidFilters()method in both Snowflake and SqlServer; callers inQueryConfigExtensionsupdated). One suppressed (HierarchicalData.AllChildDatais an interface contract member tied to JSON shape; suppression with justification is documented in code and PR description).Behavior changes
CalculationFilterGroupJSON output now serializes the raw filter collection rather than the pre-filtered one. Round-trip is preserved; callers needing the filtered view must callGetValidFilters().PostgreSql.CommandVisitorparameter indices now restart at$1per visitor instance instead of accumulating across instances. This is the intended PostgreSQL behavior.Out of scope
.gitea/workflows/sonarqube.ymliteration)Test plan
dotnet build Strata.SqlTools.QueryBreakdown.sln -c Release— cleandotnet test Strata.SqlTools.QueryBreakdown.sln -c Release --no-build— 1,180 passed, 7 skipped, 0 failed (baseline + 1 newCommandVisitorTests.TwoVisitors_HaveIndependentParameterIndices)./scan-sonar.ps1after merge to confirm the 8 issues clear in SonarQube (S3875 + S2365 onAllChildDatawill show as suppressed; the other 6 should resolve outright)Spec & plan
docs/superpowers/specs/2026-05-19-sonar-tech-debt-design.mddocs/superpowers/plans/2026-05-19-sonar-tech-debt.md🤖 Generated with Claude Code