Section A (server inspection / API access) is unchanged. Section B is rewritten around the tiered model: - Tier 1 — mechanical: bulk fix multiple rule IDs in one dotnet format invocation (IDE0028, CA1825, CA1834, etc). - Tier 2 — judgment: one commit per rule, audit diff before staging. Calls out CA1822 public-static = binary break, CA1861 cryptic generated field names + duplicate-field collisions, CA1859 has no batch fixer. - Tier 3 — manual / no fixer: csharpsquid:Sxxxx, public-API design calls. Reference [[sonarqube-wontfix-rules]] memory for triage. Adds the local-Java-version gotcha: `./scan-sonar.ps1` is blocked here (system JRE is 8, scanner needs 17); CI handles the upload via actions/setup-java@v4 with temurin 17. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.4 KiB
name, description
| name | description |
|---|---|
| sonarqube | Use when working with SonarQube tech debt in this repo — querying the bermudalamb 9.9 server's web API from PowerShell, triaging open issues for the sql-utilities project, running scan-sonar.ps1 and verifying net-down after a cleanup commit. |
Sonarqube (sql-utilities)
Overview
Two halves: (A) how to inspect the server from a PowerShell prompt, and (B) the per-rule-group cleanup loop that keeps tech-debt fixes from introducing new warnings.
The repo has SonarAnalyzer.CSharp referenced in Directory.Build.props, so every dotnet build surfaces the same rule set the server reports — in the Error List / build output, not the SonarLint pane. Local build is the fast feedback loop; scan-sonar.ps1 is what publishes results to the server.
A — Inspecting the server
Server: https://snrqbe.bermudalamb.synology.me (9.9 LTS). Project key: sql-utilities.
Auth is HTTP Basic with the token as the username and an empty password — not Bearer (Bearer was added in 10.0 and returns 401 here).
$url = $env:SONARQUBE_URL.TrimEnd('/') # the env var is stored with a trailing /
$tok = $env:SONARQUBE_TOKEN # squ_ user token, Windows user-scope
$b64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${tok}:"))
$h = @{ Authorization = "Basic $b64" }
Invoke-RestMethod -Uri "$url/api/issues/search?componentKeys=sql-utilities&resolved=false&ps=500&facets=severities,types,rules" -Headers $h
Gotcha: SONARQUBE_URL is stored with a trailing /. Without TrimEnd('/') you build //api/... and the server answers with the SPA HTML shell — Invoke-RestMethod happily returns it and downstream .issues/.facets access yields blank output silently.
Useful endpoints:
/api/issues/search—?severities=MAJOR,MINOR,?rules=csharpsquid:S1168,?resolutions=WONTFIX,&facets=rules,severities,typesfor triage views./api/issues/add_comment(POST:issue,text) — justification before a Won't Fix transition./api/issues/do_transition(POST:issue,transition=wontfix) — mark a known false-positive without changing code./api/qualitygates/project_status?projectKey=sql-utilities— current gate status./api/measures/component?component=sql-utilities&metricKeys=code_smells,coverage,duplicated_lines_density,ncloc,sqale_index— headline numbers./api/ce/component?component=sql-utilities— most-recent analysis task status (queued / in-progress / failed).
SONARQUBE_URL / SONARQUBE_TOKEN are read here; scan-sonar.ps1 reads the separate SONAR_TOKEN / SONAR_HOST_URL for the upload path — do not conflate them.
B — Cleanup loop (tiered)
digraph cleanup {
query [label="Query open issues + facets=rules"];
triage [label="Tier each rule:\nmechanical / judgment / API-impact"];
t1 [label="Tier 1 — mechanical:\nbulk-fix in one commit"];
t2 [label="Tier 2 — judgment:\none commit per rule, audit diff"];
t3 [label="Tier 3 — API-impact:\nmanual, accept !breaking or skip"];
build [label="dotnet build + dotnet test\nafter EACH commit"];
push [label="git push + open PR"];
ci [label="CI runs SonarScanner\non Java 17"];
verify [label="Re-query API:\nconfirm rule counts dropped"];
query -> triage;
triage -> t1 -> build;
triage -> t2 -> build;
triage -> t3 -> build;
build -> push -> ci -> verify;
}
The loop has shifted from per-rule commits to tiered batches once a project has more than a handful of issues left. Use dotnet format analyzers --diagnostics <ids> --severity info — it runs all the Roslyn-shipped code fixers for the listed diagnostics, including CA*, IDE*, and NUnit*. (The csharpsquid:Sxxxx family from SonarAnalyzer.CSharp usually has no dotnet format fixer; those still need manual edits.)
Tier 1 — mechanical (one bulk commit)
Rules where the fixer's rewrite is purely syntactic and can't change behavior — pile them into one dotnet format invocation, audit the diff for sanity, build, test, single commit:
dotnet format analyzers Strata.SqlTools.QueryBreakdown.sln `
--diagnostics IDE0028 CA1825 CA1834 CA1845 CA1847 CA1860 CA1866 CA1853 CA1830 CA2249 `
--severity info --verbosity normal
Typical safe rules: IDE0028, CA1825, CA1834, CA1845, CA1847, CA1853, CA1860, CA1866, CA2249, CA1830. Some rules in this family report "no associated code fix" — they'll need manual handling separately.
Tier 2 — judgment (one commit per rule)
Rules whose fixer can produce mediocre output or surprise the reader — bulk-fix but audit before staging:
CA1510—ArgumentNullException.ThrowIfNullrollup; always safe but voluminous, deserves its own commit.CA1822— make-method-static; the fixer also rewrites internal callers to use type-name form. Public methods becoming static are a binary-break for external NuGet consumers — commit withchore(sonar)!:and aBREAKING CHANGE:footer naming each affected member, or revert those file diffs and apply only the private-helper changes.CA1861— hoists constant array args tostatic readonlyfields. The fixer's field names are sometimes cryptic (stringArray,arg2) or it emits duplicateseparatorfields in the same class. After running, rename ugly fields and dedupe collisions before committing.CA1854—TryGetValue-style double-lookup elimination; trivial.CA1859— concrete return type for perf. No batch fixer: edit each site manually after checking visibility. Apply only to private/internal/test; on public/protected, either revert or treat as a!breakingchange (most cases are private helpers, so this is usually fine).
Tier 3 — manual (no fixer)
CA1806, CA1846, CA1869, the csharpsquid:Sxxxx family that has no dotnet format fixer, and individual public-API rules that need design judgment. Edit by hand, one rule at a time. If a fix would degrade clarity or break a tested contract, prefer server-side Won't Fix with justification — see sonarqube-wontfix-rules for the catalog already triaged that way (S1168, S3925, CS8601, S107).
Guardrail
After every commit (Tier 1, 2, or 3):
dotnet build Strata.SqlTools.QueryBreakdown.sln -c Release --nologo
dotnet test Strata.SqlTools.QueryBreakdown.sln -c Release --no-build --nologo
Build succeeds with no new warnings beyond the baseline. Tests stay green. If either fails, fix or revert before continuing to the next tier — incremental builds can mask warning regressions, so re-run with --no-incremental if the warning count looks suspicious.
Scan & verify
Local ./scan-sonar.ps1 is blocked here — the system JRE is 8 and the SonarScanner CLI requires Java 17 (UnsupportedClassVersionError). CI handles the scan: .gitea/workflows/sonarqube.yml runs actions/setup-java@v4 with temurin 17 and uploads on every push and PR. So the path is push → open PR → CI scans → re-query the API to confirm net-down:
$h = @{ Authorization = "Basic $b64" }
Invoke-RestMethod -Uri "$url/api/issues/search?componentKeys=sql-utilities&resolved=false&facets=rules&ps=500" -Headers $h
The targeted rule IDs should each drop to 0 (or near-0 if the fixer left a few cases it couldn't batch-resolve). Commit subject style stays chore(sonar): … (RuleId) — bang (!) if the change is a deliberate breaking rename or visibility shift.