Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
open-pull-requests-limit: 10
commit-message:
prefix: "ci"
include: "scope"
groups:
github-actions:
patterns: ["*"]

- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
open-pull-requests-limit: 10
versioning-strategy: "increase-if-necessary"
commit-message:
prefix: "fix"
prefix-development: "chore"
include: "scope"
69 changes: 69 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions

name: "Build"

on:
pull_request:
push:
branches:
- "main"

permissions:
contents: "read"

jobs:
tests:
name: "Tests"

runs-on: ${{ matrix.operating-system }}

strategy:
matrix:
dependencies: ["lowest", "highest"]
php-version:
- "8.4"
- "8.5"
operating-system: ["ubuntu-latest"]

steps:
- name: "Checkout"
uses: "actions/checkout@v4"

- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"

- name: "Determine composer cache directory"
id: "composer-cache"
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: "Cache dependencies"
uses: "actions/cache@v4"
with:
path: "${{ steps.composer-cache.outputs.dir }}"
key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }}"
restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-"

- name: "Validate composer.json"
run: "composer validate --strict"

- name: "Install lowest dependencies"
if: ${{ matrix.dependencies == 'lowest' }}
run: "composer update --prefer-lowest --prefer-dist --no-interaction --no-progress"

- name: "Install highest dependencies"
if: ${{ matrix.dependencies == 'highest' }}
run: "composer update --prefer-dist --no-interaction --no-progress"

- name: "Tests"
run: "vendor/bin/phpunit"

- name: "Coding standards"
if: ${{ matrix.dependencies == 'highest' }}
run: "vendor/bin/phpcs"

- name: "Static analysis"
if: ${{ matrix.dependencies == 'highest' }}
run: "vendor/bin/phpstan analyse --no-progress"
63 changes: 37 additions & 26 deletions .github/workflows/code_coverage.yaml
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
name: Code Coverage
name: "Code Coverage"

on:
pull_request: null
pull_request:
push:
branches:
- main
- "main"

permissions:
contents: "read"

jobs:
code_coverage:
name: "Code Coverage"

runs-on: "ubuntu-latest"

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
- name: "Checkout"
uses: "actions/checkout@v4"

- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: 8.4
coverage: xdebug
- name: Load dependencies from cache
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v4
coverage: "xdebug"
php-version: "8.5"

- name: "Determine composer cache directory"
id: "composer-cache"
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: "Cache dependencies"
uses: "actions/cache@v4"
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.4-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php8.4-composer-
path: "${{ steps.composer-cache.outputs.dir }}"
key: "coverage-php-8.5-composer-${{ hashFiles('**/composer.json') }}"
restore-keys: "coverage-php-8.5-composer-"

- name: "Install dependencies"
run: "composer update --prefer-dist --no-interaction --no-progress"

- run: composer install --prefer-dist --no-progress --no-suggest
- run: php vendor/bin/phpunit --coverage-clover build/logs/clover.xml
- name: "Tests with coverage"
run: "vendor/bin/phpunit --coverage-clover build/logs/clover.xml"

- uses: codecov/codecov-action@v5
- name: "Upload coverage"
uses: "codecov/codecov-action@v5"
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: build/logs/clover.xml # optional
flags: unittests # optional
name: codecov-umbrella # optional
fail_ci_if_error: true # optional (default = false)
verbose: true # optional (default = false)
token: ${{ secrets.CODECOV_TOKEN }}
files: "build/logs/clover.xml"
flags: "unittests"
fail_ci_if_error: true
31 changes: 0 additions & 31 deletions .github/workflows/coding_standards.yaml

This file was deleted.

30 changes: 0 additions & 30 deletions .github/workflows/phpstan.yaml

This file was deleted.

60 changes: 0 additions & 60 deletions .github/workflows/tests.yaml

This file was deleted.

76 changes: 76 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# CLAUDE.md

## Projekt

`brainbits/phpstan-rules` ist eine PHPStan-Extension (Composer-Typ `phpstan-extension`) mit
zwei strikten Regeln für PHPUnit-Testklassen. Reine Library, keine Anwendung.

