2020-02-17 15:13:38 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
2020-08-18 15:57:30 +00:00
|
|
|
namespace Rector\SOLID\Rector\Class_;
|
2020-02-17 15:13:38 +00:00
|
|
|
|
|
|
|
use PhpParser\Node;
|
|
|
|
use PhpParser\Node\Const_;
|
|
|
|
use PhpParser\Node\Expr;
|
2020-02-18 22:09:25 +00:00
|
|
|
use PhpParser\Node\Expr\Assign;
|
2020-02-17 15:13:38 +00:00
|
|
|
use PhpParser\Node\Expr\ClassConstFetch;
|
|
|
|
use PhpParser\Node\Expr\Variable;
|
|
|
|
use PhpParser\Node\Identifier;
|
|
|
|
use PhpParser\Node\Name;
|
|
|
|
use PhpParser\Node\Stmt\Class_;
|
|
|
|
use PhpParser\Node\Stmt\ClassConst;
|
|
|
|
use PhpParser\Node\Stmt\ClassMethod;
|
2020-08-07 00:25:25 +00:00
|
|
|
use Rector\BetterPhpDocParser\PhpDocManipulator\VarAnnotationManipulator;
|
2020-02-17 15:13:38 +00:00
|
|
|
use Rector\Core\Exception\ShouldNotHappenException;
|
|
|
|
use Rector\Core\PhpParser\Node\Manipulator\ClassMethodAssignManipulator;
|
|
|
|
use Rector\Core\Rector\AbstractRector;
|
2020-05-03 19:30:01 +00:00
|
|
|
use Rector\Core\Util\StaticRectorStrings;
|
2020-02-17 15:13:38 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2020-11-16 17:50:38 +00:00
|
|
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
|
|
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
2020-02-17 15:13:38 +00:00
|
|
|
|
|
|
|
/**
|
2020-08-18 15:57:30 +00:00
|
|
|
* @see \Rector\SOLID\Tests\Rector\Class_\ChangeReadOnlyVariableWithDefaultValueToConstantRector\ChangeReadOnlyVariableWithDefaultValueToConstantRectorTest
|
2020-02-17 15:13:38 +00:00
|
|
|
*/
|
|
|
|
final class ChangeReadOnlyVariableWithDefaultValueToConstantRector extends AbstractRector
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var ClassMethodAssignManipulator
|
|
|
|
*/
|
|
|
|
private $classMethodAssignManipulator;
|
|
|
|
|
2020-08-07 00:25:25 +00:00
|
|
|
/**
|
|
|
|
* @var VarAnnotationManipulator
|
|
|
|
*/
|
|
|
|
private $varAnnotationManipulator;
|
|
|
|
|
|
|
|
public function __construct(
|
|
|
|
ClassMethodAssignManipulator $classMethodAssignManipulator,
|
|
|
|
VarAnnotationManipulator $varAnnotationManipulator
|
|
|
|
) {
|
2020-02-17 15:13:38 +00:00
|
|
|
$this->classMethodAssignManipulator = $classMethodAssignManipulator;
|
2020-08-07 00:25:25 +00:00
|
|
|
$this->varAnnotationManipulator = $varAnnotationManipulator;
|
2020-02-17 15:13:38 +00:00
|
|
|
}
|
|
|
|
|
2020-11-16 17:50:38 +00:00
|
|
|
public function getRuleDefinition(): RuleDefinition
|
2020-02-17 15:13:38 +00:00
|
|
|
{
|
2020-11-16 17:50:38 +00:00
|
|
|
return new RuleDefinition(
|
|
|
|
'Change variable with read only status with default value to constant',
|
|
|
|
[
|
|
|
|
new CodeSample(
|
|
|
|
<<<'CODE_SAMPLE'
|
2020-02-17 15:13:38 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
$replacements = [
|
|
|
|
'PHPUnit\Framework\TestCase\Notice' => 'expectNotice',
|
|
|
|
'PHPUnit\Framework\TestCase\Deprecated' => 'expectDeprecation',
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ($replacements as $class => $method) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 08:23:13 +00:00
|
|
|
CODE_SAMPLE
|
2020-02-17 15:13:38 +00:00
|
|
|
,
|
2020-11-16 17:50:38 +00:00
|
|
|
<<<'CODE_SAMPLE'
|
2020-02-17 15:13:38 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
2020-02-18 22:09:25 +00:00
|
|
|
/**
|
|
|
|
* @var string[]
|
|
|
|
*/
|
2020-02-17 15:13:38 +00:00
|
|
|
private const REPLACEMENTS = [
|
|
|
|
'PHPUnit\Framework\TestCase\Notice' => 'expectNotice',
|
|
|
|
'PHPUnit\Framework\TestCase\Deprecated' => 'expectDeprecation',
|
|
|
|
];
|
|
|
|
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
foreach (self::REPLACEMENTS as $class => $method) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 08:23:13 +00:00
|
|
|
CODE_SAMPLE
|
2020-11-16 17:50:38 +00:00
|
|
|
),
|
|
|
|
|
|
|
|
]);
|
2020-02-17 15:13:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public function getNodeTypes(): array
|
|
|
|
{
|
2020-02-18 22:09:25 +00:00
|
|
|
return [Class_::class];
|
2020-02-17 15:13:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-02-18 22:09:25 +00:00
|
|
|
* @param Class_ $node
|
2020-02-17 15:13:38 +00:00
|
|
|
*/
|
|
|
|
public function refactor(Node $node): ?Node
|
|
|
|
{
|
2020-02-18 22:09:25 +00:00
|
|
|
$readOnlyVariableAssigns = $this->collectReadOnlyVariableAssigns($node);
|
|
|
|
$readOnlyVariableAssigns = $this->filterOutUniqueNames($readOnlyVariableAssigns);
|
2020-02-17 15:13:38 +00:00
|
|
|
|
2020-02-21 12:59:25 +00:00
|
|
|
if ($readOnlyVariableAssigns === []) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-02-18 22:09:25 +00:00
|
|
|
foreach ($readOnlyVariableAssigns as $readOnlyVariable) {
|
|
|
|
$methodName = $readOnlyVariable->getAttribute(AttributeKey::METHOD_NAME);
|
|
|
|
if (! is_string($methodName)) {
|
|
|
|
throw new ShouldNotHappenException();
|
|
|
|
}
|
2020-02-17 15:13:38 +00:00
|
|
|
|
2020-02-18 22:09:25 +00:00
|
|
|
$classMethod = $node->getMethod($methodName);
|
|
|
|
if ($classMethod === null) {
|
2020-02-17 15:13:38 +00:00
|
|
|
throw new ShouldNotHappenException();
|
|
|
|
}
|
|
|
|
|
2020-02-18 22:09:25 +00:00
|
|
|
$this->refactorClassMethod($classMethod, $node, $readOnlyVariableAssigns);
|
2020-02-17 15:13:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $node;
|
|
|
|
}
|
|
|
|
|
2020-02-18 22:09:25 +00:00
|
|
|
/**
|
|
|
|
* @return Assign[]
|
|
|
|
*/
|
|
|
|
private function collectReadOnlyVariableAssigns(Class_ $class): array
|
|
|
|
{
|
|
|
|
$readOnlyVariables = [];
|
|
|
|
|
|
|
|
foreach ($class->getMethods() as $classMethod) {
|
|
|
|
$readOnlyVariableAssignScalarVariables = $this->classMethodAssignManipulator->collectReadyOnlyAssignScalarVariables(
|
|
|
|
$classMethod
|
|
|
|
);
|
|
|
|
|
|
|
|
$readOnlyVariables = array_merge($readOnlyVariables, $readOnlyVariableAssignScalarVariables);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $readOnlyVariables;
|
|
|
|
}
|
|
|
|
|
2020-04-26 00:57:47 +00:00
|
|
|
/**
|
|
|
|
* @param Assign[] $assigns
|
|
|
|
* @return Assign[]
|
|
|
|
*/
|
|
|
|
private function filterOutUniqueNames(array $assigns): array
|
|
|
|
{
|
|
|
|
$assignsByName = [];
|
|
|
|
foreach ($assigns as $assign) {
|
|
|
|
/** @var string $variableName */
|
|
|
|
$variableName = $this->getName($assign->var);
|
|
|
|
|
|
|
|
$assignsByName[$variableName][] = $assign;
|
|
|
|
}
|
|
|
|
|
|
|
|
$assignsWithUniqueName = [];
|
|
|
|
foreach ($assignsByName as $assigns) {
|
|
|
|
if (count($assigns) > 1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$assignsWithUniqueName = array_merge($assignsWithUniqueName, $assigns);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $assignsWithUniqueName;
|
|
|
|
}
|
|
|
|
|
2020-02-18 22:09:25 +00:00
|
|
|
/**
|
|
|
|
* @param Assign[] $readOnlyVariableAssigns
|
|
|
|
*/
|
|
|
|
private function refactorClassMethod(ClassMethod $classMethod, Class_ $class, array $readOnlyVariableAssigns): void
|
|
|
|
{
|
|
|
|
foreach ($readOnlyVariableAssigns as $readOnlyVariableAssign) {
|
|
|
|
$this->removeNode($readOnlyVariableAssign);
|
|
|
|
|
2020-02-19 13:35:43 +00:00
|
|
|
/** @var Variable|ClassConstFetch $variable */
|
2020-02-18 22:09:25 +00:00
|
|
|
$variable = $readOnlyVariableAssign->var;
|
2020-10-10 17:42:43 +00:00
|
|
|
// already overridden
|
2020-02-19 13:35:30 +00:00
|
|
|
if (! $variable instanceof Variable) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-10 17:42:43 +00:00
|
|
|
$classConst = $this->createPrivateClassConst($variable, $readOnlyVariableAssign->expr);
|
2020-02-18 22:09:25 +00:00
|
|
|
|
|
|
|
// replace $variable usage in the code with constant
|
|
|
|
$this->addConstantToClass($class, $classConst);
|
|
|
|
|
|
|
|
$variableName = $this->getName($variable);
|
|
|
|
if ($variableName === null) {
|
|
|
|
throw new ShouldNotHappenException();
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->replaceVariableWithClassConstFetch($classMethod, $variableName, $classConst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-10 17:42:43 +00:00
|
|
|
private function createPrivateClassConst(Variable $variable, Expr $expr): ClassConst
|
2020-04-26 00:57:47 +00:00
|
|
|
{
|
|
|
|
$constantName = $this->createConstantNameFromVariable($variable);
|
|
|
|
|
2020-06-29 21:19:37 +00:00
|
|
|
$const = new Const_($constantName, $expr);
|
2020-04-26 00:57:47 +00:00
|
|
|
|
2020-06-29 21:19:37 +00:00
|
|
|
$classConst = new ClassConst([$const]);
|
2020-04-26 00:57:47 +00:00
|
|
|
$classConst->flags = Class_::MODIFIER_PRIVATE;
|
|
|
|
|
|
|
|
$this->mirrorComments($classConst, $variable);
|
|
|
|
|
2020-08-07 00:25:25 +00:00
|
|
|
$constantType = $this->getStaticType($classConst->consts[0]->value);
|
|
|
|
$this->varAnnotationManipulator->decorateNodeWithType($classConst, $constantType);
|
2020-04-26 00:57:47 +00:00
|
|
|
|
|
|
|
return $classConst;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function replaceVariableWithClassConstFetch(
|
|
|
|
ClassMethod $classMethod,
|
|
|
|
string $variableName,
|
|
|
|
ClassConst $classConst
|
|
|
|
): void {
|
|
|
|
$constantName = $this->getName($classConst);
|
|
|
|
if ($constantName === null) {
|
|
|
|
throw new ShouldNotHappenException();
|
2020-02-18 22:09:25 +00:00
|
|
|
}
|
|
|
|
|
2020-09-01 17:56:30 +00:00
|
|
|
$this->traverseNodesWithCallable($classMethod, function (Node $node) use (
|
|
|
|
$variableName,
|
|
|
|
$constantName
|
|
|
|
): ?ClassConstFetch {
|
2020-05-02 18:52:16 +00:00
|
|
|
if (! $this->isVariableName($node, $variableName)) {
|
2020-04-26 00:57:47 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// replace with constant fetch
|
|
|
|
$classConstFetch = new ClassConstFetch(new Name('self'), new Identifier($constantName));
|
|
|
|
|
|
|
|
// needed later
|
|
|
|
$classConstFetch->setAttribute(AttributeKey::CLASS_NAME, $node->getAttribute(AttributeKey::CLASS_NAME));
|
|
|
|
|
|
|
|
return $classConstFetch;
|
|
|
|
});
|
2020-02-18 22:09:25 +00:00
|
|
|
}
|
2020-08-05 20:45:36 +00:00
|
|
|
|
|
|
|
private function createConstantNameFromVariable(Variable $variable): string
|
|
|
|
{
|
|
|
|
$variableName = $this->getName($variable);
|
|
|
|
if ($variableName === null) {
|
|
|
|
throw new ShouldNotHappenException();
|
|
|
|
}
|
|
|
|
|
|
|
|
$constantName = StaticRectorStrings::camelCaseToUnderscore($variableName);
|
|
|
|
|
|
|
|
return strtoupper($constantName);
|
|
|
|
}
|
2020-02-17 15:13:38 +00:00
|
|
|
}
|