Skip to content

Commit

Permalink
Add MaxLengthSymfonyFormOptionToAttrRector
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Sep 19, 2022
1 parent 4e464b4 commit 5bc75ab
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 5 deletions.
6 changes: 5 additions & 1 deletion config/sets/symfony/symfony25.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]);
};
44 changes: 41 additions & 3 deletions docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 68 Rules Overview
# 70 Rules Overview

## ActionSuffixRemoverRector

Expand Down Expand Up @@ -714,6 +714,27 @@ Turns properties with `@inject` to private properties and constructor injection

<br>

## 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();
}
}
```

<br>

## LiteralGetToRequestClassConstantRector

Replace "GET" string by Symfony Request object class constants
Expand Down Expand Up @@ -927,6 +948,23 @@ Make event object a first argument of `dispatch()` method, event name as second

<br>

## 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],
]);
```

<br>

## MergeMethodAnnotationToRouteAnnotationRector

Merge removed `@Method` annotation to `@Route` one
Expand Down Expand Up @@ -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;
Expand Down
161 changes: 161 additions & 0 deletions src/Rector/MethodCall/MaxLengthSymfonyFormOptionToAttrRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

declare(strict_types=1);

namespace Rector\Symfony\Rector\MethodCall;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Scalar\String_;
use PHPStan\Type\ObjectType;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @changelog https://github.com/symfony/symfony/commit/feea36df99205fd12be52515edeb10fc18523232
* @changelog https://github.com/symfony/symfony-docs/pull/3461
* @changelog https://github.com/symfony/symfony/issues/7148
*
* @see \Rector\Symfony\Tests\Rector\Array_\MaxLengthSymfonyFormOptionToAttrRector\MaxLengthSymfonyFormOptionToAttrRectorTest
*/
final class MaxLengthSymfonyFormOptionToAttrRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change form option "max_length" to a form "attr" > "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<class-string<Node>>
*/
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']);
}
}
4 changes: 3 additions & 1 deletion stubs/Symfony/Component/Form/FormBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@

class FormBuilder implements FormBuilderInterface
{

public function add()
{
}
}
14 changes: 14 additions & 0 deletions stubs/Symfony/Component/Form/FormFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Symfony\Component\Form;

if (class_exists('Symfony\Component\Form\FormFactory')) {
return;
}

class FormFactory implements FormFactoryInterface
{

}
12 changes: 12 additions & 0 deletions stubs/Symfony/Component/Form/FormFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);

namespace Symfony\Component\Form;

if (interface_exists('Symfony\Component\Form\FormFactoryInterface')) {
return;
}

interface FormFactoryInterface
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Rector\Symfony\Tests\Rector\Array_\MaxLengthSymfonyFormOptionToAttrRector\Fixture;

use Symfony\Component\Form\FormBuilder;

$formBuilder = new FormBuilder();

$form = $formBuilder->add('name', 'text', [
'max_length' => 123,
]);

?>
-----
<?php

namespace Rector\Symfony\Tests\Rector\Array_\MaxLengthSymfonyFormOptionToAttrRector\Fixture;

use Symfony\Component\Form\FormBuilder;

$formBuilder = new FormBuilder();

$form = $formBuilder->add('name', 'text', [
'attr' => ['maxlength' => 123],
]);

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Rector\Symfony\Tests\Rector\Array_\MaxLengthSymfonyFormOptionToAttrRector\Fixture;

$formBuilder = new \Symfony\Component\Form\FormFactory();

$form = $formBuilder->create('name', 'text', [
'max_length' => 123,
]);

?>
-----
<?php

namespace Rector\Symfony\Tests\Rector\Array_\MaxLengthSymfonyFormOptionToAttrRector\Fixture;

$formBuilder = new \Symfony\Component\Form\FormFactory();

$form = $formBuilder->create('name', 'text', [
'attr' => ['maxlength' => 123],
]);

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Rector\Symfony\Tests\Rector\Array_\MaxLengthSymfonyFormOptionToAttrRector;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class MaxLengthSymfonyFormOptionToAttrRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}

/**
* @return Iterator<string[]>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Symfony\Rector\MethodCall\MaxLengthSymfonyFormOptionToAttrRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->import(__DIR__ . '/../../../../../config/config.php');

$rectorConfig->rule(MaxLengthSymfonyFormOptionToAttrRector::class);
};

0 comments on commit 5bc75ab

Please sign in to comment.