66
77use Nette \Utils \FileSystem ;
88use Rector \Console \Command \CustomRuleCommand ;
9- use Symfony \Component \Finder \Finder ;
10- use Symfony \Component \Finder \SplFileInfo ;
119
1210/**
11+ * Generates scaffolding for a new Rector rule
12+ *
1313 * Modified version of
1414 *
1515 * @see CustomRuleCommand
1616 */
1717final class MakeRuleCommand
1818{
19- public function execute (string $ ruleName ): int
19+ private const string TEMPLATE_DIR = __DIR__ . '/../templates ' ;
20+
21+ private bool $ configurable = false ;
22+ private ?string $ directory = null ;
23+ private string $ rulesNamespace = 'RectorLaravel \\Rector ' ;
24+ private string $ testsNamespace = 'RectorLaravel \\Tests \\Rector ' ;
25+ private string $ testDir = 'tests/Rector/ ' ;
26+ private string $ currentDirectory ;
27+
28+ public function execute (string $ ruleName , bool $ configurable = false ): int
2029 {
21- $ rectorName = $ this ->getRuleName ($ ruleName );
22- $ rulesNamespace = 'RectorLaravel \\Rector ' ;
23- $ testsNamespace = 'RectorLaravel \\Tests \\Rector ' ;
30+ $ this ->configurable = $ configurable ;
2431
25- // find all files in templates directory
26- $ finder = Finder::create ()
27- ->files ()
28- ->in (__DIR__ . '/../templates/new-rule ' )
29- ->notName ('__NAME__Test.php ' );
32+ // Validate rule name
33+ if (empty ($ ruleName )) {
34+ echo PHP_EOL . 'Rule name must not be empty. ' . PHP_EOL ;
35+
36+ return 1 ;
37+ }
3038
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- ]);
39+ $ this ->currentDirectory = (string ) getcwd ();
3840
39- $ currentDirectory = getcwd ( );
41+ $ rectorName = $ this -> setNamespacesAndDirectories ( $ ruleName );
4042
41- $ generatedFilePaths = [];
43+ $ generatedFilePaths = [
44+ $ this ->createRule ($ rectorName ),
45+ $ this ->createTest ($ rectorName ),
46+ $ this ->createTestFixture ($ rectorName ),
47+ $ this ->createTestConfig ($ rectorName ),
48+ ];
4249
43- $ fileInfos = iterator_to_array ($ finder ->getIterator ());
50+ echo 'Generated files: ' . PHP_EOL . PHP_EOL . "\t" ;
51+ echo implode (PHP_EOL . "\t" , array_filter ($ generatedFilePaths )) . PHP_EOL ;
4452
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 ());
53+ return 0 ;
54+ }
5055
51- FileSystem::write ($ currentDirectory . '/ ' . $ newFilePath , $ newContent , null );
56+ private function setNamespacesAndDirectories (string $ ruleName ): string
57+ {
58+ // Extract directory and rule name if format contains slashes like "Directory/SubDir/More/RuleName"
59+ if (str_contains ($ ruleName , '/ ' )) {
60+ $ parts = explode ('/ ' , $ ruleName );
61+ // The last part is the rule name
62+ $ ruleName = array_pop ($ parts );
63+ // All other parts form the directory path
64+ if (! empty ($ parts )) {
65+ $ this ->directory = implode ('/ ' , $ parts ) . '/ ' ;
66+ }
67+ }
5268
53- $ generatedFilePaths [] = $ newFilePath ;
69+ // Add directory to namespace if provided
70+ if ($ this ->directory !== null ) {
71+ // Clean directory name (ensure it ends with a backslash for paths but not for namespace)
72+ $ cleanDir = rtrim ($ this ->directory , '\\/ ' );
73+ $ this ->directory = $ cleanDir . '/ ' ;
74+ $ namespaceDir = str_replace ('/ ' , '\\' , $ cleanDir );
75+ $ this ->rulesNamespace .= '\\' . $ namespaceDir ;
76+ $ this ->testsNamespace .= '\\' . $ namespaceDir ;
5477 }
5578
56- echo PHP_EOL . 'Generated files: ' . PHP_EOL . PHP_EOL . "\t" ;
57- echo implode (PHP_EOL . "\t" , $ generatedFilePaths ) . PHP_EOL ;
79+ $ rectorName = $ this ->formatRuleName ($ ruleName );
5880
59- return 0 ;
81+ $ this ->testDir .= $ this ->directory . $ rectorName ;
82+
83+ return $ rectorName ;
84+ }
85+
86+ private function createRule (string $ rectorName ): ?string
87+ {
88+ $ ruleFilePath = null ;
89+ $ ruleTemplateFile = $ this ->configurable
90+ ? self ::TEMPLATE_DIR . '/configurable-rule.php.template '
91+ : self ::TEMPLATE_DIR . '/non-configurable-rule.php.template ' ;
92+
93+ if (file_exists ($ ruleTemplateFile )) {
94+ $ contents = file_get_contents ($ ruleTemplateFile );
95+
96+ $ newContent = $ this ->replaceNameVariable ($ rectorName , $ contents );
97+ $ newContent = $ this ->replaceNamespaceVariable ($ newContent );
98+ $ newContent = $ this ->replaceTestsNamespaceVariable ($ newContent );
99+
100+ // Create the rule file path
101+ $ ruleFilePath = 'src/Rector/ ' ;
102+ if ($ this ->directory !== null ) {
103+ $ ruleFilePath .= $ this ->directory ;
104+ }
105+ $ ruleFilePath .= $ rectorName . '.php ' ;
106+
107+ // Ensure directory exists
108+ $ this ->ensureDirectoryExists ($ this ->currentDirectory . '/ ' . dirname ($ ruleFilePath ));
109+ FileSystem::write ($ this ->currentDirectory . '/ ' . $ ruleFilePath , $ newContent , null );
110+ }
111+
112+ return $ ruleFilePath ;
113+ }
114+
115+ private function createTest (string $ rectorName ): ?string
116+ {
117+ $ testFilePath = null ;
118+ $ testTemplateFile = self ::TEMPLATE_DIR . '/test.php.template ' ;
119+
120+ if (file_exists ($ testTemplateFile )) {
121+ $ contents = file_get_contents ($ testTemplateFile );
122+ $ newContent = $ this ->replaceNameVariable ($ rectorName , $ contents );
123+ $ newContent = $ this ->replaceNamespaceVariable ($ newContent );
124+ $ newContent = $ this ->replaceTestsNamespaceVariable ($ newContent );
125+
126+ $ testFilePath = $ this ->testDir . '/ ' . $ rectorName . 'Test.php ' ;
127+ $ this ->ensureDirectoryExists ($ this ->currentDirectory . '/ ' . dirname ($ testFilePath ));
128+ FileSystem::write ($ this ->currentDirectory . '/ ' . $ testFilePath , $ newContent , null );
129+ }
130+
131+ return $ testFilePath ;
132+ }
133+
134+ private function createTestFixture (string $ rectorName ): ?string
135+ {
136+ $ fixtureFilePath = null ;
137+ $ fixtureDir = $ this ->testDir . '/Fixture ' ;
138+ $ this ->ensureDirectoryExists ($ this ->currentDirectory . '/ ' . $ fixtureDir );
139+
140+ // Create fixture file from template
141+ $ fixtureTemplateFile = self ::TEMPLATE_DIR . '/fixture.php.inc.template ' ;
142+
143+ if (file_exists ($ fixtureTemplateFile )) {
144+ $ fixtureContents = file_get_contents ($ fixtureTemplateFile );
145+ $ fixtureContents = $ this ->replaceNameVariable ($ rectorName , $ fixtureContents );
146+ $ fixtureContents = $ this ->replaceTestsNamespaceVariable ($ fixtureContents );
147+
148+ $ fixtureFilePath = $ fixtureDir . '/some_class.php.inc ' ;
149+ FileSystem::write ($ this ->currentDirectory . '/ ' . $ fixtureFilePath , $ fixtureContents , null );
150+ }
151+
152+ return $ fixtureFilePath ;
60153 }
61154
62- private function getRuleName (string $ ruleName ): string
155+ private function createTestConfig (string $ rectorName ): ?string
156+ {
157+ $ configFilePath = null ;
158+ $ configDir = $ this ->testDir . '/config ' ;
159+ $ this ->ensureDirectoryExists ($ this ->currentDirectory . '/ ' . $ configDir );
160+
161+ // Create config file from template - select based on configurability
162+ $ configTemplateFile = $ this ->configurable
163+ ? self ::TEMPLATE_DIR . '/configurable-config.php.template '
164+ : self ::TEMPLATE_DIR . '/non-configurable-config.php.template ' ;
165+
166+ if (file_exists ($ configTemplateFile )) {
167+ $ configContents = file_get_contents ($ configTemplateFile );
168+ $ configContents = $ this ->replaceNameVariable ($ rectorName , $ configContents );
169+ $ configContents = $ this ->replaceNamespaceVariable ($ configContents );
170+
171+ $ configFilePath = $ configDir . '/configured_rule.php ' ;
172+ FileSystem::write ($ this ->currentDirectory . '/ ' . $ configFilePath , $ configContents , null );
173+ }
174+
175+ return $ configFilePath ;
176+ }
177+
178+ private function formatRuleName (string $ ruleName ): string
63179 {
64180 if (! str_ends_with ($ ruleName , 'Rector ' )) {
65181 $ ruleName .= 'Rector ' ;
@@ -73,13 +189,20 @@ private function replaceNameVariable(string $rectorName, string $contents): stri
73189 return str_replace ('__NAME__ ' , $ rectorName , $ contents );
74190 }
75191
76- private function replaceNamespaceVariable (string $ namespace , string $ contents ): string
192+ private function replaceNamespaceVariable (string $ contents ): string
77193 {
78- return str_replace ('__NAMESPACE__ ' , $ namespace , $ contents );
194+ return str_replace ('__NAMESPACE__ ' , $ this -> rulesNamespace , $ contents );
79195 }
80196
81- private function replaceTestsNamespaceVariable (string $ testsNamespace , string $ contents ): string
197+ private function replaceTestsNamespaceVariable (string $ contents ): string
82198 {
83- return str_replace ('__TESTS_NAMESPACE__ ' , $ testsNamespace , $ contents );
199+ return str_replace ('__TESTS_NAMESPACE__ ' , $ this ->testsNamespace , $ contents );
200+ }
201+
202+ private function ensureDirectoryExists (string $ directory ): void
203+ {
204+ if (! is_dir ($ directory )) {
205+ mkdir ($ directory , 0777 , true );
206+ }
84207 }
85208}
0 commit comments