diff --git a/CLAUDE.md b/CLAUDE.md index 625ad04c..ba7c3a39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,6 +19,9 @@ Design document: `/dev-docs/design-document.md` - `composer analyse` — Larastan static analysis at level 6 with accept-all baseline. New errors beyond the baseline must be fixed before merge. See `/dev-docs/LARASTAN.md`. +- `composer rector` — Rector dry-run for modernisation suggestions. + See `/dev-docs/RECTOR.md`. Apply only in scoped sprints, never + automatically. ## Repository layout diff --git a/api/.gitignore b/api/.gitignore index 0dcf67c3..4c038f7b 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -18,6 +18,7 @@ /public/storage /storage/*.key /storage/app/phpstan-tmp +/storage/app/rector-cache /storage/pail /vendor Homestead.json diff --git a/api/composer.json b/api/composer.json index 54c1a17b..9ea71d31 100644 --- a/api/composer.json +++ b/api/composer.json @@ -18,6 +18,7 @@ "spatie/laravel-permission": "^7.2" }, "require-dev": { + "driftingly/rector-laravel": "^2.0", "fakerphp/faker": "^1.23", "larastan/larastan": "^3.0", "laravel/pail": "^1.2.2", @@ -25,7 +26,8 @@ "laravel/sail": "^1.41", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.6", - "phpunit/phpunit": "^11.5.3" + "phpunit/phpunit": "^11.5.3", + "rector/rector": "^2.0" }, "autoload": { "psr-4": { @@ -59,6 +61,9 @@ "analyse": "vendor/bin/phpstan analyse --memory-limit=2G", "analyse:baseline": "vendor/bin/phpstan analyse --generate-baseline --memory-limit=2G", "analyse:clear-cache": "vendor/bin/phpstan clear-result-cache", + "rector": "vendor/bin/rector process --dry-run --memory-limit=2G", + "rector:apply": "vendor/bin/rector process --memory-limit=2G", + "rector:clear-cache": "vendor/bin/rector --clear-cache", "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi" diff --git a/api/composer.lock b/api/composer.lock index 0e6723f1..62ee0fbe 100644 --- a/api/composer.lock +++ b/api/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4c61afc38c9aea624f684146a9b0719a", + "content-hash": "0bf117afd2eabe5e7f1c5576276666cb", "packages": [ { "name": "bacon/bacon-qr-code", @@ -7580,6 +7580,42 @@ } ], "packages-dev": [ + { + "name": "driftingly/rector-laravel", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/driftingly/rector-laravel.git", + "reference": "3c1c13f335b3b4d1a1f944a8ea194020044871ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/3c1c13f335b3b4d1a1f944a8ea194020044871ed", + "reference": "3c1c13f335b3b4d1a1f944a8ea194020044871ed", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "rector/rector": "^2.2.7", + "webmozart/assert": "^1.11 || ^2.0" + }, + "type": "rector-extension", + "autoload": { + "psr-4": { + "RectorLaravel\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Rector upgrades rules for Laravel Framework", + "support": { + "issues": "https://github.com/driftingly/rector-laravel/issues", + "source": "https://github.com/driftingly/rector-laravel/tree/2.3.0" + }, + "time": "2026-04-08T10:52:44+00:00" + }, { "name": "fakerphp/faker", "version": "v1.24.1", @@ -8975,6 +9011,66 @@ ], "time": "2026-02-18T12:37:06+00:00" }, + { + "name": "rector/rector", + "version": "2.4.2", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/e645b6463c6a88ea5b44b17d3387d35a912c7946", + "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.48" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.4.2" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2026-04-16T13:07:34+00:00" + }, { "name": "sebastian/cli-parser", "version": "3.0.2", @@ -10138,6 +10234,68 @@ } ], "time": "2025-11-17T20:03:58+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.3.0" + }, + "time": "2026-04-11T10:33:05+00:00" } ], "aliases": [], diff --git a/api/rector.php b/api/rector.php new file mode 100644 index 00000000..a5755d83 --- /dev/null +++ b/api/rector.php @@ -0,0 +1,40 @@ +withPaths([ + __DIR__ . '/app', + __DIR__ . '/database', + __DIR__ . '/tests', + ]) + ->withSkip([ + __DIR__ . '/bootstrap/cache', + __DIR__ . '/storage', + __DIR__ . '/vendor', + ]) + // PHP language-level upgrades up to current target + ->withPhpSets(php82: true) + + // Quality + safety rule sets (low-risk for adoption) + ->withSets([ + SetList::CODE_QUALITY, + SetList::DEAD_CODE, + SetList::EARLY_RETURN, + SetList::TYPE_DECLARATION, + SetList::PRIVATIZATION, + ]) + + // Laravel-specific rule sets + ->withSets([ + LaravelSetList::LARAVEL_CODE_QUALITY, + LaravelSetList::LARAVEL_COLLECTION, + ]) + + ->withCache( + cacheDirectory: __DIR__ . '/storage/app/rector-cache', + ); diff --git a/dev-docs/BACKLOG.md b/dev-docs/BACKLOG.md index b73e3750..4a4d0b28 100644 --- a/dev-docs/BACKLOG.md +++ b/dev-docs/BACKLOG.md @@ -886,5 +886,86 @@ baseline; volledige test suite groen. --- +## Rector application sprints + +Rector is geïnstalleerd en geconfigureerd op PHP 8.2 + safe quality ++ Laravel code-quality rule sets. Dry-run rapporteert **487 +rule-applications over 357 files** verdeeld over ~35 distinct rules. +Zie `/dev-docs/RECTOR.md` voor werkmodel. Per-ruleset sprints +hieronder — elke sprint beperkt zich tot één scope, past toe, +verifieert tests + Larastan, regenereert waar nodig. + +### TECH-RECTOR-01 — DEAD_CODE sprint + +**Priority:** Middel +**Scope:** `SetList::DEAD_CODE` over app/, database/, tests/. +**Estimate:** Top 13 unused-variable removals via +`RemoveUnusedVariableAssignRector`, plus verwante dead-code rules. +Exact totaal: ~30-50 changes. +**Completion gate:** `composer rector:apply` clean voor deze set; +test suite groen; Larastan baseline geregenereerd en kleiner (of +gelijk). Commit per sub-directory indien >50 wijzigingen. + +**Approach:** +- Tijdelijk in rector.php alleen DEAD_CODE aanzetten, andere sets + uitcommentariseren. +- `composer rector` om diff te reviewen voor zekerheid. +- `composer rector:apply`. +- `composer test` + `composer analyse`. +- Herstel rector.php naar volledige config. +- Commit. + +### TECH-RECTOR-02 — TYPE_DECLARATION sprint + +**Priority:** Middel (hoogste volume — 174+ changes) +**Scope:** `SetList::TYPE_DECLARATION`. +**Estimate:** ~174+ changes — top drivers: +`AddClosureVoidReturnTypeWhereNoReturnRector` (103), +`AddArrowFunctionReturnTypeRector` (71), plus +`AddArrayFunctionClosureParamTypeRector` en kleinere. +**Completion gate:** zie TECH-RECTOR-01. **Extra**: deze set lost +waarschijnlijk veel `missingType.*` errors in Larastan baseline op — +regenereer en commit gereduceerde phpstan-baseline.neon mee. + +### TECH-RECTOR-03 — LARAVEL_CODE_QUALITY + LARAVEL_COLLECTION sprint + +**Priority:** Middel +**Scope:** `LaravelSetList::LARAVEL_CODE_QUALITY` + +`LaravelSetList::LARAVEL_COLLECTION`. +**Estimate:** ~80 changes — `AppToResolveRector` (51), +`DispatchToHelperFunctionsRector` (8), +`EloquentOrderByToLatestOrOldestRector` (7), +`CarbonToDateFacadeRector` (4), plus collection-idioms. +**Completion gate:** zie TECH-RECTOR-01. + +**Approach:** +- Splits in twee commits (één per set) als het totaal te groot is + om in één review te laten passen. + +### TECH-RECTOR-04 — CODE_QUALITY + EARLY_RETURN + PRIVATIZATION + +**Priority:** Laag +**Scope:** resterende quality-sets (~80 changes). +**Estimate:** `ConvertStaticToSelfRector` (34), +`ReadOnlyClassRector` (27), `ReturnBinaryOrToEarlyReturnRector` (16), +`ClosureToArrowFunctionRector` (9), etc. +**Completion gate:** zie TECH-RECTOR-01. + +### TECH-RECTOR-05 — Laravel modernisation sprint + +**Priority:** Laag +**Scope:** review en selectief enable van +`LaravelLevelSetList::UP_TO_LARAVEL_*` in rector.php. +**Estimate:** onbekend tot per-set dry-runs zijn bekeken. +**Completion gate:** per-set applicatie als gescoopte commits. + +### TECH-RECTOR-CI — CI integration + +**Priority:** Laag +**Scope:** `composer rector` (dry-run) als PR-comment-surface. Apply +blijft handmatig. + +--- + _Laatste update: April 2026_ _Voeg nieuwe items toe met prefix: ARCH-, COMM-, OPS-, VOL-, ART-, FORM-, SUP-, DIFF-, APPS-, TECH-, UX-_ diff --git a/dev-docs/RECTOR.md b/dev-docs/RECTOR.md new file mode 100644 index 00000000..0aed1fba --- /dev/null +++ b/dev-docs/RECTOR.md @@ -0,0 +1,94 @@ +# Rector (automated refactoring) + +Modernisation tool for the Crewli backend. Provides automated +refactoring against PHP-version and Laravel-version rule sets. + +## Running locally + +- `composer rector` — DRY-RUN. Lists proposed changes, modifies + nothing. Default mode for safety. Exit code is 2 when suggestions + exist, 0 when the codebase is clean — this is Rector's convention + and NOT an error state. +- `composer rector:apply` — APPLIES changes. Use only inside a + scoped reduction sprint with a planned set of rule-applications. +- `composer rector:clear-cache` — clear Rector cache. + +Memory: 2G default. Bump to 4G if OOM. + +## The dry-run-then-apply model + +Rector configuration in `rector.php` defines the rule sets that +SHOULD apply. `composer rector` reports what WOULD change without +modifying code. `composer rector:apply` actually performs the +changes. + +### Rules + +1. **Default workflow is dry-run.** Never run `composer rector:apply` + outside a scoped reduction sprint. Mass-applying produces + unreviewable PRs. +2. **One rule-set per sprint.** Pick `SetList::DEAD_CODE` (or + similar) for one PR. Apply, review, regenerate baselines, + commit. Then move to the next set. +3. **Run all tests + Larastan after each apply.** Rector is + deterministic but rule-set interactions can produce surprises + (e.g., `TYPE_DECLARATION` may surface new Larastan errors that + were previously hidden by dynamic typing). + +## Currently configured rule sets + +PHP language: +- `withPhpSets(php82: true)` — language features up to PHP 8.2 + +Quality: +- `SetList::CODE_QUALITY` +- `SetList::DEAD_CODE` +- `SetList::EARLY_RETURN` +- `SetList::TYPE_DECLARATION` +- `SetList::PRIVATIZATION` + +Laravel: +- `LaravelSetList::LARAVEL_CODE_QUALITY` +- `LaravelSetList::LARAVEL_COLLECTION` + +**Not yet enabled** (deferred to specific reduction sprints): +- `LaravelLevelSetList::UP_TO_LARAVEL_*` — disruptive bulk upgrades +- `SetList::NAMING` — reformats variable names; high churn +- `SetList::INSTANCEOF` — substantial logic changes + +Add a set during a reduction sprint by editing `rector.php` and +running `composer rector` (dry-run) before applying. + +## Adding skip-rules + +If a specific rule produces incorrect changes for the project, add +to `withSkip([...])` in `rector.php`: + +```php +->withSkip([ + SomeSpecificRule::class => [ + __DIR__ . '/app/path/to/file.php', + ], +]) +``` + +Document the skip with a code comment in `rector.php` explaining +why the rule is bypassed for that path. + +## Relationship to Larastan + +Different concerns: +- **Larastan**: "this code is wrong" (types, null-safety, missing + methods) +- **Rector**: "this code can be modernised" (PHP version idioms, + Laravel idioms, dead code, early-return patterns) + +Both run on the same codebase. After a Rector apply-sprint, +regenerate the Larastan baseline — Rector changes often resolve +Larastan errors automatically. + +## CI integration + +Not enabled yet. When added, `composer rector` (dry-run) becomes +a non-blocking PR comment that surfaces suggested modernisations. +Apply happens manually, never automatically in CI.