Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/wiki
Submodule wiki updated from 5e683c to 5b3806
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ composer dev-tools code-style

# Refactor code using Rector
composer dev-tools refactor
composer dev-tools refactor -- --type-perfect
composer dev-tools refactor -- --type-perfect-groups=null_over_false,no_mixed

# Check and fix PHPDoc comments
composer dev-tools phpdoc
Expand Down Expand Up @@ -113,6 +115,14 @@ The `metrics` command ships with `phpmetrics/phpmetrics` as a direct
dependency of `fast-forward/dev-tools`, so consumer repositories can generate
metrics reports without extra setup.

Type Perfect support is opt-in on top of the `refactor` command. To enable the
Fast Forward integration path in a consumer repository, install the companion
packages first:

```bash
composer require --dev rector/type-perfect phpstan/extension-installer
```

The `skills` command keeps `.agents/skills` aligned with the packaged Fast
Forward skill set. It creates missing links, repairs broken links, and
preserves existing non-symlink directories. The `dev-tools:sync` command calls
Expand Down
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
"thecodingmachine/safe": "^3.4",
"twig/twig": "^3.0"
},
"suggest": {
"phpstan/extension-installer": "Recommended when enabling Type Perfect through `composer dev-tools refactor -- --type-perfect` so PHPStan extension wiring is loaded automatically.",
"rector/type-perfect": "Optional companion package for running Type Perfect checks through `composer dev-tools refactor -- --type-perfect`."
},
"minimum-stability": "stable",
"autoload": {
"psr-4": {
Expand Down
17 changes: 17 additions & 0 deletions docs/advanced/rector-and-phpdoc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ The default ``rector.php`` also loads shared Rector sets, imports names,
removes unused imports, skips generated directories, and enables Safe migration
rules when ``thecodingmachine/safe`` is installed.

Type Perfect in the Refactor Workflow
-------------------------------------

The ``refactor`` command can optionally run Type Perfect immediately after the
Rector pass:

.. code-block:: bash

composer dev-tools refactor -- --type-perfect

The Fast Forward integration path is intentionally opt-in. It expects
``rector/type-perfect`` and ``phpstan/extension-installer`` to be installed in
the consumer project. When the consumer already has ``phpstan.neon`` or
``phpstan.neon.dist``, DevTools includes that file automatically in the
generated ``tmp/cache/phpstan/type-perfect.neon`` and then enables the selected
Type Perfect groups there.

Why ``.docheader`` Appears Automatically
----------------------------------------

Expand Down
31 changes: 30 additions & 1 deletion docs/commands/refactor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Description
-----------

The ``refactor`` command (alias: ``rector``) runs Rector to automatically
refactor PHP code. Without ``--fix``, it runs in dry-run mode.
refactor PHP code. Without ``--fix``, it runs in dry-run mode. It can also run
`Type Perfect <https://getrector.com/blog/introducing-type-perfect-for-extra-safety>`_
as a follow-up PHPStan safety pass when the companion packages are installed.

Usage
-----
Expand All @@ -29,6 +31,16 @@ Options
``--config, -c`` (optional)
Path to the Rector configuration file. Default: ``rector.php``.

``--type-perfect``
Runs Type Perfect after Rector using a generated PHPStan config in
``tmp/cache/phpstan/type-perfect.neon``.

``--type-perfect-groups=<groups>`` (optional)
Comma-separated Type Perfect groups to enable. Supported groups:
``null_over_false``, ``no_mixed``, and ``narrow_param``.

Default: ``null_over_false,no_mixed,narrow_param``.

Examples
--------

Expand All @@ -44,6 +56,18 @@ Apply fixes automatically:

composer refactor --fix

Run Rector and Type Perfect together:

.. code-block:: bash

composer dev-tools refactor -- --type-perfect

Limit Type Perfect to selected groups:

.. code-block:: bash

composer dev-tools refactor -- --type-perfect --type-perfect-groups=null_over_false,no_mixed

Exit Codes
---------

Expand All @@ -63,3 +87,8 @@ Behavior
- Local ``rector.php`` is preferred when present.
- Packaged default includes Fast Forward custom Rector rules plus shared Rector sets.
- Uses ``--dry-run`` mode unless ``--fix`` is specified.
- ``--type-perfect`` requires ``rector/type-perfect`` and
``phpstan/extension-installer`` in the consumer project.
- When the consumer already has ``phpstan.neon`` or ``phpstan.neon.dist``,
the generated Type Perfect config includes it automatically before enabling
the requested Type Perfect groups.
18 changes: 18 additions & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,24 @@ Use the ``RectorConfig`` class to extend instead of replace:

This approach automatically receives upstream updates while allowing additive customization.

How do I enable Type Perfect together with ``refactor``?
--------------------------------------------------------

Install the companion packages in the consumer project:

.. code-block:: bash

composer require --dev rector/type-perfect phpstan/extension-installer

Then run:

.. code-block:: bash

composer dev-tools refactor -- --type-perfect

You can narrow the rollout by selecting groups with
``--type-perfect-groups=null_over_false,no_mixed``.

Can I generate coverage without running the full ``standards`` pipeline?
------------------------------------------------------------------------

Expand Down
7 changes: 6 additions & 1 deletion docs/running/specialized-commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,18 @@ Runs Rector against the current project.
.. code-block:: bash

composer refactor --fix
composer dev-tools refactor -- --type-perfect

Important details:

- without ``--fix``, Rector runs in dry-run mode;
- local ``rector.php`` is preferred when present;
- the packaged default includes Fast Forward custom Rector rules plus shared
Rector sets.
Rector sets;
- ``--type-perfect`` adds a PHPStan Type Perfect pass after Rector using a
generated config in ``tmp/cache/phpstan/type-perfect.neon``;
- the Fast Forward Type Perfect path expects ``rector/type-perfect`` and
``phpstan/extension-installer`` to be installed in the consumer project.

``phpdoc``
----------
Expand Down
137 changes: 136 additions & 1 deletion src/Console/Command/RefactorCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
namespace FastForward\DevTools\Console\Command;

use Composer\Command\BaseCommand;
use FastForward\DevTools\Filesystem\FilesystemInterface;
use FastForward\DevTools\Process\ProcessBuilderInterface;
use FastForward\DevTools\Process\ProcessQueueInterface;
use Symfony\Component\Config\FileLocatorInterface;
Expand All @@ -45,15 +46,31 @@ final class RefactorCommand extends BaseCommand
*/
public const string CONFIG = 'rector.php';

/**
* @var string the generated PHPStan config used to run Type Perfect checks
*/
private const string TYPE_PERFECT_CONFIG = 'tmp/cache/phpstan/type-perfect.neon';

/**
* @var list<string> the supported Type Perfect rule groups
*/
private const array TYPE_PERFECT_GROUPS = [
'null_over_false',
'no_mixed',
'narrow_param',
];

/**
* Creates a new RefactorCommand instance.
*
* @param FileLocatorInterface $fileLocator the file locator
* @param FilesystemInterface $filesystem the filesystem used for Type Perfect configuration generation
* @param ProcessBuilderInterface $processBuilder the process builder
* @param ProcessQueueInterface $processQueue the process queue
*/
public function __construct(
private readonly FileLocatorInterface $fileLocator,
private readonly FilesystemInterface $filesystem,
private readonly ProcessBuilderInterface $processBuilder,
private readonly ProcessQueueInterface $processQueue,
) {
Expand Down Expand Up @@ -83,6 +100,17 @@ protected function configure(): void
mode: InputOption::VALUE_OPTIONAL,
description: 'The path to the Rector configuration file.',
default: self::CONFIG
)
->addOption(
name: 'type-perfect',
mode: InputOption::VALUE_NONE,
description: 'Run PHPStan Type Perfect checks after Rector using the supported Fast Forward preset.'
)
->addOption(
name: 'type-perfect-groups',
mode: InputOption::VALUE_OPTIONAL,
description: 'Comma-separated Type Perfect groups to enable.',
default: implode(',', self::TYPE_PERFECT_GROUPS)
);
}

Expand All @@ -101,17 +129,124 @@ protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>Running Rector for code refactoring...</info>');

$config = (string) $input->getOption('config');
$processBuilder = $this->processBuilder
->withArgument('process')
->withArgument('--config')
->withArgument($this->fileLocator->locate(self::CONFIG));
->withArgument($this->fileLocator->locate($config));

if (! $input->getOption('fix')) {
$processBuilder = $processBuilder->withArgument('--dry-run');
}

$this->processQueue->add($processBuilder->build('vendor/bin/rector'));

if ($input->getOption('type-perfect')) {
$typePerfectGroups = $this->resolveTypePerfectGroups((string) $input->getOption('type-perfect-groups'));

if ([] === $typePerfectGroups) {
$output->writeln(
'<error>No valid Type Perfect groups were provided. Supported groups: '
. implode(', ', self::TYPE_PERFECT_GROUPS)
. '.</error>'
);

return self::FAILURE;
}

if (! $this->filesystem->exists('vendor/rector/type-perfect')) {
$output->writeln(
'<error>Type Perfect support requires rector/type-perfect. Install it with '
. '"composer require rector/type-perfect --dev" before using --type-perfect.</error>'
);

return self::FAILURE;
}

if (! $this->filesystem->exists('vendor/phpstan/extension-installer')) {
$output->writeln(
'<error>Type Perfect support requires phpstan/extension-installer for the Fast Forward integration path. '
. 'Install it with "composer require phpstan/extension-installer --dev" before using --type-perfect.</error>'
);

return self::FAILURE;
}

$output->writeln('<info>Running Type Perfect safety checks...</info>');

$typePerfectConfig = $this->writeTypePerfectConfig($typePerfectGroups);
$typePerfect = $this->processBuilder
->withArgument('analyse')
->withArgument('--configuration', $typePerfectConfig)
->build('vendor/bin/phpstan');

$this->processQueue->add($typePerfect);
}

return $this->processQueue->run($output);
}

