diff --git a/README.ru.md b/README.ru.md index 5b75fe7..7d7d2d6 100644 --- a/README.ru.md +++ b/README.ru.md @@ -1,4 +1,5 @@ # rasuvaeff/rector-datetime-immutable + [![Stable Version](https://img.shields.io/packagist/v/rasuvaeff/rector-datetime-immutable.svg)](https://packagist.org/packages/rasuvaeff/rector-datetime-immutable) [![Total Downloads](https://img.shields.io/packagist/dt/rasuvaeff/rector-datetime-immutable.svg)](https://packagist.org/packages/rasuvaeff/rector-datetime-immutable) [![Build](https://img.shields.io/github/actions/workflow/status/rasuvaeff/rector-datetime-immutable/build.yml?branch=master)](https://github.com/rasuvaeff/rector-datetime-immutable/actions) @@ -8,10 +9,10 @@ [![License](https://img.shields.io/packagist/l/rasuvaeff/rector-datetime-immutable.svg)](LICENSE.md) [English version](README.md) -[Rector](https://getrector.com) rules that migrate mutable `DateTime` to -`DateTimeImmutable` — и **автоматическое исправление потерянных мутаций**, создаваемых миграцией -, классическая тихая ошибка, при которой `$date->modify('+1 day');` выбрасывает новый экземпляр -: +Правила [Rector](https://getrector.com), мигрирующие мутабельный `DateTime` в +`DateTimeImmutable` — и **автоматически чинящие потерянные мутации**, которые +создаёт миграция: классическую тихую ошибку, когда `$date->modify('+1 day');` +выбрасывает новый экземпляр: ```php // before — mutable construction, in-place mutation @@ -22,65 +23,77 @@ $deadline->modify('+1 month'); $deadline = new \DateTimeImmutable('2026-01-01'); $deadline = $deadline->modify('+1 month'); ``` -PHPStan (уровень 4) и Psalm *report* игнорировали результаты мутатора DateTimeImmutable -; этот пакет — это часть, которая **исправляет их массово** во время миграции -. - - > Используете помощника по программированию с искусственным интеллектом? [llms.txt](llms.txt) имеет компактную ссылку - > которую можно передать в качестве контекста. @@ЛИНИЯ@@ -## ТЛ;ДР -Два способа выполнить миграцию: - - | Путь | Как | - |---|---| - | **Оболочка CLI** (рекомендуется) | `vendor/bin/rector-datetime-immutable src` — граничная предполетная проверка, миграция к конвергенции и проход диагностики в одной команде; см. [Миграция одной командой](#one-command-migration) | - | **Руководство `rector.php`** | зарегистрировать правила самостоятельно; см. [Ручная настройка Rector](#manual-rector-setup) | - - **Предупреждение при ручной настройке:** один запуск Rector не может одновременно выполнить миграцию и восстановление — запускайте - `vendor/bin/rectorprocess` **пока он не сообщит об отсутствии изменений** (обычно дважды), - в противном случае потерянные мутации, созданные при первом проходе, останутся в коде. Обертка - сделает это за вас. @@ЛИНИЯ@@ + +PHPStan (level 4) и Psalm *сообщают* про проигнорированные результаты +`DateTimeImmutable`-мутаторов; этот пакет — то, что **чинит их массово** во +время миграции. + +> Используете AI-ассистента? [llms.txt](llms.txt) содержит компактный +> API-справочник, который можно передать как контекст. + +## TL;DR + +Два способа запустить миграцию: + +| Способ | Как | +|---|---| +| **CLI-обёртка** (рекомендуется) | `vendor/bin/rector-datetime-immutable src` — boundary-предполёт, миграция до сходимости и диагностический проход одной командой; см. [Миграция одной командой](#миграция-одной-командой) | +| **Ручной `rector.php`** | зарегистрируйте правила сами; см. [Ручная настройка Rector](#ручная-настройка-rector) | + +**Предупреждение для ручной настройки:** один запуск Rector не может одновременно +мигрировать и чинить — запускайте `vendor/bin/rector process` **пока он не +сообщит об отсутствии изменений** (обычно дважды), иначе потерянные мутации, +созданные первым проходом, останутся в коде. Обёртка делает это за вас. + ## Оглавление -- [Требования](#requirements) - - [Установка](#install) - - [Использование](#использование) - - [Миграция одной командой](#one-command-migration) - - [Предварительный просмотр](#dry-run-preview) - - [Выход CI](#ci-output) - - [Разрешение результатов предполетной проверки](#resolve-preflight-findings) - - [Совместная миграция столбцов Doctrine](#doctrine-columns-co-migration) - - [Ручная настройка Rector](#manual-rector-setup) - - [`MutableDateTimeBoundaryRector`](#mutabledatetimeboundaryrector) - - [`DateTimeImmutableRector`](#datetimeimmutablerector) - - [`LostDateTimeMutationRector`](#lostdatetimemutationrector) - - [Маркеры](#маркеры) - - [Безопасность](#security) - - [Примеры](#examples) - - [Разработка](#development) - - [Лицензия](#лицензия) + +- [Требования](#требования) +- [Установка](#установка) +- [Использование](#использование) + - [Миграция одной командой](#миграция-одной-командой) + - [Предпросмотр dry-run](#предпросмотр-dry-run) + - [Вывод CI](#вывод-ci) + - [Разрешение находок предполёта](#разрешение-находок-предполёта) + - [Совместная миграция столбцов Doctrine](#совместная-миграция-столбцов-doctrine) + - [Ручная настройка Rector](#ручная-настройка-rector) + - [`MutableDateTimeBoundaryRector`](#mutabledatetimeboundaryrector) + - [`DateTimeImmutableRector`](#datetimeimmutablerector) + - [`LostDateTimeMutationRector`](#lostdatetimemutationrector) + - [Маркеры](#маркеры) +- [Безопасность](#безопасность) +- [Примеры](#примеры) +- [Разработка](#разработка) +- [Лицензия](#лицензия) ## Требования + - PHP 8.3 - 8.5 для запуска правил - - `rector/rector` ^2.5 - - `webmozart/assert` ^1.11 || ^2.0 - - `proc_open` включен при использовании оболочки конвергенции — доступен в сборке PHP - по умолчанию, если хост не отключит его через `disable_functions` +- `rector/rector` ^2.5 +- `webmozart/assert` ^1.11 || ^2.0 +- `proc_open` включён при использовании обёртки сходимости — доступен в + дефолтной сборке PHP, если только хост не отключил его через `disable_functions` ## Установка + ```bash composer require --dev rasuvaeff/rector-datetime-immutable ``` + ## Использование + ### Миграция одной командой -Установленный двоичный файл Composer сначала запускает предварительную проверку изменяемой границы только для чтения, - многократно применяет миграцию по умолчанию до полного прохождения подтверждения, затем - запускает `LostDateTimeMutationRector` в `MODE_REPORT` без изменения файлов: + +Установленный Composer-бинарник сначала запускает read-only предполёт по +мутабельным границам, затем многократно применяет миграцию по умолчанию до +чистого подтверждающего прохода, после чего запускает `LostDateTimeMutationRector` +в `MODE_REPORT`, ничего не меняя в файлах: ```bash vendor/bin/rector-datetime-immutable src ``` -Команда редактирует выбранные пути. Сначала выполните или спрячьте несвязанную работу. - Типичный вывод: + +Команда редактирует выбранные пути. Сначала закоммитьте или спрячьте несвязанную +работу. Типичный вывод: ```text Preflight: no mutable DateTime boundaries found. @@ -91,22 +104,24 @@ Converged after 2 change-producing pass(es). Diagnostic pass: no manual review cases found. Summary: 14 file(s) changed across 2 change-producing pass(es); 0 manual review case(s). ``` -Если предварительная проверка обнаруживает собственный, унаследованный, абстрактный/интерфейсный или вызываемый поставщиком объект -, параметр которого принимает DateTime, но отклоняет DateTimeImmutable, или параметр метода -, который передает свойство, которое миграция сохраняет как изменяемое, он печатает записи - `file:line` плюс подсказку по разрешению для каждой категории поиска, завершает работу с кодом - `2` и не изменяет файлы. Тот же выход используется после конвергенции, когда отчет - о потерянной мутации обнаруживает случай, который не может быть назначен безопасно. - - | Выход | Значение | - |---|---| - | `0` | миграция совпала, и дел, выполняемых вручную, не осталось | - | `1` | Ошибка ректора/процесса/JSON | - | `2` | предварительная блокированная миграция или ручная проверка после миграции остается | - | `3` | миграция не сошлась в пределах пропуска | - | `64` | неверные аргументы оболочки | - - Полезные опции: + +Если предполёт находит native, унаследованный, abstract/interface или +vendor-callable, чей параметр принимает `DateTime`, но отвергает +`DateTimeImmutable`, либо параметр метода, питающий свойство, которое миграция +сохраняет мутабельным, он печатает записи `file:line` плюс resolution-подсказку +по каждой категории находок, выходит с кодом `2` и не меняет файлы. Тот же +exit-код используется после сходимости, когда отчёт о потерянных мутациях +находит кейс, который нельзя безопасно назначить. + +| Exit | Значение | +|---|---| +| `0` | миграция сошлась, ручных кейсов не осталось | +| `1` | сбой Rector/process/JSON | +| `2` | предполёт заблокировал миграцию либо осталась ручная проверка после миграции | +| `3` | миграция не сошлась в пределах лимита проходов | +| `64` | некорректные аргументы обёртки | + +Полезные опции: ```bash vendor/bin/rector-datetime-immutable --dry-run src # full preview, no writes @@ -121,60 +136,69 @@ vendor/bin/rector-datetime-immutable \ --report-config=rector-report.php \ src ``` -Упакованные значения по умолчанию: `config/preflight.php`, `config/migration.php` и -`config/report.php`. Используйте пользовательские конфигурации для пропусков, специфичных для проекта, поэтапные параметры - или `ALLOW_SUBCLASS`. @@ЛИНИЯ@@ -### Предварительный просмотр пробного прогона -`--dry-run` копирует пути во временное рабочее пространство, запускает там весь поток - — предполетную проверку, конвергенцию, диагностический проход — печатает все потенциальные различия - с путями, сопоставленными с оригиналами, и не изменяет ни одного файла проекта. Коды выхода - сохраняют свое значение, поэтому предварительный просмотр также сообщает вам, чем закончится реальный запуск -. Объявления вне скопированных путей (классы поставщиков, родительские каталоги -, которые вы не передали) по-прежнему считываются из исходных файлов; прогон записи - остается авторитетным. @@ЛИНИЯ@@ -### выход CI -`--format=github` сохраняет результаты, полученные человеком, и дополнительно выдает - `::error file=…,line=…::…` аннотации рабочего процесса для предполетных блокировщиков и - `::warning …` для случаев проверки вручную, поэтому PR миграции отображает каждое обнаружение - в строке. - - `--format=json` подавляет повествование и печатает один машиночитаемый объект - на стандартный вывод: `status` (`clean`, `blocked`, `manual-review`, - `not-converged`, `acknowledged`), `exitCode`, `passes` для каждого прохода`, - `changedFiles` и Результаты `preflight`/`manualReview`/`acknowledged` как - `{файл, строка, сообщение, категория}`, где `category` — это одно из - `requires-datetime`, `feeds-mutable-property`, `lost-mutation`, `iagnostic`. - При `--dry-run` объект также содержит потенциальные `diffs`. @@ЛИНИЯ@@ -### Решение предполетных предполетных выводов -| Нахождение | Разрешение | - |---|---| - | `параметр $x передает изменяемое свойство $y` | отметьте включающий метод `@mutable-datetime` — его подпись и связанные аргументы места вызова остаются изменяемыми — выполните совместную миграцию столбцов ORM с помощью `--doctrine-columns` или сначала перенесите контракт хранения | - | `параметр $x требует DateTime` | перепишите вызов в API, безопасный для DateTimeImmutable, или просмотрите поток и подтвердите его | - - `@mutable-datetime` в методе **calling** не заглушает поиск - `requires DateTime`: маркер сохраняет собственный контракт этого метода, -, в то время как поиск указывает на вызываемый собственный/вендорный/унаследованный параметр. Сама миграция - сохраняет изменяемые значения, связанные с таким вызываемым объектом с помощью простых - назначений, поэтому после проверки потока подтвердите это: + +Упакованные дефолты — `config/preflight.php`, `config/migration.php` и +`config/report.php. Используйте кастомные конфиги для проектных skip'ов, +постадийных опций или `ALLOW_SUBCLASS`. + +### Предпросмотр dry-run + +`--dry-run` копирует пути во временное рабочее пространство, прогоняет там весь +поток — предполёт, сходимость, диагностический проход — печатает все потенциальные +diff'ы с путями, отмапленными обратно на оригиналы, и не меняет ни одного +проектного файла. Exit-коды сохраняют смысл, поэтому предпросмотр также +сообщает, чем закончился бы реальный запуск. Объявления вне скопированных +путей (vendor-классы, родителя в директориях, которые вы не передали) всё равно +читаются из своих оригинальных файлов; write-прогон остаётся авторитетным. + +### Вывод CI + +`--format=github` сохраняет человекочитаемый вывод и дополнительно эммитит +workflow-аннотации `::error file=…,line=…::…` для предполётных блокировок и +`::warning …` для кейсов ручной проверки, поэтому в migration-PR каждая находка +видна инлайн. + +`--format=json` подавляет повествование и печатает один машиночитаемый объект +в stdout: `status` (`clean`, `blocked`, `manual-review`, `not-converged`, +`acknowledged`), `exitCode`, `passes` по каждому проходу, `changedFiles` и +находки `preflight`/`manualReview`/`acknowledged` как `{file, line, message, +category}`, где `category` — одно из `requires-datetime`, `feeds-mutable-property`, +`lost-mutation`, `diagnostic`. При `--dry-run` объект также несёт потенциальные +`diffs`. + +### Разрешение находок предполёта + +| Находка | Решение | +|---|---| +| `parameter $x feeds mutable property $y` | пометьте охватывающий метод `@mutable-datetime` — его сигнатура и связанные аргументы call-site остаются мутабельными — ко-мигрируйте ORM-столбцы через `--doctrine-columns`, либо сначала мигрируйте storage-контракт | +| `parameter $x requires DateTime` | перепишите вызов на `DateTimeImmutable`-безопасный API, либо ревьюните flow и acknowledge'ните его | + +`@mutable-datetime` на **вызывающем** методе не глушит находку +`requires DateTime`: маркер сохраняет собственный контракт метода, тогда как +находка указывает на вызываемый native/vendor/inherited-параметр. Сама миграция +держит значения, подключённые к такому callable простыми присваиваниями, +мутабельными, поэтому после ревью flow acknowledge'ните это: ```bash vendor/bin/rector-datetime-immutable --acknowledge-boundaries src ``` -При этом над каждым вызовом границы пишется самодокументируемый комментарий и повторно выполняется - предполетная проверка: + +Это пишет самодокументирующий комментарий над каждым boundary-call и +перепрогоняет предполёт: ```php // @mutable-datetime-boundary: parameter $object requires DateTime date_modify($moment, '+1 hour'); ``` -Оператор, содержащий `@mutable-datetime-boundary`, пропускается во всех дальнейших предварительных проверках - — проверка живет в коде и выдерживает повторные запуски. Находки - типа `feeds mutable property` **никогда** не подтверждаются автоматически: если отключить - их, это позволит миграции нарушить назначение свойств во время выполнения, поэтому - они сохраняют свои собственные разрешения, указанные выше. - Пропуск на уровне файла через пользовательскую предполетную конфигурацию остается доступным как - грубая альтернатива: +Оператор с `@mutable-datetime-boundary` пропускается всеми дальнейшими +предполётами — ревью живёт в коде и переживает перезапуски. Находки вида +`feeds mutable property` **никогда** не acknowledge'ятся автоматически: +заглушить их значило бы позволить миграции сломать присваивание свойства в +runtime, поэтому у них остаются собственные разрешения выше. + +Скип на уровне файла через кастомный предполёт-конфиг остаётся грубой +альтернативой: ```php // rector-preflight.php @@ -195,18 +219,23 @@ return RectorConfig::configure() ], ]); ``` + ```bash vendor/bin/rector-datetime-immutable --preflight-config=rector-preflight.php src ``` -После сходимости тот же выход `2` сообщает об утраченных мутациях, которые режим исправления не может безопасно назначить - — разрешите их, назначив результат мутатора самостоятельно - (`$date = $date->modify(...)`). @@ЛИНИЯ@@ -### Совместная миграция столбцов доктрины -По умолчанию элементы, сопоставленные с ORM, сохраняются. `--doctrine-columns` (опция - `DOCTRINE_COLUMNS` в обоих правилах) выбирает совместную миграцию столбцов с сопоставлением атрибутов -: свойство, его методы доступа и подключенные параметры конструктора - мигрируют вместе с сопоставлением, которое перемещается в собственный неизменяемый вариант DBAL - — та же схема базы данных, неизменяемая гидратация. @@ЛИНИЯ@@ + +После сходимости тот же exit `2` сообщает потерянные мутации, которые fix-режим +не может безопасно назначить, — разрешите их, присвоив результат мутатора сами +(`$date = $date->modify(...)`). + +### Совместная миграция столбцов Doctrine + +По умолчанию ORM-mapped-члены сохраняются. `--doctrine-columns` (опция +`DOCTRINE_COLUMNS` обоих правил) включают совместную миграцию столбцов с +attribute-mapping'ом: свойство, его accessors и связанные параметры конструктора +мигрируют вместе с mapping'ом, который переезжает на нативный иммутабельный +DBAL-вариант — та же схема БД, иммутабельная гидратация. + ```php #[ORM\Column(type: 'datetime')] // → type: 'datetime_immutable' private \DateTime $expiresAt; // → private \DateTimeImmutable $expiresAt; @@ -214,14 +243,17 @@ private \DateTime $expiresAt; // → private \DateTimeImmutable $ #[ORM\Column(type: Types::DATETIME_MUTABLE)] // → Types::DATETIME_IMMUTABLE #[ORM\Column] // no type: Doctrine infers it from the PHP type ``` -Покрытые сопоставления: `datetime`, `date`, `time`, `datetimetz` как строковые литералы - или соответствующие константы `Types::*_MUTABLE`, а также столбцы без аргумента `type` -. Строки пользовательских типов, выражения динамического типа, аргументы позиционного атрибута - и аннотации docblock `@ORM\Column` остаются сохраненными. Требуется - `doctrine/dbal` ≥ 2,6 (собственные типы `*_immutable`). Просмотрите код жизненного цикла -, в котором были изменены даты объекта — диагностический проход сообщает об этом как об утраченных мутациях -. @@ЛИНИЯ@@ -### Ручная настройка ректора + +Покрытые mapping'и: `datetime`, `date`, `time`, `datetimetz` как строковые +литералы или соответствующие `Types::*_MUTABLE`-константы, плюс столбцы без +аргумента `type`. Кастомные строки типов, динамические выражения типа, +позиционные аргументы атрибутов и docblock-аннотации `@ORM\Column` остаются +сохранёнными. Требуется `doctrine/dbal` ≥ 2.6 (нативные `*_immutable`-типы). +Проверьте lifecycle-код, который мутировал даты сущности на месте — диагностический +проход сообщит о них как о потерянных мутациях. + +### Ручная настройка Rector + ```php // rector.php ormColumn = $param;`, включая ветки `??`/ternary -): миграция такого параметра гарантирует `TypeError` при назначении свойства -. Решите проблему, пометив метод `@mutable-datetime` (его подпись - и подключенные аргументы места вызова остаются изменяемыми), путем совместной миграции столбцов ORM - с `--doctrine-columns` или путем миграции самого контракта хранения -. - - Операторы, содержащие комментарий `@mutable-datetime-boundary`, пропускаются как - уже проверенные граничные вызовы. Опция `MODE` правила выбирает `report` - (по умолчанию — прикрепить diff-маркеры `@todo`) или `acknowledge` (запишите комментарий - `@mutable-datetime-boundary` над каждым вызовом границы; используется - CLI `--acknowledge-boundaries`). Результаты фида никогда не записываются в режиме подтверждения -. @@ЛИНИЯ@@ + +Сообщает про аргументы, текущие в стабильные callable, чей объявленный параметр +принимает `DateTime`, но отвергает `DateTimeImmutable`. Стабильные callable — +это native PHP-функции, vendor-функции/методы, interface- или abstract-методы, а +также методы, ограниченные предком или помеченные `@mutable-datetime`. Другие +локальные конкретные callable не сообщаются, потому что их объявления мигрируют +вместе со своими call-site'ами. + +Анализ поддерживает позиционные, именованные и variadic-аргументы в функциях, +instance/static-методах и конструкторах. CLI запускает это правило как +обязательный dry-run перед изменением файлов. + +Правило также сообщает про параметры методов, питающие свойство, которое +миграция сохраняет мутабельным — ORM-столбцы, объявления `@mutable-datetime`, +унаследованные свойства (`$this->ormColumn = $param;`, включая `??`/ternary-ветки): +миграция такого параметра гарантирует `TypeError` на присваивании свойства. +Решение — пометить метод `@mutable-datetime` (его сигнатура и связанные +call-site-аргументы тогда остаются мутабельными), ко-мигрировать ORM-столбцы +через `--doctrine-columns` либо мигрировать сам storage-контракт. + +Операторы с комментарием `@mutable-datetime-boundary` пропускаются как уже +проверенные boundary-call'ы. Опция `MODE` правила выбирает `report` (по +умолчанию — навешивать `@todo` diff-маркеры) или `acknowledge` (писать +комментарий `@mutable-datetime-boundary` над каждым boundary-call'ом; +используется CLI'ным `--acknowledge-boundaries`). Feed-находки никогда не +пишутся в acknowledge-режиме. + ### `DateTimeImmutableRector` -Переносит конструкцию DateTime и объявления конкретных типов в - DateTimeImmutable. - - | Вариант | По умолчанию | Что это позволяет | - |---|---|---| - | `КОНСТРУКТОРЫ` | `правда` | `new \DateTime(...)`, общие статические фабрики, включая `createFromTimestamp()`, и две процедурные `date_create*()` | - | `ПОДСКАЗКИ` | `правда` | `\DateTime` в именованных функциях, методах, замыканиях, стрелочных функциях и методах перечислений (включая типы, допускающие значение NULL, и типы объединения) | - | `НЕДВИЖИМОСТЬ` | `правда` | `\DateTime` в типизированных свойствах и расширенных параметрах конструктора | - | `ALLOW_SUBCLASS` | `ложь` | переписать `class X расширяет \DateTime` на `extends \DateTimeImmutable` (рискованно — разрывы мутаций на месте в нисходящем направлении; пара с `LostDateTimeMutationRector`) | - | `DOCTRINE_COLUMNS` | `ложь` | совместная миграция столбцов Doctrine с сопоставлением атрибутов вместе с их типом сопоставления (см. «Совместная миграция столбцов Doctrine») | - - Миграция также сохраняет целостность файла: - - - типы докблоков `@var`/`@param`/`@return` перенесенного объявления - (включая варианты тега `@psalm-`/`@phpstan-`) перезаписываются на - `DateTimeImmutable` — меняется только токен типа, описания остаются; - объявления только для докблоков без собственного типа никогда не перезаписываются; - — импорт `use DateTime;` (с псевдонимом или без него) удаляется, если в файле - больше ничего не ссылается на него — код, блок документации и комментарии ссылаются на все счетчики -, и сканер ошибается, сохраняя импорт. @@ЛИНИЯ@@ + +Мигрирует конструирование `DateTime` и конкретные объявления типов в +`DateTimeImmutable`. + +| Опция | По умолчанию | Что включает | +|---|---|---| +| `CONSTRUCTORS` | `true` | `new \DateTime(...)`, разделяемые статические фабрики включая `createFromTimestamp()`, и две процедурные `date_create*()`-фабрики | +| `TYPEHINTS` | `true` | `\DateTime` в именованных функциях, методах, замыканиях, arrow-функциях и enum-методах (вкл. nullable и union-типы) | +| `PROPERTIES` | `true` | `\DateTime` в типизированных свойствах и promoted-параметрах конструктора | +| `ALLOW_SUBCLASS` | `false` | переписать `class X extends \DateTime` на `extends \DateTimeImmutable` (рискованно — downstream-мутации на месте ломаются; парите с `LostDateTimeMutationRector`) | +| `DOCTRINE_COLUMNS` | `false` | ко-мигрировать attribute-mapped Doctrine-столбцы вместе с их mapping-типом (см. «Совместная миграция столбцов Doctrine») | + +Миграция также держит файл консистентным: + +- `@var`/`@param`/`@return` docblock-типы мигрированного объявления (включая + варианты тега `@psalm-`/`@phpstan-`) переписываются на `DateTimeImmutable` — + меняется только токен типа, описания остаются; docblock-only объявления без + нативного типа никогда не переписываются; +- `use DateTime;`-импорт (с алиасом или без) удаляется, как только в файле на + него больше ничего не ссылается — код, docblock и комментарии все идут в счёт, + и сканер скорее сохранит импорт зря, чем удалит нужный. + ```php ->withConfiguredRule(DateTimeImmutableRector::class, [ DateTimeImmutableRector::CONSTRUCTORS => true, @@ -303,65 +340,75 @@ return RectorConfig::configure() DateTimeImmutableRector::ALLOW_SUBCLASS => false, ]) ``` -Явное отключение одной категории поддерживается для поэтапной миграции, но промежуточный этап - может оказаться невыполнимым до тех пор, пока не будут перенесены связанные конструкции и объявления типа -. Запускайте статический анализ и тесты после каждого этапа. - - Никогда не трогал: - - | Дело | Почему | - |---|---| - | `класс X расширяет \DateTime` (без `ALLOW_SUBCLASS`) | переписывание родителя нарушает локальную мутацию подкласса | - | Сигнатуры и свойства, объявленные предком/интерфейсом/признаком | реализации должны сохранять унаследованные контракты | - | Интерфейсы, особенности, абстрактные классы | их подписи являются договорами на внедрение | - | `#[Column]` / `@ORM\Column` сопоставленные элементы | ORM определяет конкретный класс для каждого отображаемого типа | - | Все, чей блок документации содержит `@mutable-datetime` | явный маркер отказа | - | `\DateTime::createFromImmutable(...)` | не имеет аналога DateTimeImmutable; содержащий возвращаемый тип также остается изменяемым | - | Конструирование внутри анонимных/абстрактных/типовых областей, а также значения по умолчанию, прямое присвоение свойств и возвраты с сохранением изменяемых контрактов | предотвращает внедрение неизменяемого значения в пропущенное объявление, не блокируя несвязанные миграции в том же классе/методе | - | Значения, связанные простым присвоением со стабильным вызываемым объектом, доступным только для DateTime, таким как date_modify() или API поставщика | связанные параметры, свойства, возвраты и конструкция остаются неизменными | - | Объединения, уже содержащие `\DateTimeImmutable`, в том числе внутри пересечения DNF | перезапись может привести к созданию дублирующего или избыточного типа | - | Типы возвращаемых значений, `return` которых напрямую возвращает сохраненное изменяемое свойство, в т.ч. `??`/тройные ветви | значение времени выполнения остается `DateTime`; перенесенное объявление будет гарантированно `TypeError` | - | Типы докблоков в объявлениях без перенесенного собственного типа | контракт, содержащий только докблок, не содержит доказательств времени выполнения; теги в перенесенных объявлениях синхронизируются автоматически | - | `new $class()`, типы пересечений | не является статически доказуемым | @@ЛИНИЯ@@ + +Явное отключение отдельной категории поддерживается для постадийных миграций, +но промежуточная стадия может оказаться не исполняемой, пока связанные +construction и type-объявления не будут мигрированы. Запускайте статический +анализ и тесты после каждой стадии. + +Никогда не трогается: + +| Кейс | Почему | +|---|---| +| `class X extends \DateTime` (без `ALLOW_SUBCLASS`) | переписывание родителя ломает мутацию на месте у подкласса | +| Сигнатуры и свойства, объявленные предком/интерфейсом/трейтом | реализации обязаны сохранять унаследованные контракты | +| Интерфейсы, трейты, абстрактные классы | их сигнатуры — контракты для реализаций | +| `#[Column]` / `@ORM\Column` mapped-члены | ORM решает конкретный класс по mapping-типу | +| Всё, чей docblock несёт `@mutable-datetime` | явный opt-out-маркер | +| `\DateTime::createFromImmutable(...)` | не имеет `DateTimeImmutable`-аналога; содержащий return-тип также остаётся мутабельным | +| Конструирование внутри anonymous/abstract/trait-скоупов, плюс default'ы, прямые property-присваивания и return'ы, питающие сохранённые мутабельные контракты | не даёт иммутабельному значению инжектиться в пропущенное объявление, не блокируя несвязанные миграции в том же классе/методе | +| Значения, подключённые простыми присваиваниями к стабильному `DateTime`-only callable вроде `date_modify()` или vendor-API | связанные параметры, свойства, return'ы и конструирование остаются мутабельными | +| Union'ы, уже содержащие `\DateTimeImmutable`, включая внутри DNF-intersection | переписывание создаст дублирующий или избыточный тип | +| Return-типы, чей `return` напрямую отдаёт сохранённое мутабельное свойство, вкл. `??`/ternary-ветки | runtime-значение остаётся `DateTime`; мигрированное объявление гарантированно даст `TypeError` | +| Docblock-типы на объявлениях без мигрированного нативного типа | docblock-only-контракт не имеет runtime-доказательства; теги на мигрированных объявлениях синхронизируются автоматически | +| `new $class()`, intersection-типы | статически недоказуемо | + ### `LostDateTimeMutationRector` -Находит вызовы мутаторов на уровне оператора для объекта DateTimeImmutable, возвращаемое значение - которого отбрасывается: modify, add, sub, setDate, setTime, - setISODate, setTimezone, setTimestamp, setMicro Second. - - | Режим | Поведение | - |---|---| - | `MODE_FIX` (по умолчанию) | перезаписывает `$d->modify(...);` на `$d = $d->modify(...);` для непосредственно инициализированных точных встроенных переменных и конечных подклассов/объединений; никогда не присваивает `$this` | - | `MODE_REPORT` | вместо этого прикрепляет комментарий-маркер `// @todo Lost DateTimeImmutable Mutation…`; запустить с `--dry-run`, чтобы вывести из строя CI, оставив код нетронутым | @@ЛИНИЯ@@ + +Находит statement-level вызовы мутаторов на `DateTimeImmutable`, чей return +выбрасывается: `modify`, `add`, `sub`, `setDate`, `setTime`, `setISODate`, +`setTimezone`, `setTimestamp`, `setMicrosecond`. + +| Режим | Поведение | +|---|---| +| `MODE_FIX` (по умолчанию) | переписывает `$d->modify(...);` в `$d = $d->modify(...);` для напрямую инициализированных точных built-in-переменных и final-подклассов/union'ов; никогда не присваивает `$this` | +| `MODE_REPORT` | вместо этого навешивает комментарий-маркер `// @todo lost DateTimeImmutable mutation…`; запускайте с `--dry-run`, чтобы валить CI, не трогая код | + ```php ->withConfiguredRule(LostDateTimeMutationRector::class, [ LostDateTimeMutationRector::MODE => LostDateTimeMutationRector::MODE_REPORT, ]) ``` -Пропускаются в обоих режимах: используемые результаты, изменяемые приемники, не-подтипы (включая оболочки - PHPStan `@mixin`) и статически видимые переопределения мутаторов. Режим исправления - также пропускает `$this`, приемники свойств/вызовов, открытые объявленные типы, такие как параметр - `DateTimeImmutable`, и локальные значения, заполненные открытым типом возвращаемого значения: подкласс времени выполнения - может переопределить мутатор и законно мутировать на месте. Локальное - становится точным только после безусловного присвоения верхнего уровня из прямой встроенной конструкции -, общей статической фабрики, процедурной - `date_create_immutable*()` фабрики, `клона` точного значения или другого - проверенного точного выражения. Простой псевдоним (`$b = $a;`) намеренно не обеспечивает - точности: в изменяемой программе перед миграцией оба имени использовали - один мутировавший объект, поэтому назначение только получателя могло незаметно отличаться от устаревшего поведения - — о таких утверждениях сообщается. - Присвоения, вложенные в условные выражения, циклы, ветки переключения/попробования/сопоставления и сокращенные выражения -, никогда не обеспечивают точности и не делают недействительным открытое - консервативное доказательство. Поэтому присвоение и потерянная мутация, содержащиеся в - одной и той же условной ветви, могут намеренно оставаться неизменными. Финальные подклассы - и объединения финальных подклассов можно безопасно исправить. Режим отчета может диагностически отмечать - как открытый подтип, поскольку он не изменяет программу. Вызовы Nullsafe - (`$d?->modify(...)`) выходят за рамки. - - `MODE_REPORT` перекрывается с PHPStan уровня 4 («вызов на отдельной строке не имеет эффекта -») — используйте его только в том случае, если ваш конвейер запускает Rector без статического анализатора. @@ЛИНИЯ@@ + +Пропускается в обоих режимах: использованные результаты, мутабельные ресиверы, +не-подтипы (включая PHPStan `@mixin`-обёртки) и статически видимые переопределения +мутаторов. Fix-режим также пропускает `$this`, property/call-ресиверы, открытые +объявленные типы вроде параметра `DateTimeImmutable` и локальные значения, +полученные из открытого return-типа: runtime-подкласс может переопределить +мутатор и легально мутировать на месте. Локал становится точным только после +безусловного top-level-присваивания из прямой точной built-in-конструкции, +разделяемой статической фабрики, процедурной `date_create_immutable*()`-фабрики, +`clone` точного значения или иного доказанно точного выражения. Простой алиас +(`$b = $a;`) намеренно не устанавливает точности: в домиграционной мутабельной +программе оба имени разделяли один мутируемый объект, поэтому receiver-only +присваивание могло бы молча разойтись с легаси-поведением — такие statement'ы +остаются в отчёте. Присваивания, вложенные в conditionals, циклы, +switch/try/match-ветки и short-circuit-выражения, никогда не устанавливают +точности и консервативно инвалидируют открытое доказательство. Поэтому +присваивание и потерянная мутация в одной условной ветке могут намеренно +остаться без изменений. Final-подклассы и union'ы final-подклассов безопасно +чинить. Report-режим может диагностически помечать открытый подтип, поскольку +он не меняет программу. Nullsafe-вызовы (`$d?->modify(...)`) вне области +применения. + +`MODE_REPORT` пересекается с PHPStan level 4 («call on a separate line has no +effect») — используйте его, только если ваш pipeline гоняет Rector без +статического анализатора. + ### Маркеры -Добавьте `@mutable-datetime` в блок документации, чтобы сохранить изменяемое объявление для цели -: + +Добавьте `@mutable-datetime` в docblock, чтобы намеренно сохранить объявление +мутабельным: ```php /** @@ -369,26 +416,33 @@ return RectorConfig::configure() */ private \DateTime $sdkClock; ``` -Добавьте `@mutable-datetime-boundary` в качестве комментария к оператору вызова, чтобы отметить проверенный граничный вызов - — тогда предварительная проверка его пропускает. `--acknowledge-boundaries` - пишет для вас эти комментарии: + +Добавьте `@mutable-datetime-boundary` как комментарий на statement-вызова, чтобы +отметить проверенный boundary-call — предполёт тогда его пропустит. +`--acknowledge-boundaries` пишет эти комментарии за вас: ```php // @mutable-datetime-boundary: parameter $object requires DateTime date_modify($moment, '+1 hour'); ``` + ## Безопасность -Это миграция, меняющая контракт. Значения по умолчанию переносят конструкцию и - конкретные локальные объявления вместе. Типизированные собственные/поставочные/наследуемые вызываемые границы -, унаследованные свойства/сигнатуры, сопоставления ORM и динамические имена - охраняются. Динамические вызовы, магическая диспетчеризация, отражение и потоки нетипизированных внешних данных - не могут быть подтверждены правилом «от источника к источнику». Просматривайте разницу и запускайте полную сборку проекта - после каждого прохода, особенно при использовании поэтапных параметров или - `ALLOW_SUBCLASS`, которые намеренно изменяют поведение во время выполнения подклассов `DateTime` -. @@ЛИНИЯ@@ + +Это меняющая контракт миграция. Дефолты мигрируют construction и конкретные +локальные объявления вместе. Типизированные native/vendor/inherited callable-границы, +унаследованные свойства/сигнатуры, ORM-mapping'и и динамические имена +охраняются. Динамические вызовы, magic-диспетчер, рефлексия и нетипизированные +внешние data-flow'ы не могут быть доказаны source-to-source-правилом. +Ревьюньте diff и запускайте полную сборку проекта после каждого прохода, +особенно при использовании постадийных опций или `ALLOW_SUBCLASS`, которые +намеренно меняют runtime-поведение подклассов `DateTime`. + ## Примеры -Запускаемые сценарии находятся в [`examples/`](examples/README.md). @@ЛИНИЯ@@ + +Исполняемые скрипты — в [`examples/`](examples/README.md). + ## Разработка + ```bash make install # composer install (Docker, no local PHP needed) make build # validate + normalize + require-checker + cs + psalm + tests @@ -397,13 +451,16 @@ make mutation # infection, minMsi=100 — gates the Internal/ decision core; # the rule shells run inside rector subprocesses and are # covered by the e2e fixture suites instead ``` -Тестирование мутаций по своей конструкции ограничено `src/Internal/`: ядро ​​принятия решений - (каталог мутаторов, карта фабрики, средства перезаписи типов/докблоков, детектор столбца Doctrine -, средство сопоставления маркеров) работает в процессе и ограничивается значением `minMsi = 100`. Классы общедоступных правил - и CLI выполняются внутри подпроцессов Rector, которые - Infection не может наблюдать — они покрываются наборами фикстур e2e вместо - (выходные данные с байтовым сравнением, `php -l` для каждого преобразованного файла, исполняемые фикстуры - во время выполнения). Таким образом, номера заражений удостоверяют ядро ​​`Internal/`, а не - оценку мутаций всего пакета; обоснование см. в [AGENTS.md](AGENTS.md). @@ЛИНИЯ@@ + +Мутационное тестирование по дизайну ограничено `src/Internal/`: ядро принятия +решений (каталог мутаторов, фабричный map, type/docblock-переписчики, детектор +Doctrine-столбцов, matcher маркеров) работает in-process и гейтится +`minMsi = 100`. Публичные классы правил и CLI исполняются внутри subprocess'ов +Rector, которые Infection наблюдать не может — они покрываются e2e fixture-наборами +(байтовое сравнение вывода, `php -l` на каждом трансформированном файле, +исполняемые runtime-фикстуры). Поэтому числа Infection сертифицируют ядро +`Internal/`, а не package-wide mutation-score; обоснование — см. [AGENTS.md](AGENTS.md). + ## Лицензия -[BSD-3-пункт](LICENSE.md) + +[BSD-3-Clause](LICENSE.md)