Merge pull request #3790 from rectorphp/cover-array-dim-fetch

[Nette] Add ChangeControlArrayAccessToAnnotatedControlVariableRector
This commit is contained in:
kodiakhq[bot] 2020-07-26 13:58:33 +00:00 committed by GitHub
commit b99bf8e0a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 839 additions and 197 deletions

View File

@ -58,6 +58,7 @@
"autoload": {
"psr-4": {
"Rector\\Architecture\\": "rules/architecture/src",
"Rector\\AnonymousClass\\": "packages/anonymous-class/src",
"Rector\\PostRector\\": "packages/post-rector/src",
"Rector\\AttributeAwarePhpDoc\\": "packages/attribute-aware-php-doc/src",
"Rector\\Autodiscovery\\": "rules/autodiscovery/src",

View File

@ -2,6 +2,7 @@
declare(strict_types=1);
use Rector\Nette\Rector\ArrayDimFetch\ChangeControlArrayAccessToAnnotatedControlVariableRector;
use Rector\Nette\Rector\Assign\MakeGetComponentAssignAnnotatedRector;
use Rector\Nette\Rector\ClassMethod\TemplateMagicAssignToExplicitVariableArrayRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
@ -12,4 +13,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(TemplateMagicAssignToExplicitVariableArrayRector::class);
$services->set(MakeGetComponentAssignAnnotatedRector::class);
$services->set(ChangeControlArrayAccessToAnnotatedControlVariableRector::class);
};

View File

@ -1,4 +1,4 @@
# All 534 Rectors Overview
# All 536 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -31,7 +31,7 @@
- [MockistaToMockery](#mockistatomockery) (2)
- [MysqlToMysqli](#mysqltomysqli) (4)
- [Naming](#naming) (3)
- [Nette](#nette) (14)
- [Nette](#nette) (15)
- [NetteCodeQuality](#nettecodequality) (1)
- [NetteKdyby](#nettekdyby) (4)
- [NetteTesterToPHPUnit](#nettetestertophpunit) (3)
@ -5184,6 +5184,37 @@ Nextras/Form upgrade of addDatePicker method call to DateControl assign
<br><br>
### `ChangeControlArrayAccessToAnnotatedControlVariableRector`
- class: [`Rector\Nette\Rector\ArrayDimFetch\ChangeControlArrayAccessToAnnotatedControlVariableRector`](/../master/rules/nette/src/Rector/ArrayDimFetch/ChangeControlArrayAccessToAnnotatedControlVariableRector.php)
- [test fixtures](/../master/rules/nette/tests/Rector/ArrayDimFetch/ChangeControlArrayAccessToAnnotatedControlVariableRector/Fixture)
Change magic `$this["some_component"]` to variable assign with @var annotation
```diff
use Nette\Application\UI\Presenter;
use Nette\Application\UI\Form;
final class SomePresenter extends Presenter
{
public function run()
{
- if ($this['some_form']->isSubmitted()) {
+ /** @var \Nette\Application\UI\Form $someForm */
+ $someForm = $this['some_form'];
+ if ($someForm->isSubmitted()) {
}
}
protected function createComponentSomeForm()
{
return new Form();
}
}
```
<br><br>
### `ChangeFormArrayAccessToAnnotatedControlVariableRector`
- class: [`Rector\Nette\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector`](/../master/rules/nette/src/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector.php)

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->defaults()
->autowire()
->public();
$services->load('Rector\AnonymousClass\\', __DIR__ . '/../src');
};

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Rector\AnonymousClass\NodeAnalyzer;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use Rector\NodeNameResolver\NodeNameResolver;
final class ClassNodeAnalyzer
{
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
public function __construct(NodeNameResolver $nodeNameResolver)
{
$this->nodeNameResolver = $nodeNameResolver;
}
public function isAnonymousClass(Node $node): bool
{
if (! $node instanceof Class_) {
return false;
}
$className = $this->nodeNameResolver->getName($node);
if ($className === null) {
return true;
}
// match PHPStan pattern for anonymous classes
return (bool) Strings::match($className, '#AnonymousClass\w+$#');
}
}

View File

@ -12,7 +12,6 @@ use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ArrayType;
use PHPStan\Type\FloatType;
@ -25,12 +24,13 @@ use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\AnonymousClass\NodeAnalyzer\ClassNodeAnalyzer;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeCorrector\ParentClassesInterfacesAndUsedTraitsCorrector;
use Rector\NodeTypeResolver\TypeAnalyzer\ArrayTypeAnalyzer;
use Rector\PHPStanStaticTypeMapper\Utils\TypeUnwrapper;
use Rector\TypeDeclaration\PHPStan\Type\ObjectTypeSpecifier;
final class NodeTypeResolver
@ -40,11 +40,6 @@ final class NodeTypeResolver
*/
private $nodeTypeResolvers = [];
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
/**
* @var ObjectTypeSpecifier
*/
@ -60,23 +55,34 @@ final class NodeTypeResolver
*/
private $parentClassesInterfacesAndUsedTraitsCorrector;
/**
* @var TypeUnwrapper
*/
private $typeUnwrapper;
/**
* @var ClassNodeAnalyzer
*/
private $classNodeAnalyzer;
/**
* @param NodeTypeResolverInterface[] $nodeTypeResolvers
*/
public function __construct(
NodeNameResolver $nodeNameResolver,
ObjectTypeSpecifier $objectTypeSpecifier,
ParentClassesInterfacesAndUsedTraitsCorrector $parentClassesInterfacesAndUsedTraitsCorrector,
TypeUnwrapper $typeUnwrapper,
ClassNodeAnalyzer $classNodeAnalyzer,
array $nodeTypeResolvers
) {
$this->nodeNameResolver = $nodeNameResolver;
foreach ($nodeTypeResolvers as $nodeTypeResolver) {
$this->addNodeTypeResolver($nodeTypeResolver);
}
$this->objectTypeSpecifier = $objectTypeSpecifier;
$this->parentClassesInterfacesAndUsedTraitsCorrector = $parentClassesInterfacesAndUsedTraitsCorrector;
$this->typeUnwrapper = $typeUnwrapper;
$this->classNodeAnalyzer = $classNodeAnalyzer;
}
/**
@ -166,7 +172,7 @@ final class NodeTypeResolver
return new MixedType();
}
if ($node instanceof New_ && $this->isAnonymousClass($node->class)) {
if ($node instanceof New_ && $this->classNodeAnalyzer->isAnonymousClass($node->class)) {
return new ObjectWithoutClassType();
}
@ -178,6 +184,48 @@ final class NodeTypeResolver
return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAlaisedObjectType($node, $staticType);
}
public function isNumberType(Node $node): bool
{
return $this->isStaticType($node, IntegerType::class) || $this->isStaticType($node, FloatType::class);
}
public function isStaticType(Node $node, string $staticTypeClass): bool
{
if (! is_a($staticTypeClass, Type::class, true)) {
throw new ShouldNotHappenException(sprintf(
'"%s" in "%s()" must be type of "%s"',
$staticTypeClass,
__METHOD__,
Type::class
));
}
return is_a($this->resolve($node), $staticTypeClass);
}
/**
* @param ObjectType|string $desiredType
*/
public function isObjectTypeOrNullableObjectType(Node $node, $desiredType): bool
{
if ($this->isObjectType($node, $desiredType)) {
return true;
}
$nodeType = $this->getStaticType($node);
if (! $nodeType instanceof UnionType) {
return false;
}
$unwrappedNodeType = $this->typeUnwrapper->unwrapNullableType($nodeType);
if (! $unwrappedNodeType instanceof TypeWithClassName) {
return false;
}
$desiredTypeString = $desiredType instanceof ObjectType ? $desiredType->getClassName() : $desiredType;
return is_a($unwrappedNodeType->getClassName(), $desiredTypeString, true);
}
public function isNullableObjectType(Node $node): bool
{
$nodeType = $this->resolve($node);
@ -203,25 +251,6 @@ final class NodeTypeResolver
return false;
}
public function isNumberType(Node $node): bool
{
return $this->isStaticType($node, IntegerType::class) || $this->isStaticType($node, FloatType::class);
}
public function isStaticType(Node $node, string $staticTypeClass): bool
{
if (! is_a($staticTypeClass, Type::class, true)) {
throw new ShouldNotHappenException(sprintf(
'"%s" in "%s()" must be type of "%s"',
$staticTypeClass,
__METHOD__,
Type::class
));
}
return is_a($this->resolve($node), $staticTypeClass);
}
private function addNodeTypeResolver(NodeTypeResolverInterface $nodeTypeResolver): void
{
foreach ($nodeTypeResolver->getNodeClasses() as $nodeClass) {
@ -296,7 +325,7 @@ final class NodeTypeResolver
}
// skip anonymous classes, ref https://github.com/rectorphp/rector/issues/1574
if ($node instanceof New_ && $this->isAnonymousClass($node->class)) {
if ($node instanceof New_ && $this->classNodeAnalyzer->isAnonymousClass($node->class)) {
return new ObjectWithoutClassType();
}
@ -331,17 +360,6 @@ final class NodeTypeResolver
return new ArrayType(new MixedType(), new MixedType());
}
private function isAnonymousClass(Node $node): bool
{
if (! $node instanceof Class_) {
return false;
}
$className = $this->nodeNameResolver->getName($node);
return $className === null || Strings::contains($className, 'AnonymousClass');
}
private function resolveByNodeTypeResolvers(Node $node): ?Type
{
foreach ($this->nodeTypeResolvers as $nodeClass => $nodeTypeResolver) {

View File

@ -8,6 +8,7 @@ use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\PHPStan\TypeFactoryStaticHelper;
final class TypeUnwrapper
{
@ -51,4 +52,22 @@ final class TypeUnwrapper
return $type;
}
/**
* @return Type|UnionType
*/
public function removeNullTypeFromUnionType(UnionType $unionType): Type
{
$unionedTypesWithoutNullType = [];
foreach ($unionType->getTypes() as $type) {
if ($type instanceof UnionType) {
continue;
}
$unionedTypesWithoutNullType[] = $type;
}
return TypeFactoryStaticHelper::createUnionObjectType($unionedTypesWithoutNullType);
}
}

View File

@ -9,6 +9,7 @@ use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\If_;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\PostRector\Contract\Collector\NodeCollectorInterface;
@ -41,6 +42,11 @@ final class NodesToAddCollector implements NodeCollectorInterface
public function addNodeBeforeNode(Node $addedNode, Node $positionNode): void
{
if ($positionNode->getAttributes() === []) {
$message = sprintf('Switch arguments in "%s()" method', __METHOD__);
throw new ShouldNotHappenException($message);
}
$position = $this->resolveNearestExpressionPosition($positionNode);
$this->nodesToAddBefore[$position][] = $this->wrapToExpression($addedNode);
}

View File

@ -23,7 +23,7 @@ use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PHPStan\Type\StaticTypeAnalyzer;
use Rector\PHPStan\TypeFactoryStaticHelper;
use Rector\PHPStanStaticTypeMapper\Utils\TypeUnwrapper;
/**
* @see \Rector\CodeQuality\Tests\Rector\If_\SimplifyIfReturnBoolRector\SimplifyIfReturnBoolRectorTest
@ -40,12 +40,19 @@ final class SimplifyIfReturnBoolRector extends AbstractRector
*/
private $mergedNodeCommentPreserver;
/**
* @var TypeUnwrapper
*/
private $typeUnwrapper;
public function __construct(
MergedNodeCommentPreserver $mergedNodeCommentPreserver,
TypeUnwrapper $typeUnwrapper,
StaticTypeAnalyzer $staticTypeAnalyzer
) {
$this->staticTypeAnalyzer = $staticTypeAnalyzer;
$this->mergedNodeCommentPreserver = $mergedNodeCommentPreserver;
$this->typeUnwrapper = $typeUnwrapper;
$this->staticTypeAnalyzer = $staticTypeAnalyzer;
}
public function getDefinition(): RectorDefinition
@ -205,7 +212,7 @@ PHP
$exprStaticType = $this->getStaticType($expr);
// if we remove null type, still has to be trueable
if ($exprStaticType instanceof UnionType) {
$unionTypeWithoutNullType = $this->removeNullTypeFromUnionType($exprStaticType);
$unionTypeWithoutNullType = $this->typeUnwrapper->removeNullTypeFromUnionType($exprStaticType);
if ($this->staticTypeAnalyzer->isAlwaysTruableType($unionTypeWithoutNullType)) {
return new NotIdentical($expr, $this->createNull());
}
@ -221,23 +228,6 @@ PHP
return new Bool_($expr);
}
/**
* @return Type|UnionType
*/
private function removeNullTypeFromUnionType(UnionType $unionType): Type
{
$unionedTypesWithoutNullType = [];
foreach ($unionType->getTypes() as $type) {
if ($type instanceof UnionType) {
continue;
}
$unionedTypesWithoutNullType[] = $type;
}
return TypeFactoryStaticHelper::createUnionObjectType($unionedTypesWithoutNullType);
}
private function isBoolCastNeeded(Expr $expr): bool
{
if ($expr instanceof BooleanNot) {

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\Core\Naming;
namespace Rector\Naming;
use Nette\Utils\Strings;
use Rector\Core\ValueObject\RenamedNamespaceValueObject;

View File

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Rector\Nette\DocBlock;
use PhpParser\Node;
use PhpParser\Node\Stmt\Expression;
use PHPStan\Type\TypeWithClassName;
use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareVarTagValueNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareFullyQualifiedIdentifierTypeNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class VarAnnotationManipulator
{
/**
* @var PhpDocInfoFactory
*/
private $phpDocInfoFactory;
public function __construct(PhpDocInfoFactory $phpDocInfoFactory)
{
$this->phpDocInfoFactory = $phpDocInfoFactory;
}
public function decorateNodeWithInlineVarType(
Node $node,
TypeWithClassName $controlTypeWithClassName,
string $variableName
): void {
$phpDocInfo = $this->resolvePhpDocInfo($node);
// already done
if ($phpDocInfo->getVarTagValue() !== null) {
return;
}
$attributeAwareFullyQualifiedIdentifierTypeNode = new AttributeAwareFullyQualifiedIdentifierTypeNode(
$controlTypeWithClassName->getClassName()
);
$attributeAwareVarTagValueNode = new AttributeAwareVarTagValueNode(
$attributeAwareFullyQualifiedIdentifierTypeNode,
'$' . $variableName,
''
);
$phpDocInfo->addTagValueNode($attributeAwareVarTagValueNode);
}
private function resolvePhpDocInfo(Node $node): PhpDocInfo
{
$currentStmt = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
if ($currentStmt instanceof Expression) {
/** @var PhpDocInfo|null $phpDocInfo */
$phpDocInfo = $currentStmt->getAttribute(AttributeKey::PHP_DOC_INFO);
} else {
/** @var PhpDocInfo|null $phpDocInfo */
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
}
if ($phpDocInfo === null) {
$phpDocInfo = $this->phpDocInfoFactory->createEmpty($node);
}
$phpDocInfo->makeSingleLined();
return $phpDocInfo;
}
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Rector\Nette\FormControlTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Type\TypeWithClassName;
use Rector\Nette\Contract\FormControlTypeResolverInterface;
use Rector\Nette\Naming\NetteControlNaming;
use Rector\Nette\NodeAnalyzer\ControlDimFetchAnalyzer;
use Rector\NodeCollector\NodeFinder\FunctionLikeParsedNodesFinder;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
final class ArrayDimFetchControlTypeResolver implements FormControlTypeResolverInterface
{
/**
* @var FunctionLikeParsedNodesFinder
*/
private $functionLikeParsedNodesFinder;
/**
* @var ControlDimFetchAnalyzer
*/
private $controlDimFetchAnalyzer;
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
/**
* @var NetteControlNaming
*/
private $netteControlNaming;
/**
* @var ReturnTypeInferer
*/
private $returnTypeInferer;
public function __construct(
FunctionLikeParsedNodesFinder $functionLikeParsedNodesFinder,
ControlDimFetchAnalyzer $controlDimFetchAnalyzer,
NodeTypeResolver $nodeTypeResolver,
NetteControlNaming $netteControlNaming,
ReturnTypeInferer $returnTypeInferer
) {
$this->functionLikeParsedNodesFinder = $functionLikeParsedNodesFinder;
$this->controlDimFetchAnalyzer = $controlDimFetchAnalyzer;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->netteControlNaming = $netteControlNaming;
$this->returnTypeInferer = $returnTypeInferer;
}
/**
* @return array<string, string>
*/
public function resolve(Node $node): array
{
if (! $node instanceof ArrayDimFetch) {
return [];
}
$controlShortName = $this->controlDimFetchAnalyzer->matchName($node);
if ($controlShortName === null) {
return [];
}
$createComponentClassMethod = $this->matchCreateComponentClassMethod($node, $controlShortName);
if ($createComponentClassMethod === null) {
return [];
}
$createComponentClassMethodReturnType = $this->returnTypeInferer->inferFunctionLike(
$createComponentClassMethod
);
if (! $createComponentClassMethodReturnType instanceof TypeWithClassName) {
return [];
}
return [
$controlShortName => $createComponentClassMethodReturnType->getClassName(),
];
}
private function matchCreateComponentClassMethod(
ArrayDimFetch $arrayDimFetch,
string $controlShortName
): ?ClassMethod {
$callerType = $this->nodeTypeResolver->getStaticType($arrayDimFetch->var);
if (! $callerType instanceof TypeWithClassName) {
return null;
}
$createComponentClassMethodName = $this->netteControlNaming->createCreateComponentClassMethodName(
$controlShortName
);
return $this->functionLikeParsedNodesFinder->findClassMethod(
$createComponentClassMethodName,
$callerType->getClassName()
);
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Rector\Nette\Naming;
use Nette\Utils\Strings;
use Rector\Core\Util\StaticRectorStrings;
final class NetteControlNaming
{
public function createVariableName(string $shortName): string
{
$variableName = StaticRectorStrings::underscoreToPascalCase($shortName);
if (Strings::endsWith($variableName, 'Form')) {
return $variableName;
}
return $variableName . 'Control';
}
public function createCreateComponentClassMethodName(string $shortName): string
{
return 'createComponent' . ucfirst($this->createVariableName($shortName));
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Rector\Nette\NodeAnalyzer;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use Rector\NodeTypeResolver\NodeTypeResolver;
final class ControlDimFetchAnalyzer
{
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
public function __construct(NodeTypeResolver $nodeTypeResolver)
{
$this->nodeTypeResolver = $nodeTypeResolver;
}
public function matchName(Node $node): ?string
{
if (! $node instanceof ArrayDimFetch) {
return null;
}
if (! $this->isContainerVariable($node->var)) {
return null;
}
if (! $node->dim instanceof String_) {
return null;
}
return $node->dim->value;
}
private function isContainerVariable(Node $node): bool
{
if (! $node instanceof Variable) {
return false;
}
return $this->nodeTypeResolver->isObjectTypeOrNullableObjectType($node, 'Nette\ComponentModel\IContainer');
}
}

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Rector\Nette\NodeResolver;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Expr;
use Rector\Core\Exception\NotImplementedYetException;
use Rector\Core\Exception\ShouldNotHappenException;
@ -38,14 +38,13 @@ final class FormVariableInputNameTypeResolver
$this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver;
}
public function resolveControlTypeByInputName(Variable $formVariable, string $inputName): string
public function resolveControlTypeByInputName(Expr $formOrControlExpr, string $inputName): string
{
$methodNamesByInputNames = $this->methodNamesByInputNamesResolver->resolveExpr($formVariable);
$methodNamesByInputNames = $this->methodNamesByInputNamesResolver->resolveExpr($formOrControlExpr);
$formAddMethodName = $methodNamesByInputNames[$inputName] ?? null;
if ($formAddMethodName === null) {
$message = sprintf('Not found for "%s" input name', $inputName);
$message = sprintf('Type was not found for "%s" input name', $inputName);
throw new ShouldNotHappenException($message);
}

View File

@ -38,7 +38,6 @@ final class MethodNamesByInputNamesResolver
foreach ($this->formControlTypeResolvers as $formControlTypeResolver) {
$currentMethodNamesByInputNames = $formControlTypeResolver->resolve($node);
$methodNamesByInputNames = array_merge($methodNamesByInputNames, $currentMethodNamesByInputNames);
}

View File

@ -0,0 +1,195 @@
<?php
declare(strict_types=1);
namespace Rector\Nette\Rector\ArrayDimFetch;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PHPStan\Type\ObjectType;
use Rector\Core\Exception\NotImplementedYetException;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Nette\DocBlock\VarAnnotationManipulator;
use Rector\Nette\Naming\NetteControlNaming;
use Rector\Nette\NodeAnalyzer\ControlDimFetchAnalyzer;
use Rector\Nette\NodeResolver\MethodNamesByInputNamesResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @sponsor Thanks https://amateri.com for sponsoring this rule - visit them on https://www.startupjobs.cz/startup/scrumworks-s-r-o
*
* @see \Rector\Nette\Tests\Rector\ArrayDimFetch\ChangeControlArrayAccessToAnnotatedControlVariableRector\ChangeControlArrayAccessToAnnotatedControlVariableRectorTest
*/
final class ChangeControlArrayAccessToAnnotatedControlVariableRector extends AbstractRector
{
/**
* @var ControlDimFetchAnalyzer
*/
private $controlDimFetchAnalyzer;
/**
* @var NetteControlNaming
*/
private $netteControlNaming;
/**
* @var VarAnnotationManipulator
*/
private $varAnnotationManipulator;
/**
* @var MethodNamesByInputNamesResolver
*/
private $methodNamesByInputNamesResolver;
/**
* @var string[]
*/
private $alreadyInitializedAssignsClassMethodObjectHashes = [];
public function __construct(
VarAnnotationManipulator $varAnnotationManipulator,
ControlDimFetchAnalyzer $controlDimFetchAnalyzer,
NetteControlNaming $netteControlNaming,
MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver
) {
$this->controlDimFetchAnalyzer = $controlDimFetchAnalyzer;
$this->netteControlNaming = $netteControlNaming;
$this->varAnnotationManipulator = $varAnnotationManipulator;
$this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change magic $this["some_component"] to variable assign with @var annotation', [
new CodeSample(
<<<'PHP'
use Nette\Application\UI\Presenter;
use Nette\Application\UI\Form;
final class SomePresenter extends Presenter
{
public function run()
{
if ($this['some_form']->isSubmitted()) {
}
}
protected function createComponentSomeForm()
{
return new Form();
}
}
PHP
,
<<<'PHP'
use Nette\Application\UI\Presenter;
use Nette\Application\UI\Form;
final class SomePresenter extends Presenter
{
public function run()
{
/** @var \Nette\Application\UI\Form $someForm */
$someForm = $this['some_form'];
if ($someForm->isSubmitted()) {
}
}
protected function createComponentSomeForm()
{
return new Form();
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ArrayDimFetch::class];
}
/**
* @param ArrayDimFetch $node
*/
public function refactor(Node $node): ?Node
{
$controlName = $this->controlDimFetchAnalyzer->matchName($node);
if ($controlName === null) {
return null;
}
$variableName = $this->netteControlNaming->createVariableName($controlName);
$controlObjectType = $this->resolveControlType($node, $controlName);
$this->addAssignExpressionForFirstCase($variableName, $node, $controlObjectType);
return new Variable($variableName);
}
private function createAssignExpression(string $variableName, ArrayDimFetch $arrayDimFetch): Expression
{
$variable = new Variable($variableName);
$assignedDimFetch = clone $arrayDimFetch;
$assign = new Assign($variable, $assignedDimFetch);
return new Expression($assign);
}
private function addAssignExpressionForFirstCase(
string $variableName,
ArrayDimFetch $arrayDimFetch,
ObjectType $controlObjectType
): void {
/** @var ClassMethod|null $classMethod */
$classMethod = $arrayDimFetch->getAttribute(AttributeKey::METHOD_NODE);
if ($classMethod !== null) {
$classMethodObjectHash = spl_object_hash($classMethod);
if (in_array($classMethodObjectHash, $this->alreadyInitializedAssignsClassMethodObjectHashes, true)) {
return;
}
$this->alreadyInitializedAssignsClassMethodObjectHashes[] = $classMethodObjectHash;
}
$assignExpression = $this->createAssignExpression($variableName, $arrayDimFetch);
$this->varAnnotationManipulator->decorateNodeWithInlineVarType(
$assignExpression,
$controlObjectType,
$variableName
);
$currentStatement = $arrayDimFetch->getAttribute(AttributeKey::CURRENT_STATEMENT);
$this->addNodeBeforeNode($assignExpression, $currentStatement);
}
private function resolveControlType(ArrayDimFetch $arrayDimFetch, string $controlName): ObjectType
{
$controlTypes = $this->methodNamesByInputNamesResolver->resolveExpr($arrayDimFetch);
if ($controlTypes === []) {
throw new NotImplementedYetException();
}
if (! isset($controlTypes[$controlName])) {
throw new ShouldNotHappenException();
}
$controlType = $controlTypes[$controlName];
return new ObjectType($controlType);
}
}

View File

@ -8,15 +8,15 @@ use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Expression;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareFullyQualifiedIdentifierTypeNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use PHPStan\Type\ObjectType;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Core\Util\StaticRectorStrings;
use Rector\Nette\DocBlock\VarAnnotationManipulator;
use Rector\Nette\Naming\NetteControlNaming;
use Rector\Nette\NodeAnalyzer\ControlDimFetchAnalyzer;
use Rector\Nette\NodeResolver\FormVariableInputNameTypeResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -32,9 +32,31 @@ final class ChangeFormArrayAccessToAnnotatedControlVariableRector extends Abstra
*/
private $formVariableInputNameTypeResolver;
public function __construct(FormVariableInputNameTypeResolver $formVariableInputNameTypeResolver)
{
/**
* @var ControlDimFetchAnalyzer
*/
private $controlDimFetchAnalyzer;
/**
* @var NetteControlNaming
*/
private $netteControlNaming;
/**
* @var VarAnnotationManipulator
*/
private $varAnnotationManipulator;
public function __construct(
FormVariableInputNameTypeResolver $formVariableInputNameTypeResolver,
ControlDimFetchAnalyzer $controlDimFetchAnalyzer,
NetteControlNaming $netteControlNaming,
VarAnnotationManipulator $varAnnotationManipulator
) {
$this->formVariableInputNameTypeResolver = $formVariableInputNameTypeResolver;
$this->controlDimFetchAnalyzer = $controlDimFetchAnalyzer;
$this->netteControlNaming = $netteControlNaming;
$this->varAnnotationManipulator = $varAnnotationManipulator;
}
public function getDefinition(): RectorDefinition
@ -90,8 +112,8 @@ PHP
*/
public function refactor(Node $node): ?Node
{
$dimString = $this->matchNetteFormArrayDimString($node);
if ($dimString === null) {
$inputName = $this->controlDimFetchAnalyzer->matchName($node);
if ($inputName === null) {
return null;
}
@ -99,8 +121,7 @@ PHP
return null;
}
$inputName = $this->getValue($dimString);
$controlVariableName = $this->createControlVariableName($inputName);
$controlVariableName = $this->netteControlNaming->createVariableName($inputName);
$controlVariableToFormDimFetchAssign = new Assign(new Variable($controlVariableName), clone $node);
$assignExpression = new Expression($controlVariableToFormDimFetchAssign);
@ -114,52 +135,23 @@ PHP
$inputName
);
$this->addVarTag($controlVariableToFormDimFetchAssign, $assignExpression, $controlVariableName, $controlType);
$formVariableName = $this->getName($formVariable);
if ($formVariableName === null) {
throw new ShouldNotHappenException();
}
$controlObjectType = new ObjectType($controlType);
$this->varAnnotationManipulator->decorateNodeWithInlineVarType(
$assignExpression,
$controlObjectType,
$controlVariableName
);
$this->addNodeBeforeNode($assignExpression, $node);
return new Variable($controlVariableName);
}
private function addVarTag(
Assign $assign,
Expression $assignExpression,
string $controlName,
string $controlType
): PhpDocInfo {
$phpDocInfo = $this->phpDocInfoFactory->createEmpty($assignExpression);
$varTagValueNode = new VarTagValueNode(
new AttributeAwareFullyQualifiedIdentifierTypeNode($controlType),
'$' . $controlName,
''
);
$phpDocInfo->addTagValueNode($varTagValueNode);
$phpDocInfo->makeSingleLined();
$assign->setAttribute(AttributeKey::PHP_DOC_INFO, $phpDocInfo);
return $phpDocInfo;
}
private function matchNetteFormArrayDimString(ArrayDimFetch $arrayDimFetch): ?String_
{
if (! $arrayDimFetch->var instanceof Variable) {
return null;
}
if (! $this->isObjectTypeOrNullableObjectType($arrayDimFetch->var, 'Nette\ComponentModel\IComponent')) {
return null;
}
if (! $arrayDimFetch->dim instanceof String_) {
return null;
}
return $arrayDimFetch->dim;
}
private function isBeingAssignedOrInitialized(ArrayDimFetch $arrayDimFetch): bool
{
$parent = $arrayDimFetch->getAttribute(AttributeKey::PARENT_NODE);
@ -173,9 +165,4 @@ PHP
return $parent->expr === $arrayDimFetch;
}
private function createControlVariableName(string $inputName): string
{
return StaticRectorStrings::underscoreToPascalCase($inputName) . 'Control';
}
}

View File

@ -11,20 +11,17 @@ use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Expression;
use PHPStan\Analyser\Scope;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareFullyQualifiedIdentifierTypeNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Nette\DocBlock\VarAnnotationManipulator;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
@ -34,6 +31,16 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
*/
final class MakeGetComponentAssignAnnotatedRector extends AbstractRector
{
/**
* @var VarAnnotationManipulator
*/
private $varAnnotationManipulator;
public function __construct(VarAnnotationManipulator $varAnnotationManipulator)
{
$this->varAnnotationManipulator = $varAnnotationManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Add doc type for magic $control->getComponent(...) assign', [
@ -125,48 +132,15 @@ PHP
}
$controlType = $this->resolveControlType($node);
if ($controlType instanceof MixedType) {
if (! $controlType instanceof TypeWithClassName) {
return null;
}
$phpDocInfo = $this->resolvePhpDocInfo($node);
if ($phpDocInfo->getVarTagValue() !== null) {
return null;
}
$attributeAwareFullyQualifiedIdentifierTypeNode = new AttributeAwareFullyQualifiedIdentifierTypeNode(
$controlType->getClassName()
);
$varTagValueNode = new VarTagValueNode(
$attributeAwareFullyQualifiedIdentifierTypeNode,
'$' . $variableName,
''
);
$phpDocInfo->addTagValueNode($varTagValueNode);
$this->varAnnotationManipulator->decorateNodeWithInlineVarType($node, $controlType, $variableName);
return $node;
}
private function resolvePhpDocInfo(Assign $assign): PhpDocInfo
{
$currentStmt = $assign->getAttribute(AttributeKey::CURRENT_STATEMENT);
if ($currentStmt instanceof Expression) {
/** @var PhpDocInfo|null $phpDocInfo */
$phpDocInfo = $currentStmt->getAttribute(AttributeKey::PHP_DOC_INFO);
} else {
/** @var PhpDocInfo|null $phpDocInfo */
$phpDocInfo = $assign->getAttribute(AttributeKey::PHP_DOC_INFO);
}
if ($phpDocInfo === null) {
$phpDocInfo = $this->phpDocInfoFactory->createEmpty($assign);
}
$phpDocInfo->makeSingleLined();
return $phpDocInfo;
}
private function resolveCreateComponentMethodCallReturnType(MethodCall $methodCall): Type
{
/** @var Scope|null $scope */

View File

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

View File

@ -0,0 +1,47 @@
<?php
namespace Rector\Nette\Tests\Rector\ArrayDimFetch\ChangeControlArrayAccessToAnnotatedControlVariableRector\Fixture;
use Nette\Application\UI\Presenter;
use Nette\Application\UI\Form;
final class SomePresenter extends Presenter
{
public function run()
{
if ($this['some_form']->isSubmitted()) {
}
}
protected function createComponentSomeForm()
{
return new Form();
}
}
?>
-----
<?php
namespace Rector\Nette\Tests\Rector\ArrayDimFetch\ChangeControlArrayAccessToAnnotatedControlVariableRector\Fixture;
use Nette\Application\UI\Presenter;
use Nette\Application\UI\Form;
final class SomePresenter extends Presenter
{
public function run()
{
/** @var \Nette\Application\UI\Form $someForm */
$someForm = $this['some_form'];
if ($someForm->isSubmitted()) {
}
}
protected function createComponentSomeForm()
{
return new Form();
}
}
?>

View File

@ -0,0 +1,49 @@
<?php
namespace Rector\Nette\Tests\Rector\ArrayDimFetch\ChangeControlArrayAccessToAnnotatedControlVariableRector\Fixture;
use Nette\Application\UI\Presenter;
use Nette\Application\UI\Form;
final class SomeOtherPresenter extends Presenter
{
public function run()
{
if ($this['some_form']->isSubmitted()) {
return $this['some_form']->getValues();
}
}
protected function createComponentSomeForm()
{
return new Form();
}
}
?>
-----
<?php
namespace Rector\Nette\Tests\Rector\ArrayDimFetch\ChangeControlArrayAccessToAnnotatedControlVariableRector\Fixture;
use Nette\Application\UI\Presenter;
use Nette\Application\UI\Form;
final class SomeOtherPresenter extends Presenter
{
public function run()
{
/** @var \Nette\Application\UI\Form $someForm */
$someForm = $this['some_form'];
if ($someForm->isSubmitted()) {
return $someForm->getValues();
}
}
protected function createComponentSomeForm()
{
return new Form();
}
}
?>

View File

@ -6,6 +6,9 @@ use PhpParser\NodeTraverser;
class SkipAnonymousClass
{
/**
* @api
*/
public function run()
{
$anonymousClass = new class() extends NodeTraverser

View File

@ -11,11 +11,11 @@ use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use Rector\Core\Naming\NamespaceMatcher;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\ConfiguredCodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Core\ValueObject\RenamedNamespaceValueObject;
use Rector\Naming\NamespaceMatcher;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Rector\Core\Rector;
use Nette\Utils\Strings;
use PhpParser\BuilderFactory;
use PhpParser\Node;
use PhpParser\Node\Expr;
@ -13,11 +12,11 @@ use PhpParser\Node\Expr\Cast\Bool_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Analyser\Scope;
use Rector\AnonymousClass\NodeAnalyzer\ClassNodeAnalyzer;
use Rector\Core\Configuration\Option;
use Rector\Core\Contract\Rector\PhpRectorInterface;
use Rector\Core\Exclusion\ExclusionManager;
@ -102,6 +101,11 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
*/
private $currentRectorProvider;
/**
* @var ClassNodeAnalyzer
*/
private $classNodeAnalyzer;
/**
* @required
*/
@ -113,7 +117,8 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
DocBlockManipulator $docBlockManipulator,
StaticTypeMapper $staticTypeMapper,
ParameterProvider $parameterProvider,
CurrentRectorProvider $currentRectorProvider
CurrentRectorProvider $currentRectorProvider,
ClassNodeAnalyzer $classNodeAnalyzer
): void {
$this->symfonyStyle = $symfonyStyle;
$this->phpVersionProvider = $phpVersionProvider;
@ -123,6 +128,7 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
$this->staticTypeMapper = $staticTypeMapper;
$this->parameterProvider = $parameterProvider;
$this->currentRectorProvider = $currentRectorProvider;
$this->classNodeAnalyzer = $classNodeAnalyzer;
}
public function beforeTraverse(array $nodes)
@ -215,13 +221,7 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
protected function isAnonymousClass(Node $node): bool
{
if (! $node instanceof Class_) {
return false;
}
$className = $this->nodeNameResolver->getName($node);
return $className === null || Strings::contains($className, 'AnonymousClass');
return $this->classNodeAnalyzer->isAnonymousClass($node);
}
protected function createCountedValueName(string $countedValueName, ?Scope $scope): string

View File

@ -14,7 +14,6 @@ use PhpParser\Node\Stmt\Return_;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\NodeTypeResolver\TypeAnalyzer\ArrayTypeAnalyzer;
@ -90,25 +89,6 @@ trait NodeTypeResolverTrait
return $this->nodeTypeResolver->isObjectType($node, $type);
}
/**
* @param ObjectType|string $desiredType
*/
protected function isObjectTypeOrNullableObjectType(Node $node, $desiredType): bool
{
if ($this->isNullableObjectType($node)) {
/** @var UnionType $nodeType */
$nodeType = $this->nodeTypeResolver->resolve($node);
$nodeType = $this->typeUnwrapper->unwrapNullableType($nodeType);
if ($nodeType instanceof TypeWithClassName) {
$desiredTypeString = $desiredType instanceof ObjectType ? $desiredType->getClassName() : $desiredType;
return is_a($nodeType->getClassName(), $desiredTypeString, true);
}
}
return $this->nodeTypeResolver->isObjectType($node, $desiredType);
}
/**
* @param string[]|ObjectType[] $requiredTypes
*/