Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"require": {
"php": "^7.1",
"beberlei/assert": "^2.7",
"doctrine/annotations": "^1.5",
"nette/utils": "^2.4",
"nikic/php-parser": "4.0.x-dev#3193f7a as 3.1.1",
"rector/better-reflection": "^3.0",
Expand All @@ -29,6 +30,7 @@
"psr-4": {
"Rector\\": "src",
"Rector\\BetterReflection\\": "packages/BetterReflection/src",
"Rector\\DeprecatedAnnotation\\": "packages/DeprecatedAnnotation/src",
"Rector\\NodeTypeResolver\\": "packages/NodeTypeResolver/src",
"Rector\\NodeTraverserQueue\\": "packages/NodeTraverserQueue/src",
"Rector\\DeprecationExtractor\\": "packages/DeprecationExtractor/src",
Expand Down
3 changes: 2 additions & 1 deletion easy-coding-standard.neon
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ parameters:
- */src/Rector/Contrib/*/*Rector.php
- packages/NodeTypeResolver/src/NodeVisitor/TypeResolver.php
Symplify\CodingStandard\Sniffs\Debug\CommentedOutCodeSniff:
# false positive - @todo fix
# false positives - @todo fix
- packages/DeprecationExtractor/src/Rector/UnsupportedDeprecationFilter.php
- packages/NodeTypeResolver/src/NodeVisitor/TypeResolver.php
# false positive - fixed in master
- src/NodeAnalyzer/ClassConstAnalyzer.php
Expand Down
7 changes: 7 additions & 0 deletions packages/DeprecatedAnnotation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Deprecated Annotation

Idea to normalize and automate deprecations, thanks @JanTvrdik for pushing further

Inspire:

- https://hackernoon.com/how-kotlins-deprecated-relieves-pain-of-colossal-refactoring-8577545aaed
Copy link
Member Author

@TomasVotruba TomasVotruba Oct 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JanTvrdik I think this post shows the idea you have about smart deprecated messages. Implemented right in IDE :)
Bit better than dump annotation, but still not automated.

12 changes: 12 additions & 0 deletions packages/DeprecatedAnnotation/src/Deprecated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);

namespace Rector\DeprecatedAnnotation;

use Doctrine\Common\Annotations\Annotation;

/**
* @Annotation
*/
final class Deprecated
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@ final class UnsupportedDeprecationFilter
/**
* @var string[]
*/
private $yamlDeprecationMessages = [
private $configDeprecationMessages = [
'Autowiring-types are deprecated',
'The "=" suffix that used to disable strict references',
'The XmlFileLoader will raise an exception in Symfony 4.0, instead of silently ignoring unsupported',
'The "strict" attribute used when referencing the "" service is deprecated',
'Service names that start with an underscore are deprecated',
'configuration key',
// Laravel
"Key 'create' is deprecated, use 'factory' or 'type' in configuration",
"Option 'run' is deprecated, use 'run' as tag.",
];

/**
* @var string[]
*/
private $serviceDeprecationMessages = [
// Symfony
'It should either be deprecated or its implementation upgraded.',
'It should either be deprecated or its factory upgraded.',
'Service identifiers will be made case sensitive',
Expand All @@ -35,8 +39,8 @@ final class UnsupportedDeprecationFilter

public function matches(Deprecation $deprecation): bool
{
foreach ($this->yamlDeprecationMessages as $yamlDeprecationMessage) {
if (Strings::contains($deprecation->getMessage(), $yamlDeprecationMessage)) {
foreach ($this->configDeprecationMessages as $configDeprecationMessage) {
if (Strings::contains($deprecation->getMessage(), $configDeprecationMessage)) {
return true;
}
}
Expand Down
30 changes: 28 additions & 2 deletions src/Node/NodeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\TraitUse;

/**
* @todo decouple MethodCallNodeFactory
* @todo decouple PropertyFetchNodeFactory
*/
final class NodeFactory
{
/**
Expand Down Expand Up @@ -101,9 +105,17 @@ public function createClassConstantReference(string $className): ClassConstFetch
*/
public function createMethodCall(string $variableName, string $methodName): MethodCall
{
$varNode = new Variable($variableName);
$variableNode = new Variable($variableName);

return new MethodCall($varNode, $methodName);
return new MethodCall($variableNode, $methodName);
}

/**
* Creates "$method->call();" from existing variable
*/
public function createMethodCallWithVariable(Variable $variableNode, string $methodName): MethodCall
{
return new MethodCall($variableNode, $methodName);
}

/**
Expand Down Expand Up @@ -229,6 +241,20 @@ public function createMethodCallWithArguments(
return $methodCallNode;
}

/**
* @param mixed[] $arguments
*/
public function createMethodCallWithVariableAndArguments(
Variable $variableNode,
string $method,
array $arguments
): MethodCall {
$methodCall = $this->createMethodCallWithVariable($variableNode, $method);
$methodCall->args = $this->createArgs($arguments);

return $methodCall;
}

/**
* Creates:
* - $variable->property['key'];
Expand Down
37 changes: 37 additions & 0 deletions src/NodeAnalyzer/ExpressionAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types=1);

namespace Rector\NodeAnalyzer;

use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\Expression;

final class ExpressionAnalyzer
{
public function resolvePropertyFetch(Node $node): ?PropertyFetch
{
if (! $node instanceof Expression) {
return null;
}

if (! $node->expr instanceof Assign) {
return null;
}

return $this->resolvePropertyFetchNodeFromAssignNode($node->expr);
}

private function resolvePropertyFetchNodeFromAssignNode(Assign $assignNode): ?PropertyFetch
{
if ($assignNode->expr instanceof PropertyFetch) {
return $assignNode->expr;
}

if ($assignNode->var instanceof PropertyFetch) {
return $assignNode->var;
}

return null;
}
}
59 changes: 59 additions & 0 deletions src/NodeAnalyzer/PropertyFetchAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php declare(strict_types=1);

namespace Rector\NodeAnalyzer;

use PhpParser\Node;
use PhpParser\Node\Expr\PropertyFetch;
use Rector\BetterReflection\Reflector\SmartClassReflector;
use Rector\Node\Attribute;
use ReflectionProperty;

final class PropertyFetchAnalyzer
{
/**
* @var string[][]
*/
private $publicPropertyNamesForType = [];

/**
* @var SmartClassReflector
*/
private $smartClassReflector;

public function __construct(SmartClassReflector $smartClassReflector)
{
$this->smartClassReflector = $smartClassReflector;
}

public function isMagicPropertyFetchOnType(Node $node, string $type): bool
{
if (! $node instanceof PropertyFetch) {
return false;
}

$variableNodeType = $node->var->getAttribute(Attribute::TYPE);
if ($variableNodeType !== $type) {
return false;
}

$nodePropertyName = $node->name->name;
$publicPropertyNames = $this->getPublicPropertyNamesForType($type);

return ! in_array($nodePropertyName, $publicPropertyNames, true);
}

/**
* @return string[]
*/
private function getPublicPropertyNamesForType(string $type): array
{
if (isset($this->publicPropertyNamesForType[$type])) {
return $this->publicPropertyNamesForType[$type];
}

$classReflection = $this->smartClassReflector->reflect($type);
$publicProperties = $classReflection->getProperties(ReflectionProperty::IS_PUBLIC);

return $this->publicPropertyNamesForType[$type] = array_keys($publicProperties);
}
}
138 changes: 138 additions & 0 deletions src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php declare(strict_types=1);

namespace Rector\Rector\MagicDisclosure;

use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\Expression;
use Rector\Node\NodeFactory;
use Rector\NodeAnalyzer\ExpressionAnalyzer;
use Rector\NodeAnalyzer\PropertyFetchAnalyzer;
use Rector\Rector\AbstractRector;

/**
* __get/__set to specific call
*
* Example - from:
* - $someService = $container->someService;
* - $container->someService = $someService;
*
* To
* - $container->getService('someService');
* - $container->setService('someService', $someService);
*/
final class GetAndSetToMethodCallRector extends AbstractRector
{
/**
* @var string[]
*/
private $typeToMethodCalls = [];

/**
* @var PropertyFetchAnalyzer
*/
private $propertyAccessAnalyzer;

/**
* @var NodeFactory
*/
private $nodeFactory;

/**
* @var string[]
*/
private $activeTransformation = [];

/**
* @var ExpressionAnalyzer
*/
private $expressionAnalyzer;

/**
* Type to method call()
*
* @param string[] $typeToMethodCalls
*/
public function __construct(
array $typeToMethodCalls,
PropertyFetchAnalyzer $propertyAccessAnalyzer,
NodeFactory $nodeFactory,
ExpressionAnalyzer $expressionAnalyzer
) {
$this->typeToMethodCalls = $typeToMethodCalls;
$this->propertyAccessAnalyzer = $propertyAccessAnalyzer;
$this->nodeFactory = $nodeFactory;
$this->expressionAnalyzer = $expressionAnalyzer;
}

public function isCandidate(Node $node): bool
{
$this->activeTransformation = [];

$propertyFetchNode = $this->expressionAnalyzer->resolvePropertyFetch($node);
if ($propertyFetchNode === null) {
return false;
}

foreach ($this->typeToMethodCalls as $type => $transformation) {
if ($this->propertyAccessAnalyzer->isMagicPropertyFetchOnType($propertyFetchNode, $type)) {
$this->activeTransformation = $transformation;

return true;
}
}

return false;
}

/**
* @param Expression $expressionNode
*/
public function refactor(Node $expressionNode): ?Node
{
if ($expressionNode->expr->expr instanceof PropertyFetch) {
/** @var PropertyFetch $propertyFetchNode */
$propertyFetchNode = $expressionNode->expr->expr;
$method = $this->activeTransformation['get'];
$expressionNode->expr->expr = $this->createMethodCallNodeFromPropertyFetchNode($propertyFetchNode, $method);

return $expressionNode;
}

/** @var Assign $assignNode */
$assignNode = $expressionNode->expr;
$method = $this->activeTransformation['set'];
$expressionNode->expr = $this->createMethodCallNodeFromAssignNode($assignNode, $method);

return $expressionNode;
}

private function createMethodCallNodeFromPropertyFetchNode(
PropertyFetch $propertyFetchNode,
string $method
): MethodCall {
$value = $propertyFetchNode->name->name;

return $this->nodeFactory->createMethodCallWithVariableAndArguments(
$propertyFetchNode->var,
$method,
[$value]
);
}

private function createMethodCallNodeFromAssignNode(Assign $assignNode, string $method): MethodCall
{
/** @var PropertyFetch $propertyFetchNode */
$propertyFetchNode = $assignNode->var;

$key = $propertyFetchNode->name->name;

return $this->nodeFactory->createMethodCallWithVariableAndArguments(
$propertyFetchNode->var,
$method,
[$key, $assignNode->expr]
);
}
}
Loading