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>
6.4 KiB
Critical-tier SonarQube tech-debt sweep — 2026-05-19
Context
The sql-utilities SonarQube project (https://snrqbe.bermudalamb.synology.me, key sql-utilities) is in passing state overall (quality gate OK, 0 bugs, 0 vulnerabilities) but carries 809 open code smells. The vast majority are mechanical noise — external_roslyn:NUnit2045/2046 (487 combined), style/perf analyzer suggestions (~150) — concentrated in test files.
This spec covers a first, focused pass at the high-impact tier: the 8 non-cognitive-complexity issues at BLOCKER/CRITICAL severity, all in production code. They map cleanly to a handful of files across six projects. The 11 S3776 cognitive-complexity hotspots are deferred to a separate plan.
Coverage reporting (currently 0% in SonarQube despite ~818 passing tests) is also out of scope here — it's being worked separately via the .gitea/workflows/sonarqube.yml iteration visible in git log.
Goal
Resolve the 8 high-impact issues with changes that respect the code's intent — fix where the rule reflects a genuine defect, suppress with justification where the rule conflicts with deliberate design.
Scope (the 8 issues)
| Rule | Severity | File | Treatment |
|---|---|---|---|
| S3875 | BLOCKER | src/Strata.SqlTools.Rules/Rule/Expression/Expression.Operators.cs:28 |
Suppress — the operator== returning a Comparison is intentional DSL syntax; the file already has #pragma warning disable CS0660, CS0661 for the same reason. |
| S927 | CRITICAL | src/Strata.SqlTools.Rules/ExpressionVisitor.cs:44, :80 |
Fix the interface, not the impls. Rename the IVisitor<T>.VisitNotEquals parameter from Equal (wrong) to notEqual. Implementations are already correct. |
| S2365 | CRITICAL | src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/CalculationFilterGroup.cs:15 |
Fix. Convert filtering getter to method GetValidFilters(); keep raw Filters as a plain auto-property for JSON round-trip. Update callers (currently IsValid()). |
| S2365 | CRITICAL | src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/CalculationFilterGroup.cs:15 |
Fix. Same pattern as above (likely a near-clone). |
| S2365 | CRITICAL | src/Strata.SqlTools.SqlBreakdown/Expressions/InputPropertyExpression.cs:378 |
Fix. Convert AllChildData getter (_childDataMap.SelectMany(...).ToList()) to method GetAllChildData(). Update callers. |
| S2696 | CRITICAL | src/Strata.SqlTools.PostgreSql/Visitors/CommandVisitor.cs:12 |
Fix (latent bug). Change private static int _parameterIndex = 1; to instance field. The static field is a cross-instance shared counter that never resets — almost certainly unintended. |
| S4487 | CRITICAL | src/Strata.SqlTools.LinqToSql/Builders/LinqQueryBreakdownBuilder.cs:11 |
Fix. Delete the unread _breakdown field and its constructor initialization. |
Approach
Approach B — pragmatic mix (selected from three options during brainstorming):
- A — Strict "do what Sonar says" was rejected because it would remove the DSL
==operator (real regression) and rename correctly-named implementation parameters to match a wrong interface name. - C — Suppress all 8 was rejected because two of the issues (S2696 static field, S4487 unused field) are genuine defects and S2365 reflects a real per-access allocation cost.
- B treats each rule as advice: fix where it reflects a real defect, suppress with justification where it conflicts with deliberate design.
Commit plan
Branch: fix/Sonarqube-Tech-Debt off main (already created — the spec commit is the first commit on it).
Five commits, ordered easiest → trickiest so any blocking issue surfaces late and can be deferred without losing the earlier wins:
fix(rules): rename IVisitor.VisitNotEquals parameter to match impls— S927 x2fix(linq2sql): remove unread _breakdown field from builder— S4487fix(rules): suppress S3875 on intentional DSL operator==— S3875fix(pgsql): make CommandVisitor parameter index instance-scoped— S2696refactor: convert filtering collection-property getters to methods— S2365 x3
Verification
Per commit:
dotnet build Strata.SqlTools.QueryBreakdown.sln -c Release— clean builddotnet test Strata.SqlTools.QueryBreakdown.sln -c Release --no-build— all tests must remain green; skip count must not increase beyond the current baseline (PostgreSql 1 skipped, Snowflake 1 skipped, Markdown 4 skipped)- If a commit's verification fails: fix inside the same commit, or revert before moving on
Final: fast-forward merge to main (matches existing direct-to-main pattern in git log). Run ./scan-sonar.ps1 to push a fresh analysis; confirm the 8 issues clear in SonarQube (S3875 will show as suppressed; the other 7 should resolve outright).
Risks & mitigations
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
S2365 property→method breaks consumers of Filters / AllChildData |
Medium | Medium — compile errors in callers | Grep call sites before each commit; update in the same commit. For JSON round-trip, keep Filters as auto-property holding raw collection, move filtering logic into GetValidFilters(). |
| S2696 static→instance changes numbering | Low | Medium if any downstream consumer relies on cross-instance counter | Tests don't depend on it. Document in commit message: parameter indices now reset per visitor instance — this is the intended Postgres behavior. |
| S927 interface rename breaks named-arg callers | Low | Low — C# parameter names are not in the binary contract | Grep confirms no VisitNotEquals(Equal: …) callers in repo. External NuGet consumers using named args would need a one-line update. |
S3875 suppression hides a future real bug in Expression equality |
Low | Low | Justification string makes intent explicit. The existing #pragma warning disable CS0660, CS0661 already documents the deliberate omission of Equals/GetHashCode. |
| Tests stay 0% covered in SonarQube | N/A | N/A | Pre-existing, out of scope. Tracked separately via .gitea/workflows/sonarqube.yml iteration. |
Rollback: each commit is self-contained → git revert <sha> restores the prior state for any single fix. Worst case, abandon the branch.
Out of scope
- The 11 S3776 cognitive-complexity hotspots (separate follow-up plan)
- The ~487 NUnit modernization issues in test files
- The ~150 style/perf analyzer issues in test files
- Coverage reporting (0% in SonarQube)
- Duplication (11.1%)
- 73 security hotspots