name: Lint

on:
  pull_request: {}

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

jobs:
  repo-integrity:
    name: Repo integrity
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          fetch-depth: 300
      - name: Fetch base branch ref
        run: |
          git fetch origin --depth=300 "+${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }}"
      - name: File permissions
        if: ${{ !cancelled() }}
        run: "! grep -Ev '^644' <(git ls-files src/ tests/ | xargs stat '--format=%a %n')"
      - name: File encodings
        if: ${{ !cancelled() }}
        run: "! grep -E -e 'UTF-[^8]' -e 'UTF-[^ ]+ \\(with BOM\\)' <(git ls-files src/ tests/ | xargs stat '--format=%a %n')"
      - name: Line terminators
        if: ${{ !cancelled() }}
        run: "! grep 'with CRLF line terminators' <(git ls-files | file -nNf-)"
      - name: No unicode bidirectional control characters
        if: ${{ !cancelled() }}
        run: "! git grep -EIn $'[\\u2066\\u2067\\u2068\\u2069\\u202A\\u202B\\u202C\\u202D\\u202E]'"
      - name: Linear git history
        if: ${{ !cancelled() }}
        run: |
          git log -z --format="%H%n%s" --min-parents=2 "origin/${{ github.base_ref }}..HEAD" \
            | awk -v RS='\0' -F'\n' \
              -f .github/workflows/scripts/lint-git-common.awk \
              -f .github/workflows/scripts/lint-git-linear-history.awk
      - name: Git commit messages
        if: ${{ !cancelled() }}
        run: |
          git log -z --format="%H%n%B" "origin/${{ github.base_ref }}..HEAD" \
            | awk -v RS='\0' -F'\n' \
              -f .github/workflows/scripts/lint-git-common.awk \
              -f .github/workflows/scripts/lint-git-commit-msg-format.awk

  style:
    name: Code style
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
      - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
        with:
          python-version: "3.14"
      - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      - name: ruff check
        if: ${{ !cancelled() }}
        run: uv run --frozen --group lint ruff check .
      - name: ruff format
        if: ${{ !cancelled() }}
        run: uv run --frozen --group lint ruff format --diff .

  typing:
    name: Typing
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
      - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
        with:
          python-version: "3.14"
      - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      - name: ty
        if: ${{ !cancelled() }}
        run: uv run --frozen --group typing ty check
      - name: mypy
        if: ${{ !cancelled() }}
        run: uv run --frozen --group typing mypy --no-incremental
      - name: mypy docs
        if: ${{ !cancelled() }}
        run: uv run --frozen --group typing mypy --python-version=3.12 --no-incremental docs
