Skip to content

Commit c4df130

Browse files
GeniJahopeterfox
andauthored
Add a command to generate new rules (#298)
Co-authored-by: Peter Fox <[email protected]>
1 parent 06b1195 commit c4df130

File tree

8 files changed

+217
-4
lines changed

8 files changed

+217
-4
lines changed

Diff for: commands/MakeRuleCommand.php

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Commands;
6+
7+
use Nette\Utils\FileSystem;
8+
use Rector\Console\Command\CustomRuleCommand;
9+
use Symfony\Component\Finder\Finder;
10+
use Symfony\Component\Finder\SplFileInfo;
11+
12+
/**
13+
* Modified version of
14+
*
15+
* @see CustomRuleCommand
16+
*/
17+
final class MakeRuleCommand
18+
{
19+
public function execute(string $ruleName): int
20+
{
21+
$rectorName = $this->getRuleName($ruleName);
22+
$rulesNamespace = 'RectorLaravel\\Rector';
23+
$testsNamespace = 'RectorLaravel\\Tests\\Rector';
24+
25+
// find all files in templates directory
26+
$finder = Finder::create()
27+
->files()
28+
->in(__DIR__ . '/../templates/new-rule')
29+
->notName('__NAME__Test.php');
30+
31+
$finder->append([
32+
new SplFileInfo(
33+
__DIR__ . '/../templates/new-rule/tests/Rector/__NAME__/__NAME__Test.php',
34+
'tests/Rector/__NAME__',
35+
'tests/Rector/__NAME__/__NAME__Test.php',
36+
),
37+
]);
38+
39+
$currentDirectory = getcwd();
40+
41+
$generatedFilePaths = [];
42+
43+
$fileInfos = iterator_to_array($finder->getIterator());
44+
45+
foreach ($fileInfos as $fileInfo) {
46+
$newContent = $this->replaceNameVariable($rectorName, $fileInfo->getContents());
47+
$newContent = $this->replaceNamespaceVariable($rulesNamespace, $newContent);
48+
$newContent = $this->replaceTestsNamespaceVariable($testsNamespace, $newContent);
49+
$newFilePath = $this->replaceNameVariable($rectorName, $fileInfo->getRelativePathname());
50+
51+
FileSystem::write($currentDirectory . '/' . $newFilePath, $newContent, null);
52+
53+
$generatedFilePaths[] = $newFilePath;
54+
}
55+
56+
echo PHP_EOL . 'Generated files:' . PHP_EOL . PHP_EOL . "\t";
57+
echo implode(PHP_EOL . "\t", $generatedFilePaths) . PHP_EOL;
58+
59+
return 0;
60+
}
61+
62+
private function getRuleName(string $ruleName): string
63+
{
64+
if (! str_ends_with($ruleName, 'Rector')) {
65+
$ruleName .= 'Rector';
66+
}
67+
68+
return ucfirst($ruleName);
69+
}
70+
71+
private function replaceNameVariable(string $rectorName, string $contents): string
72+
{
73+
return str_replace('__NAME__', $rectorName, $contents);
74+
}
75+
76+
private function replaceNamespaceVariable(string $namespace, string $contents): string
77+
{
78+
return str_replace('__NAMESPACE__', $namespace, $contents);
79+
}
80+
81+
private function replaceTestsNamespaceVariable(string $testsNamespace, string $contents): string
82+
{
83+
return str_replace('__TESTS_NAMESPACE__', $testsNamespace, $contents);
84+
}
85+
}

Diff for: commands/make-rule.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
require __DIR__ . '/../vendor/autoload.php';
5+
6+
// get the first argument, the rule name
7+
// make sure we have at least one argument
8+
if ($argc < 2) {
9+
echo PHP_EOL . 'Please provide the name of the rule!' . PHP_EOL;
10+
exit(1);
11+
}
12+
13+
$ruleName = $argv[1];
14+
15+
$command = new \RectorLaravel\Commands\MakeRuleCommand;
16+
exit($command->execute($ruleName));

Diff for: composer.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
"phpstan/phpstan-webmozart-assert": "^2.0",
1919
"phpunit/phpunit": "^10.5",
2020
"symplify/rule-doc-generator": "^12.2",
21-
"tightenco/duster": "^3.1"
21+
"tightenco/duster": "^3.1",
22+
"nette/utils": "^4.0",
23+
"symfony/finder": "^7.2"
2224
},
2325
"autoload": {
2426
"psr-4": {
@@ -27,7 +29,8 @@
2729
},
2830
"autoload-dev": {
2931
"psr-4": {
30-
"RectorLaravel\\Tests\\": "tests"
32+
"RectorLaravel\\Tests\\": "tests",
33+
"RectorLaravel\\Commands\\": "commands"
3134
},
3235
"classmap": ["stubs"]
3336
},
@@ -38,7 +41,8 @@
3841
"fix": "vendor/bin/duster fix",
3942
"rector-dry-run": "vendor/bin/rector process --dry-run --ansi",
4043
"rector": "vendor/bin/rector process --ansi",
41-
"docs": "vendor/bin/rule-doc-generator generate src --output-file docs/rector_rules_overview.md --ansi"
44+
"docs": "vendor/bin/rule-doc-generator generate src --output-file docs/rector_rules_overview.md --ansi",
45+
"make:rule": "php commands/make-rule.php"
4246
},
4347
"minimum-stability": "dev",
4448
"prefer-stable": true,

