From 7a45ef627d0ee647ab460bb599d18c3a5698a7b7 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 19 Sep 2022 22:56:38 +0200 Subject: [PATCH] Add MaxLengthSymfonyFormOptionToAttrRector (#248) --- config/sets/symfony/symfony25.php | 6 +- docs/rector_rules_overview.md | 44 ++++- ...MaxLengthSymfonyFormOptionToAttrRector.php | 161 ++++++++++++++++++ stubs/Symfony/Component/Form/FormBuilder.php | 4 +- stubs/Symfony/Component/Form/FormFactory.php | 14 ++ .../Component/Form/FormFactoryInterface.php | 12 ++ .../Fixture/some_class.php.inc | 27 +++ .../Fixture/some_form_factory.php.inc | 23 +++ ...engthSymfonyFormOptionToAttrRectorTest.php | 32 ++++ .../config/configured_rule.php | 12 ++ 10 files changed, 330 insertions(+), 5 deletions(-) create mode 100644 src/Rector/MethodCall/MaxLengthSymfonyFormOptionToAttrRector.php create mode 100644 stubs/Symfony/Component/Form/FormFactory.php create mode 100644 stubs/Symfony/Component/Form/FormFactoryInterface.php create mode 100644 tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/Fixture/some_class.php.inc create mode 100644 tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/Fixture/some_form_factory.php.inc create mode 100644 tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/MaxLengthSymfonyFormOptionToAttrRectorTest.php create mode 100644 tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/config/configured_rule.php diff --git a/config/sets/symfony/symfony25.php b/config/sets/symfony/symfony25.php index b574690d..28bc5d70 100644 --- a/config/sets/symfony/symfony25.php +++ b/config/sets/symfony/symfony25.php @@ -4,7 +4,11 @@ use Rector\Config\RectorConfig; use Rector\Symfony\Rector\MethodCall\AddViolationToBuildViolationRector; +use Rector\Symfony\Rector\MethodCall\MaxLengthSymfonyFormOptionToAttrRector; return static function (RectorConfig $rectorConfig): void { - $rectorConfig->rule(AddViolationToBuildViolationRector::class); + $rectorConfig->rules([ + AddViolationToBuildViolationRector::class, + MaxLengthSymfonyFormOptionToAttrRector::class, + ]); }; diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index c83ebb87..966c78e1 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# 68 Rules Overview +# 70 Rules Overview ## ActionSuffixRemoverRector @@ -714,6 +714,27 @@ Turns properties with `@inject` to private properties and constructor injection
+## KernelTestCaseContainerPropertyDeprecationRector + +Simplify use of assertions in WebTestCase + +- class: [`Rector\Symfony\Rector\StaticPropertyFetch\KernelTestCaseContainerPropertyDeprecationRector`](../src/Rector/StaticPropertyFetch/KernelTestCaseContainerPropertyDeprecationRector.php) + +```diff + use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; + + class SomeTest extends KernelTestCase + { + protected function setUp(): void + { +- $container = self::$container; ++ $container = self::getContainer(); + } + } +``` + +
+ ## LiteralGetToRequestClassConstantRector Replace "GET" string by Symfony Request object class constants @@ -927,6 +948,23 @@ Make event object a first argument of `dispatch()` method, event name as second
+## MaxLengthSymfonyFormOptionToAttrRector + +Change form option "max_length" to a form "attr" > "max_length" + +- class: [`Rector\Symfony\Rector\MethodCall\MaxLengthSymfonyFormOptionToAttrRector`](../src/Rector/MethodCall/MaxLengthSymfonyFormOptionToAttrRector.php) + +```diff + $formBuilder = new Symfony\Component\Form\FormBuilder(); + + $form = $formBuilder->create('name', 'text', [ +- 'max_length' => 123, ++ 'attr' => ['maxlength' => 123], + ]); +``` + +
+ ## MergeMethodAnnotationToRouteAnnotationRector Merge removed `@Method` annotation to `@Route` one @@ -1354,9 +1392,9 @@ Change RouteCollectionBuilder to RoutingConfiguratorRector ## ServiceSetStringNameToClassNameRector -Change `$service->set()` string names to class-type-based names, to allow `$container->get()` by types in Symfony 2.8 +Change `$service->set()` string names to class-type-based names, to allow `$container->get()` by types in Symfony 2.8. Provide XML config via `$rectorConfig->symfonyContainerXml(...);` -- class: [`Rector\Symfony\Rector\Closure\ServiceSetStringNameToClassNameRector`](../src/Rector/MethodCall/ServiceSetStringNameToClassNameRector.php) +- class: [`Rector\Symfony\Rector\Closure\ServiceSetStringNameToClassNameRector`](../src/Rector/Closure/ServiceSetStringNameToClassNameRector.php) ```diff use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; diff --git a/src/Rector/MethodCall/MaxLengthSymfonyFormOptionToAttrRector.php b/src/Rector/MethodCall/MaxLengthSymfonyFormOptionToAttrRector.php new file mode 100644 index 00000000..3d0e23ea --- /dev/null +++ b/src/Rector/MethodCall/MaxLengthSymfonyFormOptionToAttrRector.php @@ -0,0 +1,161 @@ + "max_length"', [ + new CodeSample( + <<<'CODE_SAMPLE' +$formBuilder = new Symfony\Component\Form\FormBuilder(); + +$form = $formBuilder->create('name', 'text', [ + 'max_length' => 123, +]); +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +$formBuilder = new Symfony\Component\Form\FormBuilder(); + +$form = $formBuilder->create('name', 'text', [ + 'attr' => ['maxlength' => 123], +]); +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isFormCreateMethodCallMatch($node)) { + return null; + } + + $optionsArg = $node->getArgs()[2] ?? null; + if (! $optionsArg instanceof Arg) { + return null; + } + + if (! $optionsArg->value instanceof Array_) { + return null; + } + + $optionsArray = $optionsArg->value; + + $itemToAddToAttrs = null; + + foreach ($optionsArray->items as $arrayKey => $arrayItem) { + if (! $arrayItem instanceof ArrayItem) { + continue; + } + + if (! $arrayItem->key instanceof String_) { + continue; + } + + if (! $this->valueResolver->isValue($arrayItem->key, 'max_length')) { + continue; + } + + unset($optionsArray->items[$arrayKey]); + + $itemToAddToAttrs = $arrayItem; + break; + } + + if (! $itemToAddToAttrs instanceof ArrayItem) { + return null; + } + + $this->addArrayItemToAttrsItemOrCreateOne($optionsArray, $itemToAddToAttrs); + return $node; + } + + private function matchAttrArrayItem(Array_ $array): ?ArrayItem + { + foreach ($array->items as $arrayItem) { + if (! $arrayItem instanceof ArrayItem) { + continue; + } + + if (! $arrayItem->key instanceof String_) { + continue; + } + + if (! $this->valueResolver->isValue($arrayItem->key, 'attrs')) { + continue; + } + + return $arrayItem; + } + + return null; + } + + private function addArrayItemToAttrsItemOrCreateOne(Array_ $array, ArrayItem $arrayItem): Array_ + { + // rename + $arrayItem->key = new String_('maxlength'); + + $attrArrayItem = $this->matchAttrArrayItem($array); + + if ($attrArrayItem instanceof ArrayItem) { + if (! $attrArrayItem->value instanceof Array_) { + throw new ShouldNotHappenException(); + } + + $attrArrayItem->value->items[] = $arrayItem; + return $array; + } + + $array->items[] = new ArrayItem(new Array_([$arrayItem]), new String_('attr')); + return $array; + } + + private function isFormCreateMethodCallMatch(MethodCall $methodCall): bool + { + if ( + ! $this->isObjectType($methodCall->var, new ObjectType('Symfony\Component\Form\FormFactoryInterface')) + && ! $this->isObjectType($methodCall->var, new ObjectType('Symfony\Component\Form\FormBuilderInterface')) + ) { + return false; + } + + return $this->isNames($methodCall->name, ['create', 'add']); + } +} diff --git a/stubs/Symfony/Component/Form/FormBuilder.php b/stubs/Symfony/Component/Form/FormBuilder.php index 2f51de62..25e82c78 100644 --- a/stubs/Symfony/Component/Form/FormBuilder.php +++ b/stubs/Symfony/Component/Form/FormBuilder.php @@ -10,5 +10,7 @@ class FormBuilder implements FormBuilderInterface { - + public function add() + { + } } diff --git a/stubs/Symfony/Component/Form/FormFactory.php b/stubs/Symfony/Component/Form/FormFactory.php new file mode 100644 index 00000000..b0a48892 --- /dev/null +++ b/stubs/Symfony/Component/Form/FormFactory.php @@ -0,0 +1,14 @@ +add('name', 'text', [ + 'max_length' => 123, +]); + +?> +----- +add('name', 'text', [ + 'attr' => ['maxlength' => 123], +]); + +?> diff --git a/tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/Fixture/some_form_factory.php.inc b/tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/Fixture/some_form_factory.php.inc new file mode 100644 index 00000000..f2e90b9b --- /dev/null +++ b/tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/Fixture/some_form_factory.php.inc @@ -0,0 +1,23 @@ +create('name', 'text', [ + 'max_length' => 123, +]); + +?> +----- +create('name', 'text', [ + 'attr' => ['maxlength' => 123], +]); + +?> diff --git a/tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/MaxLengthSymfonyFormOptionToAttrRectorTest.php b/tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/MaxLengthSymfonyFormOptionToAttrRectorTest.php new file mode 100644 index 00000000..6a0cde34 --- /dev/null +++ b/tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/MaxLengthSymfonyFormOptionToAttrRectorTest.php @@ -0,0 +1,32 @@ +doTestFile($file); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/config/configured_rule.php b/tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/config/configured_rule.php new file mode 100644 index 00000000..6ef67f6a --- /dev/null +++ b/tests/Rector/Array_/MaxLengthSymfonyFormOptionToAttrRector/config/configured_rule.php @@ -0,0 +1,12 @@ +import(__DIR__ . '/../../../../../config/config.php'); + + $rectorConfig->rule(MaxLengthSymfonyFormOptionToAttrRector::class); +};