Files
anki/docs/sonarcloud.md
Fernando Lins f4c684a96e fix(ci): fetch PR base branch so SonarCloud diffs only changed files (#5659)
## Linked issue

Fixes #5657

## Summary / motivation

SonarCloud PR analysis derives the changed-file set from git and
requires the target branch to be present in the local repository. The
scan checks out only the fork's head history (`fetch-depth: 0` on the
head ref), so the base branch was never fetched. Without a base to diff
against, the scanner attributed every analysed file to the PR as new
code, e.g. 977 files on the single-file change in #5641
(`qt/aqt/preferences.py`), and recurring noise in `tools/`.

This adds a step that fetches the target branch from the trusted
upstream repository (`github.repository`, never the fork) and pins it
locally under the name passed as `sonar.pullrequest.base`, so new code
is measured against the real PR diff. It does not disable any rule or
exclude any file.

The isolation boundary is preserved: the upstream branch is read as data
only, nothing is built or executed, the trusted config and
no-fork-code-execution constraints are unchanged, and the Quality Gate
stays informative.

## Steps to reproduce

1. Open a fork PR that changes a single file (e.g. #5641, base `main`).
2. Let CI complete and the SonarCloud workflow run.
3. Observe SonarCloud reporting new-code issues across hundreds of
unchanged files (~977) instead of only the changed file.

## How to test
### Details

Reproduced the scanner's git-based new-code computation locally against
the exact commits from #5641:

- head = `1e7914a`
- base branch `main` fetched from upstream → `merge-base` = `bdeec0877`,
which matches the PR's `baseRefOid` reported by the GitHub API.
- `git diff --name-only <merge-base> <head>` → **only**
`qt/aqt/preferences.py`.

The new step also logs the analysed commit, base repo/branch, base
commit and merge-base, and fails closed if the base reference cannot be
resolved (no silent fallback to a missing base). YAML parse and the
cog-generated `docs/sonarcloud.md` sync were verified.

## Before / after behavior

- Before: fork/PR scan treats all indexed files as new code (977 files
for a
  one-file PR).
- After: only the files actually changed vs the upstream base are
considered
  new code.
2026-09-22 11:45:03 +03:00

4.1 KiB

SonarCloud analysis

SonarCloud provides informative static-analysis and coverage results. Its Quality Gate is not a required merge check, so findings guide review without blocking maintainers. Automatic Analysis must remain disabled because this project uses CI-based analysis.

For the test commands and coverage thresholds enforced by CI, see Testing and Coverage.

Analysis matrix

Event Static analysis Coverage in CI Coverage in SonarCloud
Internal pull request Yes Yes Yes
Push to main or release/** Yes Yes Yes
Fork pull request Yes Yes No

Fork pull requests still run the complete test coverage and coverage-regression checks in the unprivileged CI workflow. Their reports are not imported by the privileged SonarCloud workflow, so SonarCloud does not calculate a coverage metric for those pull requests.

The fork scan still reports static bugs, vulnerabilities, security hotspots, and code smells. Scanner-side Clippy and software composition analysis are disabled; Clippy remains part of the regular CI checks.

How it runs

.github/workflows/sonar.yml runs after the complete CI workflow succeeds. Keeping it separate prevents SONAR_TOKEN from being available to jobs that build or test contributor code.

  1. The workflow validates the pull request number, state, head SHA, repositories, and branches against the GitHub API. Invalid or stale context fails closed instead of becoming a branch analysis.
  2. It checks out the exact commit tested by CI. Fork source is read only by the scanner and is never built, installed, or used as tooling.
  3. It replaces sonar-project.properties with the default branch's trusted copy, preventing a fork from changing the server or enabling build tools. The analysed version comes from the same trusted checkout of .version, so that new code is measured since the previous release rather than since a constant version.
  4. For a pull request, it fetches the target branch from the trusted upstream repository and pins it locally under the name passed as sonar.pullrequest.base. SonarCloud derives the changed-file set from git, so without the correct upstream base reference it may attribute unrelated changes to the pull request. A fork's local base reference may be outdated. The step logs the analysed commit, base repository and branch, base commit, and merge-base, and fails closed if the reference cannot be resolved.
  5. For internal changes, it downloads the coverage artifact from the triggering CI run. For forks, report paths are cleared and all sources are excluded only from the coverage calculation.
  6. Only the scanner step receives SONAR_TOKEN.

Trust boundary

This workflow has access to the base repository's `SONAR_TOKEN`. Never add a build, test, package install, PR-provided configuration, local Action from the PR, or restored CI cache to it. In particular, do not invoke `just`, `cargo`, `npm`, `pip`, or repository scripts in this workflow.
  • Keep Automatic Analysis disabled and SonarCloud checks non-required.
  • Restrict SONAR_TOKEN to analysis of this project.
  • Keep scanner-side Clippy and software composition analysis disabled.
  • Do not import artifacts produced by a fork into the privileged scan.

The scanner necessarily parses untrusted source when analyzing a fork. These constraints prevent direct execution of contributor code and reduce the token's exposure, but they do not eliminate vulnerabilities in third-party Actions or analyzers.