Diff for: duster.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
],
77
"exclude": [
88
"tests/Rector/**/**/Fixture",
9-
"tests/Sets/**/Fixture"
9+
"tests/Sets/**/Fixture",
10+
"templates"
1011
]
1112
}

Diff for: templates/new-rule/src/Rector/__NAME__.php

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace __NAMESPACE__;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\Class_;
9+
use RectorLaravel\AbstractRector;
10+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
11+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
12+
13+
/**
14+
* @see \__TESTS_NAMESPACE__\__NAME__\__NAME__Test
15+
*/
16+
final class __NAME__ extends AbstractRector
17+
{
18+
public function getRuleDefinition(): RuleDefinition
19+
{
20+
return new RuleDefinition(
21+
'Changes something',
22+
[new CodeSample(
23+
<<<'CODE_SAMPLE'
24+
before
25+
CODE_SAMPLE,
26+
<<<'CODE_SAMPLE'
27+
after
28+
CODE_SAMPLE
29+
)]
30+
);
31+
}
32+
33+
/**
34+
* @return array<class-string<Node>>
35+
*/
36+
public function getNodeTypes(): array
37+
{
38+
// @todo select node type
39+
return [Class_::class];
40+
}
41+
42+
/**
43+
* @param Class_ $node
44+
*/
45+
public function refactor(Node $node): ?Node
46+
{
47+
// @todo change the node
48+
49+
return $node;
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace __TESTS_NAMESPACE__\__NAME__\Fixture;
4+
5+
// @todo fill code before
6+
7+
?>
8+
-----
9+
<?php
10+
11+
namespace __TESTS_NAMESPACE__\__NAME__\Fixture;
12+
13+
// @todo fill code after
14+
15+
?>
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace __TESTS_NAMESPACE__\__NAME__;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class __NAME__Test extends AbstractRectorTestCase
12+
{
13+
public static function provideData(): Iterator
14+
{
15+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
16+
}
17+
18+
/**
19+
* @test
20+
*/
21+
#[DataProvider('provideData')]
22+
public function test(string $filePath): void
23+
{
24+
$this->doTestFile($filePath);
25+
}
26+
27+
public function provideConfigFilePath(): string
28+
{
29+
return __DIR__ . '/config/configured_rule.php';
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use __NAMESPACE__\__NAME__;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(__NAME__::class);
10+
};

0 commit comments

Comments
 (0)