Skip to content

Commit

Permalink
[Nette 3.0] Add CallableInMethodCallToVariableRector
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Feb 2, 2021
1 parent 5ac925c commit 44ced8e
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 1 deletion.
10 changes: 10 additions & 0 deletions config/set/nette-31.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
use Rector\Renaming\ValueObject\MethodCallRename;
use Rector\Renaming\ValueObject\RenameStaticMethod;
use Rector\Transform\Rector\Assign\DimFetchAssignToMethodCallRector;
use Rector\Transform\Rector\MethodCall\CallableInMethodCallToVariableRector;
use Rector\Transform\ValueObject\CallableInMethodCallToVariable;
use Rector\Transform\ValueObject\DimFetchAssignToMethodCall;
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeDeclarationRector;
use Rector\TypeDeclaration\ValueObject\AddParamTypeDeclaration;
Expand All @@ -24,6 +26,14 @@
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();

$services->set(CallableInMethodCallToVariableRector::class)
->call('configure', [[
// see https://github.com/nette/caching/commit/5ffe263752af5ccf3866a28305e7b2669ab4da82
CallableInMethodCallToVariableRector::CALLABLE_IN_METHOD_CALL_TO_VARIABLE => ValueObjectInliner::inline([
new CallableInMethodCallToVariable('Nette\Caching\Cache', 'save', 1),
])
]]);

