Files
XC_VM/build/rector.php
T
Divarion_D b9de28f624 build: widen the Rector set, fix its cache trap, and quiet the fixerless phpcs sniffs
Rector — enable instanceOf, earlyReturn and if. Each was measured at zero changes
across the analysed tree, so they cost nothing now and only hold the line on new
code. The counts for every set that stays off are recorded next to them, with the
reason, so the decision does not have to be re-derived: typeDeclarations(319) is
the native-type footgun TYPE_AUDIT.md tracks, typeDeclarationDocblocks(86) feeds
that same footgun one step removed because `make cs-fix` derives native types from
docblocks, codingStyle(255) rewrites the `use` imports check-procedural-use depends
on, naming(132) fights the $r-prefix convention and render()-by-name views, and
privatization reads as 0 only because there are almost no final classes yet.

Makefile — `make rector` now passes --clear-cache. Rector's cache key does not
track the rule set, so after editing the config a cached run prints "Rector is
done!" and a newly enabled set looks like a no-op. The measurements above first
came back all-zero for exactly this reason.

phpcs — silence the four sniffs that were still reporting. Every one of them was
checked for a fixer first (none calls addFixable), so `make cs-fix` could never
have cleared them; what was left was 769 warnings no tool can act on:

  NoSilencedErrors(473)  each @ is either deliberate or masking a real error, so
                         they need judging one at a time, with a test each
  CyclomaticComplexity   (201 err + 190 warn) and NestingLevel (2 + 84) — already
                         ratcheted per-function by the CRAP gate, which blocks NEW
                         complexity; the sniffs only restated the old backlog
  PSR1.Files.SideEffects(22)  needs files split, which moves loading and autoload

LineLength(3480) went the same way earlier and is marked as not part of the
ratchet: it has no fixer at all, so "re-enable and fix" would mean splitting those
lines by hand.

Own rules are commented out so they read as toggles; the PSR12-inherited ones
(SideEffects, LineLength) can only be <exclude>d, which the ratchet summary now
says out loud. That summary is updated with every new toggle and its count.

`make cs` is now clean, with 85 sniffs still active — checked, because an empty
report looks the same whether nothing is wrong or everything got muted.
2026-09-22 18:20:45 +03:00

