From 4c86550a62db38fc3c3b9c4595ed7f110e29dea3 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 20 Oct 2017 20:10:52 +0200 Subject: [PATCH 1/8] [Symfony][3.0] add few dynamic --- src/config/level/symfony/symfony30.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/config/level/symfony/symfony30.yml diff --git a/src/config/level/symfony/symfony30.yml b/src/config/level/symfony/symfony30.yml new file mode 100644 index 00000000000..1870c606bb1 --- /dev/null +++ b/src/config/level/symfony/symfony30.yml @@ -0,0 +1,16 @@ +rectors: + # @todo dynamic constant rename rector + + Rector\Rector\Dynamic\MethodNameReplacerRector: + # form + 'Symfony\Component\Form\AbstractType': + 'setDefaultOptions': 'configureOptions' + 'Symfony\Component\Form\FormTypeInterface': + 'getName': 'getBlockPrefix' + + Rector\Rector\Dynamic\ClassReplacerRector: + # form + 'Symfony\Component\Form\Util\VirtualFormAwareIterator': 'Symfony\Component\Form\Util\InheritDataAwareIterator' + # property access + Symfony\Component\PropertyAccess\PropertyAccess: + 'getPropertyAccessor': 'createPropertyAccessor' From 647dbd0cc68d7a3274e365857b86529614756580 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 20 Oct 2017 20:29:23 +0200 Subject: [PATCH 2/8] add ClassConstantReplacerRector --- src/NodeAnalyzer/ClassConstAnalyzer.php | 35 +++++--- .../Dynamic/ClassConstantReplacerRector.php | 84 +++++++++++++++++++ src/config/level/symfony/symfony30.yml | 6 +- .../ClassConstantReplacerRector/Test.php | 30 +++++++ .../config/rector.yml | 6 ++ .../correct/correct.php.inc | 13 +++ .../wrong/wrong.php.inc | 13 +++ 7 files changed, 174 insertions(+), 13 deletions(-) create mode 100644 src/Rector/Dynamic/ClassConstantReplacerRector.php create mode 100644 tests/Rector/Dynamic/ClassConstantReplacerRector/Test.php create mode 100644 tests/Rector/Dynamic/ClassConstantReplacerRector/config/rector.yml create mode 100644 tests/Rector/Dynamic/ClassConstantReplacerRector/correct/correct.php.inc create mode 100644 tests/Rector/Dynamic/ClassConstantReplacerRector/wrong/wrong.php.inc diff --git a/src/NodeAnalyzer/ClassConstAnalyzer.php b/src/NodeAnalyzer/ClassConstAnalyzer.php index 183d139ebb6..23f95d09e21 100644 --- a/src/NodeAnalyzer/ClassConstAnalyzer.php +++ b/src/NodeAnalyzer/ClassConstAnalyzer.php @@ -27,17 +27,9 @@ final class ClassConstAnalyzer private function isClassName(ClassConstFetch $classConstFetchNode, string $className): bool { - /** @var FullyQualified $className */ - $classFullyQualifiedName = $classConstFetchNode->class->getAttribute(Attribute::RESOLVED_NAME); + $nodeClass = $this->resolveClass($classConstFetchNode); - if ($classFullyQualifiedName instanceof FullyQualified) { - return $classFullyQualifiedName->toString() === $className; - } - - // e.g. "$form::FILLED" - $nodeClassName = $classConstFetchNode->class->getAttribute(Attribute::CLASS_NAME); - - return $nodeClassName === $className; + return $nodeClass === $className; } /** @@ -49,4 +41,27 @@ final class ClassConstAnalyzer return in_array($nodeConstantName, $constantNames, true); } + + public function matchTypes(Node $node, array $types): ?string + { + if (! $node instanceof ClassConstFetch) { + return null; + } + + $class = $this->resolveClass($node); + + return in_array($class, $types, true) ? $class : null; + } + + private function resolveClass(ClassConstFetch $classConstFetchNode): string + { + $classFullyQualifiedName = $classConstFetchNode->class->getAttribute(Attribute::RESOLVED_NAME); + + if ($classFullyQualifiedName instanceof FullyQualified) { + return $classFullyQualifiedName->toString(); + } + + // e.g. "$form::FILLED" + return (string) $classConstFetchNode->class->getAttribute(Attribute::CLASS_NAME); + } } diff --git a/src/Rector/Dynamic/ClassConstantReplacerRector.php b/src/Rector/Dynamic/ClassConstantReplacerRector.php new file mode 100644 index 00000000000..84e6f3e7093 --- /dev/null +++ b/src/Rector/Dynamic/ClassConstantReplacerRector.php @@ -0,0 +1,84 @@ + [ + * OLD_CONSTANT => NEW_CONSTANT + * ] + * + * @var string[] + */ + private $oldToNewConstantsByClass = []; + + /** + * @var ClassConstAnalyzer + */ + private $classConstAnalyzer; + + /** + * @var string + */ + private $activeType; + + /** + * @param string[] $oldToNewConstantsByClass + */ + public function __construct(array $oldToNewConstantsByClass, ClassConstAnalyzer $classConstAnalyzer) + { + $this->oldToNewConstantsByClass = $oldToNewConstantsByClass; + $this->classConstAnalyzer = $classConstAnalyzer; + } + + public function isCandidate(Node $node): bool + { + $this->activeType = null; + + foreach ($this->oldToNewConstantsByClass as $type => $oldToNewConstants) { + $matchedType = $this->classConstAnalyzer->matchTypes($node, $this->getTypes()); + if ($matchedType) { + $this->activeType = $matchedType; + return true; + } + } + + return false; + } + + /** + * @param ClassConstFetch $classConstFetchNode + */ + public function refactor(Node $classConstFetchNode): ?Node + { + $configuration = $this->oldToNewConstantsByClass[$this->activeType]; + $constantName = $classConstFetchNode->name->toString(); + + if (! isset($configuration[$constantName])) { + return $classConstFetchNode; + } + + $newConstantName = $configuration[$constantName]; + + $classConstFetchNode->name = new Identifier($newConstantName); + + return $classConstFetchNode; + } + + /** + * @return string[] + */ + private function getTypes(): array + { + return array_keys($this->oldToNewConstantsByClass); + } +} diff --git a/src/config/level/symfony/symfony30.yml b/src/config/level/symfony/symfony30.yml index 1870c606bb1..32fb7199757 100644 --- a/src/config/level/symfony/symfony30.yml +++ b/src/config/level/symfony/symfony30.yml @@ -7,10 +7,10 @@ rectors: 'setDefaultOptions': 'configureOptions' 'Symfony\Component\Form\FormTypeInterface': 'getName': 'getBlockPrefix' + # property access + Symfony\Component\PropertyAccess\PropertyAccess: + 'getPropertyAccessor': 'createPropertyAccessor' Rector\Rector\Dynamic\ClassReplacerRector: # form 'Symfony\Component\Form\Util\VirtualFormAwareIterator': 'Symfony\Component\Form\Util\InheritDataAwareIterator' - # property access - Symfony\Component\PropertyAccess\PropertyAccess: - 'getPropertyAccessor': 'createPropertyAccessor' diff --git a/tests/Rector/Dynamic/ClassConstantReplacerRector/Test.php b/tests/Rector/Dynamic/ClassConstantReplacerRector/Test.php new file mode 100644 index 00000000000..719b2c83684 --- /dev/null +++ b/tests/Rector/Dynamic/ClassConstantReplacerRector/Test.php @@ -0,0 +1,30 @@ +doTestFileMatchesExpectedContent( + __DIR__ . '/wrong/wrong.php.inc', + __DIR__ . '/correct/correct.php.inc' + ); + } + + protected function provideConfig(): string + { + return __DIR__ . '/config/rector.yml'; + } + + /** + * @return string[] + */ + protected function getRectorClasses(): array + { + return [ClassConstantReplacerRector::class]; + } +} diff --git a/tests/Rector/Dynamic/ClassConstantReplacerRector/config/rector.yml b/tests/Rector/Dynamic/ClassConstantReplacerRector/config/rector.yml new file mode 100644 index 00000000000..6efc9972062 --- /dev/null +++ b/tests/Rector/Dynamic/ClassConstantReplacerRector/config/rector.yml @@ -0,0 +1,6 @@ +rectors: + Rector\Rector\Dynamic\ClassConstantReplacerRector: + 'Symfony\Component\Form\FormEvents': + 'PRE_BIND': 'PRE_SUBMIT' + 'BIND': 'SUBMIT' + 'POST_BIND': 'POST_SUBMIT' diff --git a/tests/Rector/Dynamic/ClassConstantReplacerRector/correct/correct.php.inc b/tests/Rector/Dynamic/ClassConstantReplacerRector/correct/correct.php.inc new file mode 100644 index 00000000000..869774d2820 --- /dev/null +++ b/tests/Rector/Dynamic/ClassConstantReplacerRector/correct/correct.php.inc @@ -0,0 +1,13 @@ + Date: Fri, 20 Oct 2017 20:35:04 +0200 Subject: [PATCH 3/8] add FormEvents rector to symfony30.yml --- README.md | 14 +++++ src/NodeAnalyzer/ClassConstAnalyzer.php | 53 ++++++++++--------- .../Nette/Forms/FormNegativeRulesRector.php | 2 +- ...oleExceptionToErrorEventConstantRector.php | 2 +- .../Dynamic/ClassConstantReplacerRector.php | 3 +- src/config/level/symfony/symfony30.yml | 7 ++- 6 files changed, 50 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 5ef6217825a..09f802cb11f 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,20 @@ You can: 'renderFormBegin': ['Nette\Bridges\FormsLatte\Runtime', 'renderFormBegin'] ``` +- **change class constant name** + + ```yml + # symfony30.yml + rectors: + Rector\Rector\Dynamic\ClassConstantReplacerRector: + # class: + # OLD_CONSTANT: NEW_CONSTANT + 'Symfony\Component\Form\FormEvents': + 'PRE_BIND': 'PRE_SUBMIT' + 'BIND': 'SUBMIT' + 'POST_BIND': 'POST_SUBMIT' + ``` + - or **replace underscore naming `_` with namespaces `\`** ```yml diff --git a/src/NodeAnalyzer/ClassConstAnalyzer.php b/src/NodeAnalyzer/ClassConstAnalyzer.php index 23f95d09e21..f840af762d9 100644 --- a/src/NodeAnalyzer/ClassConstAnalyzer.php +++ b/src/NodeAnalyzer/ClassConstAnalyzer.php @@ -12,34 +12,14 @@ final class ClassConstAnalyzer /** * @param string[] $constantNames */ - public function isClassConstFetchOfClassAndConstantNames(Node $node, string $class, array $constantNames): bool + public function isTypeAndNames(Node $node, string $type, array $constantNames): bool { - if (! $node instanceof ClassConstFetch) { + if (! $this->isType($node, $type)) { return false; } - if (! $this->isClassName($node, $class)) { - return false; - } - - return $this->isConstantName($node, $constantNames); - } - - private function isClassName(ClassConstFetch $classConstFetchNode, string $className): bool - { - $nodeClass = $this->resolveClass($classConstFetchNode); - - return $nodeClass === $className; - } - - /** - * @param string[] $constantNames - */ - private function isConstantName(ClassConstFetch $node, array $constantNames): bool - { - $nodeConstantName = $node->name->name; - - return in_array($nodeConstantName, $constantNames, true); + /** @var ClassConstFetch $node */ + return $this->isNames($node, $constantNames); } public function matchTypes(Node $node, array $types): ?string @@ -48,12 +28,33 @@ final class ClassConstAnalyzer return null; } - $class = $this->resolveClass($node); + $class = $this->resolveType($node); return in_array($class, $types, true) ? $class : null; } - private function resolveClass(ClassConstFetch $classConstFetchNode): string + private function isType(Node $node, string $type): bool + { + if (! $node instanceof ClassConstFetch) { + return false; + } + + $nodeClass = $this->resolveType($node); + + return $nodeClass === $type; + } + + /** + * @param string[] $names + */ + private function isNames(ClassConstFetch $node, array $names): bool + { + $nodeConstantName = $node->name->name; + + return in_array($nodeConstantName, $names, true); + } + + private function resolveType(ClassConstFetch $classConstFetchNode): string { $classFullyQualifiedName = $classConstFetchNode->class->getAttribute(Attribute::RESOLVED_NAME); diff --git a/src/Rector/Contrib/Nette/Forms/FormNegativeRulesRector.php b/src/Rector/Contrib/Nette/Forms/FormNegativeRulesRector.php index 477f938fd0d..a0fb0e3fd5a 100644 --- a/src/Rector/Contrib/Nette/Forms/FormNegativeRulesRector.php +++ b/src/Rector/Contrib/Nette/Forms/FormNegativeRulesRector.php @@ -48,7 +48,7 @@ final class FormNegativeRulesRector extends AbstractRector return false; } - return $this->classConstAnalyzer->isClassConstFetchOfClassAndConstantNames( + return $this->classConstAnalyzer->isTypeAndNames( $node->expr, self::FORM_CLASS, self::RULE_NAMES diff --git a/src/Rector/Contrib/Symfony/Console/ConsoleExceptionToErrorEventConstantRector.php b/src/Rector/Contrib/Symfony/Console/ConsoleExceptionToErrorEventConstantRector.php index d25ee7cba82..0c4bfd479b3 100644 --- a/src/Rector/Contrib/Symfony/Console/ConsoleExceptionToErrorEventConstantRector.php +++ b/src/Rector/Contrib/Symfony/Console/ConsoleExceptionToErrorEventConstantRector.php @@ -47,7 +47,7 @@ final class ConsoleExceptionToErrorEventConstantRector extends AbstractRector public function isCandidate(Node $node): bool { - if ($this->classConstAnalyzer->isClassConstFetchOfClassAndConstantNames( + if ($this->classConstAnalyzer->isTypeAndNames( $node, self::CONSOLE_EVENTS_CLASS, ['EXCEPTION'] diff --git a/src/Rector/Dynamic/ClassConstantReplacerRector.php b/src/Rector/Dynamic/ClassConstantReplacerRector.php index 84e6f3e7093..03417c0b4e5 100644 --- a/src/Rector/Dynamic/ClassConstantReplacerRector.php +++ b/src/Rector/Dynamic/ClassConstantReplacerRector.php @@ -5,8 +5,6 @@ namespace Rector\Rector\Dynamic; use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Identifier; -use PhpParser\Node\Name; -use PhpParser\Node\Stmt\UseUse; use Rector\NodeAnalyzer\ClassConstAnalyzer; use Rector\Rector\AbstractRector; @@ -48,6 +46,7 @@ final class ClassConstantReplacerRector extends AbstractRector $matchedType = $this->classConstAnalyzer->matchTypes($node, $this->getTypes()); if ($matchedType) { $this->activeType = $matchedType; + return true; } } diff --git a/src/config/level/symfony/symfony30.yml b/src/config/level/symfony/symfony30.yml index 32fb7199757..71f094c6d97 100644 --- a/src/config/level/symfony/symfony30.yml +++ b/src/config/level/symfony/symfony30.yml @@ -1,5 +1,10 @@ rectors: - # @todo dynamic constant rename rector + Rector\Rector\Dynamic\ClassConstantReplacerRector: + # form + 'Symfony\Component\Form\FormEvents': + 'PRE_BIND': 'PRE_SUBMIT' + 'BIND': 'SUBMIT' + 'POST_BIND': 'POST_SUBMIT' Rector\Rector\Dynamic\MethodNameReplacerRector: # form From 16828a53a32ef53a1afe8356f07eb9c1722d920b Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 20 Oct 2017 21:00:20 +0200 Subject: [PATCH 4/8] add MethodResolver, add FormTypeGetParentRector --- .../src/NodeVisitor/MethodResolver.php | 40 ++++++++ src/Node/Attribute.php | 5 + .../Symfony/Form/FormTypeGetParentRector.php | 97 +++++++++++++++++++ .../Form/StringFormTypeToClassRector.php | 6 +- src/config/level/symfony/symfony30.yml | 2 + src/config/services.yml | 2 + .../Correct/correct.php.inc | 16 +++ .../Form/FormTypeGetParentRector/Test.php | 25 +++++ .../Wrong/wrong.php.inc | 16 +++ 9 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 packages/NodeTypeResolver/src/NodeVisitor/MethodResolver.php create mode 100644 src/Rector/Contrib/Symfony/Form/FormTypeGetParentRector.php create mode 100644 tests/Rector/Contrib/Symfony/Form/FormTypeGetParentRector/Correct/correct.php.inc create mode 100644 tests/Rector/Contrib/Symfony/Form/FormTypeGetParentRector/Test.php create mode 100644 tests/Rector/Contrib/Symfony/Form/FormTypeGetParentRector/Wrong/wrong.php.inc diff --git a/packages/NodeTypeResolver/src/NodeVisitor/MethodResolver.php b/packages/NodeTypeResolver/src/NodeVisitor/MethodResolver.php new file mode 100644 index 00000000000..589eef06133 --- /dev/null +++ b/packages/NodeTypeResolver/src/NodeVisitor/MethodResolver.php @@ -0,0 +1,40 @@ +methodName = null; + } + + public function enterNode(Node $node): void + { + if ($node instanceof ClassMethod) { + $this->methodName = $node->name->toString(); + } + + if ($this->methodName === null) { + return; + } + + $node->setAttribute(Attribute::METHOD_NAME, $this->methodName); + } +} diff --git a/src/Node/Attribute.php b/src/Node/Attribute.php index 799ee3f56ef..2c3591fd49a 100644 --- a/src/Node/Attribute.php +++ b/src/Node/Attribute.php @@ -49,6 +49,11 @@ final class Attribute */ public const CLASS_NAME = 'className'; + /** + * @var string + */ + public const METHOD_NAME = 'methodName'; + /** * @var string */ diff --git a/src/Rector/Contrib/Symfony/Form/FormTypeGetParentRector.php b/src/Rector/Contrib/Symfony/Form/FormTypeGetParentRector.php new file mode 100644 index 00000000000..a80f9d036db --- /dev/null +++ b/src/Rector/Contrib/Symfony/Form/FormTypeGetParentRector.php @@ -0,0 +1,97 @@ + 'Symfony\Component\Form\Extension\Core\Type\BirthdayType', + 'checkbox' => 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', + 'collection' => 'Symfony\Component\Form\Extension\Core\Type\CollectionType', + 'country' => 'Symfony\Component\Form\Extension\Core\Type\CountryType', + 'currency' => 'Symfony\Component\Form\Extension\Core\Type\CurrencyType', + 'date' => 'Symfony\Component\Form\Extension\Core\Type\DateType', + 'datetime' => 'Symfony\Component\Form\Extension\Core\Type\DatetimeType', + 'email' => 'Symfony\Component\Form\Extension\Core\Type\EmailType', + 'file' => 'Symfony\Component\Form\Extension\Core\Type\FileType', + 'hidden' => 'Symfony\Component\Form\Extension\Core\Type\HiddenType', + 'integer' => 'Symfony\Component\Form\Extension\Core\Type\IntegerType', + 'language' => 'Symfony\Component\Form\Extension\Core\Type\LanguageType', + 'locale' => 'Symfony\Component\Form\Extension\Core\Type\LocaleType', + 'money' => 'Symfony\Component\Form\Extension\Core\Type\MoneyType', + 'number' => 'Symfony\Component\Form\Extension\Core\Type\NumberType', + 'password' => 'Symfony\Component\Form\Extension\Core\Type\PasswordType', + 'percent' => 'Symfony\Component\Form\Extension\Core\Type\PercentType', + 'radio' => 'Symfony\Component\Form\Extension\Core\Type\RadioType', + 'range' => 'Symfony\Component\Form\Extension\Core\Type\RangeType', + 'repeated' => 'Symfony\Component\Form\Extension\Core\Type\RepeatedType', + 'search' => 'Symfony\Component\Form\Extension\Core\Type\SearchType', + 'textarea' => 'Symfony\Component\Form\Extension\Core\Type\TextareaType', + 'text' => 'Symfony\Component\Form\Extension\Core\Type\TextType', + 'time' => 'Symfony\Component\Form\Extension\Core\Type\TimeType', + 'timezone' => 'Symfony\Component\Form\Extension\Core\Type\TimezoneType', + 'url' => 'Symfony\Component\Form\Extension\Core\Type\UrlType', + 'button' => 'Symfony\Component\Form\Extension\Core\Type\ButtonType', + 'submit' => 'Symfony\Component\Form\Extension\Core\Type\SubmitType', + 'reset' => 'Symfony\Component\Form\Extension\Core\Type\ResetType', + ]; + + /** + * @var NodeFactory + */ + private $nodeFactory; + + public function __construct(NodeFactory $nodeFactory) + { + $this->nodeFactory = $nodeFactory; + } + + public function isCandidate(Node $node): bool + { + if (! $node instanceof String_ || ! isset($this->nameToClassMap[$node->value])) { + return false; + } + + $parentClassName = $node->getAttribute(Attribute::PARENT_CLASS_NAME); + if ($parentClassName !== 'Symfony\Component\Form\AbstractType') { + return false; + } + + $methodName = $node->getAttribute(Attribute::METHOD_NAME); + if ($methodName !== 'getParent') { + return false; + } + + return true; + } + + /** + * @param String_ $stringNode + */ + public function refactor(Node $stringNode): ?Node + { + $class = $this->nameToClassMap[$stringNode->value]; + + return $this->nodeFactory->createClassConstantReference($class); + } +} diff --git a/src/Rector/Contrib/Symfony/Form/StringFormTypeToClassRector.php b/src/Rector/Contrib/Symfony/Form/StringFormTypeToClassRector.php index ee6c00f5eb8..6e1a70b857c 100644 --- a/src/Rector/Contrib/Symfony/Form/StringFormTypeToClassRector.php +++ b/src/Rector/Contrib/Symfony/Form/StringFormTypeToClassRector.php @@ -69,11 +69,11 @@ final class StringFormTypeToClassRector extends AbstractRector } /** - * @param String_ $node + * @param String_ $stringNode */ - public function refactor(Node $node): ?Node + public function refactor(Node $stringNode): ?Node { - $class = $this->nameToClassMap[$node->value]; + $class = $this->nameToClassMap[$stringNode->value]; return $this->nodeFactory->createClassConstantReference($class); } diff --git a/src/config/level/symfony/symfony30.yml b/src/config/level/symfony/symfony30.yml index 71f094c6d97..cf7a37ed670 100644 --- a/src/config/level/symfony/symfony30.yml +++ b/src/config/level/symfony/symfony30.yml @@ -1,4 +1,6 @@ rectors: + Rector\Rector\Contrib\Symfony\Form\FormTypeGetParentRector: ~ + Rector\Rector\Dynamic\ClassConstantReplacerRector: # form 'Symfony\Component\Form\FormEvents': diff --git a/src/config/services.yml b/src/config/services.yml index 4bdd6a7eaf1..f0825316847 100644 --- a/src/config/services.yml +++ b/src/config/services.yml @@ -18,6 +18,8 @@ services: - ['addNodeVisitor', ['@Rector\NodeVisitor\NodeConnector']] # adds current class to all nodes via attribute - ['addNodeVisitor', ['@Rector\NodeTypeResolver\NodeVisitor\ClassResolver']] + # adds current method to all nodes via attribute + - ['addNodeVisitor', ['@Rector\NodeTypeResolver\NodeVisitor\MethodResolver']] # adds type to variable and property nodes via attribute - ['addNodeVisitor', ['@Rector\NodeTypeResolver\NodeVisitor\TypeResolver']] # adds current namespace to all nodes via attribute diff --git a/tests/Rector/Contrib/Symfony/Form/FormTypeGetParentRector/Correct/correct.php.inc b/tests/Rector/Contrib/Symfony/Form/FormTypeGetParentRector/Correct/correct.php.inc new file mode 100644 index 00000000000..c318d6fac7f --- /dev/null +++ b/tests/Rector/Contrib/Symfony/Form/FormTypeGetParentRector/Correct/correct.php.inc @@ -0,0 +1,16 @@ +doTestFileMatchesExpectedContent( + __DIR__ . '/Wrong/wrong.php.inc', + __DIR__ . '/Correct/correct.php.inc' + ); + } + + /** + * @return string[] + */ + protected function getRectorClasses(): array + { + return [FormTypeGetParentRector::class]; + } +} diff --git a/tests/Rector/Contrib/Symfony/Form/FormTypeGetParentRector/Wrong/wrong.php.inc b/tests/Rector/Contrib/Symfony/Form/FormTypeGetParentRector/Wrong/wrong.php.inc new file mode 100644 index 00000000000..cf810cd9fcd --- /dev/null +++ b/tests/Rector/Contrib/Symfony/Form/FormTypeGetParentRector/Wrong/wrong.php.inc @@ -0,0 +1,16 @@ + Date: Fri, 20 Oct 2017 21:00:54 +0200 Subject: [PATCH 5/8] cs fixes --- src/NodeAnalyzer/ClassConstAnalyzer.php | 3 +++ src/Rector/Contrib/Symfony/Form/FormTypeGetParentRector.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NodeAnalyzer/ClassConstAnalyzer.php b/src/NodeAnalyzer/ClassConstAnalyzer.php index f840af762d9..c80a0735d6a 100644 --- a/src/NodeAnalyzer/ClassConstAnalyzer.php +++ b/src/NodeAnalyzer/ClassConstAnalyzer.php @@ -22,6 +22,9 @@ final class ClassConstAnalyzer return $this->isNames($node, $constantNames); } + /** + * @param string[] $types + */ public function matchTypes(Node $node, array $types): ?string { if (! $node instanceof ClassConstFetch) { diff --git a/src/Rector/Contrib/Symfony/Form/FormTypeGetParentRector.php b/src/Rector/Contrib/Symfony/Form/FormTypeGetParentRector.php index a80f9d036db..ce94d79324b 100644 --- a/src/Rector/Contrib/Symfony/Form/FormTypeGetParentRector.php +++ b/src/Rector/Contrib/Symfony/Form/FormTypeGetParentRector.php @@ -2,8 +2,8 @@ namespace Rector\Rector\Contrib\Symfony\Form; -use PhpParser\Node\Scalar\String_; use PhpParser\Node; +use PhpParser\Node\Scalar\String_; use Rector\Node\Attribute; use Rector\Node\NodeFactory; use Rector\Rector\AbstractRector; From d500d445a5ccce09a8094ef9a41294329df84d7d Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 20 Oct 2017 22:42:18 +0200 Subject: [PATCH 6/8] [Symfony] add complete GetRequestRector --- src/Node/NodeFactory.php | 17 ++- .../Contrib/ControllerMethodAnalyzer.php | 48 ++++++++ .../SymfonyContainerCallsAnalyzer.php | 2 +- .../CommandToConstructorInjectionRector.php | 2 +- .../Symfony/HttpKernel/GetRequestRector.php | 110 ++++++++++++++++++ .../HttpKernel/GetterToPropertyRector.php | 2 +- src/config/level/symfony/symfony30.yml | 1 + .../GetRequestRector/Correct/correct.php.inc | 11 ++ .../HttpKernel/GetRequestRector/Test.php | 25 ++++ .../GetRequestRector/Wrong/wrong.php.inc | 11 ++ 10 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 src/NodeAnalyzer/Contrib/ControllerMethodAnalyzer.php rename src/NodeAnalyzer/{ => Contrib}/SymfonyContainerCallsAnalyzer.php (97%) create mode 100644 src/Rector/Contrib/Symfony/HttpKernel/GetRequestRector.php create mode 100644 tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Correct/correct.php.inc create mode 100644 tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Test.php create mode 100644 tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Wrong/wrong.php.inc diff --git a/src/Node/NodeFactory.php b/src/Node/NodeFactory.php index 7358c9d0332..036253f80b8 100644 --- a/src/Node/NodeFactory.php +++ b/src/Node/NodeFactory.php @@ -18,6 +18,7 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Identifier; use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Param; use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Declare_; @@ -105,7 +106,7 @@ final class NodeFactory */ public function createMethodCall(string $variableName, string $methodName): MethodCall { - $variableNode = new Variable($variableName); + $variableNode = $this->createVariable($variableName); return new MethodCall($variableNode, $methodName); } @@ -283,4 +284,18 @@ final class NodeFactory { return new PropertyFetch($propertyFetchNode->var, $propertyFetchNode->name); } + + public function createParam(string $name, string $type): Param + { + return new Param( + $this->createVariable($name), + null, + $type + ); + } + + public function createVariable(string $name): Variable + { + return new Variable($name); + } } diff --git a/src/NodeAnalyzer/Contrib/ControllerMethodAnalyzer.php b/src/NodeAnalyzer/Contrib/ControllerMethodAnalyzer.php new file mode 100644 index 00000000000..10498bc5630 --- /dev/null +++ b/src/NodeAnalyzer/Contrib/ControllerMethodAnalyzer.php @@ -0,0 +1,48 @@ +standardPrinter = $standardPrinter; + } + + /** + * Detect if is Action() in Controller + */ + public function isAction(Node $node): bool + { + if (! $node instanceof ClassMethod) { + return false; + } + + $parentClassName = $node->getAttribute(Attribute::PARENT_CLASS_NAME); + $controllerClass = 'Symfony\Bundle\FrameworkBundle\Controller\Controller'; + + if ($parentClassName !== $controllerClass) { + return false; + } + + return Strings::endsWith($node->name->toString(), 'Action'); + } + + public function doesNodeContain(ClassMethod $classMethodNode, string $part): bool + { + $methodInString = $this->standardPrinter->prettyPrint([$classMethodNode]); + + return Strings::contains($methodInString, $part); + } +} diff --git a/src/NodeAnalyzer/SymfonyContainerCallsAnalyzer.php b/src/NodeAnalyzer/Contrib/SymfonyContainerCallsAnalyzer.php similarity index 97% rename from src/NodeAnalyzer/SymfonyContainerCallsAnalyzer.php rename to src/NodeAnalyzer/Contrib/SymfonyContainerCallsAnalyzer.php index 00fb61589b4..f6db65b4466 100644 --- a/src/NodeAnalyzer/SymfonyContainerCallsAnalyzer.php +++ b/src/NodeAnalyzer/Contrib/SymfonyContainerCallsAnalyzer.php @@ -1,6 +1,6 @@ getRequest()->...(); + * + * into: + * public action(Request $request) + * { + * $request->...(); + * } + */ +final class GetRequestRector extends AbstractRector +{ + /** + * @var ControllerMethodAnalyzer + */ + private $controllerMethodAnalyzer; + + /** + * @var NodeFactory + */ + private $nodeFactory; + + /** + * @var MethodCallAnalyzer + */ + private $methodCallAnalyzer; + + public function __construct( + ControllerMethodAnalyzer $controllerMethodAnalyzer, + MethodCallAnalyzer $methodCallAnalyzer, + NodeFactory $nodeFactory + ) { + $this->controllerMethodAnalyzer = $controllerMethodAnalyzer; + $this->nodeFactory = $nodeFactory; + $this->methodCallAnalyzer = $methodCallAnalyzer; + } + + public function isCandidate(Node $node): bool + { + if ($this->isActionWithGetRequestInBody($node)) { + return true; + } + + if ($this->isGetRequestInAction($node)) { + return true; + } + + return false; + } + + /** + * @param ClassMethod|MethodCall $classMethodOrMethodCallNode + */ + public function refactor(Node $classMethodOrMethodCallNode): ?Node + { + if ($classMethodOrMethodCallNode instanceof ClassMethod) { + $requestParam = $this->nodeFactory->createParam('request', 'Symfony\Component\HttpFoundation\Request'); + + $classMethodOrMethodCallNode->params[] = $requestParam; + + return $classMethodOrMethodCallNode; + } + + return $this->nodeFactory->createVariable('request'); + } + + private function isActionWithGetRequestInBody(Node $node): bool + { + if (! $this->controllerMethodAnalyzer->isAction($node)) { + return false; + } + + /** @var ClassMethod $node */ + if (! $this->controllerMethodAnalyzer->doesNodeContain($node, '$this->getRequest()')) { + return false; + } + + return true; + } + + private function isGetRequestInAction(Node $node): bool + { + if (! $this->methodCallAnalyzer->isMethod($node, 'getRequest')) { + return false; + } + + $scopeNode = $node->getAttribute(Attribute::SCOPE_NODE); + + if (! $this->controllerMethodAnalyzer->isAction($scopeNode)) { + return false; + } + + return true; + } +} diff --git a/src/Rector/Contrib/Symfony/HttpKernel/GetterToPropertyRector.php b/src/Rector/Contrib/Symfony/HttpKernel/GetterToPropertyRector.php index 79a99d0894f..e7903804815 100644 --- a/src/Rector/Contrib/Symfony/HttpKernel/GetterToPropertyRector.php +++ b/src/Rector/Contrib/Symfony/HttpKernel/GetterToPropertyRector.php @@ -9,7 +9,7 @@ use Rector\Contract\Bridge\ServiceTypeForNameProviderInterface; use Rector\Naming\PropertyNaming; use Rector\Node\Attribute; use Rector\Node\NodeFactory; -use Rector\NodeAnalyzer\SymfonyContainerCallsAnalyzer; +use Rector\NodeAnalyzer\Contrib\SymfonyContainerCallsAnalyzer; use Rector\Rector\AbstractRector; /** diff --git a/src/config/level/symfony/symfony30.yml b/src/config/level/symfony/symfony30.yml index cf7a37ed670..bb3aa733418 100644 --- a/src/config/level/symfony/symfony30.yml +++ b/src/config/level/symfony/symfony30.yml @@ -1,4 +1,5 @@ rectors: + Rector\Rector\Contrib\Symfony\HttpKernel\GetRequestRector: ~ Rector\Rector\Contrib\Symfony\Form\FormTypeGetParentRector: ~ Rector\Rector\Dynamic\ClassConstantReplacerRector: diff --git a/tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Correct/correct.php.inc b/tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Correct/correct.php.inc new file mode 100644 index 00000000000..12f7df60333 --- /dev/null +++ b/tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Correct/correct.php.inc @@ -0,0 +1,11 @@ +getSomething(); + } +} diff --git a/tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Test.php b/tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Test.php new file mode 100644 index 00000000000..0f048222601 --- /dev/null +++ b/tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Test.php @@ -0,0 +1,25 @@ +doTestFileMatchesExpectedContent( + __DIR__ . '/Wrong/wrong.php.inc', + __DIR__ . '/Correct/correct.php.inc' + ); + } + + /** + * @return string[] + */ + protected function getRectorClasses(): array + { + return [GetRequestRector::class]; + } +} diff --git a/tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Wrong/wrong.php.inc b/tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Wrong/wrong.php.inc new file mode 100644 index 00000000000..c68af9f4736 --- /dev/null +++ b/tests/Rector/Contrib/Symfony/HttpKernel/GetRequestRector/Wrong/wrong.php.inc @@ -0,0 +1,11 @@ +getRequest()->getSomething(); + } +} From 75f94ba050090a3ce380e433990769d92342e165 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 20 Oct 2017 22:57:36 +0200 Subject: [PATCH 7/8] [Symfony] add OptionNameRector --- .../Contrib/Symfony/Form/OptionNameRector.php | 61 +++++++++++++++++++ src/config/level/symfony/symfony30.yml | 1 + .../OptionNameRector/Correct/correct.php.inc | 26 ++++++++ .../Symfony/Form/OptionNameRector/Test.php | 25 ++++++++ .../Form/OptionNameRector/Wrong/wrong.php.inc | 26 ++++++++ 5 files changed, 139 insertions(+) create mode 100644 src/Rector/Contrib/Symfony/Form/OptionNameRector.php create mode 100644 tests/Rector/Contrib/Symfony/Form/OptionNameRector/Correct/correct.php.inc create mode 100644 tests/Rector/Contrib/Symfony/Form/OptionNameRector/Test.php create mode 100644 tests/Rector/Contrib/Symfony/Form/OptionNameRector/Wrong/wrong.php.inc diff --git a/src/Rector/Contrib/Symfony/Form/OptionNameRector.php b/src/Rector/Contrib/Symfony/Form/OptionNameRector.php new file mode 100644 index 00000000000..94d6b6a7bce --- /dev/null +++ b/src/Rector/Contrib/Symfony/Form/OptionNameRector.php @@ -0,0 +1,61 @@ +add('...', ['precision' => '...', 'virtual' => '...']; + * + * + * into: + * - $builder->add('...', ['scale' => '...', 'inherit_data' => '...']; + */ +final class OptionNameRector extends AbstractRector +{ + /** + * @var string + */ + private $oldToNewOption = [ + 'precision' => 'scale', + 'virtual' => 'inherit_data', + ]; + + public function isCandidate(Node $node): bool + { + if (! $node instanceof String_) { + return false; + } + + if (! isset($this->oldToNewOption[$node->value])) { + return false; + } + + $arrayItemParentNode = $node->getAttribute(Attribute::PARENT_NODE); + if (! $arrayItemParentNode instanceof ArrayItem) { + return false; + } + + $arrayParentNode = $arrayItemParentNode->getAttribute(Attribute::PARENT_NODE); + $argParentNode = $arrayParentNode->getAttribute(Attribute::PARENT_NODE); + + /** @var MethodCall $methodCallNode */ + $methodCallNode = $argParentNode->getAttribute(Attribute::PARENT_NODE); + + return $methodCallNode->name->toString() === 'add'; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + return new String_($this->oldToNewOption[$node->value]); + } +} diff --git a/src/config/level/symfony/symfony30.yml b/src/config/level/symfony/symfony30.yml index bb3aa733418..8f5dc1fe84f 100644 --- a/src/config/level/symfony/symfony30.yml +++ b/src/config/level/symfony/symfony30.yml @@ -1,6 +1,7 @@ rectors: Rector\Rector\Contrib\Symfony\HttpKernel\GetRequestRector: ~ Rector\Rector\Contrib\Symfony\Form\FormTypeGetParentRector: ~ + Rector\Rector\Contrib\Symfony\Form\OptionNameRector: ~ Rector\Rector\Dynamic\ClassConstantReplacerRector: # form diff --git a/tests/Rector/Contrib/Symfony/Form/OptionNameRector/Correct/correct.php.inc b/tests/Rector/Contrib/Symfony/Form/OptionNameRector/Correct/correct.php.inc new file mode 100644 index 00000000000..dca35e12e3e --- /dev/null +++ b/tests/Rector/Contrib/Symfony/Form/OptionNameRector/Correct/correct.php.inc @@ -0,0 +1,26 @@ +add('name', 'text', array('label' => 'form.name')) + ->add('price1', 'text', array( + 'label' => 'form.price1', + 'scale' => 3, + )) + ->add('price2', 'text', array( + 'scale' => 3, + )) + ->add('discount', 'integer', [ + 'label' => 'form.email', + 'inherit_data' => true, + ]) + ->add('password', 'password') + ; + } +} diff --git a/tests/Rector/Contrib/Symfony/Form/OptionNameRector/Test.php b/tests/Rector/Contrib/Symfony/Form/OptionNameRector/Test.php new file mode 100644 index 00000000000..2fea2e19db7 --- /dev/null +++ b/tests/Rector/Contrib/Symfony/Form/OptionNameRector/Test.php @@ -0,0 +1,25 @@ +doTestFileMatchesExpectedContent( + __DIR__ . '/Wrong/wrong.php.inc', + __DIR__ . '/Correct/correct.php.inc' + ); + } + + /** + * @return string[] + */ + protected function getRectorClasses(): array + { + return [OptionNameRector::class]; + } +} diff --git a/tests/Rector/Contrib/Symfony/Form/OptionNameRector/Wrong/wrong.php.inc b/tests/Rector/Contrib/Symfony/Form/OptionNameRector/Wrong/wrong.php.inc new file mode 100644 index 00000000000..027304a7f35 --- /dev/null +++ b/tests/Rector/Contrib/Symfony/Form/OptionNameRector/Wrong/wrong.php.inc @@ -0,0 +1,26 @@ +add('name', 'text', array('label' => 'form.name')) + ->add('price1', 'text', array( + 'label' => 'form.price1', + 'precision' => 3, + )) + ->add('price2', 'text', array( + 'precision' => 3, + )) + ->add('discount', 'integer', [ + 'label' => 'form.email', + 'virtual' => true, + ]) + ->add('password', 'password') + ; + } +} From f0036936989df22493122abe60b7843a4ad31bf1 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Fri, 20 Oct 2017 22:58:53 +0200 Subject: [PATCH 8/8] [Symfony] add ProgressHelper to ProgressBar --- src/config/level/symfony/symfony30.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config/level/symfony/symfony30.yml b/src/config/level/symfony/symfony30.yml index 8f5dc1fe84f..cedfc5425b1 100644 --- a/src/config/level/symfony/symfony30.yml +++ b/src/config/level/symfony/symfony30.yml @@ -21,5 +21,7 @@ rectors: 'getPropertyAccessor': 'createPropertyAccessor' Rector\Rector\Dynamic\ClassReplacerRector: + # console + 'Symfony\Component\Console\Helper\ProgressHelper': 'Symfony\Component\Console\Helper\ProgressBar' # form 'Symfony\Component\Form\Util\VirtualFormAwareIterator': 'Symfony\Component\Form\Util\InheritDataAwareIterator'