Skip to content

Commit ca00ca6

Browse files
committed
Merge branches 'feature/6-make-ecs-php-extensible' and 'feature/7-make-rector-php-extensible' into main
2 parents 874b190 + 1ca9dfc commit ca00ca6

File tree

4 files changed

+163
-63
lines changed

4 files changed

+163
-63
lines changed

docs/configuration/overriding-defaults.rst

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Overriding Defaults
2-
===================
2+
==================
33

44
Local override files let a consumer project keep the Fast Forward baseline
55
without forking the whole package.
@@ -70,14 +70,38 @@ default configuration using the ``ECSConfig`` class:
7070
7171
return $config;
7272
73+
Extending Rector Configuration
74+
-------------------------------
75+
76+
Instead of copying the entire ``rector.php`` file, consumers can extend the
77+
default configuration using the ``RectorConfig`` class:
78+
79+
.. code-block:: php
80+
81+
<?php
82+
83+
use FastForward\DevTools\Config\RectorConfig;
84+
85+
return RectorConfig::configure(
86+
static function (\Rector\Config\RectorConfig $rectorConfig): void {
87+
$rectorConfig->rules([
88+
// custom rules
89+
]);
90+
91+
$rectorConfig->skip([
92+
// custom skips
93+
]);
94+
}
95+
);
96+
7397
This approach:
7498

7599
- Eliminates duplication of the base configuration
76100
- Automatically receives upstream updates
77101
- Only requires overriding what is needed
78102

79103
What Is Not Overwritten Automatically
80-
-------------------------------------
104+
--------------------------------------
81105

82106
- existing workflow files in ``.github/workflows/``;
83107
- an existing ``.editorconfig``;

docs/faq.rst

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Create only the local configuration file you want to customize, such as
7777
rest on the packaged defaults.
7878

7979
How do I extend the ECS configuration without copying the whole file?
80-
----------------------------------------------------------------------
80+
-----------------------------------------------------------------------
8181

8282
Use the ``ECSConfig`` class to extend instead of replace:
8383

@@ -94,6 +94,25 @@ Use the ``ECSConfig`` class to extend instead of replace:
9494
9595
This approach automatically receives upstream updates while allowing additive customization.
9696

97+
How do I extend the Rector configuration without copying the whole file?
98+
-------------------------------------------------------------------------
99+
100+
Use the ``RectorConfig`` class to extend instead of replace:
101+
102+
.. code-block:: php
103+
104+
<?php
105+
106+
use FastForward\DevTools\Config\RectorConfig;
107+
108+
return RectorConfig::configure(
109+
static function (\Rector\Config\RectorConfig $rectorConfig): void {
110+
$rectorConfig->rules([CustomRule::class]);
111+
}
112+
);
113+
114+
This approach automatically receives upstream updates while allowing additive customization.
115+
97116
Can I generate coverage without running the full ``standards`` pipeline?
98117
------------------------------------------------------------------------
99118

rector.php

Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -16,64 +16,6 @@
1616
* @see https://datatracker.ietf.org/doc/html/rfc2119
1717
*/
1818

19-
use Rector\Configuration\PhpLevelSetResolver;
20-
use Composer\InstalledVersions;
21-
use Ergebnis\Rector\Rules\Faker\GeneratorPropertyFetchToMethodCallRector;
22-
use FastForward\DevTools\Rector\AddMissingMethodPhpDocRector;
23-
use FastForward\DevTools\Rector\RemoveEmptyDocBlockRector;
24-
use Rector\Config\RectorConfig;
25-
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessParamTagRector;
26-
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector;
27-
use Rector\Php\PhpVersionResolver\ComposerJsonPhpVersionResolver;
28-
use Rector\Set\ValueObject\SetList;
19+
use FastForward\DevTools\Config\RectorConfig;
2920

30-
use function Safe\getcwd;
31-
32-
return static function (RectorConfig $rectorConfig): void {
33-
$rectorConfig->sets([
34-
SetList::DEAD_CODE,
35-
SetList::CODE_QUALITY,
36-
SetList::CODING_STYLE,
37-
SetList::TYPE_DECLARATION,
38-
SetList::PRIVATIZATION,
39-
SetList::INSTANCEOF,
40-
SetList::EARLY_RETURN,
41-
]);
42-
$rectorConfig->paths([getcwd()]);
43-
$rectorConfig->skip([
44-
getcwd() . '/public',
45-
getcwd() . '/resources',
46-
getcwd() . '/vendor',
47-
getcwd() . '/tmp',
48-
RemoveUselessReturnTagRector::class,
49-
RemoveUselessParamTagRector::class,
50-
]);
51-
$rectorConfig->cacheDirectory(getcwd() . '/tmp/cache/rector');
52-
$rectorConfig->importNames();
53-
$rectorConfig->removeUnusedImports();
54-
$rectorConfig->fileExtensions(['php']);
55-
$rectorConfig->parallel(600);
56-
$rectorConfig->rules([
57-
GeneratorPropertyFetchToMethodCallRector::class,
58-
AddMissingMethodPhpDocRector::class,
59-
RemoveEmptyDocBlockRector::class,
60-
]);
61-
62-
$projectPhpVersion = ComposerJsonPhpVersionResolver::resolveFromCwdOrFail();
63-
$phpLevelSets = PhpLevelSetResolver::resolveFromPhpVersion($projectPhpVersion);
64-
65-
$rectorConfig->sets($phpLevelSets);
66-
67-
if (InstalledVersions::isInstalled('thecodingmachine/safe', false)) {
68-
$packageLocation = InstalledVersions::getInstallPath('thecodingmachine/safe');
69-
$safeRectorMigrateFile = $packageLocation . '/rector-migrate.php';
70-
71-
if (file_exists($safeRectorMigrateFile)) {
72-
$callback = require_once $safeRectorMigrateFile;
73-
74-
if (is_callable($callback)) {
75-
$callback($rectorConfig);
76-
}
77-
}
78-
}
79-
};
21+
return RectorConfig::configure();

