[Nette] Add form dim access to standalone node control

This commit is contained in:
TomasVotruba 2020-07-22 15:08:40 +02:00
parent d007ae0896
commit d4f6c497a7
19 changed files with 534 additions and 79 deletions

View File

@ -17,18 +17,23 @@ jobs:
coverage: none # disable xdebug, pcov
extensions: "intl"
# Run standalone install in non-root package, ref https://github.com/rectorphp/rector/issues/732
- run: |
# wait till packagist gets information about new branches
sleep 30
# see https://github.com/rlespinasse/github-slug-action
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v2.x
# wait till packagist gets information about new branches
- run: sleep 30
- run:
# 1. install locally
mkdir test-paths
cd test-paths
# 2. install rector to "rector-dir"
mkdir rector-dir
# get branch name see https://stackoverflow.com/a/61699863/1348344
composer require rector/rector:dev-${GITHUB_REF#refs/heads/}#${GITHUB_SHA} -d rector-dir --no-progress --ansi
composer require rector/rector:dev-${{ env.GITHUB_REF_SLUG_URL }}#${GITHUB_SHA} -d rector-dir --no-progress --ansi
# 3. download symfony demo to "symfony-demo-dir"
mkdir symfony-demo-dir
composer create-project symfony/symfony-demo symfony-demo-dir --dev --no-progress --ansi
@ -36,7 +41,7 @@ jobs:
composer require doctrine/doctrine-fixtures-bundle -d symfony-demo-dir --no-progress --ansi
composer dump-autoload --no-dev -d symfony-demo-dir --ansi
# 2. run an another project
# 4. run an another project
rector-dir/vendor/bin/rector --ansi
cd symfony-demo-dir

View File

@ -21,6 +21,8 @@ final class StaticEasyPrefixer
'JMS\DiExtraBundle\Annotation\Inject',
// part of public interface of configs.php
'Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator',
// well, this is a function
'Symfony\Component\DependencyInjection\Loader\Configurator\ref',
];
/**

View File

@ -28,8 +28,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$parameters->set(Option::AUTOLOAD_PATHS, []);
$parameters->set('rector_recipe', []);
$parameters->set('project_type', 'proprietary');
$parameters->set('nested_chain_method_call_limit', 30);

View File

@ -3,6 +3,7 @@
declare(strict_types=1);
use Rector\DeadCode\Rector\StaticCall\RemoveParentCallWithoutParentRector;
use Rector\Nette\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector;
use Rector\Nette\Rector\MethodCall\AddDatePickerToDateControlRector;
use Rector\Nette\Rector\MethodCall\SetClassWithArgumentToSetFactoryRector;
use Rector\Renaming\Rector\Class_\RenameClassRector;
@ -11,6 +12,7 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->import(__DIR__ . '/nette-30-return-types.php');
$containerConfigurator->import(__DIR__ . '/nette-30-param-types.php');
$services = $containerConfigurator->services();
@ -47,4 +49,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(AddDatePickerToDateControlRector::class);
$services->set(SetClassWithArgumentToSetFactoryRector::class);
$services->set(ChangeFormArrayAccessToAnnotatedControlVariableRector::class);
};

View File

@ -7,8 +7,6 @@ use Rector\PHPUnit\Rector\MethodCall\RemoveExpectAnyFromMockRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
# [configurable]
# Rector\PHPUnit\Rector\Class_\ArrayArgumentInTestToDataProviderRector: null
$services = $containerConfigurator->services();
$services->set(RemoveExpectAnyFromMockRector::class);

View File

@ -4,14 +4,14 @@ declare(strict_types=1);
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Core\Configuration\Option;
use Rector\Set\ValueObject\Set;
use Rector\Set\ValueObject\SetList;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::RECTOR_RECIPE, [
# run "bin/rector create" to create a new Rector + tests from this config
// run "bin/rector create" to create a new Rector + tests from this config
'package' => 'Symfony',
'name' => 'RemoveDefaultGetBlockPrefixRector',
'node_types' => [
@ -40,11 +40,11 @@ class TaskType extends AbstractType
{
}
CODE_SAMPLE,
// e.g. link to RFC or headline in upgrade guide, 1 or more in the list
'source' => [
# e.g. link to RFC or headline in upgrade guide, 1 or more in the list
'https://github.com/symfony/symfony/blob/3.4/UPGRADE-3.0.md',
],
# e.g. symfony30, target config to append this rector to
'set' => Set::SYMFONY_30,
// e.g. symfony30, target config to append this rector to
'set' => SetList::SYMFONY_30,
]);
};

View File

@ -2,50 +2,62 @@
## 1. Configure a Rector Recipe in `rector.yaml`
```yaml
# rector.yaml
parameters:
rector_recipe:
# run "bin/rector create" to create a new Rector + tests from this config
package: "Celebrity"
name: "SplitToExplodeRector"
node_types:
# put the main node first, it is used to create the namespace
- "Assign"
description: "Removes unneeded $a = $a assignments"
code_before: >
<?php
```php
<?php
class SomeClass
{
public function run()
{
$a = $a;
}
}
declare(strict_types=1);
code_after: >
<?php
use PhpParser\Node\Expr\Assign;
use Rector\Core\Configuration\Option;
use Rector\Set\ValueObject\SetList;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
class SomeClass
{
public function run()
{
}
}
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
source: # e.g. link to RFC or headline in upgrade guide, 1 or more in the list
- ""
set: "celebrity" # e.g. symfony30, target config to append this rector to
$parameters->set(Option::RECTOR_RECIPE, [
'package' => 'Celebrity',
'name' => 'SplitToExplodeRector',
'node_types' => [
Assign::class,
],
'description' => 'Removes unneeded $a = $a assignments',
'code_before' => <<<'CODE_SAMPLE'
<?php
class SomeClass
{
public function run()
{
$a = $a;
}
}
CODE_SAMPLE,
'code_after' => <<<'CODE_SAMPLE'
<?php
class SomeClass
{
public function run()
{
}
}
CODE_SAMPLE,
// e.g. link to RFC or headline in upgrade guide, 1 or more in the list
'source' => [
],
// e.g. symfony30, target config to append this rector to
'set' => SetList::CELEBRITY,
]);
};
```
## 2. Generate it
```bash
vendor/bin/rector create-rector
```
There is also a shortcut command:
```bash
# or for short
vendor/bin/rector c
```

View File

@ -1,4 +1,4 @@
# All 528 Rectors Overview
# All 530 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -30,7 +30,7 @@
- [MockistaToMockery](#mockistatomockery) (2)
- [MysqlToMysqli](#mysqltomysqli) (4)
- [Naming](#naming) (3)
- [Nette](#nette) (12)
- [Nette](#nette) (13)
- [NetteCodeQuality](#nettecodequality) (1)
- [NetteKdyby](#nettekdyby) (4)
- [NetteTesterToPHPUnit](#nettetestertophpunit) (3)
@ -59,6 +59,7 @@
- [PhpSpecToPHPUnit](#phpspectophpunit) (7)
- [Polyfill](#polyfill) (2)
- [Privatization](#privatization) (7)
- [RectorGenerator](#rectorgenerator) (1)
- [RemovingStatic](#removingstatic) (4)
- [Renaming](#renaming) (9)
- [Restoration](#restoration) (7)
@ -1039,7 +1040,7 @@ Complete missing 3rd argument in case `is_a()` function in case of strings
- class: [`Rector\CodeQuality\Rector\Concat\JoinStringConcatRector`](/../master/rules/code-quality/src/Rector/Concat/JoinStringConcatRector.php)
- [test fixtures](/../master/rules/code-quality/tests/Rector/Concat/JoinStringConcatRector/Fixture)
Joins concat of 2 strings
Joins concat of 2 strings, unless the lenght is too long
```diff
class SomeClass
@ -5004,7 +5005,8 @@ Rename property and method param to match its type
Rename variable to match get method name
```diff
class SomeClass {
class SomeClass
{
public function run()
{
- $a = $this->getRunner();
@ -5062,6 +5064,33 @@ Nextras/Form upgrade of addDatePicker method call to DateControl assign
<br><br>
### `ChangeFormArrayAccessToAnnotatedControlVariableRector`
- class: [`Rector\Nette\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector`](/../master/rules/nette/src/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector.php)
- [test fixtures](/../master/rules/nette/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture)
Change array access magic on `$form` to explicit standalone typed variable
```diff
use Nette\Application\UI\Form;
class SomePresenter
{
public function run()
{
$form = new Form();
$this->addText('email', 'Email');
- $form['email']->value = 'hey@hi.hello';
+ /** @var \Nette\Forms\Controls\BaseControl $emailControl */
+ $emailControl = $form['email'];
+ $emailControl->value = 'hey@hi.hello';
}
}
```
<br><br>
### `ContextGetByTypeToConstructorInjectionRector`
- class: [`Rector\Nette\Rector\MethodCall\ContextGetByTypeToConstructorInjectionRector`](/../master/rules/nette/src/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector.php)
@ -8032,13 +8061,11 @@ Changes `$this->call()` to static method to static call
Ensure variable variables are wrapped in curly braces
```diff
function run($foo)
{
- // Valid in PHP 5 only
function run($foo)
{
- global $$foo->bar;
+ // Valid in PHP 5 and 7
+ global ${$foo->bar};
}
}
```
<br><br>
@ -9560,6 +9587,25 @@ Privatize local-only property to private property
<br><br>
## RectorGenerator
### `AddNewServiceToSymfonyPhpConfigRector`
- class: [`Rector\RectorGenerator\Rector\Closure\AddNewServiceToSymfonyPhpConfigRector`](/../master/packages/rector-generator/src/Rector/Closure/AddNewServiceToSymfonyPhpConfigRector.php)
Adds a new `$services->set(...)` call to PHP Config
```diff
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
+ $services->set(AddNewServiceToSymfonyPhpConfigRector::class);
};
```
<br><br>
## RemovingStatic
### `NewUniqueObjectToEntityFactoryRector`

View File

@ -83,6 +83,11 @@ final class PhpDocInfo
*/
private $paramPhpDocNodeFactory;
/**
* @var bool
*/
private $isSingleLine = false;
/**
* @param mixed[] $tokens
*/
@ -434,6 +439,16 @@ final class PhpDocInfo
return $throwsClasses;
}
public function makeSingleLined(): void
{
$this->isSingleLine = true;
}
public function isSingleLine(): bool
{
return $this->isSingleLine;
}
private function getParamTagValueByName(string $name): ?AttributeAwareParamTagValueNode
{
/** @var AttributeAwareParamTagValueNode $paramTagValue */

View File

@ -139,7 +139,12 @@ final class DocBlockManipulator
{
// new node, needs to be reparsed
if ($phpDocInfo->isNewNode()) {
return (string) $phpDocInfo->getPhpDocNode();
$docContent = (string) $phpDocInfo->getPhpDocNode();
if (! $phpDocInfo->isSingleLine()) {
return $docContent;
}
return $this->inlineDocContent($docContent);
}
return $this->phpDocInfoPrinter->printFormatPreserving($phpDocInfo);
@ -191,4 +196,11 @@ final class DocBlockManipulator
{
return Strings::replace($content, '#(\s|\*)+#');
}
private function inlineDocContent(string $docContent): string
{
$docContent = Strings::replace($docContent, "#\n \* #", ' ');
return Strings::replace($docContent, "#\n \*\/$#", ' */');
}
}

View File

@ -2,7 +2,10 @@
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\RectorGenerator\Rector\Closure\AddNewServiceToSymfonyPhpConfigRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\ref;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
@ -10,12 +13,20 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->defaults()
->public()
->autowire()
->autoconfigure();
->autoconfigure()
->bind(AddNewServiceToSymfonyPhpConfigRector::class, ref(AddNewServiceToSymfonyPhpConfigRector::class));
$services->load('Rector\RectorGenerator\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/../src/Exception/*', __DIR__ . '/../src/ValueObject/*']);
->exclude([
__DIR__ . '/../src/Exception/*',
__DIR__ . '/../src/ValueObject/*',
__DIR__ . '/../src/Rector/*',
]);
$services->set(AddNewServiceToSymfonyPhpConfigRector::class)
->autowire(false);
$parameters = $containerConfigurator->parameters();
$parameters->set('rector_recipe', []);
$parameters->set(Option::RECTOR_RECIPE, []);
};

View File

@ -144,10 +144,10 @@ final class CreateRectorCommand extends Command
return ShellCode::SUCCESS;
}
$this->generateFiles($templateFileInfos, $templateVariables, $configuration);
$this->configFilesystem->appendRectorServiceToSet($configuration, $templateVariables);
$this->generateFiles($templateFileInfos, $templateVariables, $configuration);
$this->printSuccess($configuration->getName());
return ShellCode::SUCCESS;

View File

@ -6,7 +6,12 @@ namespace Rector\RectorGenerator\Config;
use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use Rector\Core\Exception\NotImplementedYetException;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use Rector\Core\PhpParser\Parser\Parser;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\RectorGenerator\Rector\Closure\AddNewServiceToSymfonyPhpConfigRector;
use Rector\RectorGenerator\TemplateFactory;
use Rector\RectorGenerator\ValueObject\Configuration;
@ -22,9 +27,31 @@ final class ConfigFilesystem
*/
private $templateFactory;
public function __construct(TemplateFactory $templateFactory)
{
/**
* @var Parser
*/
private $parser;
/**
* @var AddNewServiceToSymfonyPhpConfigRector
*/
private $addNewServiceToSymfonyPhpConfigRector;
/**
* @var BetterStandardPrinter
*/
private $betterStandardPrinter;
public function __construct(
TemplateFactory $templateFactory,
Parser $parser,
AddNewServiceToSymfonyPhpConfigRector $addNewServiceToSymfonyPhpConfigRector,
BetterStandardPrinter $betterStandardPrinter
) {
$this->templateFactory = $templateFactory;
$this->parser = $parser;
$this->addNewServiceToSymfonyPhpConfigRector = $addNewServiceToSymfonyPhpConfigRector;
$this->betterStandardPrinter = $betterStandardPrinter;
}
/**
@ -36,20 +63,40 @@ final class ConfigFilesystem
return;
}
if (! file_exists($configuration->getSetConfig())) {
return;
}
$setConfigFileInfo = $configuration->getSetConfig();
$setFileContents = $setConfigFileInfo->getContents();
$setConfigContent = FileSystem::read($configuration->getSetConfig());
// already added
// already added?
$rectorFqnName = $this->templateFactory->create(self::RECTOR_FQN_NAME_PATTERN, $templateVariables);
if (Strings::contains($setConfigContent, $rectorFqnName)) {
if (Strings::contains($setFileContents, $rectorFqnName)) {
return;
}
throw new NotImplementedYetException('Add *.php config rule append support');
// 1. parse the file
$setConfigNodes = $this->parser->parseFileInfo($setConfigFileInfo);
// FileSystem::write($configuration->getSetConfig(), $newSetConfigContent);
// 2. add the set() call
$this->decorateNamesToFullyQualified($setConfigNodes);
$nodeTraverser = new NodeTraverser();
$this->addNewServiceToSymfonyPhpConfigRector->setRectorClass($rectorFqnName);
$nodeTraverser->addVisitor($this->addNewServiceToSymfonyPhpConfigRector);
$setConfigNodes = $nodeTraverser->traverse($setConfigNodes);
// 3. print the content back to file
$changedSetConfigContent = $this->betterStandardPrinter->prettyPrintFile($setConfigNodes);
FileSystem::write($setConfigFileInfo->getRealPath(), $changedSetConfigContent);
}
/**
* @param Node[] $nodes
*/
private function decorateNamesToFullyQualified(array $nodes): void
{
// decorate nodes with names first
$nameResolverNodeTraverser = new NodeTraverser();
$nameResolverNodeTraverser->addVisitor(new NameResolver());
$nameResolverNodeTraverser->traverse($nodes);
}
}

View File

@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace Rector\RectorGenerator\Rector\Closure;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Expression;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
final class AddNewServiceToSymfonyPhpConfigRector extends AbstractRector
{
/**
* @var string|null
*/
private $rectorClass;
public function setRectorClass(string $rectorClass): void
{
$this->rectorClass = $rectorClass;
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Closure::class];
}
/**
* @param Closure $node
*/
public function refactor(Node $node): ?Node
{
if ($this->rectorClass === null) {
return null;
}
if (! $this->isPhpConfigClosure($node)) {
return null;
}
$methodCall = $this->createServicesSetMethodCall($this->rectorClass);
$node->stmts[] = new Expression($methodCall);
return $node;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Adds a new $services->set(...) call to PHP Config', [
new CodeSample(
<<<'CODE_SAMPLE'
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
};
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(AddNewServiceToSymfonyPhpConfigRector::class);
};
CODE_SAMPLE
),
]);
}
private function isPhpConfigClosure(Closure $closure): bool
{
if (count($closure->params) !== 1) {
return false;
}
$onlyParam = $closure->params[0];
if ($onlyParam->type === null) {
return false;
}
return $this->isName(
$onlyParam->type,
'Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator'
);
}
private function createServicesSetMethodCall(string $className): MethodCall
{
$servicesVariable = new Variable('services');
$referenceClassConstFetch = new ClassConstFetch(new FullyQualified($className), new Identifier('class'));
$args = [new Arg($referenceClassConstFetch)];
return new MethodCall($servicesVariable, 'set', $args);
}
}

View File

@ -7,6 +7,7 @@ namespace Rector\RectorGenerator\ValueObject;
use Nette\Utils\Strings;
use Rector\Core\Util\StaticRectorStrings;
use Symplify\SetConfigResolver\ValueObject\Set;
use Symplify\SmartFileSystem\SmartFileInfo;
final class Configuration
{
@ -158,13 +159,13 @@ final class Configuration
return $this->source;
}
public function getSetConfig(): ?string
public function getSetConfig(): ?SmartFileInfo
{
if ($this->set === null) {
return null;
}
return $this->set->getSetFileInfo()->getRealPath();
return $this->set->getSetFileInfo();
}
public function isPhpSnippet(): bool

View File

@ -369,5 +369,16 @@ parameters:
message: '#Method call "isObjectType\(\)" argument on position 1 cannot use "\:\:class" reference#'
path: 'packages/dynamic-type-analysis/src/Rector/StaticCall/RemoveArgumentTypeProbeRector.php'
# only local use
-
message: '#Class "Rector\\RectorGenerator\\Rector\\Closure\\AddNewServiceToSymfonyPhpConfigRector" is missing @see annotation with test case class reference#'
path: 'packages/rector-generator/src/Rector/Closure/AddNewServiceToSymfonyPhpConfigRector.php'
- '#Call to an undefined method PhpParser\\Node\\Expr\\Error\|PhpParser\\Node\\Identifier\:\:toString\(\)#'
- '#Class Rector\\Renaming\\Tests\\Rector\\MethodCall\\RenameMethodRector\\Fixture\\SkipSelfMethodRename not found#'
# fixed in symplfiy dev
-
message: '#Separate function "Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ref\(\)" in method call to standalone row to improve readability#'
path: 'packages/rector-generator/config/config.php'

View File

@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace Rector\Nette\Rector\ArrayDimFetch;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Expression;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @see \Rector\Nette\Tests\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector\ChangeFormArrayAccessToAnnotatedControlVariableRectorTest
*/
final class ChangeFormArrayAccessToAnnotatedControlVariableRector extends AbstractRector
{
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change array access magic on $form to explicit standalone typed variable', [
new CodeSample(
<<<'PHP'
use Nette\Application\UI\Form;
class SomePresenter
{
public function run()
{
$form = new Form();
$this->addText('email', 'Email');
$form['email']->value = 'hey@hi.hello';
}
}
PHP
,
<<<'PHP'
use Nette\Application\UI\Form;
class SomePresenter
{
public function run()
{
$form = new Form();
$this->addText('email', 'Email');
/** @var \Nette\Forms\Controls\BaseControl $emailControl */
$emailControl = $form['email'];
$emailControl->value = 'hey@hi.hello';
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ArrayDimFetch::class];
}
/**
* @param ArrayDimFetch $node
*/
public function refactor(Node $node): ?Node
{
if (! $node->var instanceof Variable) {
return null;
}
if (! $this->isObjectType($node->var, 'Nette\Application\UI\Form')) {
return null;
}
if (! $node->dim instanceof String_) {
return null;
}
$inputName = $this->getValue($node->dim);
$controlName = $inputName . 'Control';
$controlVariableToFormDimFetchAssign = new Assign(new Variable($controlName), clone $node);
$assignExpression = new Expression($controlVariableToFormDimFetchAssign);
$this->addVarTag($controlVariableToFormDimFetchAssign, $assignExpression, $controlName);
$this->addNodeBeforeNode($assignExpression, $node);
return new Variable($controlName);
}
private function addVarTag(Assign $assign, Expression $assignExpression, string $controlName): PhpDocInfo
{
$phpDocInfo = $this->phpDocInfoFactory->createEmpty($assignExpression);
$identifierTypeNode = new IdentifierTypeNode('\Nette\Forms\Controls\BaseControl');
$varTagValueNode = new VarTagValueNode($identifierTypeNode, '$' . $controlName, '');
$phpDocInfo->addTagValueNode($varTagValueNode);
$phpDocInfo->makeSingleLined();
$assign->setAttribute(AttributeKey::PHP_DOC_INFO, $phpDocInfo);
return $phpDocInfo;
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Nette\Tests\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Nette\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ChangeFormArrayAccessToAnnotatedControlVariableRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return ChangeFormArrayAccessToAnnotatedControlVariableRector::class;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\Nette\Tests\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector\Fixture;
use Nette\Application\UI\Form;
class SomePresenter
{
public function run()
{
$form = new Form();
$this->addText('email', 'Email');
$form['email']->value = 'hey@hi.hello';
}
}
?>
-----
<?php
namespace Rector\Nette\Tests\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector\Fixture;
use Nette\Application\UI\Form;
class SomePresenter
{
public function run()
{
$form = new Form();
$this->addText('email', 'Email');
/** @var \Nette\Forms\Controls\BaseControl $emailControl */
$emailControl = $form['email'];
$emailControl->value = 'hey@hi.hello';
}
}
?>