136 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
use Rector\CodeQuality\Rector\BooleanNot\SimplifyDeMorganBinaryRector;
use Rector\CodeQuality\Rector\Equal\UseIdenticalOverEqualWithSameTypeRector;
use Rector\Config\RectorConfig;
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\SafeDeclareStrictTypesRector;
/**
* Rector configuration for XC_VM — stage 2 (class-based trees).
*
* Scope is the PSR-4, class-based trees: Core / Domain / Cli / Infrastructure
* plus Streaming, Public\Controllers and the Ministra classes. Deliberately
* EXCLUDED (see withSkip / withPaths): view templates (Public/Views, short
* tags), procedural front-controllers (Public/stream, Public/admin,
* Ministra/portal.php), the legacy \TMDB library, runtime-installed modules,
* the committed vendor and the streaming hot-path bootstraps.
*
* KNOWN BUG — review every run: several rules drop the parens around an
* assignment-in-condition, assigning the wrong value and flipping the guard.
* Two variants seen (PHPStan catches only some):
* 1. empty-if/else inversion: `if (($k = array_search(...)) === false)`
* -> `if ($k = array_search(...) !== false)`
* 2. De Morgan (now disabled below): `!(($x = f()) && g())`
* -> `!$x = f() || !g()`
* After each `make rector-fix`, grep the diff and restore parens on any hit:
* grep -rnE 'if \(\$[A-Za-z_]+ = .*(!==|===) (false|true|null)\)' src/
* grep -rnE '!\$[A-Za-z_]+ = .+ \|\|' src/
*
* Paths are anchored with __DIR__ (this file lives in build/) so the config
* behaves the same whether invoked from the repo root (make rector) or from
* src/ (composer refactor).
*
* Import-adding rules are intentionally left OFF (the default): the
* check-procedural-use gate relies on positional `use` imports, and we never
* want Rector to reshuffle them.
*
* The "empty-if with else" anti-pattern that motivated this work is handled by
* the built-in RemoveDeadIfForeachForRector (in the deadCode set) — it collapses
* `if (COND) {} else { BODY }` into `if (!COND) { BODY }` and leaves non-empty
* bodies, commented empty bodies and elseif chains untouched. No custom rule is
* needed. To add a project-specific rule later, drop a class under
* tools/rector/src/, wire it via an autoload-dev PSR-4 entry in src/composer.json,
* and register it here with ->withRules([...]).
*/
return RectorConfig::configure()
->withPaths([
__DIR__ . '/../src/Core',
__DIR__ . '/../src/Domain',
__DIR__ . '/../src/Cli',
__DIR__ . '/../src/Infrastructure',
// Stage 2 — class-based trees that were deferred:
__DIR__ . '/../src/Streaming',
__DIR__ . '/../src/Public/Controllers',
__DIR__ . '/../src/Ministra',
])
->withSkip([
// Legacy global \TMDB library — not PSR-4, vendored verbatim.
__DIR__ . '/../src/Infrastructure/Tmdb/lib',
// Ministra procedural front-controller (short tags, injected globals) —
// excluded from PHPStan for the same reason. PortalHandler/PortalHelpers
// (classes, next to it) ARE analysed.
__DIR__ . '/../src/Ministra/portal.php',
// Streaming/web-API hot paths — deliberately excluded from the mechanical
// pass; touched only under review (verified via the dev-container smokes).
__DIR__ . '/../src/Infrastructure/Bootstrap/StreamingRequestBootstrap.php',
__DIR__ . '/../src/Infrastructure/Bootstrap/WebApiBootstrap.php',
__DIR__ . '/../src/Cli/Commands/FanoutBinaryCommand.php',
__DIR__ . '/../src/Cli/Commands/FanoutSyncCommand.php',
// Never touch these even if a path ever widens to include them.
__DIR__ . '/../src/vendor',
'*/tmp/*',
'*/backups/*',
// Behaviour-changing on loosely-typed legacy code — opt in later, per
// file, after review, not as part of the safe mechanical pass:
// - strict_types changes runtime int/string coercion (many (int) casts).
// - == -> === is type-sensitive; the "same type" inference can be wrong.
SafeDeclareStrictTypesRector::class,
UseIdenticalOverEqualWithSameTypeRector::class,
// UNSAFE on assignment-in-condition: negating `!(($x = f()) && g())` this
// rule drops the assignment parens -> `!$x = f() || !g()`, which by PHP
// precedence assigns the WRONG value to $x and flips the guard. It broke
// 49 access-control checks (Authorization::check) in the API wrappers.
// The cosmetic De Morgan wins aren't worth that risk — leave it off.
SimplifyDeMorganBinaryRector::class,
// NOTE: FollowRequireByDirRector (Rector\CodingStyle\Rector\Include_)
// is UNSAFE here and must stay disabled if a future Rector version
// reintroduces it. It rewrites `include "functions.php"` (resolved via
// include_path / CWD at runtime) into `include __DIR__ . "/functions.php"`,
// pinning it to the file's own directory. The admin/reseller
// TableControllers include a legacy bootstrap that does NOT live next to
// them, so __DIR__ pointed at a non-existent path and broke every
// session-authenticated table load. It is not in the current rule set,
// so it is documented rather than listed (an unknown class in withSkip
// would fail config validation).
])
// Safe, behaviour-preserving sets. deadCode carries
// RemoveDeadIfForeachForRector (the empty-if/else collapse); codeQuality
// carries the boolean simplifiers (double-not, De Morgan, != normalisation).
//
// instanceOf / earlyReturn / if were measured at zero changes across the
// analysed tree, so they cost nothing today and only hold the line on new
// code. NB `if` merges conditions with `&&` — the same shape that made
// De Morgan corrupt assignment-in-condition guards, so keep running the two
// KNOWN BUG greps above over its diff.
//
// Deliberately NOT enabled (file counts measured on the same tree):
// - typeDeclarations (319): infers native param/return types — the exact
// footgun that keeps SlevomatCodingStandard.TypeHints.ParameterTypeHint
// disabled in build/phpcs.xml.dist. See TYPE_AUDIT.md.
// - typeDeclarationDocblocks (86): docblocks only, no runtime effect on its
// own — but `make cs-fix` derives native types FROM docblocks, so a wrong
// @param written here becomes a wrong signature on the next style pass.
// - codingStyle (255): touches `use` imports, which the check-procedural-use
// gate depends on staying positional.
// - naming (132): renames variables/params, against the $r-prefix convention
// and the views that receive render() data by variable name.
// - namedArgs (238): cosmetic churn over a quarter of the codebase.
// - privatization (0): zero only because the codebase has almost no `final`
// classes, not because it is structurally safe — its rules would start
// firing on module subclasses of BaseModule and on Reflection-based tests.
->withPreparedSets(
deadCode: true,
codeQuality: true,
instanceOf: true,
earlyReturn: true,
if: true,
);