- PHP ^8.4. Abhängigkeiten: `phpstan/phpstan` ^2.0, `nette/utils` ^4.0 (php-parser kommt
aus dem PHPStan-PHAR, nicht als eigene Abhängigkeit),
Dev: PHPUnit ^13.4, `phpstan/phpstan-phpunit` ^2.0, `brainbits/phpcs-standard` ^8.
- CI (`.github/workflows/build.yml`): PHP 8.4 und 8.5, jeweils mit `lowest`- und
`highest`-Abhängigkeiten. Bei `highest` laufen zusätzlich phpcs und PHPStan.
`composer validate --strict` muss durchgehen.
- `code_coverage.yaml`: Coverage über Codecov (PHP 8.5, Xdebug). `codecov.yml` erlaubt beim
Projekt-Status 1 % Toleranz.
- `release.yaml`: semantic-release bei Push nach `main` (siehe Release).

## Aufbau

- `src/` – Regeln, Namespace `BrainbitsPhpStan\` (PSR-4).
- `CoversClassExistsRule` – meldet `#[CoversClass(...)]`-Attribute und
`@covers`/`@coversDefaultClass`-Annotationen, die auf eine nicht existierende Klasse
zeigen. Nutzt den `ReflectionProvider` (autowired).
- `CoversClassPresentRule` – verlangt in Unit-Tests `@covers`/`@coversDefaultClass` oder
`#[CoversClass]`/`#[CoversFunction]`/`#[CoversNothing]`. Unit-Test heißt: Klasse erbt
von irgendetwas, Namespace enthält `unitTestNamespaceContainsString` (Default `Unit`),
Klassenname endet auf `Test`.
- `rules.neon` – Service-Registrierung (Tag `phpstan.rules.rule`), Parameter
`brainbits.unitTestNamespaceContainsString` samt `parametersSchema`. Wird über
`extra.phpstan.includes` in `composer.json` automatisch eingebunden. Neue Regeln und
Parameter hier registrieren.
- `tests/` – je Regel ein `RuleTestCase` (Classmap-Autoload, Namespace
`BrainbitsPhpStan\Tests`).
- `tests/Fixture/<Regel>/` – PHP-Dateien, die die Tests analysieren. Fixtures für
`CoversClassPresentRule` unter `Unit/` decken die Namespace-Prüfung ab.
- `phpstan.neon.dist` – analysiert `src/` und `tests/` dieses Repos (Level max,
bleedingEdge, eigene `rules.neon`).
- `phpcs.xml.dist` – prüft `src/` und `tests/` mit dem Brainbits-Standard.

## Befehle

```bash
composer install

vendor/bin/phpunit # Tests
vendor/bin/phpunit --filter testAttributeWithUnitRule # einzelner Test
vendor/bin/phpcs # Coding Standard
vendor/bin/phpstan analyse # Statische Analyse
```

Es gibt keine Composer-Scripts.

## Arbeitsweise

- Beide Regeln arbeiten auf `PhpParser\Node\Stmt\Class_` und prüfen Attribute und
Docblock-Annotationen getrennt. Die Annotationen werden zeilenweise per Regex gelesen;
dieselben Regexe stehen in beiden Regeln – synchron halten.
- Attributnamen werden als voll qualifizierte Strings verglichen
(`PHPUnit\Framework\Attributes\CoversClass`), PHPStan löst die Namen vorher auf.
- Tests prüfen exakte Fehlermeldungen **und Zeilennummern** in den Fixtures. Wer ein
Fixture ändert, verschiebt Zeilen – erwartete Zeilennummern im Test nachziehen.
- Fixtures sind absichtlich von phpcs und PHPStan ausgenommen (sie enthalten gewollt
fehlerhaften Code). Nicht "reparieren".
- Code muss den Brainbits-Standard erfüllen: `declare(strict_types=1);`, importierte
Funktionen (`use function`), finale Klassen, native Typen.
- CI testet auch `--prefer-lowest`, Versionsconstraints also nicht höher setzen als nötig,
aber auch nicht so niedrig, dass die niedrigste Version die Tests bricht.
- Regelverhalten oder Parameter geändert → `README.md` anpassen.
- Abhängigkeiten aktualisiert Dependabot (`.github/dependabot.yml`: GitHub Actions und
Composer).

## Release

semantic-release bei Push nach `main` (`.releaserc`, Tags ohne `v`-Präfix). Commits im Stil
Conventional Commits (`feat:`, `fix:`, `fix(deps):`, `docs:`, `chore:`, `ci:`, `test:`) –
sie bestimmen die Versionsnummer. Entwicklungsbranch ist `main`; `master` ist veraltet.
Loading
Loading