src/Config/RectorConfig.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of fast-forward/dev-tools.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @copyright Copyright (c) 2026 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
12+
* @license https://opensource.org/licenses/MIT MIT License
13+
*
14+
* @see https://github.com/php-fast-forward/dev-tools
15+
* @see https://github.com/php-fast-forward
16+
* @see https://datatracker.ietf.org/doc/html/rfc2119
17+
*/
18+
19+
namespace FastForward\DevTools\Config;
20+
21+
use Composer\InstalledVersions;
22+
use Ergebnis\Rector\Rules\Faker\GeneratorPropertyFetchToMethodCallRector;
23+
use FastForward\DevTools\Rector\AddMissingMethodPhpDocRector;
24+
use FastForward\DevTools\Rector\RemoveEmptyDocBlockRector;
25+
use Rector\Config\RectorConfig as RectorConfigInterface;
26+
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessParamTagRector;
27+
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector;
28+
use Rector\Php\PhpVersionResolver\ComposerJsonPhpVersionResolver;
29+
use Rector\Configuration\PhpLevelSetResolver;
30+
use Rector\Set\ValueObject\SetList;
31+
32+
use function Safe\getcwd;
33+
34+
/**
35+
* Provides the default Rector configuration.
36+
*
37+
* Consumers can use this as a starting point and extend it:
38+
*
39+
* return \FastForward\DevTools\Config\RectorConfig::configure(
40+
* static function (\Rector\Config\RectorConfig $rectorConfig): void {
41+
* $rectorConfig->rules([
42+
* // custom rules
43+
* ]);
44+
* }
45+
* );
46+
*
47+
* @see https://github.com/rectorphp/rector
48+
*/
49+
final class RectorConfig
50+
{
51+
/**
52+
* Creates the default Rector configuration.
53+
*
54+
* @param callable|null $customize optional callback to customize the configuration
55+
*
56+
* @return callable the configuration callback
57+
*/
58+
public static function configure(?callable $customize = null): callable
59+
{
60+
return static function (RectorConfigInterface $rectorConfig) use ($customize): void {
61+
$cwd = getcwd();
62+
63+
$rectorConfig->sets([
64+
SetList::DEAD_CODE,
65+
SetList::CODE_QUALITY,
66+
SetList::CODING_STYLE,
67+
SetList::TYPE_DECLARATION,
68+
SetList::PRIVATIZATION,
69+
SetList::INSTANCEOF,
70+
SetList::EARLY_RETURN,
71+
]);
72+
$rectorConfig->paths([$cwd]);
73+
$rectorConfig->skip([
74+
$cwd . '/public',
75+
$cwd . '/resources',
76+
$cwd . '/vendor',
77+
$cwd . '/tmp',
78+
RemoveUselessReturnTagRector::class,
79+
RemoveUselessParamTagRector::class,
80+
]);
81+
$rectorConfig->cacheDirectory($cwd . '/tmp/cache/rector');
82+
$rectorConfig->importNames();
83+
$rectorConfig->removeUnusedImports();
84+
$rectorConfig->fileExtensions(['php']);
85+
$rectorConfig->parallel(600);
86+
$rectorConfig->rules([
87+
GeneratorPropertyFetchToMethodCallRector::class,
88+
AddMissingMethodPhpDocRector::class,
89+
RemoveEmptyDocBlockRector::class,
90+
]);
91+
92+
$projectPhpVersion = ComposerJsonPhpVersionResolver::resolveFromCwdOrFail();
93+
$phpLevelSets = PhpLevelSetResolver::resolveFromPhpVersion($projectPhpVersion);
94+
95+
$rectorConfig->sets($phpLevelSets);
96+
97+
if (InstalledVersions::isInstalled('thecodingmachine/safe', false)) {
98+
$packageLocation = InstalledVersions::getInstallPath('thecodingmachine/safe');
99+
$safeRectorMigrateFile = $packageLocation . '/rector-migrate.php';
100+
101+
if (file_exists($safeRectorMigrateFile)) {
102+
$callback = require_once $safeRectorMigrateFile;
103+
104+
if (\is_callable($callback)) {
105+
$callback($rectorConfig);
106+
}
107+
}
108+
}
109+
110+
if (null !== $customize) {
111+
$customize($rectorConfig);
112+
}
113+
};
114+
}
115+
}

0 commit comments

Comments
 (0)