feat(protocol): expose analyzer quick fixes in scan output #29
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Self-scan (sonar-predictor against itself) | |
| permissions: | |
| contents: read | |
| # Runs the project's OWN scanner — the in-repo daemon, freshly built from this | |
| # branch — against the repository on every PR and on pushes to main. The point | |
| # is CI parity with the local self-scan we run during development: every change | |
| # passes through the same gate, so the bar we apply to others applies to us. | |
| # | |
| # We deliberately do NOT use the previously released bundle from Maven Central. | |
| # SONAR_PREDICTOR_HOME is repointed at the freshly-built exploded distribution | |
| # under target/ so the scan exercises this branch's analyzer code, not | |
| # yesterday's release. | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| jobs: | |
| self-scan: | |
| name: Self-scan | |
| runs-on: ubuntu-latest | |
| env: | |
| # The assembly step packages the ~150 MB distribution bundle (CLI + | |
| # daemon fat jars + 10 analyzer plugins). Maven's default heap is too | |
| # small for that on ubuntu-latest — we'd get 'Execution exception: | |
| # Java heap space' from maven-assembly-plugin:single. 2 GB is plenty | |
| # and well under the runner's ~7 GB available memory. | |
| MAVEN_OPTS: -Xmx2g | |
| steps: | |
| # fetch-depth: 0 keeps the full history available so we can switch to | |
| # `--diff`-style semantics later without re-checking out the repo. | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # JDK 21 is the project's build/runtime target (required by | |
| # sonarlint-analysis-engine 11.x / LTA 2026.1). Temurin is the safe default. | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '21' | |
| # The JS/TS analyzer plugin spawns Node at runtime to lint JS/TS sources, | |
| # so Node must be on PATH when the scan runs (not just at build time). | |
| - name: Set up Node.js 20 | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| # Cache the local Maven repo across runs. Keyed on the pom.xml so a | |
| # dependency change invalidates cleanly; restore-keys lets a partial | |
| # cache hit still seed most of ~/.m2. | |
| - name: Cache Maven repository | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.m2/repository | |
| key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-maven- | |
| # Single-module build. `verify` runs the integration tests AND produces | |
| # the JaCoCo XML report we feed back into the scan as coverage evidence, | |
| # plus the distribution bundle the self-scan invokes below. | |
| - name: Build and test (generates JaCoCo XML + distribution bundle) | |
| run: mvn -B -ntp -Dmaven.test.skip=false clean verify -Dsurefire.failIfNoSpecifiedTests=false | |
| # Derive the project version from pom.xml so SONAR_PREDICTOR_HOME below | |
| # tracks pom bumps without anyone touching CI. | |
| - name: Derive project version | |
| id: version | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(mvn -B -ntp -q -DforceStdout help:evaluate -Dexpression=project.version) | |
| if [ -z "${VERSION}" ]; then | |
| echo "::error::could not derive project.version from pom.xml" | |
| exit 1 | |
| fi | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "Project version: ${VERSION}" | |
| # The actual self-scan. We point bin/sonar at THIS branch's | |
| # freshly-built distribution under target/ (not whatever happens to | |
| # be installed globally), and capture the scan as JSON to | |
| # .sonar-predictor/scan.json via --save. One JaCoCo XML is passed | |
| # in as coverage evidence (single module = one report). | |
| # | |
| # IMPORTANT: the CLI uses three-state exit codes | |
| # 0 = clean (no findings at the floor) | |
| # 1 = issues found (a normal scan outcome, not a failure) | |
| # 2 = tool error (broken input, daemon unreachable, no Java) | |
| # Step success means "the scanner ran". Whether the *result* should | |
| # fail the build is decided by the Quality gate step below. We must | |
| # not let `bash -e` propagate exit-1 from a healthy scan as a job | |
| # failure; we propagate exit code only when it's >= 2. | |
| - name: Run self-scan | |
| run: | | |
| set +e | |
| export SONAR_PREDICTOR_HOME="$(pwd)/target/sonar-predictor-dist-${{ steps.version.outputs.version }}/sonar-predictor" | |
| mkdir -p .sonar-predictor | |
| "$SONAR_PREDICTOR_HOME/bin/sonar" \ | |
| --format json --save .sonar-predictor/scan.json \ | |
| analyze . --coverage target/site/jacoco/jacoco.xml | |
| rc=$? | |
| set -e | |
| echo "Self-scan exit code: $rc (0=clean, 1=issues found, 2+=tool error)" | |
| if [ "$rc" -ge 2 ]; then | |
| echo "::error::Self-scan tool error (exit $rc) — see step log." | |
| exit "$rc" | |
| fi | |
| # Render headline counts into the GitHub job summary so reviewers see | |
| # the scan result inline on the run page without downloading artifacts. | |
| # `// ([.files[]?.issues[]?]|length)` is the fallback path when an older | |
| # JSON shape doesn't carry a top-level issueCount. | |
| - name: Render scan summary | |
| if: always() | |
| run: | | |
| J=.sonar-predictor/scan.json | |
| if [ ! -f "$J" ]; then | |
| echo "## Sonar self-scan" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "Scan JSON not produced — see the **Run self-scan** step log." >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| { | |
| echo "## Sonar self-scan" | |
| echo | |
| echo "| Metric | Value |" | |
| echo "| --- | --- |" | |
| echo "| Total issues | $(jq -r '.issueCount // ([.files[]?.issues[]?]|length)' "$J") |" | |
| echo "| Coverage (line, overall) | $(jq -r '.coverage.overallPercent // "n/a"' "$J")% |" | |
| echo "| Severity | $(jq -rc '[.files[]?.issues[]?.severity]|group_by(.)|map("\(.[0])=\(length)")|join(" ")' "$J") |" | |
| echo "| Type | $(jq -rc '[.files[]?.issues[]?.type]|group_by(.)|map("\(.[0])=\(length)")|join(" ")' "$J") |" | |
| echo "| Warnings | $(jq -r '.warnings // [] | length' "$J") |" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # Always upload the scan JSON, even on failure, so a broken scan is | |
| # still debuggable from the run page. 14-day retention is enough to | |
| # cover a typical PR review cycle without pinning storage forever. | |
| - name: Upload scan JSON | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sonar-scan-${{ github.run_id }} | |
| path: .sonar-predictor/scan.json | |
| retention-days: 14 | |
| # Informational gate. We compute the count of CRITICAL bugs and security | |
| # hotspots and emit a `::warning::` so they show on the PR's Checks tab, | |
| # but we deliberately do NOT `exit 1` yet — first runs are baseline data | |
| # while we work the existing findings down to zero. | |
| # | |
| # TODO: once the backlog is clear, flip this to `exit 1` on any | |
| # CRITICAL bug or security hotspot and rename this step to "Quality gate". | |
| - name: Quality gate (informational only) | |
| if: always() | |
| run: | | |
| J=.sonar-predictor/scan.json | |
| if [ ! -f "$J" ]; then | |
| echo "No scan JSON found — skipping gate." | |
| exit 0 | |
| fi | |
| CRIT=$(jq -r '[.files[]?.issues[]? | select(.severity=="CRITICAL" and .type=="BUG")] | length' "$J") | |
| HOT=$(jq -r '[.files[]?.issues[]? | select(.type=="SECURITY_HOTSPOT")] | length' "$J") | |
| echo "Critical bugs: $CRIT" | |
| echo "Security hotspots: $HOT" | |
| if [ "$CRIT" -gt 0 ] || [ "$HOT" -gt 0 ]; then | |
| echo "::warning::Self-scan found $CRIT critical bug(s) and $HOT security hotspot(s). Gate is informational for now; will enforce later." | |
| fi |