[NetteCodeQuality] Add ArrayAccessSetControlToAddComponentMethodCallRector (#3867)

This commit is contained in:
Tomas Votruba 2020-08-02 00:18:40 +02:00 committed by GitHub
parent a84528af9e
commit fa2caf77ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 395 additions and 176 deletions

View File

@ -5,6 +5,8 @@ declare(strict_types=1);
use Rector\Nette\Rector\ClassMethod\TemplateMagicAssignToExplicitVariableArrayRector;
use Rector\NetteCodeQuality\Rector\ArrayDimFetch\ArrayDimFetchControlToGetComponentMethodCallRector;
use Rector\NetteCodeQuality\Rector\ArrayDimFetch\ChangeControlArrayAccessToAnnotatedControlVariableRector;
use Rector\NetteCodeQuality\Rector\Assign\ArrayAccessGetControlToGetComponentMethodCallRector;
use Rector\NetteCodeQuality\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector;
use Rector\NetteCodeQuality\Rector\Assign\MakeGetComponentAssignAnnotatedRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
@ -18,4 +20,8 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(ChangeControlArrayAccessToAnnotatedControlVariableRector::class);
$services->set(ArrayDimFetchControlToGetComponentMethodCallRector::class);
$services->set(ArrayAccessSetControlToAddComponentMethodCallRector::class);
$services->set(ArrayAccessGetControlToGetComponentMethodCallRector::class);
};

View File

