[Nette] Add TranslateClassMethodToVariadicsRector

This commit is contained in:
TomasVotruba 2020-07-29 03:15:40 +02:00
parent 94088334c7
commit 5859625d5d
8 changed files with 204 additions and 75 deletions

View File

@ -255,7 +255,8 @@
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/Function_/count.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/AnotherClass.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/YetAnotherClass.php",
"rules/solid/tests/Rector/ClassMethod/UseInterfaceOverImplementationInConstructorRector/Source/Coconut.php"
"rules/solid/tests/Rector/ClassMethod/UseInterfaceOverImplementationInConstructorRector/Source/Coconut.php",
"stubs/Nette/Localization/ITranslation.php"
]
},
"scripts": {

View File

@ -10,7 +10,7 @@ use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
/**
_Source_
__Source__
* @see \Rector\__Package__\Tests\Rector\__Category__\__Name__\__Name__Test
*/
final class __Name__ extends AbstractRector

View File

@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
namespace Rector\Nette\Rector\ClassMethod;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Coalesce;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeTraverser;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @see https://github.com/nette/utils/pull/178
* @see https://github.com/contributte/translation/commit/d374c4c05b57dff1e5b327bb9bf98c392769806c
*
* @see \Rector\Nette\Tests\Rector\ClassMethod\TranslateClassMethodToVariadicsRector\TranslateClassMethodToVariadicsRectorTest
* @note must be run before "composer update nette/utils:^3.0", because param contract break causes fatal error
*/
final class TranslateClassMethodToVariadicsRector extends AbstractRector
{
/**
* @var string
*/
private const PARAMETERS = 'parameters';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change translate() method call 2nd arg to variadic', [
new CodeSample(
<<<'PHP'
use Nette\Localization\ITranslator;
final class SomeClass implements ITranslator
{
public function translate($message, $count = null)
{
return [$message, $count];
}
}
PHP
,
<<<'PHP'
use Nette\Localization\ITranslator;
final class SomeClass implements ITranslator
{
public function translate($message, ... $parameters)
{
$count = $parameters[0] ?? null;
return [$message, $count];
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}
/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isMethodStaticCallOrClassMethodObjectType($node, 'Nette\Localization\ITranslator')) {
return null;
}
if (! $this->isName($node->name, 'translate')) {
return null;
}
if (! isset($node->params[1])) {
return null;
}
$secondParam = $node->params[1];
if (! $secondParam->var instanceof Variable) {
return null;
}
if ($secondParam->variadic) {
return null;
}
$this->replaceSecondParamInClassMethodBody($node, $secondParam);
$secondParam->default = null;
$secondParam->variadic = true;
$secondParam->var = new Identifier('$' . self::PARAMETERS);
return $node;
}
private function replaceSecondParamInClassMethodBody(ClassMethod $classMethod, Param $param): void
{
$paramName = $this->getName($param->var);
$this->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use ($paramName) {
if (! $node instanceof Variable) {
return null;
}
if (! $this->isName($node, $paramName)) {
return null;
}
// instantiate
$assign = $this->createCoalesceAssign($paramName, $node);
$currentStmt = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
$this->addNodeBeforeNode($assign, $currentStmt);
return NodeTraverser::STOP_TRAVERSAL;
});
}
private function createCoalesceAssign(string $paramName, Variable $variable): Assign
{
$arrayDimFetch = new ArrayDimFetch(new Variable(self::PARAMETERS), new LNumber(0));
$coalesce = new Coalesce($arrayDimFetch, $this->createNull());
return new Assign(new Variable($variable->name), $coalesce);
}
}

View File

