name: CI

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
  cancel-in-progress: true

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  cargo-fmt:
    name: "cargo fmt"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: "Install Rust toolchain"
        run: rustup component add rustfmt
      - run: cargo fmt --all --check

  cargo-clippy:
    name: "cargo clippy"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: "Install Rust toolchain"
        run: |
          rustup component add clippy
      - uses: Swatinem/rust-cache@v2
      - run: cargo clippy --workspace --all-targets --all-features -- -D warnings

  cargo-clippy-wasm:
    name: "cargo clippy (wasm)"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: "Install Rust toolchain"
        run: |
          rustup component add clippy
          rustup target add wasm32-unknown-unknown
      - uses: Swatinem/rust-cache@v2
      - run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings

  cargo-test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    name: "cargo test | ${{ matrix.os }}"
    steps:
      - uses: actions/checkout@v3
      - name: "Install Rust toolchain"
        run: rustup show
      - uses: Swatinem/rust-cache@v2
      - run: cargo install cargo-insta
      - run: pip install black[d]==23.1.0
      - name: "Run tests (Ubuntu)"
        if: ${{ matrix.os == 'ubuntu-latest' }}
        run: |
          cargo insta test --all --all-features --delete-unreferenced-snapshots
          git diff --exit-code
      - name: "Run tests (Windows)"
        if: ${{ matrix.os == 'windows-latest' }}
        shell: bash
        run: |
          cargo insta test --all --all-features
          git diff --exit-code
      - run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
      # Check for broken links in the documentation.
      - run: cargo doc --all --no-deps
        env:
          # Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
          RUSTDOCFLAGS: "-D warnings"
      - uses: actions/upload-artifact@v3
        if: ${{ matrix.os == 'ubuntu-latest' }}
        with:
          name: ruff
          path: target/debug/ruff

  cargo-test-wasm:
    runs-on: ubuntu-latest
    name: "cargo test (wasm)"
    steps:
      - uses: actions/checkout@v3
      - name: "Install Rust toolchain"
        run: rustup target add wasm32-unknown-unknown
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: "npm"
          cache-dependency-path: playground/package-lock.json
      - uses: jetli/wasm-pack-action@v0.4.0
      - uses: Swatinem/rust-cache@v2
      - name: "Run wasm-pack"
        run: |
          cd crates/ruff_wasm
          wasm-pack test --node

  scripts:
    name: "test scripts"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: "Install Rust toolchain"
        run: rustup component add rustfmt
      - uses: Swatinem/rust-cache@v2
      - run: ./scripts/add_rule.py --name DoTheThing --prefix PL --code C0999 --linter pylint
      - run: cargo check
      - run: cargo fmt --all --check
      - run: |
          ./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
          ./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test
      - run: cargo check
      - run: cargo fmt --all --check

  typos:
    name: "spell check"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: crate-ci/typos@master
        with:
          files: .

  ecosystem:
    name: "ecosystem"
    runs-on: ubuntu-latest
    needs: cargo-test
    # Only runs on pull requests, since that is the only we way we can find the base version for comparison.
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: "3.11"

      - uses: actions/download-artifact@v3
        name: Download Ruff binary
        id: ruff-target
        with:
          name: ruff
          path: target/debug

      - uses: dawidd6/action-download-artifact@v2
        name: Download base results
        with:
          name: ruff
          branch: ${{ github.event.pull_request.base.ref }}
          check_artifacts: true

      - name: Run ecosystem check
        run: |
          # Make executable, since artifact download doesn't preserve this
          chmod +x ruff ${{ steps.ruff-target.outputs.download-path }}/ruff

          scripts/check_ecosystem.py ruff ${{ steps.ruff-target.outputs.download-path }}/ruff | tee ecosystem-result
          cat ecosystem-result > $GITHUB_STEP_SUMMARY

          echo ${{ github.event.number }} > pr-number

      - uses: actions/upload-artifact@v3
        name: Upload PR Number
        with:
          name: pr-number
          path: pr-number

      - uses: actions/upload-artifact@v3
        name: Upload Results
        with:
          name: ecosystem-result
          path: ecosystem-result

  cargo-udeps:
    name: "cargo udeps"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: "Install nightly Rust toolchain"
        # Only pinned to make caching work, update freely
        run: rustup toolchain install nightly-2023-03-30
      - uses: Swatinem/rust-cache@v2
      - name: "Install cargo-udeps"
        uses: taiki-e/install-action@cargo-udeps
      - name: "Run cargo-udeps"
        run: |
          unused_dependencies=$(cargo +nightly-2023-03-30 udeps > unused.txt && cat unused.txt | cut -d $'\n' -f 2-)
          if [ -z "$unused_dependencies" ]; then
            echo "No unused dependencies found" > $GITHUB_STEP_SUMMARY
            exit 0
          else
            echo "Found unused dependencies" > $GITHUB_STEP_SUMMARY
            echo '```console' >> $GITHUB_STEP_SUMMARY
            echo "$unused_dependencies" >> $GITHUB_STEP_SUMMARY
            echo '```' >> $GITHUB_STEP_SUMMARY
            exit 1
          fi

  pre-commit:
    name: "pre-commit"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: "3.11"
      - name: "Install Rust toolchain"
        run: rustup show
      - uses: Swatinem/rust-cache@v2
      - name: "Install pre-commit"
        run: pip install pre-commit
      - name: "Cache pre-commit"
        uses: actions/cache@v3
        with:
            path: ~/.cache/pre-commit
            key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
      - name: "Run pre-commit"
        run: |
          echo '```console' > $GITHUB_STEP_SUMMARY
          # Enable color output for pre-commit and remove it for the summary
          SKIP=cargo-fmt,clippy,dev-generate-all pre-commit run --all-files --show-diff-on-failure --color=always | \
            tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> $GITHUB_STEP_SUMMARY) >&1
          exit_code=${PIPESTATUS[0]}
          echo '```' >> $GITHUB_STEP_SUMMARY
          exit $exit_code