$services->set(RenameClassRector::class)->call('configure', [[
RenameClassRector::OLD_TO_NEW_CLASSES => [
'Nette\Bridges\ApplicationLatte\Template' => 'Nette\Bridges\ApplicationLatte\DefaultTemplate',
Expand Down
1 change: 0 additions & 1 deletion rules/symfony/src/ServiceMapProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ private function createTagsFromData(array $tagsData): array
{
$tagValueObjects = [];
foreach ($tagsData as $key => $tag) {

if (is_string($tag)) {
$tagValueObjects[$key] = new Tag($tag);
continue;
Expand Down
38 changes: 38 additions & 0 deletions rules/transform/src/NodeFactory/UnwrapClosureFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Rector\Transform\NodeFactory;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Return_;
use Rector\Core\Exception\NotImplementedYetException;

final class UnwrapClosureFactory
{
public function createAssign(Variable $resultVariable, Arg $arg): array
{
$assignedExpr = null;
$argValue = $arg->value;

$unwrappedNodes = $argValue->stmts;

if ($argValue instanceof Closure) {
$lastStmtKey = array_key_last($argValue->stmts);
$lastStmt = $argValue->stmts[$lastStmtKey];

if ($lastStmt instanceof Return_) {
if ($lastStmt->expr !== null) {
unset($unwrappedNodes[$lastStmtKey]);
$unwrappedNodes[] = new Assign($resultVariable, $lastStmt->expr);
}
}
}

return $unwrappedNodes;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

declare(strict_types=1);

namespace Rector\Transform\Rector\MethodCall;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PHPStan\Type\ClosureType;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\Transform\NodeFactory\UnwrapClosureFactory;
use Rector\Transform\ValueObject\CallableInMethodCallToVariable;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;

/**
* @see https://github.com/nette/caching/commit/5ffe263752af5ccf3866a28305e7b2669ab4da82
*
* @see \Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\CallableInMethodCallToVariableRectorTest
*/
final class CallableInMethodCallToVariableRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var string
*/
public const CALLABLE_IN_METHOD_CALL_TO_VARIABLE = 'callable_in_method_call_to_variable';

/**
* @var CallableInMethodCallToVariable[]
*/
private $callableInMethodCallToVariable = [];

/**
* @var UnwrapClosureFactory
*/
private $unwrapClosureFactory;

public function __construct(UnwrapClosureFactory $unwrapClosureFactory)
{
$this->unwrapClosureFactory = $unwrapClosureFactory;
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change a callable in method call to standalone variable assign', [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
public function run()
{
/** @var \Nette\Caching\Cache $cache */
$cache->save($key, function () use ($container) {
return 100;
});
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
public function run()
{
/** @var \Nette\Caching\Cache $cache */
$result = 100;
$cache->save($key, $result);
}
}
CODE_SAMPLE
,
[
self::CALLABLE_IN_METHOD_CALL_TO_VARIABLE => [
new CallableInMethodCallToVariable('Nette\Caching\Cache', 'save', 1),
],
]
),
]);
}

/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [MethodCall::class];
}

/**
* @param MethodCall $node
*/
public function refactor(Node $node): ?Node
{
foreach ($this->callableInMethodCallToVariable as $callableInMethodCallToVariable) {
if (! $this->isObjectType($node->var, $callableInMethodCallToVariable->getClassType())) {
continue;
}

if (! isset($node->args[$callableInMethodCallToVariable->getArgumentPosition()])) {
continue;
}

$arg = $node->args[$callableInMethodCallToVariable->getArgumentPosition()];
$argValueType = $this->getStaticType($arg->value);
if (! $argValueType instanceof ClosureType) {
continue;
}

$resultVariable = new Variable('result');

$unwrappedNodes = $this->unwrapClosureFactory->createAssign($resultVariable, $arg);

$arg->value = $resultVariable;
$this->addNodesBeforeNode($unwrappedNodes, $node);

return $node;
}

return null;
}

/**
* @param mixed[] $configuration
*/
public function configure(array $configuration): void
{
$callableInMethodCallToVariable = $configuration[self::CALLABLE_IN_METHOD_CALL_TO_VARIABLE] ?? [];
Assert::allIsInstanceOf($callableInMethodCallToVariable, CallableInMethodCallToVariable::class);
$this->callableInMethodCallToVariable = $callableInMethodCallToVariable;
}
}
45 changes: 45 additions & 0 deletions rules/transform/src/ValueObject/CallableInMethodCallToVariable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Rector\Transform\ValueObject;

final class CallableInMethodCallToVariable
{
/**
* @var string
*/
private $classType;
/**
* @var string
*/
private $methodName;
/**
* @var int
*/
private $argumentPosition;

public function __construct(string $classType, string $methodName, int $argumentPosition)
{
$this->classType = $classType;
$this->methodName = $methodName;
$this->argumentPosition = $argumentPosition;
}

public function getClassType(): string
{
return $this->classType;
}

public function getMethodName(): string
{
return $this->methodName;
}

public function getArgumentPosition(): int
{
return $this->argumentPosition;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector;

use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Transform\Rector\MethodCall\CallableInMethodCallToVariableRector;
use Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Source\DummyCache;
use Rector\Transform\ValueObject\CallableInMethodCallToVariable;
use Symplify\SmartFileSystem\SmartFileInfo;

final class CallableInMethodCallToVariableRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}

public function provideData(): \Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

/**
* @return mixed[]
*/
protected function getRectorsWithConfiguration(): array
{
return [
CallableInMethodCallToVariableRector::class => [
CallableInMethodCallToVariableRector::CALLABLE_IN_METHOD_CALL_TO_VARIABLE => [
new CallableInMethodCallToVariable(DummyCache::class, 'save', 1),
],
],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Fixture;

use Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Source\DummyCache;

final class ManyStmts
{
public function run(DummyCache $dummyCache, $someValue)
{
$dummyCache->save('key', function ($someValue) {
$someValue= $someValue + 1000;
return $someValue + 10;
});
}
}

?>
-----
<?php

namespace Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Fixture;

use Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Source\DummyCache;

final class ManyStmts
{
public function run(DummyCache $dummyCache, $someValue)
{
$someValue= $someValue + 1000;
$result = $someValue + 10;
$dummyCache->save('key', $result);
}
}

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

namespace Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Fixture;

use Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Source\DummyCache;

final class SomeClass
{
public function run(DummyCache $dummyCache)
{
$dummyCache->save('key', function () {
return 100;
});
}
}

?>
-----
<?php

namespace Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Fixture;

use Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Source\DummyCache;

final class SomeClass
{
public function run(DummyCache $dummyCache)
{
$result = 100;
$dummyCache->save('key', $result);
}
}

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

declare(strict_types=1);

namespace Rector\Transform\Tests\Rector\MethodCall\CallableInMethodCallToVariableRector\Source;

final class DummyCache
{
public function save($key, $value)
{
}
}

0 comments on commit 44ced8e

Please sign in to comment.