From 480715f66f64bf0c822c353d5f2e253f3a8886f2 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Mon, 26 Jan 2026 12:00:06 -0800 Subject: [PATCH] Add Sonar rule suppressions and update Publish workflow for Codacy rule sync --- .editorconfig | 25 +++++- .github/workflows/Publish.yml | 153 ++++++++++++++++++++++++++++++++-- sonar-suppressions.json | 86 +++++++++++++++++++ 3 files changed, 257 insertions(+), 7 deletions(-) create mode 100644 sonar-suppressions.json diff --git a/.editorconfig b/.editorconfig index eca3c256..576f5f63 100644 --- a/.editorconfig +++ b/.editorconfig @@ -29,11 +29,32 @@ resharper_condition_is_always_true_or_false_highlighting = hint resharper_access_to_modified_closure_highlighting = hint resharper_generic_enumerator_not_disposed_highlighting = hint -dotnet_diagnostic.S3776.severity = none +# Sonar rule suppressions (synced from sonar-suppressions.json) +# See sonar-suppressions.json for detailed justifications +dotnet_diagnostic.S107.severity = none +dotnet_diagnostic.S1144.severity = none dotnet_diagnostic.S1244.severity = none +dotnet_diagnostic.S1944.severity = none +dotnet_diagnostic.S2053.severity = none +dotnet_diagnostic.S2245.severity = none +dotnet_diagnostic.S2259.severity = none +dotnet_diagnostic.S2583.severity = none +dotnet_diagnostic.S2589.severity = none +dotnet_diagnostic.S3236.severity = none +dotnet_diagnostic.S3329.severity = none +dotnet_diagnostic.S3604.severity = none +dotnet_diagnostic.S3655.severity = none +dotnet_diagnostic.S3776.severity = none +dotnet_diagnostic.S3949.severity = none +dotnet_diagnostic.S3966.severity = none +dotnet_diagnostic.S4158.severity = none +dotnet_diagnostic.S4347.severity = none +dotnet_diagnostic.S5773.severity = none +dotnet_diagnostic.S6781.severity = none + +# Other suppressions dotnet_diagnostic.IDE0028.severity = none dotnet_diagnostic.CA1416.severity = none -dotnet_diagnostic.S3236.severity = none dotnet_diagnostic.MA0046.severity = none dotnet_diagnostic.MA0003.severity = suggestion diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml index 996af7f5..ae7adb0a 100644 --- a/.github/workflows/Publish.yml +++ b/.github/workflows/Publish.yml @@ -323,10 +323,42 @@ jobs: IFS=, echo "SONAR_SARIF_PATHS=${paths[*]}" >> "$GITHUB_ENV" + - name: Generate SonarCloud multicriteria from JSON + id: sonar_rules + run: | + # Read rule IDs from sonar-suppressions.json and generate multicriteria args + if [ ! -f "sonar-suppressions.json" ]; then + echo "ERROR: sonar-suppressions.json not found" + exit 1 + fi + + # Extract rule IDs and build multicriteria parameters + rules=$(jq -r '.rules[].id' sonar-suppressions.json) + multicriteria_names="" + multicriteria_args="" + index=1 + + for rule in $rules; do + name="e${index}" + if [ -n "$multicriteria_names" ]; then + multicriteria_names="${multicriteria_names},${name}" + else + multicriteria_names="${name}" + fi + multicriteria_args="${multicriteria_args} /d:sonar.issue.ignore.multicriteria.${name}.ruleKey=csharpsquid:${rule} /d:sonar.issue.ignore.multicriteria.${name}.resourceKey=**/*" + index=$((index + 1)) + done + + echo "Generated multicriteria for $(echo "$rules" | wc -w) rules" + echo "multicriteria_names=$multicriteria_names" >> "$GITHUB_OUTPUT" + echo "multicriteria_args=$multicriteria_args" >> "$GITHUB_OUTPUT" + - name: Begin SonarCloud Analysis if: steps.check_sonar.outputs.skip != 'true' env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + MULTICRITERIA_NAMES: ${{ steps.sonar_rules.outputs.multicriteria_names }} + MULTICRITERIA_ARGS: ${{ steps.sonar_rules.outputs.multicriteria_args }} run: | args=( "/k:mihakralj_QuanTAlib" @@ -339,11 +371,12 @@ jobs: "/d:sonar.exclusions=**/TestResults/**/*,**/bin/**/*,**/obj/**/*,**/*.html,**/coverage/**/*,**/CoverageReport/**/*,**/*.md,**/*.css,**/docs/**/*,**/archive/**/*,**/notebooks/**/*" "/d:sonar.test.exclusions=**Tests.cs,**/obj/**/*,**/bin/**/*" "/d:sonar.scanner.scanAll=false" - "/d:sonar.issue.ignore.multicriteria=e1" - "/d:sonar.issue.ignore.multicriteria.e1.ruleKey=csharpsquid:S107" - "/d:sonar.issue.ignore.multicriteria.e1.resourceKey=**/*" + "/d:sonar.issue.ignore.multicriteria=$MULTICRITERIA_NAMES" ) + # Add the dynamically generated multicriteria args + eval "args+=($MULTICRITERIA_ARGS)" + if [ -n "${SONAR_SARIF_PATHS:-}" ]; then args+=("/d:sonar.sarifReportPaths=$SONAR_SARIF_PATHS") fi @@ -670,10 +703,120 @@ jobs: fi # ============================================================================== - # 7) Codacy Upload (SARIF + Coverage) + # 7) Codacy Rule Sync (disable Sonar rules from sonar-suppressions.json) + # ============================================================================== + Codacy_Rule_Sync: + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ env.CHECKOUT_REF }} + + - name: Check for Codacy API token + id: check_api_token + env: + CODACY_API_TOKEN: ${{ secrets.CODACY_API_TOKEN }} + run: | + if [ -z "${CODACY_API_TOKEN:-}" ]; then + echo "CODACY_API_TOKEN not set. Skipping Codacy rule sync." + echo "To enable automatic rule sync, create a CODACY_API_TOKEN secret" + echo "with an API token from https://app.codacy.com/account/apiTokens" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Sync Sonar rules to Codacy + if: steps.check_api_token.outputs.skip != 'true' + env: + CODACY_API_TOKEN: ${{ secrets.CODACY_API_TOKEN }} + run: | + set -euo pipefail + + # Configuration + PROVIDER="gh" + ORGANIZATION="mihakralj" + REPOSITORY="QuanTAlib" + TOOL_UUID="8954dff3-f19c-429c-ac76-c45fa5e73b62" # SonarC# tool UUID + API_BASE="https://app.codacy.com/api/v3" + + if [ ! -f "sonar-suppressions.json" ]; then + echo "ERROR: sonar-suppressions.json not found" + exit 1 + fi + + # Extract rule IDs from JSON + rules=$(jq -r '.rules[].id' sonar-suppressions.json) + rule_count=$(echo "$rules" | wc -w) + echo "Found $rule_count rules to disable in Codacy" + + # Function to disable a pattern + disable_pattern() { + local pattern_id="$1" + local response + + echo "Disabling pattern: $pattern_id" + + response=$(curl -s -w "\n%{http_code}" -X PATCH \ + "${API_BASE}/organizations/${PROVIDER}/${ORGANIZATION}/repositories/${REPOSITORY}/tools/${TOOL_UUID}/patterns/${pattern_id}" \ + -H "api-token: ${CODACY_API_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"enabled": false}') + + http_code=$(echo "$response" | tail -1) + body=$(echo "$response" | head -n -1) + + if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then + echo " ✓ Successfully disabled $pattern_id" + return 0 + elif [ "$http_code" = "404" ]; then + echo " ⚠ Pattern $pattern_id not found (may not exist in this tool)" + return 0 + else + echo " ✗ Failed to disable $pattern_id (HTTP $http_code)" + echo " Response: $body" + return 1 + fi + } + + # Process each rule + success_count=0 + fail_count=0 + + for rule in $rules; do + if disable_pattern "$rule"; then + success_count=$((success_count + 1)) + else + fail_count=$((fail_count + 1)) + fi + done + + echo "" + echo "=== Codacy Rule Sync Summary ===" + echo "Total rules: $rule_count" + echo "Successful: $success_count" + echo "Failed: $fail_count" + + if [ $fail_count -gt 0 ]; then + echo "" + echo "Some rules failed to sync. This may be due to:" + echo "- Pattern IDs not matching Codacy's internal naming" + echo "- API rate limiting" + echo "- Permission issues with the API token" + echo "" + echo "Consider manually verifying rules at:" + echo "https://app.codacy.com/gh/${ORGANIZATION}/${REPOSITORY}/patterns" + fi + + # ============================================================================== + # 8) Codacy Upload (SARIF + Coverage) # ============================================================================== Codacy_Upload: - needs: [ReSharper_Analysis, Snyk_Scan, Semgrep_Scan, Sonar_Analysis] + needs: [ReSharper_Analysis, Snyk_Scan, Semgrep_Scan, Sonar_Analysis, Codacy_Rule_Sync] runs-on: ubuntu-latest timeout-minutes: 10 permissions: diff --git a/sonar-suppressions.json b/sonar-suppressions.json new file mode 100644 index 00000000..78fcefb2 --- /dev/null +++ b/sonar-suppressions.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Single source of truth for Sonar rule suppressions across SonarCloud, Codacy, and local builds", + "rules": [ + { + "id": "S107", + "reason": "Methods should not have too many parameters - high-performance SIMD methods require multiple parameters for zero-allocation patterns" + }, + { + "id": "S1144", + "reason": "Unused private types or members should be removed - false positives on reflection/serialization patterns" + }, + { + "id": "S1244", + "reason": "Floating point numbers should not be tested for equality - intentional exact comparisons in financial calculations" + }, + { + "id": "S1944", + "reason": "Inappropriate casts should not be made - false positives on generic constraint patterns" + }, + { + "id": "S2053", + "reason": "Hashes should include unpredictable salt - not applicable to non-cryptographic hashing" + }, + { + "id": "S2245", + "reason": "Using pseudorandom number generators is security-sensitive - GBM uses deterministic seeds for reproducibility" + }, + { + "id": "S2259", + "reason": "Null pointers should not be dereferenced - false positives with nullable reference types" + }, + { + "id": "S2583", + "reason": "Conditionally executed code should be reachable - false positives on defensive programming patterns" + }, + { + "id": "S2589", + "reason": "Boolean expressions should not be gratuitous - false positives on explicit clarity patterns" + }, + { + "id": "S3236", + "reason": "Caller information arguments should not be provided explicitly - intentional for testing/debugging" + }, + { + "id": "S3329", + "reason": "Cipher Block Chaining IVs should be unpredictable - not applicable to non-cryptographic code" + }, + { + "id": "S3604", + "reason": "Member initializer values should not be redundant - null! is intentional for nullable reference types in Quantower adapters" + }, + { + "id": "S3655", + "reason": "Empty nullable value should not be accessed - false positives with HasValue checks" + }, + { + "id": "S3776", + "reason": "Cognitive Complexity of methods should not be too high - complex algorithms require complex implementations" + }, + { + "id": "S3949", + "reason": "Calculations should not overflow - false positives on checked arithmetic contexts" + }, + { + "id": "S3966", + "reason": "Objects should not be disposed more than once - false positives on defensive dispose patterns" + }, + { + "id": "S4158", + "reason": "Empty collections should not be accessed or iterated - false positives on lazy initialization" + }, + { + "id": "S4347", + "reason": "Secure random number generators should not output predictable values - deterministic seeds for reproducibility" + }, + { + "id": "S5773", + "reason": "Types allowed to be deserialized should be restricted - not applicable to this codebase" + }, + { + "id": "S6781", + "reason": "JWT tokens should not be created using insecure secrets - not applicable to this codebase" + } + ] +} \ No newline at end of file