@ -1,4 +1,4 @@
# All 547 Rectors Overview
# All 549 Rectors Overview
- [Projects](#projects)
---
@ -31,7 +31,7 @@
- [MysqlToMysqli](#mysqltomysqli) (4)
- [Naming](#naming) (3)
- [Nette](#nette) (14)
- [NetteCodeQuality](#nettecodequality) (5)
- [NetteCodeQuality](#nettecodequality) (7)
- [NetteKdyby](#nettekdyby) (4)
- [NetteTesterToPHPUnit](#nettetestertophpunit) (3)
- [NetteToSymfony](#nettetosymfony) (9)
@ -6950,6 +6950,52 @@ Change `translate()` method call 2nd arg to variadic
## NetteCodeQuality
### `ArrayAccessGetControlToGetComponentMethodCallRector`
- class: [`Rector\NetteCodeQuality\Rector\Assign\ArrayAccessGetControlToGetComponentMethodCallRector`](/../master/rules/nette-code-quality/src/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector.php)
- [test fixtures](/../master/rules/nette-code-quality/tests/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture)
Change magic arrays access get, to explicit `$this->getComponent(...)` method
```diff
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function some()
{
- $someControl = $this['whatever'];
+ $someControl = $this->getComponent('whatever');
}
}
```
<br><br>
### `ArrayAccessSetControlToAddComponentMethodCallRector`
- class: [`Rector\NetteCodeQuality\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector`](/../master/rules/nette-code-quality/src/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector.php)
- [test fixtures](/../master/rules/nette-code-quality/tests/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/Fixture)
Change magic arrays access set, to explicit `$this->setComponent(...)` method
```diff
use Nette\Application\UI\Control;
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function some()
{
$someControl = new Control();
- $this['whatever'] = $someControl;
+ $this->addComponent($someControl, 'whatever');
}
}
```
<br><br>
### `ArrayDimFetchControlToGetComponentMethodCallRector`
- class: [`Rector\NetteCodeQuality\Rector\ArrayDimFetch\ArrayDimFetchControlToGetComponentMethodCallRector`](/../master/rules/nette-code-quality/src/Rector/ArrayDimFetch/ArrayDimFetchControlToGetComponentMethodCallRector.php)

View File

@ -19,7 +19,8 @@ final class __Name__ extends AbstractRector
{
return new RectorDefinition('__Description__', [
new CodeSample(
__CodeBeforeExample__,
__CodeBeforeExample__
,
__CodeAfterExample__
)
]);

View File

@ -1,116 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\RectorGenerator\Tests\PHPUnit\Behavior;
use Rector\RectorGenerator\Tests\PHPUnit\ValueObject\ExpectedAndOutputFileInfoPair;
use Symfony\Component\Finder\Finder;
use Symplify\SmartFileSystem\Finder\FinderSanitizer;
use Symplify\SmartFileSystem\SmartFileInfo;
/**
* Use only in "\PHPUnit\Framework\TestCase"
*
* Answer here
* @see https://stackoverflow.com/questions/54263109/how-to-assert-2-directories-are-identical-in-phpunit
*
* @deprecated
* Use symplify after merged https://github.com/symplify/symplify/pull/2035
*/
trait DirectoryAssertableTrait
{
protected function assertDirectoryEquals(string $expectedDirectory, string $outputDirectory): void
{
$expectedFileInfos = $this->findFileInfosInDirectory($expectedDirectory);
$outputFileInfos = $this->findFileInfosInDirectory($outputDirectory);
$fileInfosByRelativeFilePath = $this->groupFileInfosByRelativeFilePath(
$expectedFileInfos,
$expectedDirectory,
$outputFileInfos,
$outputDirectory
);
foreach ($fileInfosByRelativeFilePath as $relativeFilePath => $expectedAndOutputFileInfoPair) {
// output file exists
$this->assertFileExists($outputDirectory . '/' . $relativeFilePath);
if (! $expectedAndOutputFileInfoPair->doesOutputFileExist()) {
continue;
}
// they have the same content
$this->assertSame(
$expectedAndOutputFileInfoPair->getExpectedFileContent(),
$expectedAndOutputFileInfoPair->getOutputFileContent(),
$relativeFilePath
);
}
}
/**
* @return SmartFileInfo[]
*/
private function findFileInfosInDirectory(string $directory): array
{
$firstDirectoryFinder = new Finder();
$firstDirectoryFinder->files()
->in($directory);
$finderSanitizer = new FinderSanitizer();
return $finderSanitizer->sanitize($firstDirectoryFinder);
}
/**
* @param SmartFileInfo[] $expectedFileInfos
* @param SmartFileInfo[] $outputFileInfos
* @return ExpectedAndOutputFileInfoPair[]
*/
private function groupFileInfosByRelativeFilePath(
array $expectedFileInfos,
string $expectedDirectory,
array $outputFileInfos,
string $outputDirectory
): array {
$fileInfosByRelativeFilePath = [];
foreach ($expectedFileInfos as $expectedFileInfo) {
$relativeFilePath = $expectedFileInfo->getRelativeFilePathFromDirectory($expectedDirectory);
// match output file info
$outputFileInfo = $this->resolveFileInfoByRelativeFilePath(
$outputFileInfos,
$outputDirectory,
$relativeFilePath
);
$fileInfosByRelativeFilePath[$relativeFilePath] = new ExpectedAndOutputFileInfoPair(
$expectedFileInfo,
$outputFileInfo
);
}
return $fileInfosByRelativeFilePath;
}
/**
* @param SmartFileInfo[] $fileInfos
*/
private function resolveFileInfoByRelativeFilePath(
array $fileInfos,
string $directory,
string $desiredRelativeFilePath
): ?SmartFileInfo {
foreach ($fileInfos as $fileInfo) {
$relativeFilePath = $fileInfo->getRelativeFilePathFromDirectory($directory);
if ($desiredRelativeFilePath !== $relativeFilePath) {
continue;
}
return $fileInfo;
}
return null;
}
}

View File

@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\RectorGenerator\Tests\PHPUnit\ValueObject;
use Rector\Core\Exception\ShouldNotHappenException;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ExpectedAndOutputFileInfoPair
{
/**
* @var SmartFileInfo
*/
private $expectedFileInfo;
/**
* @var SmartFileInfo|null
*/
private $outputFileInfo;
public function __construct(SmartFileInfo $expectedFileInfo, ?SmartFileInfo $outputFileInfo)
{
$this->expectedFileInfo = $expectedFileInfo;
$this->outputFileInfo = $outputFileInfo;
}
/**
* @noRector \Rector\Privatization\Rector\ClassMethod\PrivatizeLocalOnlyMethodRector
*/
public function getExpectedFileContent(): string
{
return $this->expectedFileInfo->getContents();
}
/**
* @noRector \Rector\Privatization\Rector\ClassMethod\PrivatizeLocalOnlyMethodRector
*/
public function getOutputFileContent(): string
{
if ($this->outputFileInfo === null) {
throw new ShouldNotHappenException();
}
return $this->outputFileInfo->getContents();
}
/**
* @noRector \Rector\Privatization\Rector\ClassMethod\PrivatizeLocalOnlyMethodRector
*/
public function doesOutputFileExist(): bool
{
return $this->outputFileInfo !== null;
}
}

View File

@ -9,9 +9,9 @@ use Rector\RectorGenerator\Configuration\ConfigurationFactory;
use Rector\RectorGenerator\Finder\TemplateFinder;
use Rector\RectorGenerator\Generator\FileGenerator;
use Rector\RectorGenerator\TemplateVariablesFactory;
use Rector\RectorGenerator\Tests\PHPUnit\Behavior\DirectoryAssertableTrait;
use Rector\RectorGenerator\Tests\RectorGenerator\Source\StaticRectorRecipeFactory;
use Rector\RectorGenerator\ValueObject\Configuration;
use Symplify\EasyTesting\PHPUnit\Behavior\DirectoryAssertableTrait;
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
use Symplify\SmartFileSystem\SmartFileSystem;
@ -80,7 +80,6 @@ final class RectorGeneratorTest extends AbstractKernelTestCase
self::DESTINATION_DIRECTORY
);
// @todo decouple to EasyTesting
$this->assertDirectoryEquals(__DIR__ . '/Fixture/expected', self::DESTINATION_DIRECTORY);
}

View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Rector\NetteCodeQuality\Rector\Assign;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Type\ObjectType;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
/**
* @sponsor Thanks https://amateri.com for sponsoring this rule - visit them on https://www.startupjobs.cz/startup/scrumworks-s-r-o
*
* @see https://github.com/nette/component-model/blob/c1fb11729423379768a71dd865ae373a3b12fa43/src/ComponentModel/Container.php#L110
*
* @see \Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessGetControlToGetComponentMethodCallRector\ArrayAccessGetControlToGetComponentMethodCallRectorTest
*/
final class ArrayAccessGetControlToGetComponentMethodCallRector extends AbstractRector
{
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change magic arrays access get, to explicit $this->getComponent(...) method', [
new CodeSample(
<<<'PHP'
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function some()
{
$someControl = $this['whatever'];
}
}
PHP
,
<<<'PHP'
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function some()
{
$someControl = $this->getComponent('whatever');
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Assign::class];
}
/**
* @param Assign $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isFetchOfControlFromPresenterDimFetch($node)) {
return null;
}
/** @var ArrayDimFetch $arrayDimFetch */
$arrayDimFetch = $node->expr;
$args = $this->createArgs([$arrayDimFetch->dim]);
$node->expr = new MethodCall($arrayDimFetch->var, 'getComponent', $args);
return $node;
}
private function isFetchOfControlFromPresenterDimFetch(Assign $assign): bool
{
if (! $assign->expr instanceof ArrayDimFetch) {
return false;
}
$exprStaticType = $this->getObjectType($assign->expr);
return $exprStaticType->isSuperTypeOf(new ObjectType('Nette\Application\UI\Presenter'))->yes();
}
}

View File

@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace Rector\NetteCodeQuality\Rector\Assign;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PHPStan\Type\ObjectType;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
/**
* @sponsor Thanks https://amateri.com for sponsoring this rule - visit them on https://www.startupjobs.cz/startup/scrumworks-s-r-o
*
* @see \Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector\ArrayAccessSetControlToAddComponentMethodCallRectorTest
*
* @see https://github.com/nette/component-model/blob/c1fb11729423379768a71dd865ae373a3b12fa43/src/ComponentModel/Container.php#L39
*/
final class ArrayAccessSetControlToAddComponentMethodCallRector extends AbstractRector
{
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change magic arrays access set, to explicit $this->setComponent(...) method', [
new CodeSample(
<<<'PHP'
use Nette\Application\UI\Control;
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function some()
{
$someControl = new Control();
$this['whatever'] = $someControl;
}
}
PHP
,
<<<'PHP'
use Nette\Application\UI\Control;
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function some()
{
$someControl = new Control();
$this->addComponent($someControl, 'whatever');
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Assign::class];
}
/**
* @param Assign $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isAssignOfControlToPresenterDimFetch($node)) {
return null;
}
/** @var ArrayDimFetch $arrayDimFetch */
$arrayDimFetch = $node->var;
$arguments = [$node->expr, $arrayDimFetch->dim];
$arg = $this->createArgs($arguments);
return new MethodCall($arrayDimFetch->var, 'addComponent', $arg);
}
private function isAssignOfControlToPresenterDimFetch(Assign $assign): bool
{
if (! $assign->var instanceof ArrayDimFetch) {
return false;
}
$exprStaticType = $this->getObjectType($assign->expr);
if (! $exprStaticType->isSuperTypeOf(new ObjectType('Nette\Application\UI\Control'))->yes()) {
return false;
}
$arrayDimFetch = $assign->var;
if (! $arrayDimFetch->var instanceof Variable) {
return false;
}
$variableStaticType = $this->getObjectType($arrayDimFetch->var);
return $variableStaticType->isSuperTypeOf(new ObjectType('Nette\Application\UI\Presenter'))->yes();
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessGetControlToGetComponentMethodCallRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\NetteCodeQuality\Rector\Assign\ArrayAccessGetControlToGetComponentMethodCallRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ArrayAccessGetControlToGetComponentMethodCallRectorTest 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 ArrayAccessGetControlToGetComponentMethodCallRector::class;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessGetControlToGetComponentMethodCallRector\Fixture;
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function some()
{
$someControl = $this['whatever'];
}
}
?>
-----
<?php
namespace Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessGetControlToGetComponentMethodCallRector\Fixture;
use Nette\Application\UI\Presenter;
class SomeClass extends Presenter
{
public function some()
{
$someControl = $this->getComponent('whatever');
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\NetteCodeQuality\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ArrayAccessSetControlToAddComponentMethodCallRectorTest 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 ArrayAccessSetControlToAddComponentMethodCallRector::class;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector\Fixture;
use Nette\Application\UI\Presenter;
use Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector\Source\ChildControl;
class SomeClass extends Presenter
{
public function some()
{
$someControl = new ChildControl();
$this['whatever'] = $someControl;
}
}
?>
-----
<?php
namespace Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector\Fixture;
use Nette\Application\UI\Presenter;
use Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector\Source\ChildControl;
class SomeClass extends Presenter
{
public function some()
{
$someControl = new ChildControl();
$this->addComponent($someControl, 'whatever');
}
}
?>

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Rector\NetteCodeQuality\Tests\Rector\Assign\ArrayAccessSetControlToAddComponentMethodCallRector\Source;
use Nette\Application\UI\Control;
final class ChildControl extends Control
{
}