@ -1,69 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Nette\Rector\MethodCall;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
/**
_Source_
* @see \Rector\Nette\Tests\Rector\MethodCall\TranslateClassMethodToVariadicsRector\TranslateClassMethodToVariadicsRectorTest
*/
final class TranslateClassMethodToVariadicsRector extends AbstractRector
{
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change translate() method call 2nd arg to variadic', [
new CodeSample(
<<<'PHP'
use Nette\Localization\ITranslator;
final class SomeClass implements ITranslator
{
public function translate($message, $count = null)
{
return [$message, $count];
}
}
PHP
,
<<<'PHP'
use Nette\Localization\ITranslator;
final class SomeClass implements ITranslator
{
public function translate($message, ... $parameters)
{
$count = $parameters[0] ?? null;
return [$message, $count];
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [MethodCall::class];
}
/**
* @param MethodCall $node
*/
public function refactor(Node $node): ?Node
{
// change the node
return $node;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Rector\Nette\Tests\Rector\ClassMethod\TranslateClassMethodToVariadicsRector\Fixture;
use Nette\Localization\ITranslator;
final class SomeClass implements ITranslator
{
public function translate($message, $count = null)
{
return [$message, $count];
}
}
?>
-----
<?php
namespace Rector\Nette\Tests\Rector\ClassMethod\TranslateClassMethodToVariadicsRector\Fixture;
use Nette\Localization\ITranslator;
final class SomeClass implements ITranslator
{
public function translate($message, ...$parameters)
{
$count = $parameters[0] ?? null;
return [$message, $count];
}
}
?>

View File

@ -2,11 +2,12 @@
declare(strict_types=1);
namespace Rector\Nette\Tests\Rector\MethodCall\TranslateClassMethodToVariadicsRector;
namespace Rector\Nette\Tests\Rector\ClassMethod\TranslateClassMethodToVariadicsRector;
use Iterator;
use Nette\Utils\FileSystem;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Nette\Rector\MethodCall\TranslateClassMethodToVariadicsRector;
use Rector\Nette\Rector\ClassMethod\TranslateClassMethodToVariadicsRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class TranslateClassMethodToVariadicsRectorTest extends AbstractRectorTestCase
@ -16,6 +17,14 @@ final class TranslateClassMethodToVariadicsRectorTest extends AbstractRectorTest
*/
public function test(SmartFileInfo $fileInfo): void
{
$localFilePath = __DIR__ . '/../../../../../../vendor/nette/utils/src/Utils/ITranslator.php';
if (file_exists($localFilePath)) {
FileSystem::delete($localFilePath);
}
require_once __DIR__ . '/../../../../../../stubs/Nette/Localization/ITranslation.php';
// to make test work with fixture
$this->doTestFileInfo($fileInfo);
}

View File

@ -4,9 +4,9 @@ sonar.projectKey=rectorphp_rector
# relative paths to source
# wildcards don't work :(
sonar.sources=compiler/src,src,rules/autodiscovery/src,rules/architecture/src,packages/attribute-aware-php-doc/src,packages/better-php-doc-parser/src,rules/cakephp/src,rules/code-quality/src,rules/coding-style/src,packages/console-differ/src,rules/dead-code/src,rules/doctrine/src,rules/doctrine-code-quality/src,rules/framework-migration/src,packages/file-system-rector/src,rules/guzzle/src,rules/laravel/src,rules/legacy/src,rules/mysql-to-mysqli/src,rules/nette-tester-to-phpunit/src,rules/nette-to-symfony/src,rules/nette/src,packages/node-collector/src,packages/node-type-resolver/src,packages/node-name-resolver/src,rules/phpstan/src,packages/phpstan-static-type-mapper/src,rules/phpunit-symfony/src,rules/phpunit/src,rules/psr4/src,rules/php-spec-to-phpunit/src,rules/php52/src,rules/php53/src,rules/php54/src,rules/php55/src,rules/php56/src,rules/php70/src,rules/php71/src,rules/php72/src,rules/php73/src,rules/php74/src,rules/php80/src,rules/removing-static/src,rules/renaming/src,rules/restoration/src,rules/solid/src,rules/sensio/src,packages/static-type-mapper/src,rules/symfony-code-quality/src,rules/symfony-phpunit/src,rules/symfony/src,rules/twig/src,rules/type-declaration/src,packages/vendor-locker/src,packages/rector-generator/src,rules/strict-code-quality/src,packages/dynamic-type-analysis/src,rules/php-deglobalize/src,rules/phalcon/src,rules/doctrine-gedmo-to-knplabs/src,packages/polyfill/src
sonar.sources=compiler/src,src,rules/autodiscovery/src,rules/architecture/src,packages/attribute-aware-php-doc/src,packages/better-php-doc-parser/src,rules/cakephp/src,rules/code-quality/src,rules/coding-style/src,packages/console-differ/src,rules/dead-code/src,rules/doctrine/src,rules/doctrine-code-quality/src,rules/framework-migration/src,packages/file-system-rector/src,rules/guzzle/src,rules/laravel/src,rules/legacy/src,rules/mysql-to-mysqli/src,rules/nette-tester-to-phpunit/src,rules/nette-to-symfony/src,rules/nette/src,packages/node-collector/src,packages/node-type-resolver/src,packages/node-name-resolver/src,rules/phpstan/src,packages/phpstan-static-type-mapper/src,rules/phpunit-symfony/src,rules/phpunit/src,rules/psr4/src,rules/php-spec-to-phpunit/src,rules/php52/src,rules/php53/src,rules/php54/src,rules/php55/src,rules/php56/src,rules/php70/src,rules/php71/src,rules/php72/src,rules/php73/src,rules/php74/src,rules/php80/src,rules/removing-static/src,rules/renaming/src,rules/restoration/src,rules/solid/src,rules/sensio/src,packages/static-type-mapper/src,rules/symfony-code-quality/src,rules/symfony-phpunit/src,rules/symfony/src,rules/twig/src,rules/type-declaration/src,packages/vendor-locker/src,packages/rector-generator/src,rules/strict-code-quality/src,packages/dynamic-type-analysis/src,rules/php-deglobalize/src,rules/phalcon/src,rules/doctrine-gedmo-to-knplabs/src,packages/polyfill/src,rules/generic/src
sonar.tests=tests,rules/autodiscovery/tests,rules/architecture/tests,packages/better-php-doc-parser/tests,rules/cakephp/tests,rules/code-quality/tests,rules/coding-style/tests,rules/dead-code/tests,rules/doctrine/tests,rules/doctrine-code-quality/tests,rules/guzzle/tests,rules/laravel/tests,rules/legacy/tests,rules/mysql-to-mysqli/tests,rules/nette-tester-to-phpunit/tests,rules/nette-to-symfony/tests,rules/nette/tests,packages/node-type-resolver/tests,utils/phpstan-extensions/src,rules/phpstan/tests,rules/phpunit-symfony/tests,rules/phpunit/tests,rules/psr4/tests,rules/php-spec-to-phpunit/tests,rules/php52/tests,rules/php53/tests,rules/php54/tests,rules/php55/tests,rules/php56/tests,rules/php70/tests,rules/php71/tests,rules/php72/tests,rules/php73/tests,rules/php74/tests,rules/php80/tests,rules/removing-static/tests,rules/renaming/tests,rules/restoration/tests,rules/solid/tests,rules/sensio/tests,rules/symfony-code-quality/tests,rules/symfony-phpunit/tests,rules/symfony/tests,rules/twig/tests,rules/type-declaration/tests,rules/strict-code-quality/tests,packages/dynamic-type-analysis/tests,rules/php-deglobalize/tests,rules/phalcon/tests,utils/documentation-generator/src,utils/phpstan-attribute-type-syncer/src,utils/phpstan-static-type-mapper-checker/src,rules/doctrine-gedmo-to-knplabs/tests,packages/polyfill/tests,rules/downgrade/tests
sonar.tests=tests,rules/autodiscovery/tests,rules/architecture/tests,packages/better-php-doc-parser/tests,rules/cakephp/tests,rules/code-quality/tests,rules/coding-style/tests,rules/dead-code/tests,rules/doctrine/tests,rules/doctrine-code-quality/tests,rules/guzzle/tests,rules/laravel/tests,rules/legacy/tests,rules/mysql-to-mysqli/tests,rules/nette-tester-to-phpunit/tests,rules/nette-to-symfony/tests,rules/nette/tests,packages/node-type-resolver/tests,utils/phpstan-extensions/src,rules/phpstan/tests,rules/phpunit-symfony/tests,rules/phpunit/tests,rules/psr4/tests,rules/php-spec-to-phpunit/tests,rules/php52/tests,rules/php53/tests,rules/php54/tests,rules/php55/tests,rules/php56/tests,rules/php70/tests,rules/php71/tests,rules/php72/tests,rules/php73/tests,rules/php74/tests,rules/php80/tests,rules/removing-static/tests,rules/renaming/tests,rules/restoration/tests,rules/solid/tests,rules/sensio/tests,rules/symfony-code-quality/tests,rules/symfony-phpunit/tests,rules/symfony/tests,rules/twig/tests,rules/type-declaration/tests,rules/strict-code-quality/tests,packages/dynamic-type-analysis/tests,rules/php-deglobalize/tests,rules/phalcon/tests,utils/documentation-generator/src,utils/phpstan-attribute-type-syncer/src,utils/phpstan-static-type-mapper-checker/src,rules/doctrine-gedmo-to-knplabs/tests,packages/polyfill/tests,rules/downgrade/tests,rules/generic/tests
# see https://docs.sonarqube.org/latest/project-administration/narrowing-the-focus/#NarrowingtheFocus-patterns
sonar.exclusions=src/**/*.php.inc,rules/**/*.php.inc,packages/**/*.php.inc,packages/**/Fixture/**/*,rules/**/Fixture/**/*,tests/**/Source/**/*

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Nette\Localization;
if (interface_exists('Nette\Localization\ITranslator')) {
return;
}
interface ITranslator
{
public function translate($message, $count = null);
}