# yaml-language-server: $schema=https://json.schemastore.org/github-action.json # permissions: # - contents: read # Required for checking out repository # - security-events: write # Required for uploading SARIF results --- name: 'C# Lint Check' description: 'Runs linters like StyleCop or dotnet-format for C# code style checks.' author: 'Ismo Vuorinen' branding: icon: 'code' color: 'blue' inputs: dotnet-version: description: 'Version of .NET SDK to use.' required: false token: description: 'GitHub token for authentication' required: false default: '' outputs: lint_status: description: 'Overall lint status (success/failure)' value: ${{ steps.dotnet-format.outcome == 'success' && 'success' || 'failure' }} errors_count: description: 'Number of formatting errors found' value: ${{ steps.dotnet-format.outputs.errors_count || '0' }} warnings_count: description: 'Number of formatting warnings found' value: ${{ steps.dotnet-format.outputs.warnings_count || '0' }} runs: using: composite steps: - name: Validate Inputs id: validate shell: sh env: DOTNET_VERSION: ${{ inputs.dotnet-version }} run: | set -eu # Validate .NET version format if provided if [ -n "$DOTNET_VERSION" ]; then if ! printf '%s' "$DOTNET_VERSION" | grep -qE '^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$'; then echo "::error::Invalid dotnet-version format: '$DOTNET_VERSION'. Expected format: X.Y or X.Y.Z (e.g., 7.0, 8.0.100)" exit 1 fi # Check for reasonable version range (prevent malicious inputs) major_version=$(echo "$DOTNET_VERSION" | cut -d'.' -f1) if [ "$major_version" -lt 3 ] || [ "$major_version" -gt 20 ]; then echo "::error::Invalid dotnet-version: '$DOTNET_VERSION'. Major version should be between 3 and 20" exit 1 fi fi echo "Input validation completed successfully" - name: Checkout Repository uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta with: token: ${{ inputs.token || github.token }} - name: Detect .NET SDK Version id: detect-dotnet-version shell: sh env: DEFAULT_VERSION: "${{ inputs.dotnet-version || '7.0' }}" run: | set -eu # Function to validate version format validate_version() { version=$1 case "$version" in [0-9]* | [0-9]*\.[0-9]* | [0-9]*\.[0-9]*\.[0-9]*) return 0 ;; *) return 1 ;; esac } # Function to clean version string clean_version() { printf '%s' "$1" | sed 's/^[vV]//' | tr -d ' \n\r' } detected_version="" # Parse .tool-versions file if [ -f .tool-versions ]; then echo "Checking .tool-versions for dotnet..." >&2 version=$(awk '/^dotnet[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "") if [ -n "$version" ]; then version=$(clean_version "$version") if validate_version "$version"; then echo "Found .NET version in .tool-versions: $version" >&2 detected_version="$version" fi fi fi # Parse Dockerfile if [ -z "$detected_version" ] && [ -f Dockerfile ]; then echo "Checking Dockerfile for dotnet..." >&2 version=$(grep -iF "FROM" Dockerfile | grep -F "dotnet:" | head -1 | \ sed -n -E "s/.*dotnet:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "") if [ -n "$version" ]; then version=$(clean_version "$version") if validate_version "$version"; then echo "Found .NET version in Dockerfile: $version" >&2 detected_version="$version" fi fi fi # Parse devcontainer.json if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then echo "Checking devcontainer.json for dotnet..." >&2 if command -v jq >/dev/null 2>&1; then version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*dotnet:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "") if [ -n "$version" ]; then version=$(clean_version "$version") if validate_version "$version"; then echo "Found .NET version in devcontainer: $version" >&2 detected_version="$version" fi fi else echo "jq not found; skipping devcontainer.json parsing" >&2 fi fi # Parse global.json if [ -z "$detected_version" ] && [ -f global.json ]; then echo "Checking global.json..." >&2 if command -v jq >/dev/null 2>&1; then version=$(jq -r '.sdk.version // empty' global.json 2>/dev/null || echo "") if [ -n "$version" ]; then version=$(clean_version "$version") if validate_version "$version"; then echo "Found .NET version in global.json: $version" >&2 detected_version="$version" fi fi else echo "jq not found; skipping global.json parsing" >&2 fi fi # Use default version if nothing detected if [ -z "$detected_version" ]; then detected_version="$DEFAULT_VERSION" echo "Using default .NET version: $detected_version" >&2 fi # Set output printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT" echo "Final detected .NET version: $detected_version" >&2 - name: Setup .NET SDK uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1 with: dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }} cache: true cache-dependency-path: '**/packages.lock.json' - name: Install dotnet-format shell: sh run: | set -eu dotnet tool install --global dotnet-format --version 7.0.1 - name: Run dotnet-format id: dotnet-format shell: sh run: | set -eu # Initialize counters errors_count=0 warnings_count=0 if ! dotnet format --check --report sarif --report-file dotnet-format.sarif; then # Parse SARIF file to count errors and warnings if it exists if [ -f "dotnet-format.sarif" ]; then if command -v jq >/dev/null 2>&1; then errors_count=$(jq '[.runs[].results[]? | select(.level == "error" or (.level // "error") == "error")] | length' dotnet-format.sarif 2>/dev/null || echo "0") warnings_count=$(jq '[.runs[].results[]? | select(.level == "warning")] | length' dotnet-format.sarif 2>/dev/null || echo "0") fi fi echo "errors_count=$errors_count" >> $GITHUB_OUTPUT echo "warnings_count=$warnings_count" >> $GITHUB_OUTPUT echo "::error::Code formatting issues found. Check the SARIF report for details." exit 1 else echo "errors_count=0" >> $GITHUB_OUTPUT echo "warnings_count=0" >> $GITHUB_OUTPUT fi - name: Upload SARIF Report uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 with: sarif_file: dotnet-format.sarif