rust-diff-analyzer 1.2.0

Semantic analyzer for Rust PR diffs that distinguishes production code from test code
Documentation
name: 'Rust PR Diff Analyzer'
description: 'Analyze and limit PR size by distinguishing production code from test code using Rust AST analysis'
author: 'RAprogramm'

branding:
  icon: 'code'
  color: 'orange'

inputs:
  max_prod_units:
    description: 'Maximum number of production units allowed'
    required: false
    default: '30'
  max_weighted_score:
    description: 'Maximum weighted score allowed'
    required: false
    default: '100'
  max_prod_lines:
    description: 'Maximum production lines added'
    required: false
    default: ''
  fail_on_exceed:
    description: 'Fail the action if limits are exceeded'
    required: false
    default: 'true'
  output_format:
    description: 'Output format (github, json, human)'
    required: false
    default: 'github'
  config_file:
    description: 'Path to configuration file'
    required: false
    default: ''
  post_comment:
    description: 'Post analysis as PR comment'
    required: false
    default: 'false'
  update_comment:
    description: 'Update existing comment instead of creating new'
    required: false
    default: 'true'

outputs:
  prod_functions_changed:
    description: 'Number of production functions changed'
    value: ${{ steps.analyze.outputs.prod_functions_changed }}
  prod_structs_changed:
    description: 'Number of production structs changed'
    value: ${{ steps.analyze.outputs.prod_structs_changed }}
  prod_other_changed:
    description: 'Number of other production units changed'
    value: ${{ steps.analyze.outputs.prod_other_changed }}
  test_units_changed:
    description: 'Number of test units changed'
    value: ${{ steps.analyze.outputs.test_units_changed }}
  prod_lines_added:
    description: 'Lines added in production code'
    value: ${{ steps.analyze.outputs.prod_lines_added }}
  prod_lines_removed:
    description: 'Lines removed from production code'
    value: ${{ steps.analyze.outputs.prod_lines_removed }}
  test_lines_added:
    description: 'Lines added in test code'
    value: ${{ steps.analyze.outputs.test_lines_added }}
  test_lines_removed:
    description: 'Lines removed from test code'
    value: ${{ steps.analyze.outputs.test_lines_removed }}
  weighted_score:
    description: 'Weighted score of changes'
    value: ${{ steps.analyze.outputs.weighted_score }}
  exceeds_limit:
    description: 'Whether limits were exceeded'
    value: ${{ steps.analyze.outputs.exceeds_limit }}

runs:
  using: 'composite'
  steps:
    - name: Get PR diff
      id: get_diff
      shell: bash
      run: |
        if [ "${{ github.event_name }}" = "pull_request" ]; then
          git fetch origin ${{ github.base_ref }} --depth=1
          git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr_diff.txt
        else
          git diff HEAD~1 > /tmp/pr_diff.txt
        fi

    - name: Setup Rust
      uses: actions-rust-lang/setup-rust-toolchain@v1
      with:
        toolchain: stable

    - name: Build analyzer
      shell: bash
      run: |
        cd ${{ github.action_path }}
        cargo build --release

    - name: Run analysis
      id: analyze
      shell: bash
      run: |
        ARGS="--diff-file /tmp/pr_diff.txt"
        ARGS="$ARGS --max-units ${{ inputs.max_prod_units }}"
        ARGS="$ARGS --max-score ${{ inputs.max_weighted_score }}"
        ARGS="$ARGS --format ${{ inputs.output_format }}"
        ARGS="$ARGS --no-fail"

        if [ -n "${{ inputs.max_prod_lines }}" ]; then
          ARGS="$ARGS --max-lines ${{ inputs.max_prod_lines }}"
        fi

        if [ -n "${{ inputs.config_file }}" ]; then
          ARGS="$ARGS --config ${{ inputs.config_file }}"
        fi

        OUTPUT=$(${{ github.action_path }}/target/release/rust-diff-analyzer $ARGS)

        echo "$OUTPUT"

        if [ "${{ inputs.output_format }}" = "github" ]; then
          echo "$OUTPUT" >> $GITHUB_OUTPUT
        fi

        if echo "$OUTPUT" | grep -q "exceeds_limit=true"; then
          echo "EXCEEDS_LIMIT=true" >> $GITHUB_ENV
        else
          echo "EXCEEDS_LIMIT=false" >> $GITHUB_ENV
        fi

    - name: Post PR comment
      if: inputs.post_comment == 'true' && github.event_name == 'pull_request'
      shell: bash
      env:
        GH_TOKEN: ${{ github.token }}
      run: |
        ARGS="--diff-file /tmp/pr_diff.txt"
        ARGS="$ARGS --max-units ${{ inputs.max_prod_units }}"
        ARGS="$ARGS --max-score ${{ inputs.max_weighted_score }}"
        ARGS="$ARGS --format comment"
        ARGS="$ARGS --no-fail"

        if [ -n "${{ inputs.max_prod_lines }}" ]; then
          ARGS="$ARGS --max-lines ${{ inputs.max_prod_lines }}"
        fi

        if [ -n "${{ inputs.config_file }}" ]; then
          ARGS="$ARGS --config ${{ inputs.config_file }}"
        fi

        COMMENT=$(${{ github.action_path }}/target/release/rust-diff-analyzer $ARGS)
        MARKER="<!-- rust-diff-analyzer-comment -->"
        PR_NUMBER=${{ github.event.pull_request.number }}

        if [ "${{ inputs.update_comment }}" = "true" ]; then
          EXISTING=$(gh api repos/${{ github.repository }}/issues/${PR_NUMBER}/comments \
            --jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" | head -1)

          if [ -n "$EXISTING" ]; then
            gh api repos/${{ github.repository }}/issues/comments/${EXISTING} \
              -X PATCH -f body="$COMMENT"
            echo "Updated existing comment"
          else
            gh pr comment $PR_NUMBER --body "$COMMENT"
            echo "Created new comment"
          fi
        else
          gh pr comment $PR_NUMBER --body "$COMMENT"
          echo "Created new comment"
        fi

    - name: Fail if limits exceeded
      if: env.EXCEEDS_LIMIT == 'true' && inputs.fail_on_exceed == 'true'
      shell: bash
      run: |
        echo "::error::Production code changes exceed configured limits"
        exit 1