[Naming] Rename foreach value variable to match method return type (#4130)

Co-authored-by: rector-bot <tomas@getrector.org>
This commit is contained in:
dobryy 2020-09-09 10:52:43 +02:00 committed by GitHub
parent 7a498e328e
commit f69ea0e023
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 985 additions and 154 deletions

View File

@ -8,6 +8,7 @@ use Rector\Naming\Rector\ClassMethod\MakeGetterClassMethodNameStartWithGetRector
use Rector\Naming\Rector\ClassMethod\MakeIsserClassMethodNameStartWithIsRector;
use Rector\Naming\Rector\ClassMethod\RenameParamToMatchTypeRector;
use Rector\Naming\Rector\ClassMethod\RenameVariableToMatchNewTypeRector;
use Rector\Naming\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
@ -18,4 +19,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(RenameVariableToMatchMethodCallReturnTypeRector::class);
$services->set(MakeGetterClassMethodNameStartWithGetRector::class);
$services->set(MakeIsserClassMethodNameStartWithIsRector::class);
$services->set(RenameForeachValueVariableToMatchMethodCallReturnTypeRector::class);
};

View File

@ -232,8 +232,8 @@ final class PhpDocInfoPrinter
): string {
// skip removed nodes
$positionJumpSet = [];
foreach ($this->getRemovedNodesPositions() as $removedTokensPosition) {
$positionJumpSet[$removedTokensPosition->getStart()] = $removedTokensPosition->getEnd();
foreach ($this->getRemovedNodesPositions() as $startAndEnd) {
$positionJumpSet[$startAndEnd->getStart()] = $startAndEnd->getEnd();
}
// include also space before, in case of inlined docs

View File

@ -50,8 +50,8 @@ final class DependencyResolver
}
$dependencies = [];
foreach ($this->phpStanDependencyResolver->resolveDependencies($node, $scope) as $dependencyReflection) {
$dependencyFile = $dependencyReflection->getFileName();
foreach ($this->phpStanDependencyResolver->resolveDependencies($node, $scope) as $nodeDependency) {
$dependencyFile = $nodeDependency->getFileName();
if (! $dependencyFile) {
continue;
}

View File

@ -53,13 +53,13 @@ final class CheckstyleOutputFormatter implements OutputFormatterInterface
$message = sprintf('<file name="%s">', $this->escape($fileDiff->getRelativeFilePath()));
$this->symfonyStyle->writeln($message);
foreach ($fileDiff->getRectorChanges() as $rectorChange) {
$message = $rectorChange->getRectorDefinitionsDescription() . ' (Reported by: ' . $rectorChange->getRectorClass() . ')';
foreach ($fileDiff->getRectorChanges() as $rectorWithFileAndLineChange) {
$message = $rectorWithFileAndLineChange->getRectorDefinitionsDescription() . ' (Reported by: ' . $rectorWithFileAndLineChange->getRectorClass() . ')';
$message = $this->escape($message);
$error = sprintf(
' <error line="%d" column="1" severity="error" message="%s" />',
$this->escape((string) $rectorChange->getLine()),
$this->escape((string) $rectorWithFileAndLineChange->getLine()),
$message
);
$this->symfonyStyle->writeln($error);
@ -73,8 +73,8 @@ final class CheckstyleOutputFormatter implements OutputFormatterInterface
if ($errorAndDiffCollector->getErrors() !== []) {
$this->symfonyStyle->writeln('<file>');
foreach ($errorAndDiffCollector->getErrors() as $error) {
$escapedMessage = $this->escape($error->getMessage());
foreach ($errorAndDiffCollector->getErrors() as $rectorError) {
$escapedMessage = $this->escape($rectorError->getMessage());
$message = sprintf(' <error severity="error" message="%s" />', $escapedMessage);
$this->symfonyStyle->writeln($message);

View File

@ -247,8 +247,8 @@ final class NodeTypeResolver
return false;
}
foreach ($nodeType->getTypes() as $subtype) {
if ($subtype instanceof ObjectType) {
foreach ($nodeType->getTypes() as $type) {
if ($type instanceof ObjectType) {
return true;
}
}

View File

@ -175,8 +175,8 @@ PHP
$node = $methodCall;
foreach ($arrayItemsAndFluentClass->getFluentCalls() as $method => $arg) {
$args = $this->createArgs([$arg]);
foreach ($arrayItemsAndFluentClass->getFluentCalls() as $method => $expr) {
$args = $this->createArgs([$expr]);
$node = $this->createMethodCall($node, $method, $args);
}

View File

@ -262,9 +262,9 @@ PHP
ClassMethod $classMethod,
ReflectionMethod $reflectionMethod
): bool {
foreach ($reflectionMethod->getParameters() as $key => $parameter) {
foreach ($reflectionMethod->getParameters() as $key => $reflectionParameter) {
if (! isset($classMethod->params[$key])) {
if ($parameter->isDefaultValueAvailable()) {
if ($reflectionParameter->isDefaultValueAvailable()) {
continue;
}
return true;
@ -272,7 +272,7 @@ PHP
$methodParam = $classMethod->params[$key];
if ($this->areDefaultValuesDifferent($parameter, $methodParam)) {
if ($this->areDefaultValuesDifferent($reflectionParameter, $methodParam)) {
return true;
}
}

View File

@ -217,17 +217,17 @@ PHP
private function removeSetAndGetMethods(Class_ $class, array $removedPropertyNames): void
{
foreach ($removedPropertyNames as $removedPropertyName) {
foreach ($class->getMethods() as $method) {
if ($this->isName($method, 'set' . ucfirst($removedPropertyName))) {
$this->removeNode($method);
foreach ($class->getMethods() as $classMethod) {
if ($this->isName($classMethod, 'set' . ucfirst($removedPropertyName))) {
$this->removeNode($classMethod);
}
if ($this->isName($method, 'get' . ucfirst($removedPropertyName))) {
$this->removeNode($method);
if ($this->isName($classMethod, 'get' . ucfirst($removedPropertyName))) {
$this->removeNode($classMethod);
}
if ($this->isName($method, 'setTranslatableLocale')) {
$this->removeNode($method);
if ($this->isName($classMethod, 'setTranslatableLocale')) {
$this->removeNode($classMethod);
}
}
}

View File

@ -198,13 +198,13 @@ PHP
private function removeClassMethodsForProperties(Class_ $class, array $removedPropertyNames): void
{
foreach ($removedPropertyNames as $removedPropertyName) {
foreach ($class->getMethods() as $method) {
if ($this->isName($method, 'get' . ucfirst($removedPropertyName))) {
$this->removeNode($method);
foreach ($class->getMethods() as $classMethod) {
if ($this->isName($classMethod, 'get' . ucfirst($removedPropertyName))) {
$this->removeNode($classMethod);
}
if ($this->isName($method, 'set' . ucfirst($removedPropertyName))) {
$this->removeNode($method);
if ($this->isName($classMethod, 'set' . ucfirst($removedPropertyName))) {
$this->removeNode($classMethod);
}
}
}

View File

@ -93,9 +93,9 @@ final class CallTypeAnalyzer
$parameterTypes = [];
/** @var ParameterReflection $parameter */
foreach ($functionVariant->getParameters() as $parameter) {
$parameterTypes[] = $parameter->getType();
/** @var ParameterReflection $parameterReflection */
foreach ($functionVariant->getParameters() as $parameterReflection) {
$parameterTypes[] = $parameterReflection->getType();
}
return $parameterTypes;

View File

@ -13,6 +13,7 @@ use Rector\Core\RectorDefinition\ConfiguredCodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Generic\ValueObject\ParentDependency;
use Rector\Naming\Naming\PropertyNaming;
use Rector\Naming\ValueObject\ExpectedName;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Webmozart\Assert\Assert;
@ -100,9 +101,9 @@ CODE_SAMPLE
}
$propertyType = new ObjectType($parentDependency->getDependencyType());
/** @var string $propertyName */
/** @var ExpectedName $propertyName */
$propertyName = $this->propertyNaming->getExpectedNameFromType($propertyType);
$this->addConstructorDependencyToClass($node, $propertyType, $propertyName);
$this->addConstructorDependencyToClass($node, $propertyType, $propertyName->getName());
}
return $node;

View File

@ -120,25 +120,25 @@ PHP
Class_ $class,
PropertyAndClassMethodName $propertyAndClassMethodName
): Class_ {
foreach ($class->getMethods() as $method) {
if ($this->isName($method, $propertyAndClassMethodName->getClassMethodName())) {
$this->removeNodeFromStatements($class, $method);
foreach ($class->getMethods() as $classMethod) {
if ($this->isName($classMethod, $propertyAndClassMethodName->getClassMethodName())) {
$this->removeNodeFromStatements($class, $classMethod);
continue;
}
if (! $this->isNames($method, [MethodName::CONSTRUCT, '__clone', '__wakeup'])) {
if (! $this->isNames($classMethod, [MethodName::CONSTRUCT, '__clone', '__wakeup'])) {
continue;
}
if ($method->isPublic()) {
if ($classMethod->isPublic()) {
continue;
}
// remove non-public empty
if ($method->stmts === []) {
$this->removeNodeFromStatements($class, $method);
if ($classMethod->stmts === []) {
$this->removeNodeFromStatements($class, $classMethod);
} else {
$this->makePublic($method);
$this->makePublic($classMethod);
}
}

View File

@ -148,12 +148,12 @@ final class FluentChainMethodCallRootExtractor
}
$fullyQualifiedObjectType = new FullyQualifiedObjectType($className);
$variableName = $this->propertyNaming->getExpectedNameFromType($fullyQualifiedObjectType);
if ($variableName === null) {
$expectedName = $this->propertyNaming->getExpectedNameFromType($fullyQualifiedObjectType);
if ($expectedName === null) {
return null;
}
$variable = new Variable($variableName);
$variable = new Variable($expectedName->getName());
return new AssignAndRootExpr($methodCall->var, $methodCall->var, $variable);
}

View File

@ -138,8 +138,8 @@ PHP
}
if ($st instanceof UnionType) {
foreach ($st->getTypes() as $candidate) {
if ($candidate->equals($resourceType)) {
foreach ($st->getTypes() as $type) {
if ($type->equals($resourceType)) {
return true;
}
}

View File

@ -11,6 +11,7 @@ use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\Property;
@ -20,6 +21,7 @@ use PHPStan\Type\TypeWithClassName;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Naming\Naming\ConflictingNameResolver;
use Rector\Naming\Naming\OverridenExistingNamesResolver;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PHPStanStaticTypeMapper\Utils\TypeUnwrapper;
@ -54,18 +56,25 @@ final class BreakingVariableRenameGuard
*/
private $typeUnwrapper;
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
public function __construct(
BetterNodeFinder $betterNodeFinder,
ConflictingNameResolver $conflictingNameResolver,
NodeTypeResolver $nodeTypeResolver,
OverridenExistingNamesResolver $overridenExistingNamesResolver,
TypeUnwrapper $typeUnwrapper
TypeUnwrapper $typeUnwrapper,
NodeNameResolver $nodeNameResolver
) {
$this->betterNodeFinder = $betterNodeFinder;
$this->conflictingNameResolver = $conflictingNameResolver;
$this->overridenExistingNamesResolver = $overridenExistingNamesResolver;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->typeUnwrapper = $typeUnwrapper;
$this->nodeNameResolver = $nodeNameResolver;
}
/**
@ -102,6 +111,10 @@ final class BreakingVariableRenameGuard
return true;
}
if ($this->isUsedInForeachKeyValueVar($variable, $currentName)) {
return true;
}
return $this->isUsedInIfAndOtherBranches($variable, $currentName);
}
@ -179,6 +192,34 @@ final class BreakingVariableRenameGuard
return $this->betterNodeFinder->hasVariableOfName((array) $functionLike->uses, $expectedName);
}
private function isUsedInForeachKeyValueVar(Variable $variable, string $currentName): bool
{
$previousForeach = $this->betterNodeFinder->findFirstPreviousOfTypes($variable, [Foreach_::class]);
if ($previousForeach instanceof Foreach_) {
if ($previousForeach->keyVar === $variable) {
return false;
}
if ($previousForeach->valueVar === $variable) {
return false;
}
if ($this->nodeNameResolver->isName($previousForeach->valueVar, $currentName)) {
return true;
}
if ($previousForeach->keyVar === null) {
return false;
}
if ($this->nodeNameResolver->isName($previousForeach->keyVar, $currentName)) {
return true;
}
}
return false;
}
private function isUsedInIfAndOtherBranches(Variable $variable, string $currentVariableName): bool
{
// is in if branches?

View File

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Rector\Naming\Matcher;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\Function_;
use Rector\Naming\ValueObject\VariableAndCallAssign;
use Rector\Naming\ValueObject\VariableAndCallForeach;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
abstract class AbstractMatcher implements MatcherInterface
{
/**
* @var NodeNameResolver
*/
protected $nodeNameResolver;
public function __construct(NodeNameResolver $nodeNameResolver)
{
$this->nodeNameResolver = $nodeNameResolver;
}
/**
* @param Assign|Foreach_ $node
* @return VariableAndCallAssign|VariableAndCallForeach|null
*/
public function match(Node $node)
{
$call = $this->matchCall($node);
if ($call === null) {
return null;
}
$variableName = $this->getVariableName($node);
if ($variableName === null) {
return null;
}
$functionLike = $this->getFunctionLike($node);
if ($functionLike === null) {
return null;
}
$variable = $this->getVariable($node);
if ($node instanceof Foreach_) {
return new VariableAndCallForeach($variable, $call, $variableName, $functionLike);
}
if ($node instanceof Assign) {
return new VariableAndCallAssign($variable, $call, $node, $variableName, $functionLike);
}
return null;
}
/**
* @return FuncCall|StaticCall|MethodCall|null
*/
protected function matchCall(Node $node): ?Node
{
if ($node->expr instanceof MethodCall) {
return $node->expr;
}
if ($node->expr instanceof StaticCall) {
return $node->expr;
}
if ($node->expr instanceof FuncCall) {
return $node->expr;
}
return null;
}
/**
* @return ClassMethod|Function_|Closure|null
*/
protected function getFunctionLike(Node $node): ?FunctionLike
{
return $node->getAttribute(AttributeKey::CLOSURE_NODE) ??
$node->getAttribute(AttributeKey::METHOD_NODE) ??
$node->getAttribute(AttributeKey::FUNCTION_NODE);
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Rector\Naming\Matcher;
use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Foreach_;
final class ForeachMatcher extends AbstractMatcher
{
/**
* @param Foreach_ $node
*/
public function getVariableName(Node $node): ?string
{
if (! $node->valueVar instanceof Variable) {
return null;
}
return $this->nodeNameResolver->getName($node->valueVar);
}
/**
* @param Foreach_ $node
*/
public function getVariable(Node $node): Variable
{
/** @var Variable $variable */
$variable = $node->valueVar;
return $variable;
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Rector\Naming\Matcher;
use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use Rector\Naming\ValueObject\VariableAndCallAssign;
use Rector\Naming\ValueObject\VariableAndCallForeach;
interface MatcherInterface
{
public function getVariable(Node $node): Variable;
public function getVariableName(Node $node): ?string;
/**
* @return VariableAndCallAssign|VariableAndCallForeach
*/
public function match(Node $node);
}

View File

@ -6,81 +6,29 @@ namespace Rector\Naming\Matcher;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Rector\Naming\ValueObject\VariableAndCallAssign;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class VariableAndCallAssignMatcher
final class VariableAndCallAssignMatcher extends AbstractMatcher
{
/**
* @var NodeNameResolver
* @param Assign $node
*/
private $nodeNameResolver;
public function __construct(NodeNameResolver $nodeNameResolver)
public function getVariableName(Node $node): ?string
{
$this->nodeNameResolver = $nodeNameResolver;
}
public function match(Assign $assign): ?VariableAndCallAssign
{
$call = $this->matchCall($assign);
if ($call === null) {
if (! $node->var instanceof Variable) {
return null;
}
if (! $assign->var instanceof Variable) {
return null;
}
$variableName = $this->nodeNameResolver->getName($assign->var);
if ($variableName === null) {
return null;
}
$functionLike = $this->getFunctionLike($assign);
if ($functionLike === null) {
return null;
}
return new VariableAndCallAssign($assign->var, $call, $assign, $variableName, $functionLike);
return $this->nodeNameResolver->getName($node->var);
}
/**
* @return FuncCall|StaticCall|MethodCall|null
* @param Assign $node
*/
private function matchCall(Assign $assign): ?Node
public function getVariable(Node $node): Variable
{
if ($assign->expr instanceof MethodCall) {
return $assign->expr;
}
if ($assign->expr instanceof StaticCall) {
return $assign->expr;
}
if ($assign->expr instanceof FuncCall) {
return $assign->expr;
}
return null;
}
/**
* @return ClassMethod|Function_|Closure|null
*/
private function getFunctionLike(Node $node): ?FunctionLike
{
return $node->getAttribute(AttributeKey::CLOSURE_NODE) ??
$node->getAttribute(AttributeKey::METHOD_NODE) ??
$node->getAttribute(AttributeKey::FUNCTION_NODE);
/** @var Variable $variable */
$variable = $node->var;
return $variable;
}
}

View File

@ -14,9 +14,12 @@ use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -106,7 +109,12 @@ final class ExpectedNameResolver
}
$staticType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($param->type);
return $this->propertyNaming->getExpectedNameFromType($staticType);
$expectedName = $this->propertyNaming->getExpectedNameFromType($staticType);
if ($expectedName === null) {
return null;
}
return $expectedName->getName();
}
public function resolveForProperty(Property $property): ?string
@ -117,7 +125,12 @@ final class ExpectedNameResolver
return null;
}
return $this->propertyNaming->getExpectedNameFromType($phpDocInfo->getVarType());
$expectedName = $this->propertyNaming->getExpectedNameFromType($phpDocInfo->getVarType());
if ($expectedName === null) {
return null;
}
return $expectedName->getName();
}
public function resolveForAssignNonNew(Assign $assign): ?string
@ -159,7 +172,12 @@ final class ExpectedNameResolver
$fullyQualifiedObjectType = new FullyQualifiedObjectType($className);
return $this->propertyNaming->getExpectedNameFromType($fullyQualifiedObjectType);
$expectedName = $this->propertyNaming->getExpectedNameFromType($fullyQualifiedObjectType);
if ($expectedName === null) {
return null;
}
return $expectedName->getName();
}
/**
@ -187,8 +205,9 @@ final class ExpectedNameResolver
}
$expectedName = $this->propertyNaming->getExpectedNameFromType($returnedType);
if ($expectedName !== null) {
return $expectedName;
return $expectedName->getName();
}
// call with args can return different value, so skip there if not sure about the type
@ -196,13 +215,57 @@ final class ExpectedNameResolver
return null;
}
// @see https://regex101.com/r/hnU5pm/2/
$matches = Strings::match($name, '#^get([A-Z].+)#');
if ($matches === null) {
$expectedNameFromMethodName = $this->propertyNaming->getExpectedNameFromMethodName($name);
if ($expectedNameFromMethodName !== null) {
return $expectedNameFromMethodName->getName();
}
return null;
}
/**
* @param MethodCall|StaticCall|FuncCall $expr
*/
public function resolveForForeach(Expr $expr): ?string
{
if ($this->isDynamicNameCall($expr)) {
return null;
}
return lcfirst($matches[1]);
$name = $this->nodeNameResolver->getName($expr->name);
if ($name === null) {
return null;
}
$returnedType = $this->nodeTypeResolver->getStaticType($expr);
if ($returnedType->isIterable()->no()) {
return null;
}
if ($returnedType instanceof ArrayType) {
$returnedType = $this->resolveReturnTypeFromArrayType($expr, $returnedType);
if ($returnedType === null) {
return null;
}
}
$expectedNameFromType = $this->propertyNaming->getExpectedNameFromType($returnedType);
if ($expectedNameFromType !== null) {
return $expectedNameFromType->getSingularized();
}
$expectedNameFromMethodName = $this->propertyNaming->getExpectedNameFromMethodName($name);
if ($expectedNameFromMethodName === null) {
return null;
}
if ($expectedNameFromMethodName->isSingular()) {
return null;
}
return $expectedNameFromMethodName->getSingularized();
}
/**
@ -230,4 +293,17 @@ final class ExpectedNameResolver
return $expr->name instanceof FuncCall;
}
private function resolveReturnTypeFromArrayType(Expr $expr, ArrayType $arrayType): ?Type
{
if (! $expr->getAttribute(AttributeKey::PARENT_NODE) instanceof Foreach_) {
return null;
}
if (! $arrayType->getItemType() instanceof ObjectType) {
return null;
}
return $arrayType->getItemType();
}
}

View File

@ -4,12 +4,14 @@ declare(strict_types=1);
namespace Rector\Naming\Naming;
use Doctrine\Inflector\Inflector;
use Nette\Utils\Strings;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StaticType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\Naming\ValueObject\ExpectedName;
use Rector\NetteKdyby\Naming\VariableNaming;
use Rector\PHPStan\Type\SelfObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
@ -37,12 +39,31 @@ final class PropertyNaming
*/
private $typeUnwrapper;
public function __construct(TypeUnwrapper $typeUnwrapper)
/**
* @var Inflector
*/
private $inflector;
public function __construct(TypeUnwrapper $typeUnwrapper, Inflector $inflector)
{
$this->typeUnwrapper = $typeUnwrapper;
$this->inflector = $inflector;
}
public function getExpectedNameFromType(Type $type): ?string
public function getExpectedNameFromMethodName(string $methodName): ?ExpectedName
{
// @see https://regex101.com/r/hnU5pm/2/
$matches = Strings::match($methodName, '#^get([A-Z].+)#');
if ($matches === null) {
return null;
}
$originalName = lcfirst($matches[1]);
return new ExpectedName($originalName, $this->inflector->singularize($originalName));
}
public function getExpectedNameFromType(Type $type): ?ExpectedName
{
if ($type instanceof UnionType) {
$type = $this->typeUnwrapper->unwrapNullableType($type);
@ -81,7 +102,8 @@ final class PropertyNaming
$shortClassName = $this->normalizeUpperCase($shortClassName);
// prolong too short generic names with one namespace up
return $this->prolongIfTooShort($shortClassName, $className);
$originalName = $this->prolongIfTooShort($shortClassName, $className);
return new ExpectedName($originalName, $this->inflector->singularize($originalName));
}
/**

View File

@ -146,6 +146,7 @@ PHP
*/
public function refactor(Node $node): ?Node
{
/** @var VariableAndCallAssign|null $variableAndCallAssign */
$variableAndCallAssign = $this->variableAndCallAssignMatcher->match($node);
if ($variableAndCallAssign === null) {
return null;
@ -189,6 +190,7 @@ PHP
private function renameVariable(VariableAndCallAssign $variableAndCallAssign, string $expectedName): void
{
// TODO: Remove in next PR, implemented in VariableRenamer::renameVariableIfMatchesName()
$this->varTagValueNodeRenamer->renameAssignVarTagVariableName(
$variableAndCallAssign->getAssign(),
$variableAndCallAssign->getVariableName(),

View File

@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace Rector\Naming\Rector\Foreach_;
use PhpParser\Node;
use PhpParser\Node\Stmt\Foreach_;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Naming\Guard\BreakingVariableRenameGuard;
use Rector\Naming\Matcher\ForeachMatcher;
use Rector\Naming\Naming\ExpectedNameResolver;
use Rector\Naming\NamingConvention\NamingConventionAnalyzer;
use Rector\Naming\ValueObject\VariableAndCallForeach;
use Rector\Naming\VariableRenamer;
/**
* @see \Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\RenameForeachValueVariableToMatchMethodCallReturnTypeRectorTest
*/
final class RenameForeachValueVariableToMatchMethodCallReturnTypeRector extends AbstractRector
{
/**
* @var ExpectedNameResolver
*/
private $expectedNameResolver;
/**
* @var VariableRenamer
*/
private $variableRenamer;
/**
* @var ForeachMatcher
*/
private $varValueAndCallForeachMatcher;
/**
* @var BreakingVariableRenameGuard
*/
private $breakingVariableRenameGuard;
/**
* @var NamingConventionAnalyzer
*/
private $namingConventionAnalyzer;
public function __construct(
BreakingVariableRenameGuard $breakingVariableRenameGuard,
ExpectedNameResolver $expectedNameResolver,
NamingConventionAnalyzer $namingConventionAnalyzer,
VariableRenamer $variableRenamer,
ForeachMatcher $foreachMatcher
) {
$this->expectedNameResolver = $expectedNameResolver;
$this->variableRenamer = $variableRenamer;
$this->breakingVariableRenameGuard = $breakingVariableRenameGuard;
$this->namingConventionAnalyzer = $namingConventionAnalyzer;
$this->varValueAndCallForeachMatcher = $foreachMatcher;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Renames value variable name in foreach loop to match method type', [
new CodeSample(
<<<'PHP'
class SomeClass
{
public function run()
{
$array = [];
foreach ($object->getMethods() as $property) {
$array[] = $property;
}
}
}
PHP
,
<<<'PHP'
class SomeClass
{
public function run()
{
$array = [];
foreach ($object->getMethods() as $method) {
$array[] = $method;
}
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Foreach_::class];
}
/**
* @param Foreach_ $node
*/
public function refactor(Node $node): ?Node
{
/** @var VariableAndCallForeach|null $variableAndCallAssign */
$variableAndCallAssign = $this->varValueAndCallForeachMatcher->match($node);
if ($variableAndCallAssign === null) {
return null;
}
$expectedName = $this->expectedNameResolver->resolveForForeach($variableAndCallAssign->getCall());
if ($expectedName === null || $this->isName($variableAndCallAssign->getVariable(), $expectedName)) {
return null;
}
if ($this->shouldSkip($variableAndCallAssign, $expectedName)) {
return null;
}
$this->renameVariable($variableAndCallAssign, $expectedName);
return $node;
}
private function shouldSkip(VariableAndCallForeach $variableAndCallForeach, string $expectedName): bool
{
if ($this->namingConventionAnalyzer->isCallMatchingVariableName(
$variableAndCallForeach->getCall(),
$variableAndCallForeach->getVariableName(),
$expectedName
)) {
return true;
}
return $this->breakingVariableRenameGuard->shouldSkipVariable(
$variableAndCallForeach->getVariableName(),
$expectedName,
$variableAndCallForeach->getFunctionLike(),
$variableAndCallForeach->getVariable()
);
}
private function renameVariable(VariableAndCallForeach $variableAndCallForeach, string $expectedName): void
{
$this->variableRenamer->renameVariableInFunctionLike(
$variableAndCallForeach->getFunctionLike(),
null,
$variableAndCallForeach->getVariableName(),
$expectedName
);
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\Naming\ValueObject;
final class ExpectedName
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $singularized;
public function __construct(string $name, string $singularized)
{
$this->name = $name;
$this->singularized = $singularized;
}
public function getName(): string
{
return $this->name;
}
public function getSingularized(): string
{
return $this->singularized;
}
public function isSingular(): bool
{
return $this->name === $this->singularized;
}
}

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Rector\Naming\ValueObject;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
final class VariableAndCallForeach
{
/**
* @var string
*/
private $variableName;
/**
* @var Variable
*/
private $variable;
/**
* @var FuncCall|MethodCall|StaticCall
*/
private $call;
/**
* @var ClassMethod|Function_|Closure
*/
private $functionLike;
/**
* @param FuncCall|StaticCall|MethodCall $expr
* @param ClassMethod|Function_|Closure $functionLike
*/
public function __construct(Variable $variable, Expr $expr, string $variableName, FunctionLike $functionLike)
{
$this->variable = $variable;
$this->call = $expr;
$this->variableName = $variableName;
$this->functionLike = $functionLike;
}
public function getVariable(): Variable
{
return $this->variable;
}
/**
* @return FuncCall|StaticCall|MethodCall
*/
public function getCall(): Expr
{
return $this->call;
}
public function getVariableName(): string
{
return $this->variableName;
}
/**
* @return ClassMethod|Function_|Closure
*/
public function getFunctionLike(): FunctionLike
{
return $this->functionLike;
}
}

View File

@ -77,6 +77,8 @@ final class VariableRenamer
return null;
}
// TODO: Remove in next PR (with above param check?),
// TODO: Should be implemented in BreakingVariableRenameGuard::shouldSkipParam()
if ($this->isParamInParentFunction($node)) {
return null;
}

View File

@ -0,0 +1,53 @@
<?php
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Fixture;
use Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Source\Method;
class SomeClass
{
public function run()
{
$array = [];
foreach ($this->getMethods() as $property) {
$array[] = $property;
}
}
/**
* @return Method[]
*/
public function getMethods(): array
{
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Fixture;
use Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Source\Method;
class SomeClass
{
public function run()
{
$array = [];
foreach ($this->getMethods() as $method) {
$array[] = $method;
}
}
/**
* @return Method[]
*/
public function getMethods(): array
{
}
}
?>

View File

@ -0,0 +1,69 @@
<?php
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Fixture;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Trait_;
class ForeachPreferParentStmtVariableName
{
public function run()
{
$searchInNodes = [];
foreach ($this->getUsedTraits() as $trait) {
$trait = $this->findTrait($trait);
if ($trait === null) {
continue;
}
$searchInNodes[] = $trait;
}
}
/**
* @return Name[]
*/
public function getUsedTraits(): array
{
}
public function findTrait(string $name): Trait_
{
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Fixture;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Trait_;
class ForeachPreferParentStmtVariableName
{
public function run()
{
$searchInNodes = [];
foreach ($this->getUsedTraits() as $name) {
$name = $this->findTrait($name);
if ($name === null) {
continue;
}
$searchInNodes[] = $name;
}
}
/**
* @return Name[]
*/
public function getUsedTraits(): array
{
}
public function findTrait(string $name): Trait_
{
}
}
?>

View File

@ -0,0 +1,47 @@
<?php
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Fixture;
use Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Source\NodeDependencies;
class IterableTypeWithSingularizationClass
{
public function run()
{
$array = [];
foreach ($this->resolveDependencies() as $method) {
$array[] = $method;
}
}
public function resolveDependencies(): NodeDependencies
{
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Fixture;
use Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Source\NodeDependencies;
class IterableTypeWithSingularizationClass
{
public function run()
{
$array = [];
foreach ($this->resolveDependencies() as $nodeDependency) {
$array[] = $nodeDependency;
}
}
public function resolveDependencies(): NodeDependencies
{
}
}
?>

View File

@ -0,0 +1,55 @@
<?php
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Fixture;
use Symplify\SmartFileSystem\SmartFileInfo;
class PluralExcludedGetNamesClass
{
public function run()
{
$words = [];
foreach ($this->getPluralNames() as $word) {
$words[] = $word;
}
return $words;
}
/**
* @return SmartFileInfo[]
*/
public function getPluralNames(): array
{
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Fixture;
use Symplify\SmartFileSystem\SmartFileInfo;
class PluralExcludedGetNamesClass
{
public function run()
{
$words = [];
foreach ($this->getPluralNames() as $pluralName) {
$words[] = $pluralName;
}
return $words;
}
/**
* @return SmartFileInfo[]
*/
public function getPluralNames(): array
{
}
}
?>

View File

@ -0,0 +1,27 @@
<?php
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Fixture;
use Symplify\SmartFileSystem\SmartFileInfo;
class SkipSingularAfterGetNamesClass
{
public function run()
{
$words = [];
foreach ($this->getSingularName() as $word) {
$words[] = $word;
}
return $words;
}
/**
* @return SmartFileInfo[]
*/
public function getSingularName(): array
{
}
}
?>

View File

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

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Source;
final class Method
{
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Rector\Naming\Tests\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector\Source;
use IteratorAggregate;
/**
* @implements \IteratorAggregate<int, ReflectionWithFilename>
*/
class NodeDependencies implements IteratorAggregate
{
public function getIterator()
{
// TODO: Implement getIterator() method.
}
}

View File

@ -30,14 +30,14 @@ final class LetManipulator
public function isLetNeededInClass(Class_ $class): bool
{
foreach ($class->getMethods() as $method) {
foreach ($class->getMethods() as $classMethod) {
// new test
if ($this->nodeNameResolver->isName($method, 'test*')) {
if ($this->nodeNameResolver->isName($classMethod, 'test*')) {
continue;
}
$hasBeConstructedThrough = (bool) $this->betterNodeFinder->find(
(array) $method->stmts,
(array) $classMethod->stmts,
function (Node $node): ?bool {
if (! $node instanceof MethodCall) {
return null;

View File

@ -128,14 +128,14 @@ final class NonVariableToVariableOnFunctionCallRector extends AbstractRector
return [];
}
/** @var ParameterReflection $parameter */
foreach ($parametersAcceptor->getParameters() as $key => $parameter) {
/** @var ParameterReflection $parameterReflection */
foreach ($parametersAcceptor->getParameters() as $key => $parameterReflection) {
// omitted optional parameter
if (! isset($node->args[$key])) {
continue;
}
if ($parameter->passedByReference()->no()) {
if ($parameterReflection->passedByReference()->no()) {
continue;
}

View File

@ -130,8 +130,8 @@ PHP
$propertyUsageByMethods = [];
foreach ($privatePropertyNames as $privatePropertyName) {
foreach ($class->getMethods() as $method) {
$hasProperty = (bool) $this->betterNodeFinder->findFirst($method, function (Node $node) use (
foreach ($class->getMethods() as $classMethod) {
$hasProperty = (bool) $this->betterNodeFinder->findFirst($classMethod, function (Node $node) use (
$privatePropertyName
): bool {
if (! $node instanceof PropertyFetch) {
@ -145,7 +145,7 @@ PHP
continue;
}
$isPropertyChangingInMultipleMethodCalls = $this->isPropertyChangingInMultipleMethodCalls($method,
$isPropertyChangingInMultipleMethodCalls = $this->isPropertyChangingInMultipleMethodCalls($classMethod,
$privatePropertyName);
if ($isPropertyChangingInMultipleMethodCalls) {
@ -153,7 +153,7 @@ PHP
}
/** @var string $classMethodName */
$classMethodName = $this->getName($method);
$classMethodName = $this->getName($classMethod);
$propertyUsageByMethods[$privatePropertyName][] = $classMethodName;
}
}

View File

@ -100,13 +100,13 @@ PHP
/** @var ReflectionMethod $constructorMethodReflection */
$constructorMethodReflection = $this->getNewNodeClassConstructorMethodReflection($node);
foreach ($constructorMethodReflection->getParameters() as $position => $parameterReflection) {
foreach ($constructorMethodReflection->getParameters() as $position => $reflectionParameter) {
// argument is already set
if (isset($node->args[$position])) {
continue;
}
$classToInstantiate = $this->resolveClassToInstantiateByParameterReflection($parameterReflection);
$classToInstantiate = $this->resolveClassToInstantiateByParameterReflection($reflectionParameter);
if ($classToInstantiate === null) {
continue;
}

View File

@ -38,10 +38,10 @@ final class NewValueObjectFactory
{
$reflectionClass = new ReflectionClass($valueObjectClass);
$propertyValues = [];
foreach ($reflectionClass->getProperties() as $propertyReflection) {
$propertyReflection->setAccessible(true);
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
$reflectionProperty->setAccessible(true);
$propertyValues[] = $propertyReflection->getValue($valueObject);
$propertyValues[] = $reflectionProperty->getValue($valueObject);
}
return $propertyValues;
}

View File

@ -250,8 +250,8 @@ PHP
}
$namesToArgs = [];
foreach ($constructorReflectionMethod->getParameters() as $parameterReflection) {
$namesToArgs[$parameterReflection->getName()] = $argNodes[$parameterReflection->getPosition()];
foreach ($constructorReflectionMethod->getParameters() as $reflectionParameter) {
$namesToArgs[$reflectionParameter->getName()] = $argNodes[$reflectionParameter->getPosition()];
}
return $namesToArgs;

View File

@ -176,15 +176,15 @@ PHP
FuncCall $funcCall
): ?Node {
$fullyQualifiedObjectType = new FullyQualifiedObjectType($argumentFuncCallToMethodCall->getClass());
$propertyName = $this->propertyNaming->getExpectedNameFromType($fullyQualifiedObjectType);
$expectedName = $this->propertyNaming->getExpectedNameFromType($fullyQualifiedObjectType);
if ($propertyName === null) {
if ($expectedName === null) {
throw new ShouldNotHappenException();
}
$this->addConstructorDependencyToClass($class, $fullyQualifiedObjectType, $propertyName);
$this->addConstructorDependencyToClass($class, $fullyQualifiedObjectType, $expectedName->getName());
$propertyFetchNode = $this->createPropertyFetch('this', $propertyName);
$propertyFetchNode = $this->createPropertyFetch('this', $expectedName->getName());
if (count($funcCall->args) === 0) {
if ($argumentFuncCallToMethodCall->getMethodIfNoArgs()) {

View File

@ -99,8 +99,8 @@ final class RemovedAndAddedFilesProcessor
private function processDeletedFiles(): void
{
foreach ($this->removedAndAddedFilesCollector->getRemovedFiles() as $smartFileInfo) {
$relativePath = $smartFileInfo->getRelativeFilePathFromDirectory(getcwd());
foreach ($this->removedAndAddedFilesCollector->getRemovedFiles() as $removedFile) {
$relativePath = $removedFile->getRelativeFilePathFromDirectory(getcwd());
if ($this->configuration->isDryRun()) {
$message = sprintf('File "%s" will be removed', $relativePath);
@ -108,7 +108,7 @@ final class RemovedAndAddedFilesProcessor
} else {
$message = sprintf('File "%s" was removed', $relativePath);
$this->symfonyStyle->warning($message);
$this->filesystem->remove($smartFileInfo->getRealPath());
$this->filesystem->remove($removedFile->getRealPath());
}
}
}

View File

@ -67,13 +67,13 @@ final class ClassConstManipulator
}
$searchInNodes = [$classLike];
foreach ($this->classManipulator->getUsedTraits($classLike) as $trait) {
$trait = $this->parsedNodeCollector->findTrait((string) $trait);
if ($trait === null) {
foreach ($this->classManipulator->getUsedTraits($classLike) as $name) {
$name = $this->parsedNodeCollector->findTrait((string) $name);
if ($name === null) {
continue;
}
$searchInNodes[] = $trait;
$searchInNodes[] = $name;
}
return $this->betterNodeFinder->find($searchInNodes, function (Node $node) use ($classConst): bool {

View File

@ -50,8 +50,8 @@ final class ClassManipulator
public function getUsedTraits(ClassLike $classLike): array
{
$usedTraits = [];
foreach ($classLike->getTraitUses() as $stmt) {
foreach ($stmt->traits as $trait) {
foreach ($classLike->getTraitUses() as $traitUse) {
foreach ($traitUse->traits as $trait) {
/** @var string $traitName */
$traitName = $this->nodeNameResolver->getName($trait);
$usedTraits[$traitName] = $trait;

View File

@ -172,8 +172,8 @@ final class AttributeAwareClassFactoryFactory
$phpDocParserNodeVariable = new Variable(self::NODE);
foreach ($constructorReflectionMethod->getParameters() as $parameter) {
$parameterName = $parameter->getName();
foreach ($constructorReflectionMethod->getParameters() as $reflectionParameter) {
$parameterName = $reflectionParameter->getName();
$new->args[] = new Arg(new PropertyFetch($phpDocParserNodeVariable, $parameterName));
}

View File

@ -44,8 +44,8 @@ final class ValidateFixtureSuffixCommand extends Command
{
$invalidFilePaths = [];
foreach ($this->getInvalidFixtureFileInfos() as $fixtureFileInfo) {
$invalidFilePaths[] = $fixtureFileInfo->getRelativeFilePathFromCwd();
foreach ($this->getInvalidFixtureFileInfos() as $invalidFixtureFileInfo) {
$invalidFilePaths[] = $invalidFixtureFileInfo->getRelativeFilePathFromCwd();
}
if (count($invalidFilePaths) > 0) {