/**
* Filters the requested Type Perfect groups down to the supported subset.
*
* @param string $groups the raw comma-separated option value
*
* @return list<string> the valid requested groups in declaration order
*/
private function resolveTypePerfectGroups(string $groups): array
{
$requestedGroups = array_map('trim', explode(',', $groups));
$requestedGroups = array_filter($requestedGroups, static fn(string $group): bool => '' !== $group);

return array_values(array_intersect(self::TYPE_PERFECT_GROUPS, $requestedGroups));
}

/**
* Writes the temporary PHPStan config used to run Type Perfect.
*
* @param list<string> $groups the enabled Type Perfect groups
*
* @return string the generated config path
*/
private function writeTypePerfectConfig(array $groups): string
{
$configPath = (string) $this->filesystem->getAbsolutePath(self::TYPE_PERFECT_CONFIG);
$this->filesystem->mkdir($this->filesystem->dirname($configPath));

$lines = [];
$projectPhpStanConfig = $this->resolveProjectPhpStanConfig();

if (null !== $projectPhpStanConfig) {
$lines[] = 'includes:';
$lines[] = sprintf(" - '%s'", str_replace("'", "''", $projectPhpStanConfig));
$lines[] = '';
}

$lines[] = 'parameters:';
$lines[] = ' type_perfect:';

foreach ($groups as $group) {
$lines[] = sprintf(' %s: true', $group);
}

$this->filesystem->dumpFile($configPath, implode("\n", $lines) . "\n");

return $configPath;
}

/**
* Resolves the consumer PHPStan config to include in the generated Type Perfect file.
*
* @return string|null the absolute PHPStan config path, or null when the consumer has no PHPStan config yet
*/
private function resolveProjectPhpStanConfig(): ?string
{
foreach (['phpstan.neon', 'phpstan.neon.dist'] as $candidate) {
if ($this->filesystem->exists($candidate)) {
return (string) $this->filesystem->getAbsolutePath($candidate);
}
}

return null;
}
}
Loading
Loading