14 - Sonar Static Analysis

Run SonarSource static analysis on the backend solution, generate a reviewable HTML report, and fix issues by severity. Everything runs locally — no SonarQube server required.

What you get

A SARIF report per project produced during every dotnet build, plus a single merged HTML report you can open in any browser. The same Sonar C# rule engine used by SonarQube Community Edition runs as a Roslyn analyzer in-process.

How it is wired

Three things are set up in the backend/ folder:

backend/Directory.Build.propsXML
<Project>
  <PropertyGroup>
    <!-- SARIF v2.1.0 error log so we can collect a machine-readable report -->
    <ErrorLog>$(MSBuildProjectDirectory)\bin\sonar.sarif,version=2.1</ErrorLog>
    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
    <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
    <RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="SonarAnalyzer.CSharp" Version="9.32.0.97167">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>

Prerequisites (one-time)

Python 3.8+ and Node.js. The rest comes from dotnet build.

BashInstall sarif-tools (once per machine)
# sarif-tools is the community-standard SARIF → HTML converter.
# It is what we use instead of spinning up a SonarQube server.
pip install sarif-tools

# Verify
sarif --version
PATH hint (Windows / Python from Microsoft Store)

If sarif isn't found after installing, the Python Scripts folder may not be on PATH. On Windows with Store-installed Python it lives at something like:

C:\Users\<you>\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\Scripts

Either add that to PATH permanently, or prefix the command as shown in the run steps below.

Step 1 — Build (produces SARIF)

Nothing special to run — a normal build already emits sonar.sarif per project thanks to Directory.Build.props. Make sure no instance of MO.Ekyc.Api.exe is running (Visual Studio F5 / dotnet run) before building, otherwise file locks will fail the copy step.

BashBuild the solution
# From repo root
cd backend

# Clean build to be sure all analyzers run
dotnet build MO.Ekyc.sln -c Debug --no-incremental

# Verify SARIF files were written
find src tests -name sonar.sarif -not -path "*/obj/*"

Step 2 — Aggregate into a summary

The Node aggregator merges all per-project SARIF files, skips #pragma warning disable suppressed items, and writes three files into backend/sonar-report/.

BashRun the aggregator
cd backend
node scripts/aggregate-sonar.js

Outputs:

Step 3 — Generate the HTML report

Collect the SARIF files into one folder and run sarif html.

BashGenerate sonar.html
cd backend

# 1. Gather the per-project SARIF files into one folder
mkdir -p sonar-report/sarif
rm -rf sonar-report/sarif/*
find src tests -name sonar.sarif -not -path "*/obj/*" \
  -exec cp --parents {} sonar-report/sarif/ \;

# 2. Convert to a single browsable HTML file
sarif html sonar-report/sarif -o sonar-report/sonar.html

# 3. Open it
start sonar-report/sonar.html      # Windows
Where everything lives

All reports are under backend/sonar-report/:

  • sonar.html — single-file HTML dashboard (open in browser)
  • sarif/ — raw per-project SARIF files (input to sarif-tools)
  • summary.txt, rules.txt, issues.json — text + JSON reports

Full run in one copy-paste

BashEnd-to-end (from backend/)
cd backend

# Build (emits per-project sonar.sarif)
dotnet build MO.Ekyc.sln -c Debug --no-incremental

# Aggregate summary/rules/issues JSON
node scripts/aggregate-sonar.js

# Produce the HTML report
mkdir -p sonar-report/sarif
rm -rf sonar-report/sarif/*
find src tests -name sonar.sarif -not -path "*/obj/*" \
  -exec cp --parents {} sonar-report/sarif/ \;
sarif html sonar-report/sarif -o sonar-report/sonar.html

# Open it
start sonar-report/sonar.html

Reading the report

Severity mapping used during the April 2026 cleanup:

Ready-made fixers

Each script is self-documenting at the top. They are idempotent — safe to re-run.

backend/scripts/Available fixers
aggregate-sonar.js        # Build summary/rules/issues from SARIF
fix-s2139.js              # Remove _logger.LogError(ex,...) before rethrow
fix-s6667.js              # Add `ex` to catch + logger call
fix-cancel-catches.js     # Simplify OperationCanceledException catches
fix-unused-logger.js      # Strip unused ILogger from controllers
fix-unused-field.js       # Strip unused injected service fields
fix-idisposable.js        # Add sealed + GC.SuppressFinalize
run-sonar.ps1             # End-to-end PowerShell driver
Don't edit scripts/fix-*.js casually

The fixers use line/column offsets from the SARIF report combined with bracket-matching. Running them with a stale report produces silent corruption (we've hit this once with a regex edit inside a comment). Always re-run dotnet build to refresh SARIF before running a fixer a second time.

Editor integration (optional)

The analyzer runs live in Rider and Visual Studio because RunAnalyzersDuringLiveAnalysis is on. You'll see the same S2139 / S6667 / CA1822 squiggles as the batch run reports — minus the SARIF file output, which only lands on a full build.

CI tip

In a CI pipeline, fail the build when new error-level findings appear:

BashGate a build on Sonar errors
dotnet build MO.Ekyc.sln -c Release --no-incremental
node backend/scripts/aggregate-sonar.js
if grep -q '"level": "error"' backend/sonar-report/issues.json; then
  echo "Sonar reported error-level findings"
  exit 1
fi

See also