diff --git a/config/rector/sets/cakephp53.php b/config/rector/sets/cakephp53.php index 407d181..643ec3f 100644 --- a/config/rector/sets/cakephp53.php +++ b/config/rector/sets/cakephp53.php @@ -2,6 +2,7 @@ declare(strict_types=1); use Cake\Upgrade\Rector\Rector\ClassMethod\FormExecuteToProcessRector; +use Cake\Upgrade\Rector\Rector\MethodCall\BreadcrumbsHelperAddManyRector; use Cake\Upgrade\Rector\Rector\MethodCall\EntityIsEmptyRector; use Cake\Upgrade\Rector\Rector\MethodCall\EntityPatchRector; use Cake\Upgrade\Rector\Rector\MethodCall\NewExprToFuncRector; @@ -25,6 +26,7 @@ 'Cake\TestSuite\Fixture\TransactionFixtureStrategy' => 'Cake\TestSuite\Fixture\TransactionStrategy', 'Cake\TestSuite\Fixture\TruncateFixtureStrategy' => 'Cake\TestSuite\Fixture\TruncateStrategy', ]); + $rectorConfig->rule(BreadcrumbsHelperAddManyRector::class); $rectorConfig->rule(EntityIsEmptyRector::class); $rectorConfig->rule(EntityPatchRector::class); $rectorConfig->rule(FormExecuteToProcessRector::class); diff --git a/src/Rector/Rector/MethodCall/BreadcrumbsHelperAddManyRector.php b/src/Rector/Rector/MethodCall/BreadcrumbsHelperAddManyRector.php new file mode 100644 index 0000000..ecfbc7f --- /dev/null +++ b/src/Rector/Rector/MethodCall/BreadcrumbsHelperAddManyRector.php @@ -0,0 +1,142 @@ +Breadcrumbs->add([ + ['title' => 'Home', 'url' => '/'], + ['title' => 'Articles', 'url' => '/articles'], +]); +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +$this->Breadcrumbs->addMany([ + ['title' => 'Home', 'url' => '/'], + ['title' => 'Articles', 'url' => '/articles'], +]); +CODE_SAMPLE, + ), + ], + ); + } + + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + public function refactor(Node $node): ?Node + { + if (!$node instanceof MethodCall) { + return null; + } + + // Must be add or prepend method + if (!$node->name instanceof Identifier) { + return null; + } + + $methodName = $node->name->toString(); + if ($methodName !== 'add' && $methodName !== 'prepend') { + return null; + } + + // Must have at least one argument + if (count($node->args) < 1) { + return null; + } + + // First argument must be an array + $firstArg = $node->args[0]->value; + if (!$firstArg instanceof Array_) { + return null; + } + + // Check if the array looks like an array of crumbs (array of arrays) + // rather than a single crumb with title/url keys + if (!$this->isArrayOfCrumbs($firstArg)) { + return null; + } + + // Check if this is called on BreadcrumbsHelper + $callerType = $this->getType($node->var); + if (!$callerType instanceof ObjectType) { + return null; + } + + if (!$callerType->isInstanceOf('Cake\View\Helper\BreadcrumbsHelper')->yes()) { + return null; + } + + // Rename to addMany or prependMany + $newMethodName = $methodName === 'add' ? 'addMany' : 'prependMany'; + $node->name = new Identifier($newMethodName); + + return $node; + } + + /** + * Determine if an array is an array of crumbs (array of arrays) + * vs a single crumb (array with title/url keys) + */ + private function isArrayOfCrumbs(Array_ $array): bool + { + // An array of crumbs is typically a numerically indexed array of arrays + // e.g. [['title' => 'Home'], ['title' => 'Articles']] + // A single crumb would have string keys like 'title', 'url' + // e.g. ['title' => 'Home', 'url' => '/'] + + if (count($array->items) === 0) { + return false; + } + + foreach ($array->items as $item) { + if (!$item instanceof ArrayItem) { + continue; + } + + // If item has a string key like 'title' or 'url', it's a single crumb + if ($item->key instanceof String_) { + $keyValue = $item->key->value; + if ($keyValue === 'title' || $keyValue === 'url' || $keyValue === 'options') { + return false; + } + } + + // If the item value is an array, it's likely an array of crumbs + if ($item->value instanceof Array_) { + return true; + } + } + + return false; + } +} diff --git a/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/BreadcrumbsHelperAddManyRectorTest.php b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/BreadcrumbsHelperAddManyRectorTest.php new file mode 100644 index 0000000..34412c0 --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/BreadcrumbsHelperAddManyRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/fixture.php.inc b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..7705a43 --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/fixture.php.inc @@ -0,0 +1,53 @@ +Breadcrumbs->add([ + ['title' => 'Home', 'url' => '/'], + ['title' => 'Articles', 'url' => '/articles'], + ]); + + // Should transform: prepend with array of crumbs + $this->Breadcrumbs->prepend([ + ['title' => 'Dashboard'], + ]); + } +} + +?> +----- +Breadcrumbs->addMany([ + ['title' => 'Home', 'url' => '/'], + ['title' => 'Articles', 'url' => '/articles'], + ]); + + // Should transform: prepend with array of crumbs + $this->Breadcrumbs->prependMany([ + ['title' => 'Dashboard'], + ]); + } +} + +?> diff --git a/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/skip_single_crumb.php.inc b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/skip_single_crumb.php.inc new file mode 100644 index 0000000..3b98de8 --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/skip_single_crumb.php.inc @@ -0,0 +1,24 @@ +Breadcrumbs->add('Home', '/'); + + // Should NOT transform: single crumb as array with title key + $this->Breadcrumbs->add(['title' => 'Home', 'url' => '/']); + + // Should NOT transform: prepend single crumb + $this->Breadcrumbs->prepend('Dashboard'); + } +} + +?> diff --git a/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/config/configured_rule.php b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/config/configured_rule.php new file mode 100644 index 0000000..809158c --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/config/configured_rule.php @@ -0,0 +1,9 @@ +rule(BreadcrumbsHelperAddManyRector::class); +}; diff --git a/tests/test_apps/original/RectorCommand-testApply53/src/SomeTest.php b/tests/test_apps/original/RectorCommand-testApply53/src/SomeTest.php index 6c8e855..c89faab 100644 --- a/tests/test_apps/original/RectorCommand-testApply53/src/SomeTest.php +++ b/tests/test_apps/original/RectorCommand-testApply53/src/SomeTest.php @@ -7,11 +7,14 @@ use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query; +use Cake\View\Helper\BreadcrumbsHelper; class SomeTest { use LocatorAwareTrait; + private BreadcrumbsHelper $Breadcrumbs; + public function testRenames(): void { $entity = new Entity(); @@ -24,6 +27,13 @@ public function testRenames(): void $table = $this->fetchTable('Articles'); $expr = $table->find()->newExpr(); + // BreadcrumbsHelper::add(array) should be changed to addMany() + $this->Breadcrumbs->add([ + ['title' => 'Home', 'url' => '/'], + ]); + // Single crumb should stay as is + $this->Breadcrumbs->add('Articles', '/articles'); + // TypeFactory::getMap($type) should be changed to getMapped($type) $class = TypeFactory::getMap('datetime'); $allTypes = TypeFactory::getMap(); // This should stay as is diff --git a/tests/test_apps/upgraded/RectorCommand-testApply53/src/SomeTest.php b/tests/test_apps/upgraded/RectorCommand-testApply53/src/SomeTest.php index d29afdf..48bf6b9 100644 --- a/tests/test_apps/upgraded/RectorCommand-testApply53/src/SomeTest.php +++ b/tests/test_apps/upgraded/RectorCommand-testApply53/src/SomeTest.php @@ -7,11 +7,14 @@ use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query; +use Cake\View\Helper\BreadcrumbsHelper; class SomeTest { use LocatorAwareTrait; + private BreadcrumbsHelper $Breadcrumbs; + public function testRenames(): void { $entity = new Entity(); @@ -24,6 +27,13 @@ public function testRenames(): void $table = $this->fetchTable('Articles'); $expr = $table->find()->expr(); + // BreadcrumbsHelper::add(array) should be changed to addMany() + $this->Breadcrumbs->addMany([ + ['title' => 'Home', 'url' => '/'], + ]); + // Single crumb should stay as is + $this->Breadcrumbs->add('Articles', '/articles'); + // TypeFactory::getMap($type) should be changed to getMapped($type) $class = TypeFactory::getMapped('datetime'); $allTypes = TypeFactory::getMap(); // This should stay as is