diff --git a/ecs.yaml b/ecs.yaml index e6a48ac213e6..6fcdbf0271b9 100644 --- a/ecs.yaml +++ b/ecs.yaml @@ -24,30 +24,6 @@ services: - 'getNodeTypes' - 'refactor' - Symplify\CodingStandard\Sniffs\DependencyInjection\NoClassInstantiationSniff: - extra_allowed_classes: - - 'PHPStan\Type\*' - - '*Type' - - 'PHPStan\Analyser\Scope' - - 'PhpParser\NodeVisitor\NameResolver' - - 'PhpParser\Node\*' - - '*Data' - - '*Recipe' - - '*ValueObject' - - 'PhpParser\Comment' - - 'PhpParser\Lexer' - - 'PhpParser\Comment\Doc' - - 'PhpParser\NodeTraverser' - - 'Rector\Reporting\FileDiff' - - 'Rector\RectorDefinition\*' - - 'Rector\Application\Error' - - 'Rector\DependencyInjection\Loader\*' - - 'Symplify\PackageBuilder\*' - - 'Symfony\Component\Console\Input\*Input' - - 'PHPStan\Analyser\NameScope' - - 'PHPStan\Rules\RuleErrors\RuleError*' - - '*\XdebugHandler' - Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer: extra_skipped_classes: - 'PhpParser\PrettyPrinter\Standard' @@ -71,6 +47,9 @@ parameters: - 'src/Rector/AbstractRector.php' skip: + # rather useless + Symplify\CodingStandard\Sniffs\DependencyInjection\NoClassInstantiationSniff: ~ + PHP_CodeSniffer\Standards\PSR2\Sniffs\Methods\MethodDeclarationSniff.Underscore: ~ Symplify\CodingStandard\Sniffs\Architecture\DuplicatedClassShortNameSniff: ~ # skip temporary due to missing "import" feature in PhpStorm @@ -184,20 +163,12 @@ parameters: - 'packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php' - 'packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php' - Symplify\CodingStandard\Sniffs\DependencyInjection\NoClassInstantiationSniff: - # 3rd party api - - 'src/PhpParser/Node/Value/ValueResolver.php' - PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer: - 'packages/BetterPhpDocParser/tests/PhpDocInfo/PhpDocInfo/PhpDocInfoTest.php' # intentional "assertEquals()" - 'tests/PhpParser/Node/NodeFactoryTest.php' - '*TypeResolverTest.php' - Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer: - # buggy with PHP heredoc - - 'packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php' - Symplify\CodingStandard\Sniffs\Commenting\AnnotationTypeExistsSniff: - '*PhpDocNodeFactory.php' - '*AnnotationReader.php' diff --git a/packages/Architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php b/packages/Architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php index 13d59c01a29c..075707c74b80 100644 --- a/packages/Architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php +++ b/packages/Architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php @@ -169,6 +169,48 @@ public function refactor(Node $node): ?Node return $node; } + private function reset(): void + { + $this->propertyFetchToParams = []; + $this->propertyFetchToParamsToRemoveFromConstructor = []; + } + + private function collectPropertyFetchToParams(ClassMethod $classMethod): void + { + foreach ((array) $classMethod->stmts as $constructorStmt) { + $propertyToVariable = $this->resolveAssignPropertyToVariableOrNull($constructorStmt); + if ($propertyToVariable === null) { + continue; + } + + [$propertyFetchName, $variableName] = $propertyToVariable; + + $param = $this->classManipulator->findMethodParamByName($classMethod, $variableName); + if ($param === null) { + continue; + } + + // random type, we cannot autowire in action + if ($param->type === null) { + continue; + } + + $paramType = $this->getName($param->type); + if ($paramType === null) { + continue; + } + + if ($this->typeAnalyzer->isPhpReservedType($paramType)) { + continue; + } + + // it's a match + $this->propertyFetchToParams[$propertyFetchName] = $param; + } + + $this->propertyFetchToParamsToRemoveFromConstructor = $this->propertyFetchToParams; + } + private function changePropertyUsageToParameter(ClassMethod $classMethod, string $propertyName, Param $param): void { $currentlyAddedLocalVariables = []; @@ -209,46 +251,15 @@ private function changePropertyUsageToParameter(ClassMethod $classMethod, string } } - private function collectPropertyFetchToParams(ClassMethod $classMethod): void + private function removeUnusedPropertiesAndConstructorParams(Class_ $class, ClassMethod $classMethod): void { - foreach ((array) $classMethod->stmts as $constructorStmt) { - $propertyToVariable = $this->resolveAssignPropertyToVariableOrNull($constructorStmt); - if ($propertyToVariable === null) { - continue; - } - - [$propertyFetchName, $variableName] = $propertyToVariable; - - $param = $this->classManipulator->findMethodParamByName($classMethod, $variableName); - if ($param === null) { - continue; - } - - // random type, we cannot autowire in action - if ($param->type === null) { - continue; - } - - $paramType = $this->getName($param->type); - if ($paramType === null) { - continue; - } - - if ($this->typeAnalyzer->isPhpReservedType($paramType)) { - continue; - } - - // it's a match - $this->propertyFetchToParams[$propertyFetchName] = $param; + $this->removeAssignsFromConstructor($classMethod); + foreach ($this->propertyFetchToParamsToRemoveFromConstructor as $propertyFetchName => $param) { + $this->changePropertyUsageToParameter($classMethod, $propertyFetchName, $param); } - - $this->propertyFetchToParamsToRemoveFromConstructor = $this->propertyFetchToParams; - } - - private function reset(): void - { - $this->propertyFetchToParams = []; - $this->propertyFetchToParamsToRemoveFromConstructor = []; + $this->classMethodManipulator->removeUnusedParameters($classMethod); + $this->removeUnusedProperties($class); + $this->removeConstructIfEmpty($class, $classMethod); } /** @@ -286,17 +297,6 @@ private function resolveAssignPropertyToVariableOrNull(Node $node): ?array return [$propertyFetchName, $variableName]; } - private function removeUnusedPropertiesAndConstructorParams(Class_ $class, ClassMethod $classMethod): void - { - $this->removeAssignsFromConstructor($classMethod); - foreach ($this->propertyFetchToParamsToRemoveFromConstructor as $propertyFetchName => $param) { - $this->changePropertyUsageToParameter($classMethod, $propertyFetchName, $param); - } - $this->classMethodManipulator->removeUnusedParameters($classMethod); - $this->removeUnusedProperties($class); - $this->removeConstructIfEmpty($class, $classMethod); - } - private function removeAssignsFromConstructor(ClassMethod $classMethod): void { foreach ((array) $classMethod->stmts as $key => $constructorStmt) { diff --git a/packages/BetterPhpDocParser/src/AnnotationReader/NodeAnnotationReader.php b/packages/BetterPhpDocParser/src/AnnotationReader/NodeAnnotationReader.php index 25321c553476..c15fa846a688 100644 --- a/packages/BetterPhpDocParser/src/AnnotationReader/NodeAnnotationReader.php +++ b/packages/BetterPhpDocParser/src/AnnotationReader/NodeAnnotationReader.php @@ -87,6 +87,14 @@ public function readPropertyAnnotation(Property $property, string $annotationCla return $propertyAnnotation; } + private function createClassReflectionFromNode(Class_ $class): ReflectionClass + { + /** @var string $className */ + $className = $this->nameResolver->getName($class); + + return new ReflectionClass($className); + } + private function createPropertyReflectionFromPropertyNode(Property $property): ?ReflectionProperty { /** @var string $propertyName */ @@ -107,12 +115,4 @@ private function createPropertyReflectionFromPropertyNode(Property $property): ? return null; } } - - private function createClassReflectionFromNode(Class_ $class): ReflectionClass - { - /** @var string $className */ - $className = $this->nameResolver->getName($class); - - return new ReflectionClass($className); - } } diff --git a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php index 3815ee7c46c0..cbc44bbd2fc0 100644 --- a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php +++ b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php @@ -79,6 +79,17 @@ public function createFromNode(Node $node): PhpDocInfo return $phpDocInfo; } + private function createUniqueDocNodeHash(Node $node): string + { + $this->ensureNodeHasDocComment($node); + + $objectHash = spl_object_hash($node); + $docCommentHash = spl_object_hash($node->getDocComment()); + $docCommentContentHash = sha1($node->getDocComment()->getText()); + + return $objectHash . $docCommentHash . $docCommentContentHash; + } + /** * Needed for printing */ @@ -101,17 +112,6 @@ private function setPositionOfLastToken( return $attributeAwarePhpDocNode; } - private function createUniqueDocNodeHash(Node $node): string - { - $this->ensureNodeHasDocComment($node); - - $objectHash = spl_object_hash($node); - $docCommentHash = spl_object_hash($node->getDocComment()); - $docCommentContentHash = sha1($node->getDocComment()->getText()); - - return $objectHash . $docCommentHash . $docCommentContentHash; - } - private function ensureNodeHasDocComment(Node $node): void { if ($node->getDocComment() !== null) { diff --git a/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php b/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php index b784028e895d..2f2f8b7a3487 100644 --- a/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php +++ b/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php @@ -206,38 +206,6 @@ private function parseChildAndStoreItsPositions(TokenIterator $tokenIterator): N return $attributeAwareNode; } - private function getOriginalContentFromTokenIterator(TokenIterator $tokenIterator): string - { - // @todo iterate through tokens... - $originalTokens = $this->privatesAccessor->getPrivateProperty($tokenIterator, 'tokens'); - $originalContent = ''; - - foreach ($originalTokens as $originalToken) { - // skip opening - if ($originalToken[1] === Lexer::TOKEN_OPEN_PHPDOC) { - continue; - } - - // skip closing - if ($originalToken[1] === Lexer::TOKEN_CLOSE_PHPDOC) { - continue; - } - - if ($originalToken[1] === Lexer::TOKEN_PHPDOC_EOL) { - $originalToken[0] = PHP_EOL; - } - - $originalContent .= $originalToken[0]; - } - - return trim($originalContent); - } - - private function getTokenIteratorIndex(TokenIterator $tokenIterator): int - { - return (int) $this->privatesAccessor->getPrivateProperty($tokenIterator, 'index'); - } - private function resolveTag(TokenIterator $tokenIterator): string { $tag = $tokenIterator->currentTokenValue(); @@ -268,18 +236,6 @@ private function resolveTag(TokenIterator $tokenIterator): string return $tag; } - private function isTagMatchedByFactories(string $tag): bool - { - $currentPhpNode = $this->currentNodeProvider->getNode(); - foreach ($this->phpDocNodeFactories as $phpDocNodeFactory) { - if ($this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory, $currentPhpNode)) { - return true; - } - } - - return false; - } - private function isTagMatchingPhpDocNodeFactory( string $tag, PhpDocNodeFactoryInterface $phpDocNodeFactory, @@ -307,6 +263,11 @@ private function isTagMatchingPhpDocNodeFactory( return false; } + private function getTokenIteratorIndex(TokenIterator $tokenIterator): int + { + return (int) $this->privatesAccessor->getPrivateProperty($tokenIterator, 'index'); + } + /** * @see https://github.com/rectorphp/rector/issues/2158 * @@ -332,4 +293,43 @@ private function adjustTokenEndToFitClassAnnotation(TokenIterator $tokenIterator return $tokenEnd; } + + private function getOriginalContentFromTokenIterator(TokenIterator $tokenIterator): string + { + // @todo iterate through tokens... + $originalTokens = $this->privatesAccessor->getPrivateProperty($tokenIterator, 'tokens'); + $originalContent = ''; + + foreach ($originalTokens as $originalToken) { + // skip opening + if ($originalToken[1] === Lexer::TOKEN_OPEN_PHPDOC) { + continue; + } + + // skip closing + if ($originalToken[1] === Lexer::TOKEN_CLOSE_PHPDOC) { + continue; + } + + if ($originalToken[1] === Lexer::TOKEN_PHPDOC_EOL) { + $originalToken[0] = PHP_EOL; + } + + $originalContent .= $originalToken[0]; + } + + return trim($originalContent); + } + + private function isTagMatchedByFactories(string $tag): bool + { + $currentPhpNode = $this->currentNodeProvider->getNode(); + foreach ($this->phpDocNodeFactories as $phpDocNodeFactory) { + if ($this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory, $currentPhpNode)) { + return true; + } + } + + return false; + } } diff --git a/packages/BetterPhpDocParser/src/PhpDocParser/ClassAnnotationMatcher.php b/packages/BetterPhpDocParser/src/PhpDocParser/ClassAnnotationMatcher.php index 5f20fce027d0..578eb4753f0d 100644 --- a/packages/BetterPhpDocParser/src/PhpDocParser/ClassAnnotationMatcher.php +++ b/packages/BetterPhpDocParser/src/PhpDocParser/ClassAnnotationMatcher.php @@ -33,14 +33,6 @@ public function isTagMatchToNodeAndClass(string $tag, Node $node, string $matchi return Strings::lower($fullyQualifiedClassNode) === Strings::lower($matchingClass); } - private function isUseMatchingName(string $tag, UseUse $useUse): bool - { - $shortName = $useUse->alias ? $useUse->alias->name : $useUse->name->getLast(); - $shortNamePattern = preg_quote($shortName, '#'); - - return (bool) Strings::match($tag, '#' . $shortNamePattern . '(\\\\[\w]+)?#i'); - } - /** * @param Use_[] $uses */ @@ -67,4 +59,12 @@ private function matchFullAnnotationClassWithUses(string $tag, array $uses): ?st return null; } + + private function isUseMatchingName(string $tag, UseUse $useUse): bool + { + $shortName = $useUse->alias ? $useUse->alias->name : $useUse->name->getLast(); + $shortNamePattern = preg_quote($shortName, '#'); + + return (bool) Strings::match($tag, '#' . $shortNamePattern . '(\\\\[\w]+)?#i'); + } } diff --git a/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php b/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php index dd1b1957cfae..f472a47b1b34 100644 --- a/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php +++ b/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php @@ -255,6 +255,14 @@ private function printPhpDocTagNode( return $output . $nodeOutput; } + private function printAttributeWithAsterisk(AttributeAwareNodeInterface $attributeAwareNode): string + { + $content = (string) $attributeAwareNode; + $content = explode(PHP_EOL, $content); + + return implode(PHP_EOL . ' * ', $content); + } + /** * @return StartEndValueObject[] */ @@ -319,12 +327,4 @@ private function isTagSeparatedBySpace(string $nodeOutput, PhpDocTagNode $phpDoc return Strings::contains($this->phpDocInfo->getOriginalContent(), $phpDocTagNode->name . ' '); } - - private function printAttributeWithAsterisk(AttributeAwareNodeInterface $attributeAwareNode): string - { - $content = (string) $attributeAwareNode; - $content = explode(PHP_EOL, $content); - - return implode(PHP_EOL . ' * ', $content); - } } diff --git a/packages/CodeQuality/src/Rector/Array_/CallableThisArrayToAnonymousFunctionRector.php b/packages/CodeQuality/src/Rector/Array_/CallableThisArrayToAnonymousFunctionRector.php index e2524213acc7..3577338ff5f7 100644 --- a/packages/CodeQuality/src/Rector/Array_/CallableThisArrayToAnonymousFunctionRector.php +++ b/packages/CodeQuality/src/Rector/Array_/CallableThisArrayToAnonymousFunctionRector.php @@ -123,18 +123,15 @@ public function refactor(Node $node): ?Node return $this->createAnonymousFunction($classMethod, $objectVariable); } - /** - * @param Param[] $params - * @return Arg[] - */ - private function convertParamsToArgs(array $params): array + private function shouldSkipArray(Array_ $array): bool { - $args = []; - foreach ($params as $key => $param) { - $args[$key] = new Arg($param->var); + // callback is exactly "[$two, 'items']" + if (count($array->items) !== 2) { + return true; } - return $args; + // can be totally empty in case of "[, $value]" + return $array->items[0] === null; } /** @@ -171,17 +168,6 @@ private function matchCallableMethod(Expr $objectExpr, String_ $methodExpr): ?Cl return null; } - private function shouldSkipArray(Array_ $array): bool - { - // callback is exactly "[$two, 'items']" - if (count($array->items) !== 2) { - return true; - } - - // can be totally empty in case of "[, $value]" - return $array->items[0] === null; - } - /** * @param Variable|PropertyFetch $node */ @@ -214,4 +200,18 @@ private function createAnonymousFunction(ClassMethod $classMethod, Node $node): return $anonymousFunction; } + + /** + * @param Param[] $params + * @return Arg[] + */ + private function convertParamsToArgs(array $params): array + { + $args = []; + foreach ($params as $key => $param) { + $args[$key] = new Arg($param->var); + } + + return $args; + } } diff --git a/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php b/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php index c27844641afa..01120e45997c 100644 --- a/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php +++ b/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php @@ -125,41 +125,6 @@ public function refactor(Node $node): ?Node return $node; } - /** - * @param Type[] $fetchedLocalPropertyNameToTypes - * @param string[] $propertiesToComplete - * @return Property[] - */ - private function createNewProperties(array $fetchedLocalPropertyNameToTypes, array $propertiesToComplete): array - { - $newProperties = []; - foreach ($fetchedLocalPropertyNameToTypes as $propertyName => $propertyType) { - if (! in_array($propertyName, $propertiesToComplete, true)) { - continue; - } - - $propertyBuilder = $this->builderFactory->property($propertyName); - $propertyBuilder->makePublic(); - $property = $propertyBuilder->getNode(); - - if ($this->isAtLeastPhpVersion('7.4')) { - $phpStanNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType); - if ($phpStanNode) { - $property->type = $phpStanNode; - } else { - // fallback to doc type in PHP 7.4 - $this->docBlockManipulator->changeVarTag($property, $propertyType); - } - } else { - $this->docBlockManipulator->changeVarTag($property, $propertyType); - } - - $newProperties[] = $property; - } - - return $newProperties; - } - /** * @return Type[] */ @@ -220,16 +185,39 @@ private function getClassPropertyNames(Class_ $class): array return $propertyNames; } - private function resolvePropertyFetchType(Node $node): Type + /** + * @param Type[] $fetchedLocalPropertyNameToTypes + * @param string[] $propertiesToComplete + * @return Property[] + */ + private function createNewProperties(array $fetchedLocalPropertyNameToTypes, array $propertiesToComplete): array { - $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + $newProperties = []; + foreach ($fetchedLocalPropertyNameToTypes as $propertyName => $propertyType) { + if (! in_array($propertyName, $propertiesToComplete, true)) { + continue; + } - // possible get type - if ($parentNode instanceof Assign) { - return $this->getStaticType($parentNode->expr); + $propertyBuilder = $this->builderFactory->property($propertyName); + $propertyBuilder->makePublic(); + $property = $propertyBuilder->getNode(); + + if ($this->isAtLeastPhpVersion('7.4')) { + $phpStanNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType); + if ($phpStanNode) { + $property->type = $phpStanNode; + } else { + // fallback to doc type in PHP 7.4 + $this->docBlockManipulator->changeVarTag($property, $propertyType); + } + } else { + $this->docBlockManipulator->changeVarTag($property, $propertyType); + } + + $newProperties[] = $property; } - return new MixedType(); + return $newProperties; } private function shouldSkipForLaravelCollection(Node $node): bool @@ -245,4 +233,16 @@ private function shouldSkipForLaravelCollection(Node $node): bool return $this->isName($staticCallOrClassMethod->class, self::LARAVEL_COLLECTION_CLASS); } + + private function resolvePropertyFetchType(Node $node): Type + { + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + + // possible get type + if ($parentNode instanceof Assign) { + return $this->getStaticType($parentNode->expr); + } + + return new MixedType(); + } } diff --git a/packages/CodeQuality/src/Rector/For_/ForToForeachRector.php b/packages/CodeQuality/src/Rector/For_/ForToForeachRector.php index 2d43867db9d9..66b02c047206 100644 --- a/packages/CodeQuality/src/Rector/For_/ForToForeachRector.php +++ b/packages/CodeQuality/src/Rector/For_/ForToForeachRector.php @@ -138,6 +138,13 @@ public function refactor(Node $node): ?Node return $foreach; } + private function reset(): void + { + $this->keyValueName = null; + $this->countValueName = null; + $this->iteratedExpr = null; + } + /** * @param Expr[] $initExprs */ @@ -209,39 +216,6 @@ private function isLoopMatch(array $loopExprs): bool return false; } - private function reset(): void - { - $this->keyValueName = null; - $this->countValueName = null; - $this->iteratedExpr = null; - } - - /** - * @param Expr[] $condExprs - */ - private function isSmallerOrGreater(array $condExprs, string $keyValueName, string $countValueName): bool - { - // $i < $count - if ($condExprs[0] instanceof Smaller) { - if (! $this->isName($condExprs[0]->left, $keyValueName)) { - return false; - } - - return $this->isName($condExprs[0]->right, $countValueName); - } - - // $i > $count - if ($condExprs[0] instanceof Greater) { - if (! $this->isName($condExprs[0]->left, $countValueName)) { - return false; - } - - return $this->isName($condExprs[0]->right, $keyValueName); - } - - return false; - } - /** * @param Stmt[] $stmts */ @@ -278,6 +252,32 @@ private function useForeachVariableInStmts(Expr $expr, array $stmts): void }); } + /** + * @param Expr[] $condExprs + */ + private function isSmallerOrGreater(array $condExprs, string $keyValueName, string $countValueName): bool + { + // $i < $count + if ($condExprs[0] instanceof Smaller) { + if (! $this->isName($condExprs[0]->left, $keyValueName)) { + return false; + } + + return $this->isName($condExprs[0]->right, $countValueName); + } + + // $i > $count + if ($condExprs[0] instanceof Greater) { + if (! $this->isName($condExprs[0]->left, $countValueName)) { + return false; + } + + return $this->isName($condExprs[0]->right, $keyValueName); + } + + return false; + } + private function isPartOfAssign(?Node $node): bool { if ($node === null) { diff --git a/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php b/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php index 10c194163a95..35ad6ccfdccd 100644 --- a/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php +++ b/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php @@ -124,6 +124,32 @@ public function refactor(Node $node): ?Node return $node; } + private function isAlwaysTruableNode(Node $node): bool + { + if (! $node instanceof If_) { + return false; + } + + // just one if + if (count($node->elseifs) !== 0) { + return false; + } + + // there is some else + if ($node->else !== null) { + return false; + } + + // only property fetch, because of constructor set + if (! $node->cond instanceof PropertyFetch) { + return false; + } + + $propertyFetchTypes = $this->resolvePropertyFetchTypes($node->cond); + + return $this->staticTypeAnalyzer->areTypesAlwaysTruable($propertyFetchTypes); + } + /** * @return Type[] */ @@ -181,30 +207,4 @@ private function resolvePropertyFetchTypes(PropertyFetch $propertyFetch): array return $resolvedTypes; } - - private function isAlwaysTruableNode(Node $node): bool - { - if (! $node instanceof If_) { - return false; - } - - // just one if - if (count($node->elseifs) !== 0) { - return false; - } - - // there is some else - if ($node->else !== null) { - return false; - } - - // only property fetch, because of constructor set - if (! $node->cond instanceof PropertyFetch) { - return false; - } - - $propertyFetchTypes = $this->resolvePropertyFetchTypes($node->cond); - - return $this->staticTypeAnalyzer->areTypesAlwaysTruable($propertyFetchTypes); - } } diff --git a/packages/CodeQuality/src/Rector/If_/SimplifyIfElseToTernaryRector.php b/packages/CodeQuality/src/Rector/If_/SimplifyIfElseToTernaryRector.php index 9576f76fe69e..c2064e309351 100644 --- a/packages/CodeQuality/src/Rector/If_/SimplifyIfElseToTernaryRector.php +++ b/packages/CodeQuality/src/Rector/If_/SimplifyIfElseToTernaryRector.php @@ -145,11 +145,6 @@ private function resolveOnlyStmtAssignExpr(array $stmts): ?Expr return $onlyStmt->expr; } - private function unwrapExpression(Node $node): Node - { - return $node instanceof Expression ? $node->expr : $node; - } - /** * @param Node[] $nodes */ @@ -168,4 +163,9 @@ private function isNodeTooLong(Assign $assign): bool { return Strings::length($this->print($assign)) > self::LINE_LENGHT_LIMIT; } + + private function unwrapExpression(Node $node): Node + { + return $node instanceof Expression ? $node->expr : $node; + } } diff --git a/packages/CodeQuality/src/Rector/If_/SimplifyIfIssetToNullCoalescingRector.php b/packages/CodeQuality/src/Rector/If_/SimplifyIfIssetToNullCoalescingRector.php index 23a92c3ce4ad..61737de468b6 100644 --- a/packages/CodeQuality/src/Rector/If_/SimplifyIfIssetToNullCoalescingRector.php +++ b/packages/CodeQuality/src/Rector/If_/SimplifyIfIssetToNullCoalescingRector.php @@ -108,22 +108,6 @@ public function refactor(Node $node): ?Node return new Assign($valueNode, $funcCallNode); } - /** - * @param If_|Else_ $node - */ - private function hasOnlyStatementAssign(Node $node): bool - { - if (count($node->stmts) !== 1) { - return false; - } - - if (! $node->stmts[0] instanceof Expression) { - return false; - } - - return $node->stmts[0]->expr instanceof Assign; - } - private function shouldSkip(If_ $ifNode): bool { if ($ifNode->else === null) { @@ -151,4 +135,20 @@ private function shouldSkip(If_ $ifNode): bool } return ! $this->areNodesEqual($ifNode->cond->vars[0], $ifNode->else->stmts[0]->expr->var); } + + /** + * @param If_|Else_ $node + */ + private function hasOnlyStatementAssign(Node $node): bool + { + if (count($node->stmts) !== 1) { + return false; + } + + if (! $node->stmts[0] instanceof Expression) { + return false; + } + + return $node->stmts[0]->expr instanceof Assign; + } } diff --git a/packages/CodingStyle/src/Application/UseAddingCommander.php b/packages/CodingStyle/src/Application/UseAddingCommander.php index d049b95a7e64..ceff26d86605 100644 --- a/packages/CodingStyle/src/Application/UseAddingCommander.php +++ b/packages/CodingStyle/src/Application/UseAddingCommander.php @@ -260,16 +260,6 @@ public function getPriority(): int return 500; } - /** - * @return FullyQualifiedObjectType[] - */ - private function getUseImportTypesByNode(Node $node): array - { - $filePath = $this->getRealPathFromNode($node); - - return $this->useImportTypesInFilePath[$filePath] ?? []; - } - private function getRealPathFromNode(Node $node): ?string { /** @var SmartFileInfo|null $fileInfo */ @@ -280,4 +270,14 @@ private function getRealPathFromNode(Node $node): ?string return $fileInfo->getRealPath(); } + + /** + * @return FullyQualifiedObjectType[] + */ + private function getUseImportTypesByNode(Node $node): array + { + $filePath = $this->getRealPathFromNode($node); + + return $this->useImportTypesInFilePath[$filePath] ?? []; + } } diff --git a/packages/CodingStyle/src/Application/UseImportsAdder.php b/packages/CodingStyle/src/Application/UseImportsAdder.php index de2eb6d8efae..4ad8a7a89d6b 100644 --- a/packages/CodingStyle/src/Application/UseImportsAdder.php +++ b/packages/CodingStyle/src/Application/UseImportsAdder.php @@ -80,29 +80,22 @@ public function addImportsToNamespace( $namespace->stmts = array_merge($newUses, $namespace->stmts); } - private function getNamespaceName(Namespace_ $namespace): ?string + /** + * @param FullyQualifiedObjectType[] $mainTypes + * @param FullyQualifiedObjectType[] $typesToRemove + * @return FullyQualifiedObjectType[] + */ + private function diffFullyQualifiedObjectTypes(array $mainTypes, array $typesToRemove): array { - if ($namespace->name === null) { - return null; - } - - return $namespace->name->toString(); - } - - private function isCurrentNamespace( - string $namespaceName, - FullyQualifiedObjectType $fullyQualifiedObjectType - ): bool { - if ($namespaceName === null) { - return false; - } - - $afterCurrentNamespace = Strings::after($fullyQualifiedObjectType->getClassName(), $namespaceName . '\\'); - if (! $afterCurrentNamespace) { - return false; + foreach ($mainTypes as $key => $mainType) { + foreach ($typesToRemove as $typeToRemove) { + if ($mainType->equals($typeToRemove)) { + unset($mainTypes[$key]); + } + } } - return ! Strings::contains($afterCurrentNamespace, '\\'); + return array_values($mainTypes); } /** @@ -134,21 +127,28 @@ private function createUses(array $useImportTypes, array $functionUseImportTypes return $newUses; } - /** - * @param FullyQualifiedObjectType[] $mainTypes - * @param FullyQualifiedObjectType[] $typesToRemove - * @return FullyQualifiedObjectType[] - */ - private function diffFullyQualifiedObjectTypes(array $mainTypes, array $typesToRemove): array + private function getNamespaceName(Namespace_ $namespace): ?string { - foreach ($mainTypes as $key => $mainType) { - foreach ($typesToRemove as $typeToRemove) { - if ($mainType->equals($typeToRemove)) { - unset($mainTypes[$key]); - } - } + if ($namespace->name === null) { + return null; } - return array_values($mainTypes); + return $namespace->name->toString(); + } + + private function isCurrentNamespace( + string $namespaceName, + FullyQualifiedObjectType $fullyQualifiedObjectType + ): bool { + if ($namespaceName === null) { + return false; + } + + $afterCurrentNamespace = Strings::after($fullyQualifiedObjectType->getClassName(), $namespaceName . '\\'); + if (! $afterCurrentNamespace) { + return false; + } + + return ! Strings::contains($afterCurrentNamespace, '\\'); } } diff --git a/packages/CodingStyle/src/Node/ConcatJoiner.php b/packages/CodingStyle/src/Node/ConcatJoiner.php index 152441d98a4d..e4ccd449f849 100644 --- a/packages/CodingStyle/src/Node/ConcatJoiner.php +++ b/packages/CodingStyle/src/Node/ConcatJoiner.php @@ -40,6 +40,12 @@ public function joinToStringAndPlaceholderNodes(Concat $concat): array return [$this->content, $this->placeholderNodes]; } + private function reset(): void + { + $this->content = ''; + $this->placeholderNodes = []; + } + private function processConcatSide(Expr $expr): void { if ($expr instanceof String_) { @@ -53,10 +59,4 @@ private function processConcatSide(Expr $expr): void $this->content .= $objectHash; } } - - private function reset(): void - { - $this->content = ''; - $this->placeholderNodes = []; - } } diff --git a/packages/CodingStyle/src/Node/NameImporter.php b/packages/CodingStyle/src/Node/NameImporter.php index 500154f6bd63..e7962acd2d0a 100644 --- a/packages/CodingStyle/src/Node/NameImporter.php +++ b/packages/CodingStyle/src/Node/NameImporter.php @@ -90,6 +90,16 @@ private function isNamespaceOrUseImportName(Name $name): bool return $parentNode instanceof UseUse; } + private function isFunctionOrConstantImportWithSingleName(Name $name): bool + { + $parentNode = $name->getAttribute(AttributeKey::PARENT_NODE); + if (! $parentNode instanceof ConstFetch && ! $parentNode instanceof FuncCall) { + return false; + } + + return count($name->parts) === 1; + } + private function importNameAndCollectNewUseStatement( Name $name, FullyQualifiedObjectType $fullyQualifiedObjectType @@ -132,14 +142,4 @@ private function addUseImport(Name $name, FullyQualifiedObjectType $fullyQualifi $this->useAddingCommander->addUseImport($name, $fullyQualifiedObjectType); } } - - private function isFunctionOrConstantImportWithSingleName(Name $name): bool - { - $parentNode = $name->getAttribute(AttributeKey::PARENT_NODE); - if (! $parentNode instanceof ConstFetch && ! $parentNode instanceof FuncCall) { - return false; - } - - return count($name->parts) === 1; - } } diff --git a/packages/CodingStyle/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php b/packages/CodingStyle/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php index 646c839af840..7070caee472e 100644 --- a/packages/CodingStyle/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php +++ b/packages/CodingStyle/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php @@ -130,26 +130,6 @@ private function isClassMethodCompatibleWithParentReflectionMethod( return false; } - private function changeClassMethodVisibilityBasedOnReflectionMethod( - ClassMethod $classMethod, - ReflectionMethod $reflectionMethod - ): void { - if ($reflectionMethod->isPublic()) { - $this->makePublic($classMethod); - return; - } - - if ($reflectionMethod->isProtected()) { - $this->makeProtected($classMethod); - return; - } - - if ($reflectionMethod->isPrivate()) { - $this->makePrivate($classMethod); - return; - } - } - /** * Parent constructor visibility override is allowed only since PHP 7.2+ * @see https://3v4l.org/RFYmn @@ -191,6 +171,26 @@ private function isConstructorWithStaticFactory(ClassMethod $classMethod, string return false; } + private function changeClassMethodVisibilityBasedOnReflectionMethod( + ClassMethod $classMethod, + ReflectionMethod $reflectionMethod + ): void { + if ($reflectionMethod->isPublic()) { + $this->makePublic($classMethod); + return; + } + + if ($reflectionMethod->isProtected()) { + $this->makeProtected($classMethod); + return; + } + + if ($reflectionMethod->isPrivate()) { + $this->makePrivate($classMethod); + return; + } + } + /** * Looks for: * public static someMethod() { return new self(); } diff --git a/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php b/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php index 1b8206a89a7d..0113aa32db65 100644 --- a/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php +++ b/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php @@ -118,6 +118,19 @@ private function reset(): void $this->previousPreviousStmtVariableName = null; } + /** + * @param Assign|MethodCall $node + */ + private function shouldSkipLeftVariable(Node $node): bool + { + if (! $node->var instanceof Variable) { + return true; + } + + // local method call + return $this->isName($node->var, 'this'); + } + /** * @param ClassMethod|Function_|Closure $node */ @@ -170,17 +183,4 @@ private function isPreceededByEmptyLine(Node $node, int $key): bool return abs($currentNode->getLine() - $previousNode->getLine()) >= 2; } - - /** - * @param Assign|MethodCall $node - */ - private function shouldSkipLeftVariable(Node $node): bool - { - if (! $node->var instanceof Variable) { - return true; - } - - // local method call - return $this->isName($node->var, 'this'); - } } diff --git a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php index f7f87becd3dd..13d04d70f547 100644 --- a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php +++ b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php @@ -161,35 +161,6 @@ private function completeDefaultArrayToPropertyNames(Class_ $node, array $proper }); } - /** - * @param string[] $propertyNames - */ - private function replaceNullComparisonOfArrayPropertiesWithArrayComparison( - Class_ $class, - array $propertyNames - ): void { - // replace comparison to "null" with "[]" - $this->traverseNodesWithCallable($class, function (Node $node) use ($propertyNames): ?BinaryOp { - if (! $node instanceof BinaryOp) { - return null; - } - - if ($this->propertyFetchManipulator->isLocalPropertyOfNames($node->left, $propertyNames) && $this->isNull( - $node->right - )) { - $node->right = new Array_(); - } - - if ($this->propertyFetchManipulator->isLocalPropertyOfNames($node->right, $propertyNames) && $this->isNull( - $node->left - )) { - $node->left = new Array_(); - } - - return $node; - }); - } - /** * @param string[] $propertyNames */ @@ -236,6 +207,35 @@ private function clearNotNullBeforeCount(Class_ $class, array $propertyNames): v }); } + /** + * @param string[] $propertyNames + */ + private function replaceNullComparisonOfArrayPropertiesWithArrayComparison( + Class_ $class, + array $propertyNames + ): void { + // replace comparison to "null" with "[]" + $this->traverseNodesWithCallable($class, function (Node $node) use ($propertyNames): ?BinaryOp { + if (! $node instanceof BinaryOp) { + return null; + } + + if ($this->propertyFetchManipulator->isLocalPropertyOfNames($node->left, $propertyNames) && $this->isNull( + $node->right + )) { + $node->right = new Array_(); + } + + if ($this->propertyFetchManipulator->isLocalPropertyOfNames($node->right, $propertyNames) && $this->isNull( + $node->left + )) { + $node->left = new Array_(); + } + + return $node; + }); + } + /** * @param string[] $propertyNames */ diff --git a/packages/CodingStyle/src/Rector/FuncCall/ConsistentPregDelimiterRector.php b/packages/CodingStyle/src/Rector/FuncCall/ConsistentPregDelimiterRector.php index d125277c863f..fa359fac72e4 100644 --- a/packages/CodingStyle/src/Rector/FuncCall/ConsistentPregDelimiterRector.php +++ b/packages/CodingStyle/src/Rector/FuncCall/ConsistentPregDelimiterRector.php @@ -126,6 +126,19 @@ public function refactor(Node $node): ?Node return $node; } + private function refactorFuncCall(FuncCall $funcCall): FuncCall + { + foreach (self::FUNCTIONS_WITH_REGEX_PATTERN as $function => $position) { + if (! $this->isName($funcCall, $function)) { + continue; + } + + $this->refactorArgument($funcCall->args[$position]); + } + + return $funcCall; + } + private function refactorArgument(Arg $arg): void { if (! $arg->value instanceof String_) { @@ -147,17 +160,4 @@ private function refactorArgument(Arg $arg): void return $innerPattern . $match['close']; }); } - - private function refactorFuncCall(FuncCall $funcCall): FuncCall - { - foreach (self::FUNCTIONS_WITH_REGEX_PATTERN as $function => $position) { - if (! $this->isName($funcCall, $function)) { - continue; - } - - $this->refactorArgument($funcCall->args[$position]); - } - - return $funcCall; - } } diff --git a/packages/CodingStyle/src/Rector/String_/ManualJsonStringToJsonEncodeArrayRector.php b/packages/CodingStyle/src/Rector/String_/ManualJsonStringToJsonEncodeArrayRector.php index 385e8e901270..b967555c3d8a 100644 --- a/packages/CodingStyle/src/Rector/String_/ManualJsonStringToJsonEncodeArrayRector.php +++ b/packages/CodingStyle/src/Rector/String_/ManualJsonStringToJsonEncodeArrayRector.php @@ -143,13 +143,6 @@ public function refactor(Node $node): ?Node return null; } - private function processJsonString(Assign $assign, string $stringValue): Node - { - $arrayNode = $this->createArrayNodeFromJsonString($stringValue); - - return $this->createAndReturnJsonEncodeFromArray($assign, $arrayNode); - } - private function isJsonString(string $stringValue): bool { if (! (bool) Strings::match($stringValue, '#{(.*?\:.*?)}#s')) { @@ -163,6 +156,85 @@ private function isJsonString(string $stringValue): bool } } + private function processJsonString(Assign $assign, string $stringValue): Node + { + $arrayNode = $this->createArrayNodeFromJsonString($stringValue); + + return $this->createAndReturnJsonEncodeFromArray($assign, $arrayNode); + } + + private function collectContentAndPlaceholderNodesFromNextExpressions(Assign $assign): ConcatExpressionJoinData + { + $concatExpressionJoinData = new ConcatExpressionJoinData(); + + $currentNode = $assign; + + while ([$nodeToRemove, $valueNode] = $this->matchNextExpressionAssignConcatToSameVariable( + $assign->var, + $currentNode + )) { + if ($valueNode instanceof String_) { + $concatExpressionJoinData->addString($valueNode->value); + } elseif ($valueNode instanceof Concat) { + /** @var Expr[] $newPlaceholderNodes */ + [$content, $newPlaceholderNodes] = $this->concatJoiner->joinToStringAndPlaceholderNodes($valueNode); + /** @var string $content */ + $concatExpressionJoinData->addString($content); + + foreach ($newPlaceholderNodes as $placeholder => $expr) { + /** @var string $placeholder */ + $concatExpressionJoinData->addPlaceholderToNode($placeholder, $expr); + } + } elseif ($valueNode instanceof Expr) { + $objectHash = '____' . spl_object_hash($valueNode) . '____'; + + $concatExpressionJoinData->addString($objectHash); + $concatExpressionJoinData->addPlaceholderToNode($objectHash, $valueNode); + } + + $concatExpressionJoinData->addNodeToRemove($nodeToRemove); + + // jump to next one + $currentNode = $this->getNextExpression($currentNode); + if ($currentNode === null) { + return $concatExpressionJoinData; + } + } + + return $concatExpressionJoinData; + } + + /** + * @param Node[] $nodesToRemove + * @param Expr[] $placeholderNodes + */ + private function removeNodesAndCreateJsonEncodeFromStringValue( + array $nodesToRemove, + string $stringValue, + array $placeholderNodes, + Assign $assign + ): ?Assign { + // quote object hashes if needed - https://regex101.com/r/85PZHm/1 + $stringValue = Strings::replace($stringValue, '#(?[^\"])(?____\w+____)#', '$1"$2"'); + if (! $this->isJsonString($stringValue)) { + return null; + } + + $this->removeNodes($nodesToRemove); + + $jsonArray = $this->createArrayNodeFromJsonString($stringValue); + $this->replaceNodeObjectHashPlaceholdersWithNodes($jsonArray, $placeholderNodes); + + return $this->createAndReturnJsonEncodeFromArray($assign, $jsonArray); + } + + private function createArrayNodeFromJsonString(string $stringValue): Array_ + { + $array = Json::decode($stringValue, Json::FORCE_ARRAY); + + return $this->createArray($array); + } + /** * Creates + adds * @@ -181,26 +253,6 @@ private function createAndReturnJsonEncodeFromArray(Assign $assign, Array_ $json return $assign; } - /** - * @param Expr[] $placeholderNodes - */ - private function replaceNodeObjectHashPlaceholdersWithNodes(Array_ $array, array $placeholderNodes): void - { - // traverse and replace placeholder by original nodes - $this->traverseNodesWithCallable($array, function (Node $node) use ($placeholderNodes): ?Expr { - if ($node instanceof Array_ && count($node->items) === 1) { - $placeholderNode = $this->matchPlaceholderNode($node->items[0]->value, $placeholderNodes); - - if ($placeholderNode && $this->isImplodeToJson($placeholderNode)) { - /** @var FuncCall $placeholderNode */ - return $placeholderNode->args[1]->value; - } - } - - return $this->matchPlaceholderNode($node, $placeholderNodes); - }); - } - /** * @param Assign|ConcatAssign $currentNode * @return Node[]|null @@ -251,52 +303,36 @@ private function matchNextExpressionAssignConcatToSameVariable(Expr $expr, Node return null; } - private function createArrayNodeFromJsonString(string $stringValue): Array_ - { - $array = Json::decode($stringValue, Json::FORCE_ARRAY); - - return $this->createArray($array); - } - - private function collectContentAndPlaceholderNodesFromNextExpressions(Assign $assign): ConcatExpressionJoinData + /** + * @param Expr[] $placeholderNodes + */ + private function replaceNodeObjectHashPlaceholdersWithNodes(Array_ $array, array $placeholderNodes): void { - $concatExpressionJoinData = new ConcatExpressionJoinData(); - - $currentNode = $assign; - - while ([$nodeToRemove, $valueNode] = $this->matchNextExpressionAssignConcatToSameVariable( - $assign->var, - $currentNode - )) { - if ($valueNode instanceof String_) { - $concatExpressionJoinData->addString($valueNode->value); - } elseif ($valueNode instanceof Concat) { - /** @var Expr[] $newPlaceholderNodes */ - [$content, $newPlaceholderNodes] = $this->concatJoiner->joinToStringAndPlaceholderNodes($valueNode); - /** @var string $content */ - $concatExpressionJoinData->addString($content); + // traverse and replace placeholder by original nodes + $this->traverseNodesWithCallable($array, function (Node $node) use ($placeholderNodes): ?Expr { + if ($node instanceof Array_ && count($node->items) === 1) { + $placeholderNode = $this->matchPlaceholderNode($node->items[0]->value, $placeholderNodes); - foreach ($newPlaceholderNodes as $placeholder => $expr) { - /** @var string $placeholder */ - $concatExpressionJoinData->addPlaceholderToNode($placeholder, $expr); + if ($placeholderNode && $this->isImplodeToJson($placeholderNode)) { + /** @var FuncCall $placeholderNode */ + return $placeholderNode->args[1]->value; } - } elseif ($valueNode instanceof Expr) { - $objectHash = '____' . spl_object_hash($valueNode) . '____'; - - $concatExpressionJoinData->addString($objectHash); - $concatExpressionJoinData->addPlaceholderToNode($objectHash, $valueNode); } - $concatExpressionJoinData->addNodeToRemove($nodeToRemove); + return $this->matchPlaceholderNode($node, $placeholderNodes); + }); + } - // jump to next one - $currentNode = $this->getNextExpression($currentNode); - if ($currentNode === null) { - return $concatExpressionJoinData; - } + /** + * @param Expr[] $placeholderNodes + */ + private function matchPlaceholderNode(Node $node, array $placeholderNodes): ?Expr + { + if (! $node instanceof String_) { + return null; } - return $concatExpressionJoinData; + return $placeholderNodes[$node->value] ?? null; } /** @@ -325,40 +361,4 @@ private function isImplodeToJson(Node $node): bool return true; } - - /** - * @param Expr[] $placeholderNodes - */ - private function matchPlaceholderNode(Node $node, array $placeholderNodes): ?Expr - { - if (! $node instanceof String_) { - return null; - } - - return $placeholderNodes[$node->value] ?? null; - } - - /** - * @param Node[] $nodesToRemove - * @param Expr[] $placeholderNodes - */ - private function removeNodesAndCreateJsonEncodeFromStringValue( - array $nodesToRemove, - string $stringValue, - array $placeholderNodes, - Assign $assign - ): ?Assign { - // quote object hashes if needed - https://regex101.com/r/85PZHm/1 - $stringValue = Strings::replace($stringValue, '#(?[^\"])(?____\w+____)#', '$1"$2"'); - if (! $this->isJsonString($stringValue)) { - return null; - } - - $this->removeNodes($nodesToRemove); - - $jsonArray = $this->createArrayNodeFromJsonString($stringValue); - $this->replaceNodeObjectHashPlaceholdersWithNodes($jsonArray, $placeholderNodes); - - return $this->createAndReturnJsonEncodeFromArray($assign, $jsonArray); - } } diff --git a/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php b/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php index a2526b1355bc..be6afc951775 100644 --- a/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php +++ b/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php @@ -152,6 +152,37 @@ private function resolveUsedNameNodes(Use_ $node): void $this->resolvedDocPossibleAliases = $this->resolveDocPossibleAliases($searchNode); } + /** + * @return string[][] + */ + private function collectUseNamesAliasToName(Use_ $use): array + { + $useNamesAliasToName = []; + + $shortNames = $this->shortNameResolver->resolveForNode($use); + foreach ($shortNames as $alias => $useImport) { + $shortName = $this->classNaming->getShortName($useImport); + if ($shortName === $alias) { + continue; + } + + $useNamesAliasToName[$shortName][] = $alias; + } + + return $useNamesAliasToName; + } + + private function shouldSkip(string $lastName, string $aliasName): bool + { + // both are used → nothing to remove + if (isset($this->resolvedNodeNames[$lastName], $this->resolvedNodeNames[$aliasName])) { + return true; + } + + // part of some @Doc annotation + return in_array($aliasName, $this->resolvedDocPossibleAliases, true); + } + /** * @param Node[][] $usedNameNodes */ @@ -337,35 +368,4 @@ private function resolveDocPossibleAliases(Node $searchNode): array return array_unique($possibleDocAliases); } - - private function shouldSkip(string $lastName, string $aliasName): bool - { - // both are used → nothing to remove - if (isset($this->resolvedNodeNames[$lastName], $this->resolvedNodeNames[$aliasName])) { - return true; - } - - // part of some @Doc annotation - return in_array($aliasName, $this->resolvedDocPossibleAliases, true); - } - - /** - * @return string[][] - */ - private function collectUseNamesAliasToName(Use_ $use): array - { - $useNamesAliasToName = []; - - $shortNames = $this->shortNameResolver->resolveForNode($use); - foreach ($shortNames as $alias => $useImport) { - $shortName = $this->classNaming->getShortName($useImport); - if ($shortName === $alias) { - continue; - } - - $useNamesAliasToName[$shortName][] = $alias; - } - - return $useNamesAliasToName; - } } diff --git a/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php b/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php index 7d94430f2064..90d0f761a8b2 100644 --- a/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php +++ b/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php @@ -102,6 +102,27 @@ public function refactor(Node $node): ?Node return $node; } + private function shouldSkipForDifferentScope(Assign $assign, Node $anotherNode): bool + { + if (! $this->areInSameClassMethod($assign, $anotherNode)) { + return true; + } + + if ($this->shouldSkipDueToForeachOverride($assign, $anotherNode)) { + return true; + } + + return $this->shouldSkipForDifferenceParent($assign, $anotherNode); + } + + private function areInSameClassMethod(Node $node, Node $previousExpression): bool + { + return $this->areNodesEqual( + $node->getAttribute(AttributeKey::METHOD_NODE), + $previousExpression->getAttribute(AttributeKey::METHOD_NODE) + ); + } + private function shouldSkipDueToForeachOverride(Assign $assign, Node $node): bool { // is nested in a foreach and the previous expression is not? @@ -126,29 +147,8 @@ private function shouldSkipForDifferenceParent(Node $firstNode, Node $secondNode return ! $this->areNodesEqual($firstNodeParent, $secondNodeParent); } - private function shouldSkipForDifferentScope(Assign $assign, Node $anotherNode): bool - { - if (! $this->areInSameClassMethod($assign, $anotherNode)) { - return true; - } - - if ($this->shouldSkipDueToForeachOverride($assign, $anotherNode)) { - return true; - } - - return $this->shouldSkipForDifferenceParent($assign, $anotherNode); - } - private function findParentControlStructure(Node $node): ?Node { return $this->betterNodeFinder->findFirstParentInstanceOf($node, self::CONTROL_STRUCTURE_NODES); } - - private function areInSameClassMethod(Node $node, Node $previousExpression): bool - { - return $this->areNodesEqual( - $node->getAttribute(AttributeKey::METHOD_NODE), - $previousExpression->getAttribute(AttributeKey::METHOD_NODE) - ); - } } diff --git a/packages/DeadCode/src/Rector/ClassMethod/RemoveDelegatingParentCallRector.php b/packages/DeadCode/src/Rector/ClassMethod/RemoveDelegatingParentCallRector.php index 56c2152dbe2f..e07dc1249a77 100644 --- a/packages/DeadCode/src/Rector/ClassMethod/RemoveDelegatingParentCallRector.php +++ b/packages/DeadCode/src/Rector/ClassMethod/RemoveDelegatingParentCallRector.php @@ -104,6 +104,40 @@ public function refactor(Node $node): ?Node return null; } + private function shouldSkipClass(?ClassLike $classLike): bool + { + if (! $classLike instanceof Class_) { + return true; + } + + if ($classLike->extends === null) { + return true; + } + + return false; + } + + /** + * @param Node|Expression $node + */ + private function unwrapExpression(Node $node): Node + { + if ($node instanceof Expression) { + return $node->expr; + } + + return $node; + } + + private function isMethodReturnType(ClassMethod $classMethod, string $type): bool + { + if ($classMethod->returnType === null) { + return false; + } + + return $this->isName($classMethod->returnType, $type); + } + private function matchStaticCall(Node $node): ?StaticCall { // must be static call @@ -147,6 +181,17 @@ private function isParentCallMatching(ClassMethod $classMethod, ?StaticCall $sta return true; } + private function hasRequiredAnnotation(Node $node): bool + { + if ($node->getDocComment() === null) { + return false; + } + + $docCommentText = $node->getDocComment()->getText(); + + return (bool) Strings::match($docCommentText, '#\s\@required\s#si'); + } + /** * @param Arg[] $args * @param Param[] $params @@ -200,49 +245,4 @@ private function isParentClassMethodVisibilityOverride(ClassMethod $classMethod, return false; } - - /** - * @param Node|Expression $node - */ - private function unwrapExpression(Node $node): Node - { - if ($node instanceof Expression) { - return $node->expr; - } - - return $node; - } - - private function shouldSkipClass(?ClassLike $classLike): bool - { - if (! $classLike instanceof Class_) { - return true; - } - - if ($classLike->extends === null) { - return true; - } - - return false; - } - - private function isMethodReturnType(ClassMethod $classMethod, string $type): bool - { - if ($classMethod->returnType === null) { - return false; - } - - return $this->isName($classMethod->returnType, $type); - } - - private function hasRequiredAnnotation(Node $node): bool - { - if ($node->getDocComment() === null) { - return false; - } - - $docCommentText = $node->getDocComment()->getText(); - - return (bool) Strings::match($docCommentText, '#\s\@required\s#si'); - } } diff --git a/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php b/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php index 082a0045126e..897485855fc7 100644 --- a/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php +++ b/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php @@ -130,6 +130,23 @@ private function resolveAssignedVariables(FunctionLike $functionLike): array }); } + /** + * @param Node[] $nodes + * @return string[] + */ + private function getNodeNames(array $nodes): array + { + $nodeNames = []; + foreach ($nodes as $node) { + $nodeName = $this->getName($node); + if ($nodeName) { + $nodeNames[] = $nodeName; + } + } + + return array_unique($nodeNames); + } + /** * @param Variable[] $assignedVariables * @return Variable[] @@ -158,23 +175,6 @@ private function resolveUsedVariables(Node $node, array $assignedVariables): arr }); } - /** - * @param Node[] $nodes - * @return string[] - */ - private function getNodeNames(array $nodes): array - { - $nodeNames = []; - foreach ($nodes as $node) { - $nodeName = $this->getName($node); - if ($nodeName) { - $nodeNames[] = $nodeName; - } - } - - return array_unique($nodeNames); - } - /** * @param Variable[] $assignedVariables * @param Variable[] $assignedVariablesUse @@ -265,6 +265,22 @@ private function resolveNodesToRemove(array $assignedVariableNames, array $nodes return $nodesToRemove; } + private function isAssignNodeUsed( + ?VariableNodeUseInfo $previousNode, + VariableNodeUseInfo $nodeByTypeAndPosition + ): bool { + // this node was just used, skip to next one + if ($previousNode !== null) { + if ($previousNode->isType(VariableNodeUseInfo::TYPE_ASSIGN) && $nodeByTypeAndPosition->isType( + VariableNodeUseInfo::TYPE_USE + )) { + return true; + } + } + + return false; + } + private function shouldRemoveAssignNode( ?VariableNodeUseInfo $previousNode, VariableNodeUseInfo $nodeByTypeAndPosition @@ -298,20 +314,4 @@ private function shouldRemoveAssignNode( return ! $isVariableAssigned; } - - private function isAssignNodeUsed( - ?VariableNodeUseInfo $previousNode, - VariableNodeUseInfo $nodeByTypeAndPosition - ): bool { - // this node was just used, skip to next one - if ($previousNode !== null) { - if ($previousNode->isType(VariableNodeUseInfo::TYPE_ASSIGN) && $nodeByTypeAndPosition->isType( - VariableNodeUseInfo::TYPE_USE - )) { - return true; - } - } - - return false; - } } diff --git a/packages/DeadCode/src/Rector/ClassMethod/RemoveUnusedParameterRector.php b/packages/DeadCode/src/Rector/ClassMethod/RemoveUnusedParameterRector.php index 06d105d1f723..d6d6f5184869 100644 --- a/packages/DeadCode/src/Rector/ClassMethod/RemoveUnusedParameterRector.php +++ b/packages/DeadCode/src/Rector/ClassMethod/RemoveUnusedParameterRector.php @@ -154,25 +154,27 @@ public function refactor(Node $node): ?Node } /** + * @param ClassMethod $classMethod + * @param string $methodName + * @param Class_[] $childrenOfClass * @return Param[] */ - private function resolveUnusedParameters(ClassMethod $classMethod): array + private function getUnusedParameters(ClassMethod $classMethod, string $methodName, array $childrenOfClass): array { - $unusedParameters = []; - - foreach ((array) $classMethod->params as $i => $param) { - if ($this->classMethodManipulator->isParameterUsedMethod($param, $classMethod)) { - // reset to keep order of removed arguments, if not construtctor - probably autowired - if (! $this->isName($classMethod, '__construct')) { - $unusedParameters = []; - } + $unusedParameters = $this->resolveUnusedParameters($classMethod); + if ($unusedParameters === []) { + return []; + } - continue; + foreach ($childrenOfClass as $childClassNode) { + $methodOfChild = $childClassNode->getMethod($methodName); + if ($methodOfChild !== null) { + $unusedParameters = $this->getParameterOverlap( + $unusedParameters, + $this->resolveUnusedParameters($methodOfChild) + ); } - - $unusedParameters[$i] = $param; } - return $unusedParameters; } @@ -193,27 +195,25 @@ function (Param $a, Param $b): int { } /** - * @param ClassMethod $classMethod - * @param string $methodName - * @param Class_[] $childrenOfClass * @return Param[] */ - private function getUnusedParameters(ClassMethod $classMethod, string $methodName, array $childrenOfClass): array + private function resolveUnusedParameters(ClassMethod $classMethod): array { - $unusedParameters = $this->resolveUnusedParameters($classMethod); - if ($unusedParameters === []) { - return []; - } + $unusedParameters = []; - foreach ($childrenOfClass as $childClassNode) { - $methodOfChild = $childClassNode->getMethod($methodName); - if ($methodOfChild !== null) { - $unusedParameters = $this->getParameterOverlap( - $unusedParameters, - $this->resolveUnusedParameters($methodOfChild) - ); + foreach ((array) $classMethod->params as $i => $param) { + if ($this->classMethodManipulator->isParameterUsedMethod($param, $classMethod)) { + // reset to keep order of removed arguments, if not construtctor - probably autowired + if (! $this->isName($classMethod, '__construct')) { + $unusedParameters = []; + } + + continue; } + + $unusedParameters[$i] = $param; } + return $unusedParameters; } } diff --git a/packages/DeadCode/src/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector.php b/packages/DeadCode/src/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector.php index d325557980d3..a384a5e422f7 100644 --- a/packages/DeadCode/src/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector.php +++ b/packages/DeadCode/src/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector.php @@ -190,33 +190,39 @@ private function removeClassPrivatePropertiesByNames(Class_ $class, array $unuse return $class; } - private function getOtherRelationProperty(Property $property): ?Property + /** + * @return string[] + */ + private function resolveClassUsedPropertyFetchNames(Class_ $class): array { - $targetEntity = $this->docBlockManipulator->getDoctrineFqnTargetEntity($property); - if ($targetEntity === null) { - return null; - } + $usedPropertyNames = []; - $otherProperty = $this->doctrineEntityManipulator->resolveOtherProperty($property); - if ($otherProperty === null) { - return null; - } + $this->traverseNodesWithCallable($class->stmts, function (Node $node) use (&$usedPropertyNames) { + if (! $node instanceof PropertyFetch) { + return null; + } - // get the class property and remove "mappedBy/inversedBy" from annotation - $relatedEntityClass = $this->parsedNodesByType->findClass($targetEntity); - if (! $relatedEntityClass instanceof Class_) { - return null; - } + if (! $this->isName($node->var, 'this')) { + return null; + } - foreach ($relatedEntityClass->getProperties() as $relatedEntityClassStmt) { - if (! $this->isName($relatedEntityClassStmt, $otherProperty)) { - continue; + /** @var string $propertyName */ + $propertyName = $this->getName($node->name); + + // skip collection initialization, e.g. "$this->someProperty = new ArrayCollection();" + if ($this->isPropertyFetchAssignOfArrayCollection($node)) { + /** @var Assign $parentNode */ + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + $this->collectionByPropertyName[$propertyName] = $parentNode; + return null; } - return $relatedEntityClassStmt; - } + $usedPropertyNames[] = $propertyName; - return null; + return null; + }); + + return $usedPropertyNames; } private function removeInversedByOrMappedByOnRelatedProperty(Property $property): void @@ -246,38 +252,32 @@ private function isPropertyFetchAssignOfArrayCollection(PropertyFetch $propertyF return $this->isName($new->class, ArrayCollection::class); } - /** - * @return string[] - */ - private function resolveClassUsedPropertyFetchNames(Class_ $class): array + private function getOtherRelationProperty(Property $property): ?Property { - $usedPropertyNames = []; - - $this->traverseNodesWithCallable($class->stmts, function (Node $node) use (&$usedPropertyNames) { - if (! $node instanceof PropertyFetch) { - return null; - } + $targetEntity = $this->docBlockManipulator->getDoctrineFqnTargetEntity($property); + if ($targetEntity === null) { + return null; + } - if (! $this->isName($node->var, 'this')) { - return null; - } + $otherProperty = $this->doctrineEntityManipulator->resolveOtherProperty($property); + if ($otherProperty === null) { + return null; + } - /** @var string $propertyName */ - $propertyName = $this->getName($node->name); + // get the class property and remove "mappedBy/inversedBy" from annotation + $relatedEntityClass = $this->parsedNodesByType->findClass($targetEntity); + if (! $relatedEntityClass instanceof Class_) { + return null; + } - // skip collection initialization, e.g. "$this->someProperty = new ArrayCollection();" - if ($this->isPropertyFetchAssignOfArrayCollection($node)) { - /** @var Assign $parentNode */ - $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); - $this->collectionByPropertyName[$propertyName] = $parentNode; - return null; + foreach ($relatedEntityClass->getProperties() as $relatedEntityClassStmt) { + if (! $this->isName($relatedEntityClassStmt, $otherProperty)) { + continue; } - $usedPropertyNames[] = $propertyName; - - return null; - }); + return $relatedEntityClassStmt; + } - return $usedPropertyNames; + return null; } } diff --git a/packages/DeadCode/src/Rector/Instanceof_/RemoveDuplicatedInstanceOfRector.php b/packages/DeadCode/src/Rector/Instanceof_/RemoveDuplicatedInstanceOfRector.php index 5bcc8095aee4..5b860dbaf292 100644 --- a/packages/DeadCode/src/Rector/Instanceof_/RemoveDuplicatedInstanceOfRector.php +++ b/packages/DeadCode/src/Rector/Instanceof_/RemoveDuplicatedInstanceOfRector.php @@ -99,24 +99,6 @@ private function resolveDuplicatedInstancesOf(Node $node): void $this->duplicatedInstanceOfs = array_keys($instanceOfsByClass); } - private function createUniqueKeyForInstanceOf(Instanceof_ $instanceof): ?string - { - if (! $instanceof->expr instanceof Variable) { - return null; - } - $variableName = $this->getName($instanceof->expr); - if ($variableName === null) { - return null; - } - - $className = $this->getName($instanceof->class); - if ($className === null) { - return null; - } - - return $variableName . '_' . $className; - } - private function traverseBinaryOpAndRemoveDuplicatedInstanceOfs(BinaryOp $binaryOp): Node { $this->traverseNodesWithCallable([&$binaryOp], function (Node &$node): ?Node { @@ -138,10 +120,22 @@ private function traverseBinaryOpAndRemoveDuplicatedInstanceOfs(BinaryOp $binary return $binaryOp; } - private function removeClassFromDuplicatedInstanceOfs(string $variableClassKey): void + private function createUniqueKeyForInstanceOf(Instanceof_ $instanceof): ?string { - // remove just once - unset($this->duplicatedInstanceOfs[array_search($variableClassKey, $this->duplicatedInstanceOfs, true)]); + if (! $instanceof->expr instanceof Variable) { + return null; + } + $variableName = $this->getName($instanceof->expr); + if ($variableName === null) { + return null; + } + + $className = $this->getName($instanceof->class); + if ($className === null) { + return null; + } + + return $variableName . '_' . $className; } private function processBinaryWithFirstInstaneOf(Instanceof_ $instanceof, Expr $otherExpr): ?Expr @@ -158,4 +152,10 @@ private function processBinaryWithFirstInstaneOf(Instanceof_ $instanceof, Expr $ // remove left instanceof return $otherExpr; } + + private function removeClassFromDuplicatedInstanceOfs(string $variableClassKey): void + { + // remove just once + unset($this->duplicatedInstanceOfs[array_search($variableClassKey, $this->duplicatedInstanceOfs, true)]); + } } diff --git a/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php b/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php index 4611c5bc1bff..936c76166e27 100644 --- a/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php +++ b/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php @@ -110,40 +110,31 @@ public function refactor(Node $node): ?Node } /** - * @param StaticCall|MethodCall|FuncCall $node - * @param Expr[]|mixed[] $defaultValues - * @return int[] + * @param MethodCall|StaticCall|FuncCall $node */ - private function resolveKeysToRemove(Node $node, array $defaultValues): array + private function shouldSkip(Node $node): bool { - $keysToRemove = []; - $keysToKeep = []; - - /** @var int $key */ - foreach ($node->args as $key => $arg) { - if (! isset($defaultValues[$key])) { - $keysToKeep[] = $key; - continue; - } + if ($node->args === []) { + return true; + } - if ($this->areNodesEqual($defaultValues[$key], $arg->value)) { - $keysToRemove[] = $key; - } else { - $keysToKeep[] = $key; - } + if (! $node instanceof FuncCall) { + return false; } - if ($keysToRemove === []) { - return []; + $functionName = $this->getName($node->name); + if ($functionName === null) { + return false; } - if ($keysToKeep !== []) { - if (max($keysToKeep) > max($keysToRemove)) { - return []; - } + if (! function_exists($functionName)) { + return false; } - return $keysToRemove; + $reflectionFunction = new ReflectionFunction($functionName); + + // skip native functions, hard to analyze without stubs (stubs would make working with IDE non-practical) + return $reflectionFunction->isInternal(); } /** @@ -177,20 +168,40 @@ private function resolveDefaultValuesFromCall(Node $node): array } /** - * @return Node[] + * @param StaticCall|MethodCall|FuncCall $node + * @param Expr[]|mixed[] $defaultValues + * @return int[] */ - private function resolveDefaultParamValuesFromFunctionLike(FunctionLike $functionLike): array + private function resolveKeysToRemove(Node $node, array $defaultValues): array { - $defaultValues = []; - foreach ($functionLike->getParams() as $key => $param) { - if ($param->default === null) { + $keysToRemove = []; + $keysToKeep = []; + + /** @var int $key */ + foreach ($node->args as $key => $arg) { + if (! isset($defaultValues[$key])) { + $keysToKeep[] = $key; continue; } - $defaultValues[$key] = $param->default; + if ($this->areNodesEqual($defaultValues[$key], $arg->value)) { + $keysToRemove[] = $key; + } else { + $keysToKeep[] = $key; + } } - return $defaultValues; + if ($keysToRemove === []) { + return []; + } + + if ($keysToKeep !== []) { + if (max($keysToKeep) > max($keysToRemove)) { + return []; + } + } + + return $keysToRemove; } /** @@ -224,30 +235,19 @@ private function resolveFuncCallDefaultParamValues(string $nodeName): array } /** - * @param MethodCall|StaticCall|FuncCall $node + * @return Node[] */ - private function shouldSkip(Node $node): bool + private function resolveDefaultParamValuesFromFunctionLike(FunctionLike $functionLike): array { - if ($node->args === []) { - return true; - } - - if (! $node instanceof FuncCall) { - return false; - } - - $functionName = $this->getName($node->name); - if ($functionName === null) { - return false; - } + $defaultValues = []; + foreach ($functionLike->getParams() as $key => $param) { + if ($param->default === null) { + continue; + } - if (! function_exists($functionName)) { - return false; + $defaultValues[$key] = $param->default; } - $reflectionFunction = new ReflectionFunction($functionName); - - // skip native functions, hard to analyze without stubs (stubs would make working with IDE non-practical) - return $reflectionFunction->isInternal(); + return $defaultValues; } } diff --git a/packages/DeadCode/src/Rector/Plus/RemoveDeadZeroAndOneOperationRector.php b/packages/DeadCode/src/Rector/Plus/RemoveDeadZeroAndOneOperationRector.php index db06bb7262a8..6421683e53db 100644 --- a/packages/DeadCode/src/Rector/Plus/RemoveDeadZeroAndOneOperationRector.php +++ b/packages/DeadCode/src/Rector/Plus/RemoveDeadZeroAndOneOperationRector.php @@ -102,46 +102,6 @@ public function refactor(Node $node): ?Node return $changedNode; } - /** - * @param Plus|Minus $binaryOp - */ - private function processBinaryPlusAndMinus(BinaryOp $binaryOp): ?Expr - { - if ($this->isValue($binaryOp->left, 0)) { - if ($this->isNumberType($binaryOp->right)) { - return $binaryOp->right; - } - } - - if ($this->isValue($binaryOp->right, 0)) { - if ($this->isNumberType($binaryOp->left)) { - return $binaryOp->left; - } - } - - return null; - } - - /** - * @param Mul|Div $binaryOp - */ - private function processBinaryMulAndDiv(BinaryOp $binaryOp): ?Expr - { - if ($this->isValue($binaryOp->left, 1)) { - if ($this->isNumberType($binaryOp->right)) { - return $binaryOp->right; - } - } - - if ($this->isValue($binaryOp->right, 1)) { - if ($this->isNumberType($binaryOp->left)) { - return $binaryOp->left; - } - } - - return null; - } - private function processAssignOp(Node $node): ?Expr { // +=, -= @@ -181,4 +141,44 @@ private function processBinaryOp(Node $node): ?Expr return null; } + + /** + * @param Plus|Minus $binaryOp + */ + private function processBinaryPlusAndMinus(BinaryOp $binaryOp): ?Expr + { + if ($this->isValue($binaryOp->left, 0)) { + if ($this->isNumberType($binaryOp->right)) { + return $binaryOp->right; + } + } + + if ($this->isValue($binaryOp->right, 0)) { + if ($this->isNumberType($binaryOp->left)) { + return $binaryOp->left; + } + } + + return null; + } + + /** + * @param Mul|Div $binaryOp + */ + private function processBinaryMulAndDiv(BinaryOp $binaryOp): ?Expr + { + if ($this->isValue($binaryOp->left, 1)) { + if ($this->isNumberType($binaryOp->right)) { + return $binaryOp->right; + } + } + + if ($this->isValue($binaryOp->right, 1)) { + if ($this->isNumberType($binaryOp->left)) { + return $binaryOp->left; + } + } + + return null; + } } diff --git a/packages/DeadCode/src/Rector/Plus/RemoveZeroAndOneBinaryRector.php b/packages/DeadCode/src/Rector/Plus/RemoveZeroAndOneBinaryRector.php index 210313697b8e..38496f463a9e 100644 --- a/packages/DeadCode/src/Rector/Plus/RemoveZeroAndOneBinaryRector.php +++ b/packages/DeadCode/src/Rector/Plus/RemoveZeroAndOneBinaryRector.php @@ -83,6 +83,46 @@ public function refactor(Node $node): ?Node } } + private function processAssignOp(Node $node): ?Expr + { + // +=, -= + if ($node instanceof Node\Expr\AssignOp\Plus || $node instanceof Node\Expr\AssignOp\Minus) { + if (! $this->isValue($node->expr, 0)) { + return null; + } + + if ($this->isNumberType($node->var)) { + return $node->var; + } + } + + // *, / + if ($node instanceof Node\Expr\AssignOp\Mul || $node instanceof Node\Expr\AssignOp\Div) { + if (! $this->isValue($node->expr, 1)) { + return null; + } + if ($this->isNumberType($node->var)) { + return $node->var; + } + } + + return null; + } + + private function processBinaryOp(Node $node): ?Expr + { + if ($node instanceof Plus || $node instanceof Minus) { + return $this->processBinaryPlusAndMinus($node); + } + + // *, / + if ($node instanceof Mul || $node instanceof Div) { + return $this->processBinaryMulAndDiv($node); + } + + return null; + } + /** * @param Plus|Minus $binaryOp */ @@ -124,44 +164,4 @@ private function processBinaryMulAndDiv(BinaryOp $binaryOp): ?Expr return null; } - - private function processAssignOp(Node $node): ?Expr - { - // +=, -= - if ($node instanceof Node\Expr\AssignOp\Plus || $node instanceof Node\Expr\AssignOp\Minus) { - if (! $this->isValue($node->expr, 0)) { - return null; - } - - if ($this->isNumberType($node->var)) { - return $node->var; - } - } - - // *, / - if ($node instanceof Node\Expr\AssignOp\Mul || $node instanceof Node\Expr\AssignOp\Div) { - if (! $this->isValue($node->expr, 1)) { - return null; - } - if ($this->isNumberType($node->var)) { - return $node->var; - } - } - - return null; - } - - private function processBinaryOp(Node $node): ?Expr - { - if ($node instanceof Plus || $node instanceof Minus) { - return $this->processBinaryPlusAndMinus($node); - } - - // *, / - if ($node instanceof Mul || $node instanceof Div) { - return $this->processBinaryMulAndDiv($node); - } - - return null; - } } diff --git a/packages/DeadCode/src/Rector/Property/RemoveUnusedPrivatePropertyRector.php b/packages/DeadCode/src/Rector/Property/RemoveUnusedPrivatePropertyRector.php index 44d76444370a..94d149688088 100644 --- a/packages/DeadCode/src/Rector/Property/RemoveUnusedPrivatePropertyRector.php +++ b/packages/DeadCode/src/Rector/Property/RemoveUnusedPrivatePropertyRector.php @@ -89,31 +89,6 @@ public function refactor(Node $node): ?Node return $node; } - /** - * Matches all-only: "$this->property = x" - * If these is ANY OTHER use of property, e.g. process($this->property), it returns [] - * - * @param PropertyFetch[]|StaticPropertyFetch[] $propertyFetches - * @return Assign[] - */ - private function resolveUselessAssignNode(array $propertyFetches): array - { - $uselessAssigns = []; - - foreach ($propertyFetches as $propertyFetch) { - $propertyFetchParentNode = $propertyFetch->getAttribute(AttributeKey::PARENT_NODE); - - if ($propertyFetchParentNode instanceof Assign && $propertyFetchParentNode->var === $propertyFetch) { - $uselessAssigns[] = $propertyFetchParentNode; - } else { - // it is used another way as well → nothing to remove - return []; - } - } - - return $uselessAssigns; - } - private function shouldSkipProperty(Property $property): bool { if (! $property->isPrivate()) { @@ -157,4 +132,29 @@ private function shouldSkipProperty(Property $property): bool return false; } + + /** + * Matches all-only: "$this->property = x" + * If these is ANY OTHER use of property, e.g. process($this->property), it returns [] + * + * @param PropertyFetch[]|StaticPropertyFetch[] $propertyFetches + * @return Assign[] + */ + private function resolveUselessAssignNode(array $propertyFetches): array + { + $uselessAssigns = []; + + foreach ($propertyFetches as $propertyFetch) { + $propertyFetchParentNode = $propertyFetch->getAttribute(AttributeKey::PARENT_NODE); + + if ($propertyFetchParentNode instanceof Assign && $propertyFetchParentNode->var === $propertyFetch) { + $uselessAssigns[] = $propertyFetchParentNode; + } else { + // it is used another way as well → nothing to remove + return []; + } + } + + return $uselessAssigns; + } } diff --git a/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php b/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php index 7432925cef73..7b0c618fdcc2 100644 --- a/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php +++ b/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php @@ -83,6 +83,44 @@ public function refactor(Node $node): ?Node return $node; } + private function shouldSkipProperty(Class_ $class, Property $property): bool + { + // this relation already is or has uuid property + if ($this->isName($property, '*Uuid')) { + return true; + } + + $uuidPropertyName = $this->getName($property) . 'Uuid'; + if ($this->hasClassPropertyName($class, $uuidPropertyName)) { + return true; + } + + $targetEntity = $this->getTargetEntity($property); + if ($targetEntity === null) { + return true; + } + + if (! property_exists($targetEntity, 'uuid')) { + return true; + } + + /** @var PhpDocInfo|null $propertyPhpDocInfo */ + $propertyPhpDocInfo = $this->getPhpDocInfo($property); + if ($propertyPhpDocInfo === null) { + return true; + } + + $oneToOneTagValueNode = $propertyPhpDocInfo->getByType(OneToOneTagValueNode::class); + if ($oneToOneTagValueNode) { + // skip mappedBy oneToOne, as the column doesn't really exist + if ($oneToOneTagValueNode->getMappedBy()) { + return true; + } + } + + return false; + } + /** * Creates duplicated property, that has "*uuidSuffix" * and nullable join column, so we cna complete them manually @@ -107,6 +145,19 @@ private function createMirrorNullable(Property $property): Property return $propertyWithUuid; } + private function hasClassPropertyName(Class_ $node, string $uuidPropertyName): bool + { + foreach ($node->getProperties() as $property) { + if (! $this->isName($property, $uuidPropertyName)) { + continue; + } + + return true; + } + + return false; + } + private function updateDocComment(Property $property): void { /** @var PhpDocInfo $propertyPhpDocInfo */ @@ -124,6 +175,25 @@ private function updateDocComment(Property $property): void $this->docBlockManipulator->updateNodeWithPhpDocInfo($property, $propertyPhpDocInfo); } + private function addNewPropertyToCollector( + Property $property, + string $oldPropertyName, + string $uuidPropertyName + ): void { + /** @var string $className */ + $className = $property->getAttribute(AttributeKey::CLASS_NAME); + + /** @var DoctrineRelationTagValueNodeInterface $doctrineRelationTagValueNode */ + $doctrineRelationTagValueNode = $this->getDoctrineRelationTagValueNode($property); + + $this->uuidMigrationDataCollector->addClassToManyRelationProperty( + $className, + $oldPropertyName, + $uuidPropertyName, + $doctrineRelationTagValueNode + ); + } + private function refactorToManyPropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo, Property $property): void { $doctrineJoinColumnTagValueNode = $propertyPhpDocInfo->getByType(JoinColumnTagValueNode::class); @@ -149,74 +219,4 @@ private function refactorToOnePropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo) $propertyPhpDocInfo->getPhpDocNode()->children[] = $this->phpDocTagNodeFactory->createJoinColumnTagNode(); } } - - private function hasClassPropertyName(Class_ $node, string $uuidPropertyName): bool - { - foreach ($node->getProperties() as $property) { - if (! $this->isName($property, $uuidPropertyName)) { - continue; - } - - return true; - } - - return false; - } - - private function shouldSkipProperty(Class_ $class, Property $property): bool - { - // this relation already is or has uuid property - if ($this->isName($property, '*Uuid')) { - return true; - } - - $uuidPropertyName = $this->getName($property) . 'Uuid'; - if ($this->hasClassPropertyName($class, $uuidPropertyName)) { - return true; - } - - $targetEntity = $this->getTargetEntity($property); - if ($targetEntity === null) { - return true; - } - - if (! property_exists($targetEntity, 'uuid')) { - return true; - } - - /** @var PhpDocInfo|null $propertyPhpDocInfo */ - $propertyPhpDocInfo = $this->getPhpDocInfo($property); - if ($propertyPhpDocInfo === null) { - return true; - } - - $oneToOneTagValueNode = $propertyPhpDocInfo->getByType(OneToOneTagValueNode::class); - if ($oneToOneTagValueNode) { - // skip mappedBy oneToOne, as the column doesn't really exist - if ($oneToOneTagValueNode->getMappedBy()) { - return true; - } - } - - return false; - } - - private function addNewPropertyToCollector( - Property $property, - string $oldPropertyName, - string $uuidPropertyName - ): void { - /** @var string $className */ - $className = $property->getAttribute(AttributeKey::CLASS_NAME); - - /** @var DoctrineRelationTagValueNodeInterface $doctrineRelationTagValueNode */ - $doctrineRelationTagValueNode = $this->getDoctrineRelationTagValueNode($property); - - $this->uuidMigrationDataCollector->addClassToManyRelationProperty( - $className, - $oldPropertyName, - $uuidPropertyName, - $doctrineRelationTagValueNode - ); - } } diff --git a/packages/Doctrine/src/Rector/Class_/ManagerRegistryGetManagerToEntityManagerRector.php b/packages/Doctrine/src/Rector/Class_/ManagerRegistryGetManagerToEntityManagerRector.php index c97d0a12e1f4..88accded04c0 100644 --- a/packages/Doctrine/src/Rector/Class_/ManagerRegistryGetManagerToEntityManagerRector.php +++ b/packages/Doctrine/src/Rector/Class_/ManagerRegistryGetManagerToEntityManagerRector.php @@ -132,46 +132,6 @@ public function refactor(Node $node): ?Node return $node; } - private function isRegistryGetManagerMethodCall(Assign $assign): bool - { - if (! $assign->expr instanceof MethodCall) { - return false; - } - - if (! $this->isObjectType($assign->expr->var, ManagerRegistry::class)) { - return false; - } - - if (! $this->isName($assign->expr->name, 'getManager')) { - return false; - } - - return true; - } - - /** - * @param Class_ $class - */ - private function removeAssignGetRepositoryCalls(Class_ $class): void - { - $this->traverseNodesWithCallable($class->stmts, function (Node $node) { - if (! $node instanceof Assign) { - return null; - } - - if (! $this->isRegistryGetManagerMethodCall($node)) { - return null; - } - - $this->removeNode($node); - }); - } - - private function createEntityManagerParam(): Param - { - return new Param(new Variable('entityManager'), null, new FullyQualified(EntityManagerInterface::class)); - } - /** * @return string[] */ @@ -198,13 +158,9 @@ private function resolveManagerRegistryCalledMethodNames(Class_ $class): array return array_unique($registryCalledMethods); } - private function removeManagerRegistryDependency( - Class_ $class, - ClassMethod $classMethod, - Param $registryParam - ): void { - // remove constructor param: $managerRegistry - foreach ($classMethod->params as $key => $param) { + private function resolveManagerRegistryParam(ClassMethod $classMethod): ?Param + { + foreach ($classMethod->params as $param) { if ($param->type === null) { continue; } @@ -213,27 +169,21 @@ private function removeManagerRegistryDependency( continue; } - unset($classMethod->params[$key]); + $classMethod->params[] = $this->createEntityManagerParam(); + + return $param; } - $this->removeRegistryDependencyAssign($class, $classMethod, $registryParam); + return null; } - private function addConstructorDependencyWithProperty( + private function removeManagerRegistryDependency( Class_ $class, ClassMethod $classMethod, - string $name, - ObjectType $objectType + Param $registryParam ): void { - $assign = $this->createSameNameThisAssign($name); - $classMethod->stmts[] = new Expression($assign); - - $this->addPropertyToClass($class, $objectType, $name); - } - - private function resolveManagerRegistryParam(ClassMethod $classMethod): ?Param - { - foreach ($classMethod->params as $param) { + // remove constructor param: $managerRegistry + foreach ($classMethod->params as $key => $param) { if ($param->type === null) { continue; } @@ -242,35 +192,69 @@ private function resolveManagerRegistryParam(ClassMethod $classMethod): ?Param continue; } - $classMethod->params[] = $this->createEntityManagerParam(); - - return $param; + unset($classMethod->params[$key]); } - return null; + $this->removeRegistryDependencyAssign($class, $classMethod, $registryParam); } - private function removeManagerRegistryProperty(Class_ $class, Assign $assign): void + /** + * Before: + * $entityRegistry-> + * + * After: + * $this->entityManager-> + */ + private function replaceEntityRegistryVariableWithEntityManagerProperty(Class_ $node): void { - $managerRegistryPropertyName = $this->getName($assign->var); + $this->traverseNodesWithCallable($node->stmts, function (Node $node): ?PropertyFetch { + if (! $node instanceof Variable) { + return null; + } - $this->traverseNodesWithCallable($class->stmts, function (Node $node) use ( - $managerRegistryPropertyName - ): ?int { - if (! $node instanceof Property) { + if (! $this->isObjectType($node, ObjectManager::class)) { return null; } - if (! $this->isName($node, $managerRegistryPropertyName)) { + return new PropertyFetch(new Variable('this'), 'entityManager'); + }); + } + + /** + * @param Class_ $class + */ + private function removeAssignGetRepositoryCalls(Class_ $class): void + { + $this->traverseNodesWithCallable($class->stmts, function (Node $node) { + if (! $node instanceof Assign) { return null; } - $this->removeNode($node); + if (! $this->isRegistryGetManagerMethodCall($node)) { + return null; + } - return NodeTraverser::STOP_TRAVERSAL; + $this->removeNode($node); }); } + private function addConstructorDependencyWithProperty( + Class_ $class, + ClassMethod $classMethod, + string $name, + ObjectType $objectType + ): void { + $assign = $this->createSameNameThisAssign($name); + $classMethod->stmts[] = new Expression($assign); + + $this->addPropertyToClass($class, $objectType, $name); + } + + private function createEntityManagerParam(): Param + { + return new Param(new Variable('entityManager'), null, new FullyQualified(EntityManagerInterface::class)); + } + private function removeRegistryDependencyAssign(Class_ $class, ClassMethod $classMethod, Param $registryParam): void { foreach ((array) $classMethod->stmts as $constructorMethodStmt) { @@ -293,6 +277,23 @@ private function removeRegistryDependencyAssign(Class_ $class, ClassMethod $clas } } + private function isRegistryGetManagerMethodCall(Assign $assign): bool + { + if (! $assign->expr instanceof MethodCall) { + return false; + } + + if (! $this->isObjectType($assign->expr->var, ManagerRegistry::class)) { + return false; + } + + if (! $this->isName($assign->expr->name, 'getManager')) { + return false; + } + + return true; + } + /** * Creates: "$this->value = $value;" */ @@ -303,25 +304,24 @@ private function createSameNameThisAssign(string $name): Assign return new Assign($propertyFetch, new Variable($name)); } - /** - * Before: - * $entityRegistry-> - * - * After: - * $this->entityManager-> - */ - private function replaceEntityRegistryVariableWithEntityManagerProperty(Class_ $node): void + private function removeManagerRegistryProperty(Class_ $class, Assign $assign): void { - $this->traverseNodesWithCallable($node->stmts, function (Node $node): ?PropertyFetch { - if (! $node instanceof Variable) { + $managerRegistryPropertyName = $this->getName($assign->var); + + $this->traverseNodesWithCallable($class->stmts, function (Node $node) use ( + $managerRegistryPropertyName + ): ?int { + if (! $node instanceof Property) { return null; } - if (! $this->isObjectType($node, ObjectManager::class)) { + if (! $this->isName($node, $managerRegistryPropertyName)) { return null; } - return new PropertyFetch(new Variable('this'), 'entityManager'); + $this->removeNode($node); + + return NodeTraverser::STOP_TRAVERSAL; }); } } diff --git a/packages/Doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php b/packages/Doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php index 2c2ebbc709ff..906c587c3010 100644 --- a/packages/Doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php +++ b/packages/Doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php @@ -86,16 +86,6 @@ public function refactor(Node $node): ?Node return $this->createMethodCall($entityMethodCall, 'equals', [$fromStringValue]); } - private function isAlreadyUuidType(Expr $expr): bool - { - $comparedValueObjectType = $this->getStaticType($expr); - if (! $comparedValueObjectType instanceof ObjectType) { - return false; - } - - return $comparedValueObjectType->getClassName() === UuidInterface::class; - } - /** * @return Expr[]|null */ @@ -119,4 +109,14 @@ private function matchEntityCallAndComparedVariable(Node $node): ?array return null; } + + private function isAlreadyUuidType(Expr $expr): bool + { + $comparedValueObjectType = $this->getStaticType($expr); + if (! $comparedValueObjectType instanceof ObjectType) { + return false; + } + + return $comparedValueObjectType->getClassName() === UuidInterface::class; + } } diff --git a/packages/Doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php b/packages/Doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php index 849d97af753d..f0320e610fc4 100644 --- a/packages/Doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php +++ b/packages/Doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php @@ -204,6 +204,14 @@ private function getSetUuidMethodCallOnSameVariable(MethodCall $methodCall): ?Me }); } + private function createUuidStringNode(): String_ + { + $uuidValue = Uuid::uuid4(); + $uuidValueString = $uuidValue->toString(); + + return new String_($uuidValueString); + } + private function isUuidType(Expr $expr): bool { $argumentStaticType = $this->getStaticType($expr); @@ -215,12 +223,4 @@ private function isUuidType(Expr $expr): bool return $argumentStaticType->getClassName() === Uuid::class; } - - private function createUuidStringNode(): String_ - { - $uuidValue = Uuid::uuid4(); - $uuidValueString = $uuidValue->toString(); - - return new String_($uuidValueString); - } } diff --git a/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php b/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php index bca95b7557f5..6d8c390a6b6d 100644 --- a/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php +++ b/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php @@ -199,6 +199,22 @@ private function areStringsSameWithoutSpaces(string $firstString, string $second return $this->clearString($firstString) === $this->clearString($secondString); } + /** + * Add empty line in the end, if it is in the original tokens + */ + private function resolveLastEmptyLine(string $prettyPrintContent): string + { + $tokens = $this->lexer->getTokens(); + $lastToken = array_pop($tokens); + if ($lastToken) { + if (Strings::contains($lastToken[1], "\n")) { + $prettyPrintContent = trim($prettyPrintContent) . PHP_EOL; + } + } + + return $prettyPrintContent; + } + private function clearString(string $string): string { $string = $this->removeComments($string); @@ -222,20 +238,4 @@ private function removeComments(string $string): string return Strings::replace($string, '#\n\s*\n#', "\n"); } - - /** - * Add empty line in the end, if it is in the original tokens - */ - private function resolveLastEmptyLine(string $prettyPrintContent): string - { - $tokens = $this->lexer->getTokens(); - $lastToken = array_pop($tokens); - if ($lastToken) { - if (Strings::contains($lastToken[1], "\n")) { - $prettyPrintContent = trim($prettyPrintContent) . PHP_EOL; - } - } - - return $prettyPrintContent; - } } diff --git a/packages/Laravel/src/Rector/Class_/InlineValidationRulesToArrayDefinitionRector.php b/packages/Laravel/src/Rector/Class_/InlineValidationRulesToArrayDefinitionRector.php index 56c880910365..2737c750e6bf 100644 --- a/packages/Laravel/src/Rector/Class_/InlineValidationRulesToArrayDefinitionRector.php +++ b/packages/Laravel/src/Rector/Class_/InlineValidationRulesToArrayDefinitionRector.php @@ -85,55 +85,31 @@ public function refactor(Node $node): ?Node return $node; } - /** - * @return Expr[] - */ - private function transformRulesSetToExpressionsArray(Expr $expr): array + private function shouldSkipArrayItem(ArrayItem $arrayItem): bool { - if ($expr instanceof String_) { - return array_map(static function (string $value): String_ { - return new String_($value); - }, explode('|', $expr->value)); + $classNode = $arrayItem->getAttribute(AttributeKey::CLASS_NODE); + if ($classNode === null) { + return true; } - if ($expr instanceof Concat) { - $left = $this->transformRulesSetToExpressionsArray($expr->left); - $expr->left = $left[count($left) - 1]; - - $right = $this->transformRulesSetToExpressionsArray($expr->right); - $expr->right = $right[0]; - - return array_merge(array_slice($left, 0, -1), [$expr], array_slice($right, 1)); + if (! $this->isObjectType($classNode, self::FORM_REQUEST_CLASS)) { + return true; } - if ($expr instanceof ClassConstFetch || $expr instanceof MethodCall || $expr instanceof FuncCall) { - return [$expr]; + $methodNode = $arrayItem->getAttribute(AttributeKey::METHOD_NODE); + if ($methodNode === null) { + return true; } - throw new ShouldNotHappenException('Unexpected call ' . get_class($expr)); - } - - private function transformConcatExpressionToSingleString(Concat $concat): ?string - { - $output = ''; - - foreach ([$concat->left, $concat->right] as $expressionPart) { - if ($expressionPart instanceof String_) { - $output .= $expressionPart->value; - } elseif ($expressionPart instanceof Concat) { - $output .= $this->transformConcatExpressionToSingleString($expressionPart); - } elseif ($expressionPart instanceof ClassConstFetch) { - /** @var Node\Name $name */ - $name = $expressionPart->class->getAttribute('originalName'); + if (! $this->isName($methodNode, 'rules')) { + return true; + } - $output .= implode('\\', $name->parts); - } else { - // unable to process - return null; - } + if (! $arrayItem->value instanceof String_ && ! $arrayItem->value instanceof Concat) { + return true; } - return $output; + return false; } /** @@ -170,30 +146,54 @@ private function createNewRules(ArrayItem $arrayItem): array return $newRules; } - private function shouldSkipArrayItem(ArrayItem $arrayItem): bool + /** + * @return Expr[] + */ + private function transformRulesSetToExpressionsArray(Expr $expr): array { - $classNode = $arrayItem->getAttribute(AttributeKey::CLASS_NODE); - if ($classNode === null) { - return true; + if ($expr instanceof String_) { + return array_map(static function (string $value): String_ { + return new String_($value); + }, explode('|', $expr->value)); } - if (! $this->isObjectType($classNode, self::FORM_REQUEST_CLASS)) { - return true; - } + if ($expr instanceof Concat) { + $left = $this->transformRulesSetToExpressionsArray($expr->left); + $expr->left = $left[count($left) - 1]; - $methodNode = $arrayItem->getAttribute(AttributeKey::METHOD_NODE); - if ($methodNode === null) { - return true; + $right = $this->transformRulesSetToExpressionsArray($expr->right); + $expr->right = $right[0]; + + return array_merge(array_slice($left, 0, -1), [$expr], array_slice($right, 1)); } - if (! $this->isName($methodNode, 'rules')) { - return true; + if ($expr instanceof ClassConstFetch || $expr instanceof MethodCall || $expr instanceof FuncCall) { + return [$expr]; } - if (! $arrayItem->value instanceof String_ && ! $arrayItem->value instanceof Concat) { - return true; + throw new ShouldNotHappenException('Unexpected call ' . get_class($expr)); + } + + private function transformConcatExpressionToSingleString(Concat $concat): ?string + { + $output = ''; + + foreach ([$concat->left, $concat->right] as $expressionPart) { + if ($expressionPart instanceof String_) { + $output .= $expressionPart->value; + } elseif ($expressionPart instanceof Concat) { + $output .= $this->transformConcatExpressionToSingleString($expressionPart); + } elseif ($expressionPart instanceof ClassConstFetch) { + /** @var Node\Name $name */ + $name = $expressionPart->class->getAttribute('originalName'); + + $output .= implode('\\', $name->parts); + } else { + // unable to process + return null; + } } - return false; + return $output; } } diff --git a/packages/Laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php b/packages/Laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php index 6e433a78e3ed..2c62231df088 100644 --- a/packages/Laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php +++ b/packages/Laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php @@ -87,26 +87,6 @@ public function refactor(Node $node): ?Node return $node; } - /** - * @param StaticCall|MethodCall $expr - * @return StaticCall|MethodCall - */ - private function processArgumentPosition(Expr $expr, int $argumentPosition): Expr - { - $oldValue = $expr->args[$argumentPosition]->value; - if (! $oldValue instanceof LNumber) { - if (! $this->getStaticType($oldValue) instanceof ConstantIntegerType) { - return $expr; - } - } - - $newArgumentValue = new Mul($oldValue, new LNumber(60)); - - $expr->args[$argumentPosition] = new Arg($newArgumentValue); - - return $expr; - } - /** * @return int[][] */ @@ -126,4 +106,24 @@ private function getTypesToMethods(): array ], ]; } + + /** + * @param StaticCall|MethodCall $expr + * @return StaticCall|MethodCall + */ + private function processArgumentPosition(Expr $expr, int $argumentPosition): Expr + { + $oldValue = $expr->args[$argumentPosition]->value; + if (! $oldValue instanceof LNumber) { + if (! $this->getStaticType($oldValue) instanceof ConstantIntegerType) { + return $expr; + } + } + + $newArgumentValue = new Mul($oldValue, new LNumber(60)); + + $expr->args[$argumentPosition] = new Arg($newArgumentValue); + + return $expr; + } } diff --git a/packages/NetteTesterToPHPUnit/src/AssertManipulator.php b/packages/NetteTesterToPHPUnit/src/AssertManipulator.php index 67c7f2e6a1ec..e1525185909e 100644 --- a/packages/NetteTesterToPHPUnit/src/AssertManipulator.php +++ b/packages/NetteTesterToPHPUnit/src/AssertManipulator.php @@ -133,6 +133,20 @@ public function processStaticCall(StaticCall $staticCall): Node return $staticCall; } + private function processContainsCall(StaticCall $staticCall): void + { + if ($this->nodeTypeResolver->isStringOrUnionStringOnlyType($staticCall->args[1]->value)) { + $name = $this->nameResolver->isName( + $staticCall, + 'contains' + ) ? 'assertStringContainsString' : 'assertStringNotContainsString'; + } else { + $name = $this->nameResolver->isName($staticCall, 'contains') ? 'assertContains' : 'assertNotContains'; + } + + $staticCall->name = new Identifier($name); + } + private function processExceptionCall(StaticCall $staticCall): void { $method = 'expectException'; @@ -184,6 +198,37 @@ private function processExceptionCall(StaticCall $staticCall): void $this->nodeRemovingCommander->addNode($staticCall); } + private function processTypeCall(StaticCall $staticCall): void + { + $value = $this->valueResolver->getValue($staticCall->args[0]->value); + + $typeToMethod = [ + 'list' => 'assertIsArray', + 'array' => 'assertIsArray', + 'bool' => 'assertIsBool', + 'callable' => 'assertIsCallable', + 'float' => 'assertIsFloat', + 'int' => 'assertIsInt', + 'integer' => 'assertIsInt', + 'object' => 'assertIsObject', + 'resource' => 'assertIsResource', + 'string' => 'assertIsString', + 'scalar' => 'assertIsScalar', + ]; + + if (isset($typeToMethod[$value])) { + $staticCall->name = new Identifier($typeToMethod[$value]); + unset($staticCall->args[0]); + array_values($staticCall->args); + } elseif ($value === 'null') { + $staticCall->name = new Identifier('assertNull'); + unset($staticCall->args[0]); + array_values($staticCall->args); + } else { + $staticCall->name = new Identifier('assertInstanceOf'); + } + } + private function processNoErrorCall(StaticCall $staticCall): void { /** @var Closure $closure */ @@ -228,51 +273,6 @@ private function processTruthyOrFalseyCall(StaticCall $staticCall): Expr return $call; } - private function processTypeCall(StaticCall $staticCall): void - { - $value = $this->valueResolver->getValue($staticCall->args[0]->value); - - $typeToMethod = [ - 'list' => 'assertIsArray', - 'array' => 'assertIsArray', - 'bool' => 'assertIsBool', - 'callable' => 'assertIsCallable', - 'float' => 'assertIsFloat', - 'int' => 'assertIsInt', - 'integer' => 'assertIsInt', - 'object' => 'assertIsObject', - 'resource' => 'assertIsResource', - 'string' => 'assertIsString', - 'scalar' => 'assertIsScalar', - ]; - - if (isset($typeToMethod[$value])) { - $staticCall->name = new Identifier($typeToMethod[$value]); - unset($staticCall->args[0]); - array_values($staticCall->args); - } elseif ($value === 'null') { - $staticCall->name = new Identifier('assertNull'); - unset($staticCall->args[0]); - array_values($staticCall->args); - } else { - $staticCall->name = new Identifier('assertInstanceOf'); - } - } - - private function processContainsCall(StaticCall $staticCall): void - { - if ($this->nodeTypeResolver->isStringOrUnionStringOnlyType($staticCall->args[1]->value)) { - $name = $this->nameResolver->isName( - $staticCall, - 'contains' - ) ? 'assertStringContainsString' : 'assertStringNotContainsString'; - } else { - $name = $this->nameResolver->isName($staticCall, 'contains') ? 'assertContains' : 'assertNotContains'; - } - - $staticCall->name = new Identifier($name); - } - private function sholdBeStaticCall(StaticCall $staticCall): bool { return ! (bool) $staticCall->getAttribute(AttributeKey::CLASS_NODE); diff --git a/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php b/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php index 89040c6ed8c0..ea4d7e6641a7 100644 --- a/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php +++ b/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php @@ -95,11 +95,6 @@ public function refactor(Node $node): ?Node return $node; } - private function processExtends(Class_ $class): void - { - $class->extends = new FullyQualified('PHPUnit\Framework\TestCase'); - } - private function processAboveTestInclude(Include_ $include): void { if ($include->getAttribute(AttributeKey::CLASS_NODE) === null) { @@ -114,6 +109,11 @@ private function processUnderTestRun(MethodCall $methodCall): void } } + private function processExtends(Class_ $class): void + { + $class->extends = new FullyQualified('PHPUnit\Framework\TestCase'); + } + private function processMethods(Class_ $class): void { foreach ($class->getMethods() as $classMethod) { diff --git a/packages/NetteToSymfony/src/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector.php b/packages/NetteToSymfony/src/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector.php index 92b104ab4ff8..df641f409408 100644 --- a/packages/NetteToSymfony/src/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector.php +++ b/packages/NetteToSymfony/src/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector.php @@ -182,6 +182,20 @@ private function matchClassConstKeys(ArrayItem $arrayItem): ?EventInfo return null; } + private function processMethodArgument(string $class, string $method, EventInfo $eventInfo): void + { + $classMethodNode = $this->parsedNodesByType->findMethod($method, $class); + if ($classMethodNode === null) { + return; + } + + if (count((array) $classMethodNode->params) !== 1) { + return; + } + + $classMethodNode->params[0]->type = new FullyQualified($eventInfo->getEventClass()); + } + private function resolveClassConstAliasMatch(ArrayItem $arrayItem, EventInfo $eventInfo): bool { foreach ($eventInfo->getOldClassConstAlaises() as $netteClassConst) { @@ -197,18 +211,4 @@ private function resolveClassConstAliasMatch(ArrayItem $arrayItem, EventInfo $ev return false; } - - private function processMethodArgument(string $class, string $method, EventInfo $eventInfo): void - { - $classMethodNode = $this->parsedNodesByType->findMethod($method, $class); - if ($classMethodNode === null) { - return; - } - - if (count((array) $classMethodNode->params) !== 1) { - return; - } - - $classMethodNode->params[0]->type = new FullyQualified($eventInfo->getEventClass()); - } } diff --git a/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php b/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php index 3976b1e2efc4..9c990075432e 100644 --- a/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php +++ b/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php @@ -235,6 +235,31 @@ private function resolveControllerClassMethod(RouteInfo $routeInfo): ?ClassMetho return $classNode->getMethod($routeInfo->getMethod()); } + private function createSymfonyRoutePhpDocTagValueNode(RouteInfo $routeInfo): SymfonyRouteTagValueNode + { + return new SymfonyRouteTagValueNode($routeInfo->getPath(), null, $routeInfo->getHttpMethods()); + } + + private function addSymfonyRouteShortTagNodeWithUse( + SymfonyRouteTagValueNode $symfonyRouteTagValueNode, + ClassMethod $classMethod + ): void { + $symfonyRoutePhpDocTagNode = new SpacelessPhpDocTagNode( + SymfonyRouteTagValueNode::SHORT_NAME, + $symfonyRouteTagValueNode + ); + + $this->docBlockManipulator->addTag($classMethod, $symfonyRoutePhpDocTagNode); + + $symfonyRouteUseObjectType = new FullyQualifiedObjectType(SymfonyRouteTagValueNode::CLASS_NAME); + $this->addUseType($symfonyRouteUseObjectType, $classMethod); + + // remove + $this->removeShortUse('Route', $classMethod); + + $classMethod->setAttribute(self::HAS_FRESH_ROUTE_ANNOTATION_ATTRIBUTE, true); + } + private function completeImplicitRoutes(): void { $presenterClasses = $this->parsedNodesByType->findClassesBySuffix('Presenter'); @@ -327,29 +352,4 @@ private function resolvePathFromClassAndMethodNodes(Class_ $classNode, ClassMeth return $presenterPart . '/' . $actionPart; } - - private function createSymfonyRoutePhpDocTagValueNode(RouteInfo $routeInfo): SymfonyRouteTagValueNode - { - return new SymfonyRouteTagValueNode($routeInfo->getPath(), null, $routeInfo->getHttpMethods()); - } - - private function addSymfonyRouteShortTagNodeWithUse( - SymfonyRouteTagValueNode $symfonyRouteTagValueNode, - ClassMethod $classMethod - ): void { - $symfonyRoutePhpDocTagNode = new SpacelessPhpDocTagNode( - SymfonyRouteTagValueNode::SHORT_NAME, - $symfonyRouteTagValueNode - ); - - $this->docBlockManipulator->addTag($classMethod, $symfonyRoutePhpDocTagNode); - - $symfonyRouteUseObjectType = new FullyQualifiedObjectType(SymfonyRouteTagValueNode::CLASS_NAME); - $this->addUseType($symfonyRouteUseObjectType, $classMethod); - - // remove - $this->removeShortUse('Route', $classMethod); - - $classMethod->setAttribute(self::HAS_FRESH_ROUTE_ANNOTATION_ATTRIBUTE, true); - } } diff --git a/packages/NetteToSymfony/src/Rector/Class_/NetteFormToSymfonyFormRector.php b/packages/NetteToSymfony/src/Rector/Class_/NetteFormToSymfonyFormRector.php index 25c71e1def23..6858952ad3af 100644 --- a/packages/NetteToSymfony/src/Rector/Class_/NetteFormToSymfonyFormRector.php +++ b/packages/NetteToSymfony/src/Rector/Class_/NetteFormToSymfonyFormRector.php @@ -152,6 +152,31 @@ private function processNew(New_ $new): ?MethodCall return $this->createMethodCall('this', 'createFormBuilder'); } + private function processAddMethod(MethodCall $methodCall, string $method, string $classType): void + { + $methodCall->name = new Identifier('add'); + + // remove unused params + if ($method === 'addText') { + unset($methodCall->args[3], $methodCall->args[4]); + } + + // has label + $optionsArray = new Array_(); + if (isset($methodCall->args[1])) { + $optionsArray->items[] = new ArrayItem($methodCall->args[1]->value, new String_('label')); + } + + $this->addChoiceTypeOptions($method, $optionsArray); + $this->addMultiFileTypeOptions($method, $optionsArray); + + $methodCall->args[1] = new Arg($this->createClassConstantReference($classType)); + + if (count($optionsArray->items) > 0) { + $methodCall->args[2] = new Arg($optionsArray); + } + } + private function addChoiceTypeOptions(string $method, Array_ $optionsArray): void { if ($method === 'addSelect') { @@ -181,31 +206,6 @@ private function addChoiceTypeOptions(string $method, Array_ $optionsArray): voi ); } - private function processAddMethod(MethodCall $methodCall, string $method, string $classType): void - { - $methodCall->name = new Identifier('add'); - - // remove unused params - if ($method === 'addText') { - unset($methodCall->args[3], $methodCall->args[4]); - } - - // has label - $optionsArray = new Array_(); - if (isset($methodCall->args[1])) { - $optionsArray->items[] = new ArrayItem($methodCall->args[1]->value, new String_('label')); - } - - $this->addChoiceTypeOptions($method, $optionsArray); - $this->addMultiFileTypeOptions($method, $optionsArray); - - $methodCall->args[1] = new Arg($this->createClassConstantReference($classType)); - - if (count($optionsArray->items) > 0) { - $methodCall->args[2] = new Arg($optionsArray); - } - } - private function addMultiFileTypeOptions(string $method, Array_ $optionsArray): void { if ($method !== 'addMultiUpload') { diff --git a/packages/NodeTypeResolver/src/NodeTypeResolver.php b/packages/NodeTypeResolver/src/NodeTypeResolver.php index adc37aff1a7e..a6e5a820e76f 100644 --- a/packages/NodeTypeResolver/src/NodeTypeResolver.php +++ b/packages/NodeTypeResolver/src/NodeTypeResolver.php @@ -413,6 +413,67 @@ private function addPerNodeTypeResolver(PerNodeTypeResolverInterface $perNodeTyp } } + /** + * @param mixed $requiredType + */ + private function ensureRequiredTypeIsStringOrObjectType($requiredType, string $location): void + { + if (is_string($requiredType)) { + return; + } + + if ($requiredType instanceof ObjectType) { + return; + } + + $reportedType = is_object($requiredType) ? get_class($requiredType) : $requiredType; + + throw new ShouldNotHappenException(sprintf( + 'Value passed to "%s()" must be string or "%s". "%s" given', + $location, + ObjectType::class, + $reportedType + )); + } + + private function isFnMatch(Node $node, string $requiredType): bool + { + $objectType = $this->getObjectType($node); + + $classNames = TypeUtils::getDirectClassNames($objectType); + foreach ($classNames as $className) { + if ($this->isObjectTypeFnMatch($className, $requiredType)) { + return true; + } + } + + return false; + } + + private function isUnionNullTypeOfRequiredType(ObjectType $objectType, Type $resolvedType): bool + { + if (! $resolvedType instanceof UnionType) { + return false; + } + + if (count($resolvedType->getTypes()) !== 2) { + return false; + } + + $firstType = $resolvedType->getTypes()[0]; + $secondType = $resolvedType->getTypes()[1]; + + if ($firstType instanceof NullType && $secondType instanceof ObjectType) { + $resolvedType = $secondType; + } elseif ($secondType instanceof NullType && $firstType instanceof ObjectType) { + $resolvedType = $firstType; + } else { + return false; + } + + return is_a($resolvedType->getClassName(), $objectType->getClassName(), true); + } + /** * @param ClassConst|ClassMethod $node */ @@ -427,6 +488,34 @@ private function resolveClassNode(Node $node): Type return $this->resolve($classNode); } + private function resolveStaticCallType(StaticCall $staticCall): Type + { + $classType = $this->resolve($staticCall->class); + $methodName = $this->nameResolver->getName($staticCall->name); + + // no specific method found, return class types, e.g. ::$method() + if (! is_string($methodName)) { + return $classType; + } + + $classNames = TypeUtils::getDirectClassNames($classType); + foreach ($classNames as $className) { + if (! method_exists($className, $methodName)) { + continue; + } + + /** @var Scope|null $nodeScope */ + $nodeScope = $staticCall->getAttribute(AttributeKey::SCOPE); + if ($nodeScope === null) { + return $classType; + } + + return $nodeScope->getType($staticCall); + } + + return $classType; + } + private function resolveFirstType(Node $node): Type { // nodes that cannot be resolver by PHPStan @@ -464,32 +553,29 @@ private function resolveFirstType(Node $node): Type return $type; } - private function resolveStaticCallType(StaticCall $staticCall): Type + private function unionWithParentClassesInterfacesAndUsedTraits(Type $type): Type { - $classType = $this->resolve($staticCall->class); - $methodName = $this->nameResolver->getName($staticCall->name); + if ($type instanceof TypeWithClassName) { + if (! ClassExistenceStaticHelper::doesClassLikeExist($type->getClassName())) { + return $type; + } - // no specific method found, return class types, e.g. ::$method() - if (! is_string($methodName)) { - return $classType; + $allTypes = $this->getClassLikeTypesByClassName($type->getClassName()); + return $this->typeFactory->createObjectTypeOrUnionType($allTypes); } - $classNames = TypeUtils::getDirectClassNames($classType); + $classNames = TypeUtils::getDirectClassNames($type); + + $allTypes = []; foreach ($classNames as $className) { - if (! method_exists($className, $methodName)) { + if (! ClassExistenceStaticHelper::doesClassLikeExist($className)) { continue; } - /** @var Scope|null $nodeScope */ - $nodeScope = $staticCall->getAttribute(AttributeKey::SCOPE); - if ($nodeScope === null) { - return $classType; - } - - return $nodeScope->getType($staticCall); + $allTypes = array_merge($allTypes, $this->getClassLikeTypesByClassName($className)); } - return $classType; + return $this->typeFactory->createObjectTypeOrUnionType($allTypes); } /** @@ -574,33 +660,6 @@ private function isPropertyFetchWithArrayDefault(Node $node): bool return $propertyPropertyNode->default instanceof Array_; } - /** - * @param Trait_|Class_ $classLike - */ - private function getClassNodeProperty(ClassLike $classLike, string $name): ?PropertyProperty - { - foreach ($classLike->getProperties() as $property) { - foreach ($property->props as $propertyProperty) { - if ($this->nameResolver->isName($propertyProperty, $name)) { - return $propertyProperty; - } - } - } - - return null; - } - - private function isAnonymousClass(Node $node): bool - { - if (! $node instanceof Class_) { - return false; - } - - $className = $this->nameResolver->getName($node); - - return $className === null || Strings::contains($className, 'AnonymousClass'); - } - private function resolveParamStaticType(Param $param): Type { $classMethod = $param->getAttribute(AttributeKey::METHOD_NODE); @@ -632,42 +691,15 @@ function (Node $node) use ($paramName, &$paramStaticType): ?int { return $paramStaticType; } - private function isUnionNullTypeOfRequiredType(ObjectType $objectType, Type $resolvedType): bool + private function isAnonymousClass(Node $node): bool { - if (! $resolvedType instanceof UnionType) { - return false; - } - - if (count($resolvedType->getTypes()) !== 2) { - return false; - } - - $firstType = $resolvedType->getTypes()[0]; - $secondType = $resolvedType->getTypes()[1]; - - if ($firstType instanceof NullType && $secondType instanceof ObjectType) { - $resolvedType = $secondType; - } elseif ($secondType instanceof NullType && $firstType instanceof ObjectType) { - $resolvedType = $firstType; - } else { + if (! $node instanceof Class_) { return false; } - return is_a($resolvedType->getClassName(), $objectType->getClassName(), true); - } - - private function isFnMatch(Node $node, string $requiredType): bool - { - $objectType = $this->getObjectType($node); - - $classNames = TypeUtils::getDirectClassNames($objectType); - foreach ($classNames as $className) { - if ($this->isObjectTypeFnMatch($className, $requiredType)) { - return true; - } - } + $className = $this->nameResolver->getName($node); - return false; + return $className === null || Strings::contains($className, 'AnonymousClass'); } private function isObjectTypeFnMatch(string $className, string $requiredType): bool @@ -675,63 +707,31 @@ private function isObjectTypeFnMatch(string $className, string $requiredType): b return fnmatch($requiredType, $className, FNM_NOESCAPE); } - private function unionWithParentClassesInterfacesAndUsedTraits(Type $type): Type - { - if ($type instanceof TypeWithClassName) { - if (! ClassExistenceStaticHelper::doesClassLikeExist($type->getClassName())) { - return $type; - } - - $allTypes = $this->getClassLikeTypesByClassName($type->getClassName()); - return $this->typeFactory->createObjectTypeOrUnionType($allTypes); - } - - $classNames = TypeUtils::getDirectClassNames($type); - - $allTypes = []; - foreach ($classNames as $className) { - if (! ClassExistenceStaticHelper::doesClassLikeExist($className)) { - continue; - } - - $allTypes = array_merge($allTypes, $this->getClassLikeTypesByClassName($className)); - } - - return $this->typeFactory->createObjectTypeOrUnionType($allTypes); - } - /** - * @param mixed $requiredType + * @return string[] */ - private function ensureRequiredTypeIsStringOrObjectType($requiredType, string $location): void + private function getClassLikeTypesByClassName(string $className): array { - if (is_string($requiredType)) { - return; - } - - if ($requiredType instanceof ObjectType) { - return; - } + $classReflection = $this->broker->getClass($className); - $reportedType = is_object($requiredType) ? get_class($requiredType) : $requiredType; + $classLikeTypes = $this->classReflectionTypesResolver->resolve($classReflection); - throw new ShouldNotHappenException(sprintf( - 'Value passed to "%s()" must be string or "%s". "%s" given', - $location, - ObjectType::class, - $reportedType - )); + return array_unique($classLikeTypes); } /** - * @return string[] + * @param Trait_|Class_ $classLike */ - private function getClassLikeTypesByClassName(string $className): array + private function getClassNodeProperty(ClassLike $classLike, string $name): ?PropertyProperty { - $classReflection = $this->broker->getClass($className); - - $classLikeTypes = $this->classReflectionTypesResolver->resolve($classReflection); + foreach ($classLike->getProperties() as $property) { + foreach ($property->props as $propertyProperty) { + if ($this->nameResolver->isName($propertyProperty, $name)) { + return $propertyProperty; + } + } + } - return array_unique($classLikeTypes); + return null; } } diff --git a/packages/NodeTypeResolver/src/NodeVisitor/ClassAndMethodNodeVisitor.php b/packages/NodeTypeResolver/src/NodeVisitor/ClassAndMethodNodeVisitor.php index 46e74da3a4a8..debbbaea275d 100644 --- a/packages/NodeTypeResolver/src/NodeVisitor/ClassAndMethodNodeVisitor.php +++ b/packages/NodeTypeResolver/src/NodeVisitor/ClassAndMethodNodeVisitor.php @@ -70,6 +70,24 @@ public function enterNode(Node $node) return $node; } + private function isClassAnonymous(Node $node): bool + { + if (! $node instanceof Class_) { + return false; + } + + if ($node->isAnonymous()) { + return true; + } + + if ($node->name === null) { + return true; + } + + // PHPStan polution + return (bool) Strings::match($node->name->toString(), '#^AnonymousClass\w+#'); + } + private function processClass(Node $node): void { if ($node instanceof ClassLike) { @@ -109,22 +127,4 @@ private function setParentClassName(Class_ $classNode, Node $node): void $node->setAttribute(AttributeKey::PARENT_CLASS_NAME, $parentClassResolvedName); } - - private function isClassAnonymous(Node $node): bool - { - if (! $node instanceof Class_) { - return false; - } - - if ($node->isAnonymous()) { - return true; - } - - if ($node->name === null) { - return true; - } - - // PHPStan polution - return (bool) Strings::match($node->name->toString(), '#^AnonymousClass\w+#'); - } } diff --git a/packages/NodeTypeResolver/src/PerNodeTypeResolver/NameTypeResolver.php b/packages/NodeTypeResolver/src/PerNodeTypeResolver/NameTypeResolver.php index dad8e7c520b7..9432e2ba272e 100644 --- a/packages/NodeTypeResolver/src/PerNodeTypeResolver/NameTypeResolver.php +++ b/packages/NodeTypeResolver/src/PerNodeTypeResolver/NameTypeResolver.php @@ -46,28 +46,6 @@ public function resolve(Node $nameNode): Type return new ObjectType($fullyQualifiedName); } - private function resolveFullyQualifiedName(Node $nameNode, string $name): string - { - if (in_array($name, ['self', 'static', 'this'], true)) { - /** @var string|null $class */ - $class = $nameNode->getAttribute(AttributeKey::CLASS_NAME); - if ($class === null) { - // anonymous class probably - return 'Anonymous'; - } - - return $class; - } - - /** @var Name|null $resolvedNameNode */ - $resolvedNameNode = $nameNode->getAttribute(AttributeKey::RESOLVED_NAME); - if ($resolvedNameNode instanceof Name) { - return $resolvedNameNode->toString(); - } - - return $name; - } - /** * @return ObjectType|UnionType|MixedType */ @@ -91,4 +69,26 @@ private function resolveParent(Name $name): Type return $type; } + + private function resolveFullyQualifiedName(Node $nameNode, string $name): string + { + if (in_array($name, ['self', 'static', 'this'], true)) { + /** @var string|null $class */ + $class = $nameNode->getAttribute(AttributeKey::CLASS_NAME); + if ($class === null) { + // anonymous class probably + return 'Anonymous'; + } + + return $class; + } + + /** @var Name|null $resolvedNameNode */ + $resolvedNameNode = $nameNode->getAttribute(AttributeKey::RESOLVED_NAME); + if ($resolvedNameNode instanceof Name) { + return $resolvedNameNode->toString(); + } + + return $name; + } } diff --git a/packages/NodeTypeResolver/src/PerNodeTypeResolver/VariableTypeResolver.php b/packages/NodeTypeResolver/src/PerNodeTypeResolver/VariableTypeResolver.php index 1e0ca2b362ae..dcfb190eea46 100644 --- a/packages/NodeTypeResolver/src/PerNodeTypeResolver/VariableTypeResolver.php +++ b/packages/NodeTypeResolver/src/PerNodeTypeResolver/VariableTypeResolver.php @@ -91,6 +91,21 @@ public function setNodeTypeResolver(NodeTypeResolver $nodeTypeResolver): void $this->nodeTypeResolver = $nodeTypeResolver; } + private function resolveTypesFromScope(Variable $variable, string $variableName): Type + { + $nodeScope = $this->resolveNodeScope($variable); + if ($nodeScope === null) { + return new MixedType(); + } + + if (! $nodeScope->hasVariableType($variableName)->yes()) { + return new MixedType(); + } + + // this → object type is easier to work with and consistent with the rest of the code + return $nodeScope->getVariableType($variableName); + } + private function resolveNodeScope(Node $node): ?Scope { /** @var Scope|null $nodeScope */ @@ -129,19 +144,4 @@ private function resolveNodeScope(Node $node): ?Scope return null; } - - private function resolveTypesFromScope(Variable $variable, string $variableName): Type - { - $nodeScope = $this->resolveNodeScope($variable); - if ($nodeScope === null) { - return new MixedType(); - } - - if (! $nodeScope->hasVariableType($variableName)->yes()) { - return new MixedType(); - } - - // this → object type is easier to work with and consistent with the rest of the code - return $nodeScope->getVariableType($variableName); - } } diff --git a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php index f66ffd5ee011..0c2ddbe7451c 100644 --- a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php +++ b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php @@ -512,6 +512,25 @@ public function getParamTypeByName(FunctionLike $functionLike, string $paramName return $paramTypes[$paramName] ?? new MixedType(); } + /** + * @todo Extract this logic to own service + */ + private function areTypesEquals(Type $firstType, Type $secondType): bool + { + $firstTypeHash = $this->staticTypeMapper->createTypeHash($firstType); + $secondTypeHash = $this->staticTypeMapper->createTypeHash($secondType); + + if ($firstTypeHash === $secondTypeHash) { + return true; + } + + if ($this->areArrayTypeWithSingleObjectChildToParent($firstType, $secondType)) { + return true; + } + + return false; + } + /** * All class-type tags are FQN by default to keep default convention through the code. * Some people prefer FQN, some short. FQN can be shorten with \Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector later, while short prolonged not @@ -541,25 +560,6 @@ private function addTypeSpecificTag(Node $node, string $name, Type $type): void } } - /** - * @todo Extract this logic to own service - */ - private function areTypesEquals(Type $firstType, Type $secondType): bool - { - $firstTypeHash = $this->staticTypeMapper->createTypeHash($firstType); - $secondTypeHash = $this->staticTypeMapper->createTypeHash($secondType); - - if ($firstTypeHash === $secondTypeHash) { - return true; - } - - if ($this->areArrayTypeWithSingleObjectChildToParent($firstType, $secondType)) { - return true; - } - - return false; - } - private function ensureParamNameStartsWithDollar(string $paramName, string $location): void { if (Strings::startsWith($paramName, '$')) { @@ -573,15 +573,6 @@ private function ensureParamNameStartsWithDollar(string $paramName, string $loca )); } - private function getFqnClassName(ObjectType $objectType): string - { - if ($objectType instanceof ShortenedObjectType) { - return $objectType->getFullyQualifiedName(); - } - - return $objectType->getClassName(); - } - /** * E.g. class A extends B, class B → A[] is subtype of B[] → keep A[] */ @@ -609,4 +600,13 @@ private function areArrayTypeWithSingleObjectChildToParent(Type $firstType, Type return false; } + + private function getFqnClassName(ObjectType $objectType): string + { + if ($objectType instanceof ShortenedObjectType) { + return $objectType->getFullyQualifiedName(); + } + + return $objectType->getClassName(); + } } diff --git a/packages/NodeTypeResolver/src/StaticTypeMapper.php b/packages/NodeTypeResolver/src/StaticTypeMapper.php index 09380ae55f4e..854b1dc06c83 100644 --- a/packages/NodeTypeResolver/src/StaticTypeMapper.php +++ b/packages/NodeTypeResolver/src/StaticTypeMapper.php @@ -628,6 +628,44 @@ public function mapPHPStanPhpDocTypeNodeToPHPStanType(TypeNode $typeNode, Node $ throw new NotImplementedException(__METHOD__ . ' for ' . get_class($typeNode)); } + private function matchArrayTypes(UnionType $unionType): ?Identifier + { + $isNullableType = false; + $hasIterable = false; + + foreach ($unionType->getTypes() as $unionedType) { + if ($unionedType instanceof IterableType) { + $hasIterable = true; + continue; + } + + if ($unionedType instanceof ArrayType) { + continue; + } + + if ($unionedType instanceof NullType) { + $isNullableType = true; + continue; + } + + if ($unionedType instanceof ObjectType) { + if ($unionedType->getClassName() === Traversable::class) { + $hasIterable = true; + continue; + } + } + + return null; + } + + $type = $hasIterable ? 'iterable' : 'array'; + if ($isNullableType) { + return new Identifier('?' . $type); + } + + return new Identifier($type); + } + private function matchTypeForNullableUnionType(UnionType $unionType): ?Type { if (count($unionType->getTypes()) !== 2) { @@ -667,44 +705,6 @@ private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node return new FullyQualified($firstObjectType->getClassName()); } - private function matchArrayTypes(UnionType $unionType): ?Identifier - { - $isNullableType = false; - $hasIterable = false; - - foreach ($unionType->getTypes() as $unionedType) { - if ($unionedType instanceof IterableType) { - $hasIterable = true; - continue; - } - - if ($unionedType instanceof ArrayType) { - continue; - } - - if ($unionedType instanceof NullType) { - $isNullableType = true; - continue; - } - - if ($unionedType instanceof ObjectType) { - if ($unionedType->getClassName() === Traversable::class) { - $hasIterable = true; - continue; - } - } - - return null; - } - - $type = $hasIterable ? 'iterable' : 'array'; - if ($isNullableType) { - return new Identifier('?' . $type); - } - - return new Identifier($type); - } - private function mapScalarStringToType(string $scalarName): ?Type { $loweredScalarName = Strings::lower($scalarName); diff --git a/packages/PHPUnit/src/Composer/ComposerAutoloadedDirectoryProvider.php b/packages/PHPUnit/src/Composer/ComposerAutoloadedDirectoryProvider.php index 0634aba5b6bc..5243047f3505 100644 --- a/packages/PHPUnit/src/Composer/ComposerAutoloadedDirectoryProvider.php +++ b/packages/PHPUnit/src/Composer/ComposerAutoloadedDirectoryProvider.php @@ -49,6 +49,20 @@ public function provide(): array return $autoloadDirectories; } + /** + * @return mixed[] + */ + private function loadComposerJsonArray(): array + { + if (! file_exists($this->composerFilePath)) { + return []; + } + + $composerFileContent = FileSystem::read($this->composerFilePath); + + return Json::decode($composerFileContent, Json::FORCE_ARRAY); + } + /** * @param string[] $composerJsonAutoload * @return string[] @@ -81,18 +95,4 @@ private function collectDirectoriesFromAutoload(array $composerJsonAutoload): ar return array_values($autoloadDirectories); } - - /** - * @return mixed[] - */ - private function loadComposerJsonArray(): array - { - if (! file_exists($this->composerFilePath)) { - return []; - } - - $composerFileContent = FileSystem::read($this->composerFilePath); - - return Json::decode($composerFileContent, Json::FORCE_ARRAY); - } } diff --git a/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php b/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php index d07087148af2..4685c25d3747 100644 --- a/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php +++ b/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php @@ -119,27 +119,6 @@ public function refactor(Node $node): ?Node return $node; } - private function addDoesNotPerformAssertion(ClassMethod $classMethod): void - { - // A. create new doc - $doc = $classMethod->getDocComment(); - if ($doc === null) { - $text = sprintf('/**%s * @doesNotPerformAssertion%s */', PHP_EOL, PHP_EOL); - $classMethod->setDocComment(new Doc($text)); - return; - } - - // B. extend current doc - /** @var PhpDocInfo $phpDocInfo */ - $phpDocInfo = $this->getPhpDocInfo($classMethod); - $phpDocNode = $phpDocInfo->getPhpDocNode(); - $phpDocNode->children[] = new AttributeAwarePhpDocTagNode('@doesNotPerformAssertion', new GenericTagValueNode( - '' - )); - - $this->docBlockManipulator->updateNodeWithPhpDocInfo($classMethod, $phpDocInfo); - } - private function shouldSkipClassMethod(ClassMethod $classMethod): bool { if (! $this->isInTestClass($classMethod)) { @@ -164,6 +143,27 @@ private function shouldSkipClassMethod(ClassMethod $classMethod): bool return false; } + private function addDoesNotPerformAssertion(ClassMethod $classMethod): void + { + // A. create new doc + $doc = $classMethod->getDocComment(); + if ($doc === null) { + $text = sprintf('/**%s * @doesNotPerformAssertion%s */', PHP_EOL, PHP_EOL); + $classMethod->setDocComment(new Doc($text)); + return; + } + + // B. extend current doc + /** @var PhpDocInfo $phpDocInfo */ + $phpDocInfo = $this->getPhpDocInfo($classMethod); + $phpDocNode = $phpDocInfo->getPhpDocNode(); + $phpDocNode->children[] = new AttributeAwarePhpDocTagNode('@doesNotPerformAssertion', new GenericTagValueNode( + '' + )); + + $this->docBlockManipulator->updateNodeWithPhpDocInfo($classMethod, $phpDocInfo); + } + private function containsAssertCall(ClassMethod $classMethod): bool { $cacheHash = md5($this->print($classMethod)); @@ -185,31 +185,56 @@ private function containsAssertCall(ClassMethod $classMethod): bool return $hasNestedAssertCall; } - private function findClassMethodInFile(string $fileName, string $methodName): ?ClassMethod + private function hasDirectAssertCall(ClassMethod $classMethod): bool { - // skip already anayzed method to prevent cycling - if (isset($this->analyzedMethodsInFileName[$fileName][$methodName])) { - return $this->analyzedMethodsInFileName[$fileName][$methodName]; - } + return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool { + if (! $node instanceof MethodCall && ! $node instanceof StaticCall) { + return false; + } - $smartFileInfo = new SmartFileInfo($fileName); - $examinedMethodNodes = $this->fileInfoParser->parseFileInfoToNodesAndDecorate($smartFileInfo); + if ($this->isName($node->name, 'assert*')) { + return true; + } - /** @var ClassMethod|null $examinedClassMethod */ - $examinedClassMethod = $this->betterNodeFinder->findFirst( - $examinedMethodNodes, - function (Node $node) use ($methodName): bool { - if (! $node instanceof ClassMethod) { - return false; - } + // expectException(...) + if ($this->isName($node->name, 'expectException*')) { + return true; + } - return $this->isName($node, $methodName); + // setExpectException (deprecated method) + if ($this->isName($node->name, 'setExpectedException*')) { + return true; } - ); - $this->analyzedMethodsInFileName[$fileName][$methodName] = $examinedClassMethod; + return false; + }); + } - return $examinedClassMethod; + private function hasNestedAssertCall(ClassMethod $classMethod): bool + { + $currentClassMethod = $classMethod; + + // over and over the same method :/ + return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) use ( + $currentClassMethod + ): bool { + if (! $node instanceof MethodCall && ! $node instanceof StaticCall) { + return false; + } + + $classMethod = $this->findClassMethod($node); + + // skip circular self calls + if ($currentClassMethod === $classMethod) { + return false; + } + + if ($classMethod) { + return $this->containsAssertCall($classMethod); + } + + return false; + }); } /** @@ -267,55 +292,30 @@ private function findClassMethodByParsingReflection(Node $node): ?ClassMethod return $this->findClassMethodInFile($fileName, $methodName); } - private function hasDirectAssertCall(ClassMethod $classMethod): bool - { - return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool { - if (! $node instanceof MethodCall && ! $node instanceof StaticCall) { - return false; - } - - if ($this->isName($node->name, 'assert*')) { - return true; - } - - // expectException(...) - if ($this->isName($node->name, 'expectException*')) { - return true; - } - - // setExpectException (deprecated method) - if ($this->isName($node->name, 'setExpectedException*')) { - return true; - } - - return false; - }); - } - - private function hasNestedAssertCall(ClassMethod $classMethod): bool + private function findClassMethodInFile(string $fileName, string $methodName): ?ClassMethod { - $currentClassMethod = $classMethod; + // skip already anayzed method to prevent cycling + if (isset($this->analyzedMethodsInFileName[$fileName][$methodName])) { + return $this->analyzedMethodsInFileName[$fileName][$methodName]; + } - // over and over the same method :/ - return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) use ( - $currentClassMethod - ): bool { - if (! $node instanceof MethodCall && ! $node instanceof StaticCall) { - return false; - } + $smartFileInfo = new SmartFileInfo($fileName); + $examinedMethodNodes = $this->fileInfoParser->parseFileInfoToNodesAndDecorate($smartFileInfo); - $classMethod = $this->findClassMethod($node); + /** @var ClassMethod|null $examinedClassMethod */ + $examinedClassMethod = $this->betterNodeFinder->findFirst( + $examinedMethodNodes, + function (Node $node) use ($methodName): bool { + if (! $node instanceof ClassMethod) { + return false; + } - // skip circular self calls - if ($currentClassMethod === $classMethod) { - return false; + return $this->isName($node, $methodName); } + ); - if ($classMethod) { - return $this->containsAssertCall($classMethod); - } + $this->analyzedMethodsInFileName[$fileName][$methodName] = $examinedClassMethod; - return false; - }); + return $examinedClassMethod; } } diff --git a/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php b/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php index ddf9910917eb..7983b47d1746 100644 --- a/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php +++ b/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php @@ -98,30 +98,6 @@ public function refactor(Node $node): ?Node return $node; } - private function resolveTestCaseClassName(string $className): ?string - { - if (class_exists($className . 'Test')) { - return $className . 'Test'; - } - - $shortClassName = Strings::after($className, '\\', -1); - $testShortClassName = $shortClassName . 'Test'; - - $phpUnitTestCaseClasses = $this->getPhpUnitTestCaseClasses(); - foreach ($phpUnitTestCaseClasses as $declaredClass) { - if (Strings::endsWith($declaredClass, '\\' . $testShortClassName)) { - return $declaredClass; - } - } - - return null; - } - - private function createSeePhpDocTagNode(string $className): PhpDocTagNode - { - return new PhpDocTagNode('@see', new AttributeAwareGenericTagValueNode('\\' . $className)); - } - private function shouldSkipClass(Class_ $class): bool { if ($class->isAnonymous()) { @@ -155,6 +131,30 @@ private function shouldSkipClass(Class_ $class): bool return false; } + private function resolveTestCaseClassName(string $className): ?string + { + if (class_exists($className . 'Test')) { + return $className . 'Test'; + } + + $shortClassName = Strings::after($className, '\\', -1); + $testShortClassName = $shortClassName . 'Test'; + + $phpUnitTestCaseClasses = $this->getPhpUnitTestCaseClasses(); + foreach ($phpUnitTestCaseClasses as $declaredClass) { + if (Strings::endsWith($declaredClass, '\\' . $testShortClassName)) { + return $declaredClass; + } + } + + return null; + } + + private function createSeePhpDocTagNode(string $className): PhpDocTagNode + { + return new PhpDocTagNode('@see', new AttributeAwareGenericTagValueNode('\\' . $className)); + } + /** * @return string[] */ diff --git a/packages/PHPUnit/src/Rector/Class_/ArrayArgumentInTestToDataProviderRector.php b/packages/PHPUnit/src/Rector/Class_/ArrayArgumentInTestToDataProviderRector.php index 57fc10ce35b1..f45f15b2d95a 100644 --- a/packages/PHPUnit/src/Rector/Class_/ArrayArgumentInTestToDataProviderRector.php +++ b/packages/PHPUnit/src/Rector/Class_/ArrayArgumentInTestToDataProviderRector.php @@ -207,29 +207,32 @@ public function refactor(Node $node): ?Node return $node; } - private function createDataProviderTagNode(string $dataProviderMethodName): PhpDocTagNode + /** + * @param mixed[] $configuration + */ + private function ensureConfigurationIsSet(array $configuration): void { - return new PhpDocTagNode('@dataProvider', new GenericTagValueNode($dataProviderMethodName . '()')); - } + if ($configuration !== []) { + return; + } - private function createParamTagNode(string $name, TypeNode $typeNode): PhpDocTagNode - { - return new PhpDocTagNode('@param', new ParamTagValueNode($typeNode, false, '$' . $name, '')); + throw new ShouldNotHappenException(sprintf( + 'Add configuration via "%s" argument for "%s"', + '$configuration', + self::class + )); } - private function resolveUniqueArrayStaticTypes(Array_ $array): Type + /** + * @param string[] $singleConfiguration + */ + private function isMethodCallMatch(MethodCall $methodCall, array $singleConfiguration): bool { - $itemStaticTypes = []; - foreach ($array->items as $arrayItem) { - $arrayItemStaticType = $this->getStaticType($arrayItem->value); - if ($arrayItemStaticType instanceof MixedType) { - continue; - } - - $itemStaticTypes[] = new ArrayType(new MixedType(), $arrayItemStaticType); + if (! $this->isObjectType($methodCall->var, $singleConfiguration['class'])) { + return false; } - return $this->typeFactory->createMixedPassedOrUnionType($itemStaticTypes); + return $this->isName($methodCall->name, $singleConfiguration['old_method']); } private function createDataProviderMethodName(Node $node): string @@ -240,20 +243,18 @@ private function createDataProviderMethodName(Node $node): string return 'provideDataFor' . ucfirst($methodName); } - /** - * @return ClassMethod[] - */ - private function createDataProviderClassMethodsFromRecipes(): array + private function resolveUniqueArrayStaticType(Array_ $array): Type { - $dataProviderClassMethods = []; + $isNestedArray = $this->isNestedArray($array); - foreach ($this->dataProviderClassMethodRecipes as $dataProviderClassMethodRecipe) { - $dataProviderClassMethods[] = $this->dataProviderClassMethodFactory->createFromRecipe( - $dataProviderClassMethodRecipe - ); + $uniqueArrayStaticType = $this->resolveUniqueArrayStaticTypes($array); + + if ($isNestedArray && $uniqueArrayStaticType instanceof ArrayType) { + // unwrap one level up + return $uniqueArrayStaticType->getItemType(); } - return $dataProviderClassMethods; + return $uniqueArrayStaticType; } /** @@ -300,42 +301,48 @@ private function collectParamAndArgsFromArray(Array_ $array, string $variableNam /** * @param ParamAndArgValueObject[] $paramAndArgs - * @return Param[] */ - private function createParams(array $paramAndArgs): array + private function refactorTestClassMethodParams(ClassMethod $classMethod, array $paramAndArgs): void { - $params = []; - foreach ($paramAndArgs as $paramAndArg) { - $param = new Param($paramAndArg->getVariable()); + $classMethod->params = $this->createParams($paramAndArgs); + foreach ($paramAndArgs as $paramAndArg) { $staticType = $paramAndArg->getType(); - if ($staticType !== null && ! $staticType instanceof UnionType) { - $phpNodeType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($staticType); - if ($phpNodeType !== null) { - $param->type = $phpNodeType; - } + if (! $staticType instanceof UnionType) { + continue; } - $params[] = $param; + /** @var string $paramName */ + $paramName = $this->getName($paramAndArg->getVariable()); + + /** @var TypeNode $staticTypeNode */ + $staticTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($staticType); + + $paramTagNode = $this->createParamTagNode($paramName, $staticTypeNode); + $this->docBlockManipulator->addTag($classMethod, $paramTagNode); } + } - return $params; + private function createDataProviderTagNode(string $dataProviderMethodName): PhpDocTagNode + { + return new PhpDocTagNode('@dataProvider', new GenericTagValueNode($dataProviderMethodName . '()')); } - private function resolveItemStaticType(Array_ $array, bool $isNestedArray): Type + /** + * @return ClassMethod[] + */ + private function createDataProviderClassMethodsFromRecipes(): array { - $staticTypes = []; - if ($isNestedArray === false) { - foreach ($array->items as $arrayItem) { - $arrayItemStaticType = $this->getStaticType($arrayItem->value); - if ($arrayItemStaticType) { - $staticTypes[] = $arrayItemStaticType; - } - } + $dataProviderClassMethods = []; + + foreach ($this->dataProviderClassMethodRecipes as $dataProviderClassMethodRecipe) { + $dataProviderClassMethods[] = $this->dataProviderClassMethodFactory->createFromRecipe( + $dataProviderClassMethodRecipe + ); } - return $this->typeFactory->createMixedPassedOrUnionType($staticTypes); + return $dataProviderClassMethods; } private function isNestedArray(Array_ $array): bool @@ -349,70 +356,63 @@ private function isNestedArray(Array_ $array): bool return false; } - /** - * @param string[] $singleConfiguration - */ - private function isMethodCallMatch(MethodCall $methodCall, array $singleConfiguration): bool + private function resolveUniqueArrayStaticTypes(Array_ $array): Type { - if (! $this->isObjectType($methodCall->var, $singleConfiguration['class'])) { - return false; + $itemStaticTypes = []; + foreach ($array->items as $arrayItem) { + $arrayItemStaticType = $this->getStaticType($arrayItem->value); + if ($arrayItemStaticType instanceof MixedType) { + continue; + } + + $itemStaticTypes[] = new ArrayType(new MixedType(), $arrayItemStaticType); } - return $this->isName($methodCall->name, $singleConfiguration['old_method']); + return $this->typeFactory->createMixedPassedOrUnionType($itemStaticTypes); } - private function resolveUniqueArrayStaticType(Array_ $array): Type + private function resolveItemStaticType(Array_ $array, bool $isNestedArray): Type { - $isNestedArray = $this->isNestedArray($array); - - $uniqueArrayStaticType = $this->resolveUniqueArrayStaticTypes($array); - - if ($isNestedArray && $uniqueArrayStaticType instanceof ArrayType) { - // unwrap one level up - return $uniqueArrayStaticType->getItemType(); + $staticTypes = []; + if ($isNestedArray === false) { + foreach ($array->items as $arrayItem) { + $arrayItemStaticType = $this->getStaticType($arrayItem->value); + if ($arrayItemStaticType) { + $staticTypes[] = $arrayItemStaticType; + } + } } - return $uniqueArrayStaticType; + return $this->typeFactory->createMixedPassedOrUnionType($staticTypes); } /** * @param ParamAndArgValueObject[] $paramAndArgs + * @return Param[] */ - private function refactorTestClassMethodParams(ClassMethod $classMethod, array $paramAndArgs): void + private function createParams(array $paramAndArgs): array { - $classMethod->params = $this->createParams($paramAndArgs); - + $params = []; foreach ($paramAndArgs as $paramAndArg) { + $param = new Param($paramAndArg->getVariable()); + $staticType = $paramAndArg->getType(); - if (! $staticType instanceof UnionType) { - continue; + if ($staticType !== null && ! $staticType instanceof UnionType) { + $phpNodeType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($staticType); + if ($phpNodeType !== null) { + $param->type = $phpNodeType; + } } - /** @var string $paramName */ - $paramName = $this->getName($paramAndArg->getVariable()); - - /** @var TypeNode $staticTypeNode */ - $staticTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($staticType); - - $paramTagNode = $this->createParamTagNode($paramName, $staticTypeNode); - $this->docBlockManipulator->addTag($classMethod, $paramTagNode); + $params[] = $param; } + + return $params; } - /** - * @param mixed[] $configuration - */ - private function ensureConfigurationIsSet(array $configuration): void + private function createParamTagNode(string $name, TypeNode $typeNode): PhpDocTagNode { - if ($configuration !== []) { - return; - } - - throw new ShouldNotHappenException(sprintf( - 'Add configuration via "%s" argument for "%s"', - '$configuration', - self::class - )); + return new PhpDocTagNode('@param', new ParamTagValueNode($typeNode, false, '$' . $name, '')); } } diff --git a/packages/PHPUnit/src/Rector/MethodCall/ReplaceAssertArraySubsetRector.php b/packages/PHPUnit/src/Rector/MethodCall/ReplaceAssertArraySubsetRector.php index 172890de705a..2c92cefa3534 100644 --- a/packages/PHPUnit/src/Rector/MethodCall/ReplaceAssertArraySubsetRector.php +++ b/packages/PHPUnit/src/Rector/MethodCall/ReplaceAssertArraySubsetRector.php @@ -124,6 +124,37 @@ private function reset(): void $this->expectedValuesByKeys = []; } + private function matchArray(Expr $expr): ?Array_ + { + if ($expr instanceof Array_) { + return $expr; + } + + $value = $this->getValue($expr); + + // nothing we can do + if ($value === null || ! is_array($value)) { + return null; + } + + // use specific array instead + return BuilderHelpers::normalizeValue($value); + } + + private function collectExpectedKeysAndValues(Array_ $expectedArray): void + { + foreach ($expectedArray->items as $arrayItem) { + if ($arrayItem->key === null) { + continue; + } + + $this->expectedKeys[] = $arrayItem->key; + + $key = $this->getValue($arrayItem->key); + $this->expectedValuesByKeys[$key] = $arrayItem->value; + } + } + /** * @param MethodCall|StaticCall $node */ @@ -153,35 +184,4 @@ private function addValueAsserts(Node $node): void $this->addNodeAfterNode($assertSame, $node); } } - - private function collectExpectedKeysAndValues(Array_ $expectedArray): void - { - foreach ($expectedArray->items as $arrayItem) { - if ($arrayItem->key === null) { - continue; - } - - $this->expectedKeys[] = $arrayItem->key; - - $key = $this->getValue($arrayItem->key); - $this->expectedValuesByKeys[$key] = $arrayItem->value; - } - } - - private function matchArray(Expr $expr): ?Array_ - { - if ($expr instanceof Array_) { - return $expr; - } - - $value = $this->getValue($expr); - - // nothing we can do - if ($value === null || ! is_array($value)) { - return null; - } - - // use specific array instead - return BuilderHelpers::normalizeValue($value); - } } diff --git a/packages/PHPUnit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php b/packages/PHPUnit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php index a030c0fd961d..9a0f37c65a46 100644 --- a/packages/PHPUnit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php +++ b/packages/PHPUnit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php @@ -146,23 +146,18 @@ public function refactor(Node $node): ?Node return $node; } - private function inferMockedMethodName(MethodCall $methodCall): string + private function areAllArgArrayTypes(MethodCall $methodCall): bool { - $previousMethodCalls = $this->methodCallManipulator->findMethodCallsIncludingChain($methodCall); - foreach ($previousMethodCalls as $previousMethodCall) { - if (! $this->isName($previousMethodCall->name, 'method')) { - continue; - } - - $firstArgumentValue = $previousMethodCall->args[0]->value; - if (! $firstArgumentValue instanceof String_) { + foreach ($methodCall->args as $arg) { + $argumentStaticType = $this->getStaticType($arg->value); + if ($argumentStaticType instanceof ArrayType) { continue; } - return $firstArgumentValue->value; + return false; } - throw new ShouldNotHappenException(); + return true; } private function inferMockedClassName(MethodCall $methodCall): ?string @@ -193,27 +188,32 @@ private function inferMockedClassName(MethodCall $methodCall): ?string return null; } - private function findRootVariableOfChainCall(MethodCall $methodCall): ?Variable + private function inferMockedMethodName(MethodCall $methodCall): string { - $currentMethodCallee = $methodCall->var; - while (! $currentMethodCallee instanceof Variable) { - $currentMethodCallee = $currentMethodCallee->var; + $previousMethodCalls = $this->methodCallManipulator->findMethodCallsIncludingChain($methodCall); + foreach ($previousMethodCalls as $previousMethodCall) { + if (! $this->isName($previousMethodCall->name, 'method')) { + continue; + } + + $firstArgumentValue = $previousMethodCall->args[0]->value; + if (! $firstArgumentValue instanceof String_) { + continue; + } + + return $firstArgumentValue->value; } - return $currentMethodCallee; + throw new ShouldNotHappenException(); } - private function areAllArgArrayTypes(MethodCall $methodCall): bool + private function findRootVariableOfChainCall(MethodCall $methodCall): ?Variable { - foreach ($methodCall->args as $arg) { - $argumentStaticType = $this->getStaticType($arg->value); - if ($argumentStaticType instanceof ArrayType) { - continue; - } - - return false; + $currentMethodCallee = $methodCall->var; + while (! $currentMethodCallee instanceof Variable) { + $currentMethodCallee = $currentMethodCallee->var; } - return true; + return $currentMethodCallee; } } diff --git a/packages/PSR4/src/Composer/PSR4AutoloadPathsProvider.php b/packages/PSR4/src/Composer/PSR4AutoloadPathsProvider.php index 110ccec829af..6c760cfdb04e 100644 --- a/packages/PSR4/src/Composer/PSR4AutoloadPathsProvider.php +++ b/packages/PSR4/src/Composer/PSR4AutoloadPathsProvider.php @@ -34,12 +34,6 @@ public function provide(): array return $this->cachedComposerJsonPSR4AutoloadPaths; } - private function getComposerJsonPath(): string - { - // assume the project has "composer.json" in root directory - return getcwd() . '/composer.json'; - } - /** * @return mixed[] */ @@ -50,6 +44,12 @@ private function readFileToJsonArray(string $composerJson): array return Json::decode($composerJsonContent, Json::FORCE_ARRAY); } + private function getComposerJsonPath(): string + { + // assume the project has "composer.json" in root directory + return getcwd() . '/composer.json'; + } + /** * @param string[] $psr4Autoloads * @return string[] diff --git a/packages/PSR4/src/Extension/RenamedClassesReportExtension.php b/packages/PSR4/src/Extension/RenamedClassesReportExtension.php index 263135a89feb..ca62bc14d46c 100644 --- a/packages/PSR4/src/Extension/RenamedClassesReportExtension.php +++ b/packages/PSR4/src/Extension/RenamedClassesReportExtension.php @@ -65,24 +65,6 @@ public function run(): void ); } - /** - * @param ClassRenameValueObject[] $classRenames - * @return Expression[] - */ - private function createClassAliasNodes(array $classRenames): array - { - $nodes = []; - foreach ($classRenames as $classRename) { - $classAlias = new FuncCall(new Name('class_alias')); - $classAlias->args[] = new Arg(new String_($classRename->getNewClass())); - $classAlias->args[] = new Arg(new String_($classRename->getOldClass())); - - $nodes[] = new Expression($classAlias); - } - - return $nodes; - } - private function createRectorYamlContent(): string { $oldToNewClasses = $this->renamedClassesCollector->getOldToNewClassesSortedByHighestParentsAsString(); @@ -107,4 +89,22 @@ private function createRenameClassAliasContent(): string return 'args[] = new Arg(new String_($classRename->getNewClass())); + $classAlias->args[] = new Arg(new String_($classRename->getOldClass())); + + $nodes[] = new Expression($classAlias); + } + + return $nodes; + } } diff --git a/packages/PSR4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php b/packages/PSR4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php index c8da00ddfb34..21b59a626447 100644 --- a/packages/PSR4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php +++ b/packages/PSR4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php @@ -128,17 +128,6 @@ private function getExpectedNamespace(Node $node): ?string return null; } - /** - * Get the extra path that is not included in root PSR-4 namespace - */ - private function resolveExtraNamespace(SmartFileInfo $smartFileInfo, string $path): string - { - $extraNamespace = Strings::substring($smartFileInfo->getRelativeDirectoryPath(), Strings::length($path) + 1); - $extraNamespace = Strings::replace($extraNamespace, '#/#', '\\'); - - return trim($extraNamespace); - } - /** * @param string[] $newUseImports * @return Use_[] @@ -154,4 +143,15 @@ private function createUses(array $newUseImports): array return $uses; } + + /** + * Get the extra path that is not included in root PSR-4 namespace + */ + private function resolveExtraNamespace(SmartFileInfo $smartFileInfo, string $path): string + { + $extraNamespace = Strings::substring($smartFileInfo->getRelativeDirectoryPath(), Strings::length($path) + 1); + $extraNamespace = Strings::replace($extraNamespace, '#/#', '\\'); + + return trim($extraNamespace); + } } diff --git a/packages/Php56/src/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector.php b/packages/Php56/src/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector.php index 6b0e5f45ecf3..9591a43ad6df 100644 --- a/packages/Php56/src/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector.php +++ b/packages/Php56/src/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector.php @@ -144,17 +144,26 @@ public function refactor(Node $node): ?Node return $node; } - private function isStaticVariable(Node $parentNode): bool + private function collectDefinedVariablesFromForeach(Node $node): void { - // definition of static variable - if ($parentNode instanceof StaticVar) { - $parentParentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE); - if ($parentParentNode instanceof Static_) { - return true; - } + if (! $node instanceof Foreach_) { + return; } - return false; + $this->traverseNodesWithCallable($node->stmts, function (Node $node): void { + if ($node instanceof Assign || $node instanceof AssignRef) { + if (! $node->var instanceof Variable) { + return; + } + + $variableName = $this->getName($node->var); + if ($variableName === null) { + return; + } + + $this->definedVariables[] = $variableName; + } + }); } private function shouldSkipVariable(Variable $variable): bool @@ -195,25 +204,16 @@ private function shouldSkipVariable(Variable $variable): bool return false; } - private function collectDefinedVariablesFromForeach(Node $node): void + private function isStaticVariable(Node $parentNode): bool { - if (! $node instanceof Foreach_) { - return; + // definition of static variable + if ($parentNode instanceof StaticVar) { + $parentParentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE); + if ($parentParentNode instanceof Static_) { + return true; + } } - $this->traverseNodesWithCallable($node->stmts, function (Node $node): void { - if ($node instanceof Assign || $node instanceof AssignRef) { - if (! $node->var instanceof Variable) { - return; - } - - $variableName = $this->getName($node->var); - if ($variableName === null) { - return; - } - - $this->definedVariables[] = $variableName; - } - }); + return false; } } diff --git a/packages/Php74/src/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php b/packages/Php74/src/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php index f1ffd95fa089..625e5713ba5d 100644 --- a/packages/Php74/src/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php +++ b/packages/Php74/src/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php @@ -87,39 +87,6 @@ public function refactor(Node $node): ?Node return null; } - private function isIteratorToArrayFuncCall(Expr $expr): bool - { - return $expr instanceof FuncCall && $this->isName($expr, 'iterator_to_array'); - } - - private function resolveValue(Expr $expr): Expr - { - if ($this->isIteratorToArrayFuncCall($expr)) { - /** @var FuncCall $expr */ - $expr = $expr->args[0]->value; - } - - if (! $expr instanceof Ternary) { - return $expr; - } - - if (! $expr->cond instanceof FuncCall) { - return $expr; - } - - if (! $this->isName($expr->cond, 'is_array')) { - return $expr; - } - - if ($expr->if instanceof Variable) { - if ($this->isIteratorToArrayFuncCall($expr->else)) { - return $expr->if; - } - } - - return $expr; - } - private function refactorArray(FuncCall $funcCall): ?Array_ { $array = new Array_(); @@ -147,11 +114,6 @@ private function refactorIteratorToArray(FuncCall $funcCall): Array_ return $array; } - private function createUnpackedArrayItem(Expr $expr): ArrayItem - { - return new ArrayItem($expr, null, false, [], true); - } - private function shouldSkipArrayForInvalidTypeOrKeys(Expr $expr): bool { // we have no idea what it is → cannot change it @@ -171,4 +133,42 @@ private function shouldSkipArrayForInvalidTypeOrKeys(Expr $expr): bool return false; } + + private function resolveValue(Expr $expr): Expr + { + if ($this->isIteratorToArrayFuncCall($expr)) { + /** @var FuncCall $expr */ + $expr = $expr->args[0]->value; + } + + if (! $expr instanceof Ternary) { + return $expr; + } + + if (! $expr->cond instanceof FuncCall) { + return $expr; + } + + if (! $this->isName($expr->cond, 'is_array')) { + return $expr; + } + + if ($expr->if instanceof Variable) { + if ($this->isIteratorToArrayFuncCall($expr->else)) { + return $expr->if; + } + } + + return $expr; + } + + private function createUnpackedArrayItem(Expr $expr): ArrayItem + { + return new ArrayItem($expr, null, false, [], true); + } + + private function isIteratorToArrayFuncCall(Expr $expr): bool + { + return $expr instanceof FuncCall && $this->isName($expr, 'iterator_to_array'); + } } diff --git a/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php b/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php index ee149f86b384..d7f953b9908a 100644 --- a/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php +++ b/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php @@ -93,24 +93,6 @@ public function refactor(Node $node): ?Node return $node; } - /** - * @return string[] - */ - private function strSplitNegative(string $string, int $length): array - { - $inversed = strrev($string); - - /** @var string[] $chunks */ - $chunks = str_split($inversed, $length); - - $chunks = array_reverse($chunks); - foreach ($chunks as $key => $chunk) { - $chunks[$key] = strrev($chunk); - } - - return $chunks; - } - /** * @param LNumber|DNumber $node */ @@ -138,4 +120,22 @@ private function shouldSkip(Node $node, string $numericValueAsString): bool return false; } + + /** + * @return string[] + */ + private function strSplitNegative(string $string, int $length): array + { + $inversed = strrev($string); + + /** @var string[] $chunks */ + $chunks = str_split($inversed, $length); + + $chunks = array_reverse($chunks); + foreach ($chunks as $key => $chunk) { + $chunks[$key] = strrev($chunk); + } + + return $chunks; + } } diff --git a/packages/Php74/src/Rector/MethodCall/ChangeReflectionTypeToStringToGetNameRector.php b/packages/Php74/src/Rector/MethodCall/ChangeReflectionTypeToStringToGetNameRector.php index 4f961ffed6a9..c6c2f5a73d28 100644 --- a/packages/Php74/src/Rector/MethodCall/ChangeReflectionTypeToStringToGetNameRector.php +++ b/packages/Php74/src/Rector/MethodCall/ChangeReflectionTypeToStringToGetNameRector.php @@ -108,49 +108,54 @@ public function refactor(Node $node): ?Node return null; } - private function isReflectionParameterGetTypeMethodCall(MethodCall $methodCall): bool + private function refactorMethodCall(MethodCall $methodCall): ?Node { - if (! $this->isObjectType($methodCall->var, ReflectionParameter::class)) { - return false; - } + $this->collectCallByVariable($methodCall); - return $this->isName($methodCall->name, 'getType'); - } + if ($this->shouldSkipMethodCall($methodCall)) { + return null; + } - private function refactorReflectionParameterGetName(MethodCall $methodCall): Ternary - { - $getNameMethodCall = $this->createMethodCall($methodCall, 'getName'); - $ternary = new Ternary($methodCall, $getNameMethodCall, $this->createNull()); + if ($this->isReflectionParameterGetTypeMethodCall($methodCall)) { + return $this->refactorReflectionParameterGetName($methodCall); + } - // to prevent looping - $methodCall->setAttribute(AttributeKey::PARENT_NODE, $ternary); + if ($this->isReflectionFunctionAbstractGetReturnTypeMethodCall($methodCall)) { + return $this->refactorReflectionFunctionGetReturnType($methodCall); + } - return $ternary; + return null; } - private function isReflectionFunctionAbstractGetReturnTypeMethodCall(MethodCall $methodCall): bool + private function refactorIfHasReturnTypeWasCalled(MethodCall $methodCall): ?Node { - if (! $this->isObjectType($methodCall->var, ReflectionFunctionAbstract::class)) { - return false; + if (! $methodCall->var instanceof Variable) { + return null; } - return $this->isName($methodCall->name, 'getReturnType'); - } + $variableName = $this->getName($methodCall->var); - private function refactorReflectionFunctionGetReturnType(MethodCall $methodCall): Node - { - $refactoredMethodCall = $this->refactorIfHasReturnTypeWasCalled($methodCall); - if ($refactoredMethodCall) { - return $refactoredMethodCall; + $callsByVariable = $this->callsByVariable[$variableName] ?? []; + + // we already know it has return type + if (in_array('hasReturnType', $callsByVariable, true)) { + return $this->createMethodCall($methodCall, 'getName'); } - $getNameMethodCall = $this->createMethodCall($methodCall, 'getName'); - $ternary = new Ternary($methodCall, $getNameMethodCall, $this->createNull()); + return null; + } - // to prevent looping - $methodCall->setAttribute(AttributeKey::PARENT_NODE, $ternary); + private function collectCallByVariable(Node $node): void + { + // bit workaround for now + if ($node->var instanceof Variable) { + $variableName = $this->getName($node->var); + $methodName = $this->getName($node->name); - return $ternary; + if ($variableName && $methodName) { + $this->callsByVariable[$variableName][] = $methodName; + } + } } private function shouldSkipMethodCall(MethodCall $methodCall): bool @@ -182,53 +187,48 @@ private function shouldSkipMethodCall(MethodCall $methodCall): bool return false; } - private function collectCallByVariable(Node $node): void + private function isReflectionParameterGetTypeMethodCall(MethodCall $methodCall): bool { - // bit workaround for now - if ($node->var instanceof Variable) { - $variableName = $this->getName($node->var); - $methodName = $this->getName($node->name); - - if ($variableName && $methodName) { - $this->callsByVariable[$variableName][] = $methodName; - } + if (! $this->isObjectType($methodCall->var, ReflectionParameter::class)) { + return false; } + + return $this->isName($methodCall->name, 'getType'); } - private function refactorIfHasReturnTypeWasCalled(MethodCall $methodCall): ?Node + private function refactorReflectionParameterGetName(MethodCall $methodCall): Ternary { - if (! $methodCall->var instanceof Variable) { - return null; - } + $getNameMethodCall = $this->createMethodCall($methodCall, 'getName'); + $ternary = new Ternary($methodCall, $getNameMethodCall, $this->createNull()); - $variableName = $this->getName($methodCall->var); + // to prevent looping + $methodCall->setAttribute(AttributeKey::PARENT_NODE, $ternary); - $callsByVariable = $this->callsByVariable[$variableName] ?? []; + return $ternary; + } - // we already know it has return type - if (in_array('hasReturnType', $callsByVariable, true)) { - return $this->createMethodCall($methodCall, 'getName'); + private function isReflectionFunctionAbstractGetReturnTypeMethodCall(MethodCall $methodCall): bool + { + if (! $this->isObjectType($methodCall->var, ReflectionFunctionAbstract::class)) { + return false; } - return null; + return $this->isName($methodCall->name, 'getReturnType'); } - private function refactorMethodCall(MethodCall $methodCall): ?Node + private function refactorReflectionFunctionGetReturnType(MethodCall $methodCall): Node { - $this->collectCallByVariable($methodCall); - - if ($this->shouldSkipMethodCall($methodCall)) { - return null; + $refactoredMethodCall = $this->refactorIfHasReturnTypeWasCalled($methodCall); + if ($refactoredMethodCall) { + return $refactoredMethodCall; } - if ($this->isReflectionParameterGetTypeMethodCall($methodCall)) { - return $this->refactorReflectionParameterGetName($methodCall); - } + $getNameMethodCall = $this->createMethodCall($methodCall, 'getName'); + $ternary = new Ternary($methodCall, $getNameMethodCall, $this->createNull()); - if ($this->isReflectionFunctionAbstractGetReturnTypeMethodCall($methodCall)) { - return $this->refactorReflectionFunctionGetReturnType($methodCall); - } + // to prevent looping + $methodCall->setAttribute(AttributeKey::PARENT_NODE, $ternary); - return null; + return $ternary; } } diff --git a/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecMocksToPHPUnitMocksRector.php b/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecMocksToPHPUnitMocksRector.php index f8230ec2e34f..3bc858d90a2c 100644 --- a/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecMocksToPHPUnitMocksRector.php +++ b/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecMocksToPHPUnitMocksRector.php @@ -79,53 +79,6 @@ public function refactor(Node $node): ?Node return $this->processMethodCall($node); } - /** - * Variable or property fetch, based on number of present params in whole class - */ - private function createCreateMockCall(Param $param, Name $name): ?Expression - { - /** @var Class_ $classNode */ - $classNode = $param->getAttribute(AttributeKey::CLASS_NODE); - - $classMocks = $this->phpSpecMockCollector->resolveClassMocksFromParam($classNode); - - $variable = $this->getName($param->var); - $method = $param->getAttribute(AttributeKey::METHOD_NAME); - - $methodsWithWThisMock = $classMocks[$variable]; - - // single use: "$mock = $this->createMock()" - if (! $this->phpSpecMockCollector->isVariableMockInProperty($param->var)) { - return $this->createNewMockVariableAssign($param, $name); - } - - $reversedMethodsWithThisMock = array_flip($methodsWithWThisMock); - - // first use of many: "$this->mock = $this->createMock()" - if ($reversedMethodsWithThisMock[$method] === 0) { - return $this->createPropertyFetchMockVariableAssign($param, $name); - } - - return null; - } - - private function createMockVarDoc(Param $param, Name $name): string - { - $paramType = (string) ($name->getAttribute('originalName') ?: $name); - $variableName = $this->getName($param->var); - - if ($variableName === null) { - throw new ShouldNotHappenException(); - } - - return sprintf( - '/** @var %s|\%s $%s */', - $paramType, - 'PHPUnit\Framework\MockObject\MockObject', - $variableName - ); - } - private function processMethodParamsToMocks(ClassMethod $classMethod): void { // remove params and turn them to instances @@ -178,6 +131,36 @@ private function processMethodCall(MethodCall $methodCall): ?MethodCall return null; } + /** + * Variable or property fetch, based on number of present params in whole class + */ + private function createCreateMockCall(Param $param, Name $name): ?Expression + { + /** @var Class_ $classNode */ + $classNode = $param->getAttribute(AttributeKey::CLASS_NODE); + + $classMocks = $this->phpSpecMockCollector->resolveClassMocksFromParam($classNode); + + $variable = $this->getName($param->var); + $method = $param->getAttribute(AttributeKey::METHOD_NAME); + + $methodsWithWThisMock = $classMocks[$variable]; + + // single use: "$mock = $this->createMock()" + if (! $this->phpSpecMockCollector->isVariableMockInProperty($param->var)) { + return $this->createNewMockVariableAssign($param, $name); + } + + $reversedMethodsWithThisMock = array_flip($methodsWithWThisMock); + + // first use of many: "$this->mock = $this->createMock()" + if ($reversedMethodsWithThisMock[$method] === 0) { + return $this->createPropertyFetchMockVariableAssign($param, $name); + } + + return null; + } + private function appendWithMethodCall(MethodCall $methodCall, Expr $expr): MethodCall { $withMethodCall = new MethodCall($methodCall, 'with'); @@ -244,4 +227,21 @@ private function createIsTypeOrIsInstanceOf(StaticCall $staticCall): MethodCall return $this->createMethodCall('this', $name, $staticCall->args); } + + private function createMockVarDoc(Param $param, Name $name): string + { + $paramType = (string) ($name->getAttribute('originalName') ?: $name); + $variableName = $this->getName($param->var); + + if ($variableName === null) { + throw new ShouldNotHappenException(); + } + + return sprintf( + '/** @var %s|\%s $%s */', + $paramType, + 'PHPUnit\Framework\MockObject\MockObject', + $variableName + ); + } } diff --git a/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php b/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php index 7dd47b7a7d0e..8fec10cf88d5 100644 --- a/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php +++ b/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php @@ -204,56 +204,56 @@ public function refactor(Node $node): ?Node return $node; } - private function thisToTestedObjectPropertyFetch(Expr $expr): Expr + private function processDuring(MethodCall $methodCall): MethodCall { - if (! $expr instanceof Variable) { - return $expr; - } - - if (! $this->isName($expr, 'this')) { - return $expr; + if (! isset($methodCall->args[0])) { + throw new ShouldNotHappenException(); } - return $this->testedObjectPropertyFetch; - } - - private function createAssertMethod(string $name, Expr $value, ?Expr $expected): MethodCall - { - $this->isBoolAssert = false; + $name = $this->getValue($methodCall->args[0]->value); + $thisObjectPropertyMethodCall = new MethodCall($this->testedObjectPropertyFetch, $name); - // special case with bool! - if ($expected !== null) { - $name = $this->resolveBoolMethodName($name, $expected); + if (isset($methodCall->args[1]) && $methodCall->args[1]->value instanceof Array_) { + /** @var Array_ $array */ + $array = $methodCall->args[1]->value; + if (isset($array->items[0])) { + $thisObjectPropertyMethodCall->args[] = new Arg($array->items[0]->value); + } } - $assetMethodCall = $this->createMethodCall('this', $name); + /** @var MethodCall $parentMethodCall */ + $parentMethodCall = $methodCall->var; + $parentMethodCall->name = new Identifier('expectException'); - if (! $this->isBoolAssert && $expected) { - $assetMethodCall->args[] = new Arg($this->thisToTestedObjectPropertyFetch($expected)); - } + // add $this->object->someCall($withArgs) + $this->addNodeAfterNode($thisObjectPropertyMethodCall, $methodCall); - $assetMethodCall->args[] = new Arg($this->thisToTestedObjectPropertyFetch($value)); + return $parentMethodCall; + } - return $assetMethodCall; + private function processDuringInstantiation(MethodCall $methodCall): MethodCall + { + /** @var MethodCall $parentMethodCall */ + $parentMethodCall = $methodCall->var; + $parentMethodCall->name = new Identifier('expectException'); + + return $parentMethodCall; } - private function resolveBoolMethodName(string $name, Expr $expr): string + private function prepare(Node $node): void { - if (! $this->isBool($expr)) { - return $name; + if ($this->isPrepared) { + return; } - if ($name === 'assertSame') { - $this->isBoolAssert = true; - return $this->isFalse($expr) ? 'assertFalse' : 'assertTrue'; - } + /** @var Class_ $classNode */ + $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); - if ($name === 'assertNotSame') { - $this->isBoolAssert = true; - return $this->isFalse($expr) ? 'assertNotFalse' : 'assertNotTrue'; - } + $this->matchersKeys = $this->matchersManipulator->resolveMatcherNamesFromClass($classNode); + $this->testedClass = $this->phpSpecRenaming->resolveTestedClass($node); + $this->testedObjectPropertyFetch = $this->createTestedObjectPropertyFetch($classNode); - return $name; + $this->isPrepared = true; } private function processBeConstructed(MethodCall $methodCall): ?Node @@ -278,23 +278,6 @@ private function processBeConstructed(MethodCall $methodCall): ?Node return null; } - private function moveConstructorArguments(MethodCall $methodCall, StaticCall $staticCall): void - { - if (! isset($methodCall->args[1])) { - return; - } - - if (! $methodCall->args[1]->value instanceof Array_) { - return; - } - - /** @var Array_ $array */ - $array = $methodCall->args[1]->value; - foreach ($array->items as $arrayItem) { - $staticCall->args[] = new Arg($arrayItem->value); - } - } - /** * @see https://johannespichler.com/writing-custom-phpspec-matchers/ */ @@ -333,6 +316,26 @@ private function processMatchersKeys(MethodCall $methodCall): void } } + private function createAssertMethod(string $name, Expr $value, ?Expr $expected): MethodCall + { + $this->isBoolAssert = false; + + // special case with bool! + if ($expected !== null) { + $name = $this->resolveBoolMethodName($name, $expected); + } + + $assetMethodCall = $this->createMethodCall('this', $name); + + if (! $this->isBoolAssert && $expected) { + $assetMethodCall->args[] = new Arg($this->thisToTestedObjectPropertyFetch($expected)); + } + + $assetMethodCall->args[] = new Arg($this->thisToTestedObjectPropertyFetch($value)); + + return $assetMethodCall; + } + private function createTestedObjectPropertyFetch(Class_ $class): PropertyFetch { $propertyName = $this->phpSpecRenaming->resolveObjectPropertyName($class); @@ -340,55 +343,52 @@ private function createTestedObjectPropertyFetch(Class_ $class): PropertyFetch return new PropertyFetch(new Variable('this'), $propertyName); } - private function prepare(Node $node): void + private function moveConstructorArguments(MethodCall $methodCall, StaticCall $staticCall): void { - if ($this->isPrepared) { + if (! isset($methodCall->args[1])) { return; } - /** @var Class_ $classNode */ - $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); - - $this->matchersKeys = $this->matchersManipulator->resolveMatcherNamesFromClass($classNode); - $this->testedClass = $this->phpSpecRenaming->resolveTestedClass($node); - $this->testedObjectPropertyFetch = $this->createTestedObjectPropertyFetch($classNode); + if (! $methodCall->args[1]->value instanceof Array_) { + return; + } - $this->isPrepared = true; + /** @var Array_ $array */ + $array = $methodCall->args[1]->value; + foreach ($array->items as $arrayItem) { + $staticCall->args[] = new Arg($arrayItem->value); + } } - private function processDuring(MethodCall $methodCall): MethodCall + private function resolveBoolMethodName(string $name, Expr $expr): string { - if (! isset($methodCall->args[0])) { - throw new ShouldNotHappenException(); + if (! $this->isBool($expr)) { + return $name; } - $name = $this->getValue($methodCall->args[0]->value); - $thisObjectPropertyMethodCall = new MethodCall($this->testedObjectPropertyFetch, $name); - - if (isset($methodCall->args[1]) && $methodCall->args[1]->value instanceof Array_) { - /** @var Array_ $array */ - $array = $methodCall->args[1]->value; - if (isset($array->items[0])) { - $thisObjectPropertyMethodCall->args[] = new Arg($array->items[0]->value); - } + if ($name === 'assertSame') { + $this->isBoolAssert = true; + return $this->isFalse($expr) ? 'assertFalse' : 'assertTrue'; } - /** @var MethodCall $parentMethodCall */ - $parentMethodCall = $methodCall->var; - $parentMethodCall->name = new Identifier('expectException'); - - // add $this->object->someCall($withArgs) - $this->addNodeAfterNode($thisObjectPropertyMethodCall, $methodCall); + if ($name === 'assertNotSame') { + $this->isBoolAssert = true; + return $this->isFalse($expr) ? 'assertNotFalse' : 'assertNotTrue'; + } - return $parentMethodCall; + return $name; } - private function processDuringInstantiation(MethodCall $methodCall): MethodCall + private function thisToTestedObjectPropertyFetch(Expr $expr): Expr { - /** @var MethodCall $parentMethodCall */ - $parentMethodCall = $methodCall->var; - $parentMethodCall->name = new Identifier('expectException'); + if (! $expr instanceof Variable) { + return $expr; + } - return $parentMethodCall; + if (! $this->isName($expr, 'this')) { + return $expr; + } + + return $this->testedObjectPropertyFetch; } } diff --git a/packages/RemovingStatic/src/Printer/FactoryClassPrinter.php b/packages/RemovingStatic/src/Printer/FactoryClassPrinter.php index 2acb23e7b342..4bc58a4f3390 100644 --- a/packages/RemovingStatic/src/Printer/FactoryClassPrinter.php +++ b/packages/RemovingStatic/src/Printer/FactoryClassPrinter.php @@ -60,14 +60,6 @@ public function printFactoryForClass(Class_ $factoryClass, Class_ $oldClass): vo $this->filesystem->dumpFile($factoryClassFilePath, $factoryClassContent); } - /** - * @param Node|Node[] $node - */ - private function rawPrintNode($node): string - { - return sprintf('betterStandardPrinter->print($node), PHP_EOL); - } - private function createFactoryClassFilePath(Class_ $oldClass): string { /** @var SmartFileInfo|null $classFileInfo */ @@ -86,4 +78,12 @@ private function createFactoryClassFilePath(Class_ $oldClass): string return $directoryPath . DIRECTORY_SEPARATOR . $bareClassName; } + + /** + * @param Node|Node[] $node + */ + private function rawPrintNode($node): string + { + return sprintf('betterStandardPrinter->print($node), PHP_EOL); + } } diff --git a/packages/RemovingStatic/src/Rector/Class_/PHPUnitStaticToKernelTestCaseGetRector.php b/packages/RemovingStatic/src/Rector/Class_/PHPUnitStaticToKernelTestCaseGetRector.php index cff2073b1209..9a78b382d567 100644 --- a/packages/RemovingStatic/src/Rector/Class_/PHPUnitStaticToKernelTestCaseGetRector.php +++ b/packages/RemovingStatic/src/Rector/Class_/PHPUnitStaticToKernelTestCaseGetRector.php @@ -192,6 +192,41 @@ private function processStaticCall(StaticCall $staticCall): ?MethodCall return null; } + private function processPHPUnitClass(Class_ $class): ?Class_ + { + // add property with the object + $newProperties = $this->collectNewProperties($class); + if ($newProperties === []) { + return null; + } + + // add all properties to class + $class = $this->addNewPropertiesToClass($class, $newProperties); + + $parentSetupStaticCall = $this->createParentSetUpStaticCall(); + foreach ($newProperties as $type) { + // container fetch assign + $assign = $this->createContainerGetTypeToPropertyAssign($type); + + $setupClassMethod = $class->getMethod('setUp'); + + // get setup or create a setup add add it there + if ($setupClassMethod !== null) { + $this->updateSetUpMethod($setupClassMethod, $parentSetupStaticCall, $assign); + } else { + $setUpMethod = $this->createSetUpMethod($parentSetupStaticCall, $assign); + $this->classManipulator->addAsFirstMethod($class, $setUpMethod); + } + } + + // update parent clsas if not already + if (! $this->isObjectType($class, KernelTestCase::class)) { + $class->extends = new FullyQualified(KernelTestCase::class); + } + + return $class; + } + /** * @return ObjectType[] */ @@ -219,13 +254,6 @@ private function collectNewProperties(Class_ $class): array return $this->newProperties; } - private function createPropertyFromType(ObjectType $objectType): Property - { - $propertyName = $this->propertyNaming->fqnToVariableName($objectType); - - return $this->nodeFactory->createPrivatePropertyFromNameAndType($propertyName, $objectType); - } - private function convertStaticCallToPropertyMethodCall(StaticCall $staticCall, ObjectType $objectType): MethodCall { // create "$this->someService" instead @@ -239,21 +267,6 @@ private function convertStaticCallToPropertyMethodCall(StaticCall $staticCall, O return $methodCall; } - private function createContainerGetTypeMethodCall(ObjectType $objectType): MethodCall - { - $containerProperty = new StaticPropertyFetch(new Name('self'), 'container'); - $getMethodCall = new MethodCall($containerProperty, 'get'); - - $className = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($objectType); - if (! $className instanceof Name) { - throw new ShouldNotHappenException(); - } - - $getMethodCall->args[] = new Arg(new ClassConstFetch($className, 'class')); - - return $getMethodCall; - } - /** * @param ObjectType[] $newProperties */ @@ -270,6 +283,11 @@ private function addNewPropertiesToClass(Class_ $class, array $newProperties): C return $class; } + private function createParentSetUpStaticCall(): Expression + { + return new Expression(new StaticCall(new Name('parent'), 'setUp')); + } + private function createContainerGetTypeToPropertyAssign(ObjectType $objectType): Expression { $getMethodCall = $this->createContainerGetTypeMethodCall($objectType); @@ -282,6 +300,55 @@ private function createContainerGetTypeToPropertyAssign(ObjectType $objectType): return new Expression($assign); } + private function updateSetUpMethod( + ClassMethod $setupClassMethod, + Expression $parentSetupStaticCall, + Expression $assign + ): void { + $parentSetUpStaticCallPosition = $this->getParentSetUpStaticCallPosition($setupClassMethod); + if ($parentSetUpStaticCallPosition === null) { + $setupClassMethod->stmts = array_merge([$parentSetupStaticCall, $assign], (array) $setupClassMethod->stmts); + } else { + assert($setupClassMethod->stmts !== null); + array_splice($setupClassMethod->stmts, $parentSetUpStaticCallPosition + 1, 0, [$assign]); + } + } + + private function createSetUpMethod(Expression $parentSetupStaticCall, Expression $assign): ClassMethod + { + $classMethodBuilder = $this->builderFactory->method('setUp'); + $classMethodBuilder->makeProtected(); + $classMethodBuilder->addStmt($parentSetupStaticCall); + $classMethodBuilder->addStmt($assign); + + $classMethod = $classMethodBuilder->getNode(); + + $this->phpUnitTypeDeclarationDecorator->decorate($classMethod); + return $classMethod; + } + + private function createPropertyFromType(ObjectType $objectType): Property + { + $propertyName = $this->propertyNaming->fqnToVariableName($objectType); + + return $this->nodeFactory->createPrivatePropertyFromNameAndType($propertyName, $objectType); + } + + private function createContainerGetTypeMethodCall(ObjectType $objectType): MethodCall + { + $containerProperty = new StaticPropertyFetch(new Name('self'), 'container'); + $getMethodCall = new MethodCall($containerProperty, 'get'); + + $className = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($objectType); + if (! $className instanceof Name) { + throw new ShouldNotHappenException(); + } + + $getMethodCall->args[] = new Arg(new ClassConstFetch($className, 'class')); + + return $getMethodCall; + } + private function getParentSetUpStaticCallPosition(ClassMethod $setupClassMethod): ?int { foreach ((array) $setupClassMethod->stmts as $position => $methodStmt) { @@ -306,71 +373,4 @@ private function getParentSetUpStaticCallPosition(ClassMethod $setupClassMethod) return null; } - - private function createParentSetUpStaticCall(): Expression - { - return new Expression(new StaticCall(new Name('parent'), 'setUp')); - } - - private function processPHPUnitClass(Class_ $class): ?Class_ - { - // add property with the object - $newProperties = $this->collectNewProperties($class); - if ($newProperties === []) { - return null; - } - - // add all properties to class - $class = $this->addNewPropertiesToClass($class, $newProperties); - - $parentSetupStaticCall = $this->createParentSetUpStaticCall(); - foreach ($newProperties as $type) { - // container fetch assign - $assign = $this->createContainerGetTypeToPropertyAssign($type); - - $setupClassMethod = $class->getMethod('setUp'); - - // get setup or create a setup add add it there - if ($setupClassMethod !== null) { - $this->updateSetUpMethod($setupClassMethod, $parentSetupStaticCall, $assign); - } else { - $setUpMethod = $this->createSetUpMethod($parentSetupStaticCall, $assign); - $this->classManipulator->addAsFirstMethod($class, $setUpMethod); - } - } - - // update parent clsas if not already - if (! $this->isObjectType($class, KernelTestCase::class)) { - $class->extends = new FullyQualified(KernelTestCase::class); - } - - return $class; - } - - private function createSetUpMethod(Expression $parentSetupStaticCall, Expression $assign): ClassMethod - { - $classMethodBuilder = $this->builderFactory->method('setUp'); - $classMethodBuilder->makeProtected(); - $classMethodBuilder->addStmt($parentSetupStaticCall); - $classMethodBuilder->addStmt($assign); - - $classMethod = $classMethodBuilder->getNode(); - - $this->phpUnitTypeDeclarationDecorator->decorate($classMethod); - return $classMethod; - } - - private function updateSetUpMethod( - ClassMethod $setupClassMethod, - Expression $parentSetupStaticCall, - Expression $assign - ): void { - $parentSetUpStaticCallPosition = $this->getParentSetUpStaticCallPosition($setupClassMethod); - if ($parentSetUpStaticCallPosition === null) { - $setupClassMethod->stmts = array_merge([$parentSetupStaticCall, $assign], (array) $setupClassMethod->stmts); - } else { - assert($setupClassMethod->stmts !== null); - array_splice($setupClassMethod->stmts, $parentSetUpStaticCallPosition + 1, 0, [$assign]); - } - } } diff --git a/packages/RemovingStatic/src/Rector/Class_/StaticTypeToSetterInjectionRector.php b/packages/RemovingStatic/src/Rector/Class_/StaticTypeToSetterInjectionRector.php index 98dd20d963d0..4eafbde8a943 100644 --- a/packages/RemovingStatic/src/Rector/Class_/StaticTypeToSetterInjectionRector.php +++ b/packages/RemovingStatic/src/Rector/Class_/StaticTypeToSetterInjectionRector.php @@ -122,15 +122,6 @@ public function refactor(Node $node): ?Node return null; } - private function isEntityFactoryStaticCall(Node $node, ObjectType $objectType): bool - { - if (! $node instanceof StaticCall) { - return false; - } - - return $this->isObjectType($node->class, $objectType); - } - private function processClass(Class_ $class): Class_ { foreach ($this->staticTypes as $implements => $staticType) { @@ -176,6 +167,15 @@ function (Node $node) use ($objectType): bool { return $class; } + private function isEntityFactoryStaticCall(Node $node, ObjectType $objectType): bool + { + if (! $node instanceof StaticCall) { + return false; + } + + return $this->isObjectType($node->class, $objectType); + } + private function createSetEntityFactoryClassMethod( string $variableName, Param $param, diff --git a/packages/RemovingStatic/src/UniqueObjectFactoryFactory.php b/packages/RemovingStatic/src/UniqueObjectFactoryFactory.php index 4245ffb3e84b..9e561ff618ad 100644 --- a/packages/RemovingStatic/src/UniqueObjectFactoryFactory.php +++ b/packages/RemovingStatic/src/UniqueObjectFactoryFactory.php @@ -93,22 +93,33 @@ public function createFactoryClass(Class_ $class, ObjectType $objectType): Class return $factoryClassBuilder->getNode(); } + private function resolveClassShortName(string $name): string + { + if (Strings::contains($name, '\\')) { + return (string) Strings::after($name, '\\', -1); + } + + return $name; + } + /** - * @param Param[] $params - * - * @return Assign[] + * @return Property[] */ - private function createAssignsFromParams(array $params): array + private function createPropertiesFromTypes(ObjectType $objectType): array { - $assigns = []; + $properties = []; - /** @var Param $param */ - foreach ($params as $param) { - $propertyFetch = new PropertyFetch(new Variable('this'), $param->var->name); - $assigns[] = new Assign($propertyFetch, new Variable($param->var->name)); - } + $propertyName = $this->propertyNaming->fqnToVariableName($objectType); + $propertyBuilder = $this->builderFactory->property($propertyName); + $propertyBuilder->makePrivate(); - return $assigns; + $property = $propertyBuilder->getNode(); + + $this->docBlockManipulator->changeVarTag($property, $objectType); + + $properties[] = $property; + + return $properties; } private function createConstructMethod(ObjectType $objectType): ClassMethod @@ -166,31 +177,20 @@ private function createCreateMethod(Class_ $class, string $className, array $pro } /** - * @return Property[] + * @param Param[] $params + * + * @return Assign[] */ - private function createPropertiesFromTypes(ObjectType $objectType): array + private function createAssignsFromParams(array $params): array { - $properties = []; - - $propertyName = $this->propertyNaming->fqnToVariableName($objectType); - $propertyBuilder = $this->builderFactory->property($propertyName); - $propertyBuilder->makePrivate(); - - $property = $propertyBuilder->getNode(); - - $this->docBlockManipulator->changeVarTag($property, $objectType); - - $properties[] = $property; - - return $properties; - } + $assigns = []; - private function resolveClassShortName(string $name): string - { - if (Strings::contains($name, '\\')) { - return (string) Strings::after($name, '\\', -1); + /** @var Param $param */ + foreach ($params as $param) { + $propertyFetch = new PropertyFetch(new Variable('this'), $param->var->name); + $assigns[] = new Assign($propertyFetch, new Variable($param->var->name)); } - return $name; + return $assigns; } } diff --git a/packages/Renaming/src/Rector/Class_/RenameClassRector.php b/packages/Renaming/src/Rector/Class_/RenameClassRector.php index 4f333d11b493..137ed9407f99 100644 --- a/packages/Renaming/src/Rector/Class_/RenameClassRector.php +++ b/packages/Renaming/src/Rector/Class_/RenameClassRector.php @@ -144,61 +144,55 @@ public function refactor(Node $node): ?Node } /** - * Checks validity: - * - * - extends SomeClass - * - extends SomeInterface - * - * - new SomeClass - * - new SomeInterface - * - * - implements SomeInterface - * - implements SomeClass + * Replace types in @var/@param/@return/@throws, + * Doctrine @ORM entity targetClass, Serialize, Assert etc. */ - private function isClassToInterfaceValidChange(Node $node, string $newName): bool + private function refactorPhpDoc(Node $node): void { - // ensure new is not with interface - $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); - if ($parentNode instanceof New_ && interface_exists($newName)) { - return false; + $nodePhpDocInfo = $this->getPhpDocInfo($node); + if ($nodePhpDocInfo === null) { + return; } - if ($parentNode instanceof Class_) { - return $this->isValidClassNameChange($node, $newName, $parentNode); + if (! $this->docBlockManipulator->hasNodeTypeTags($node)) { + return; } - // prevent to change to import, that already exists - if ($parentNode instanceof UseUse) { - return $this->isValidUseImportChange($newName, $parentNode); + foreach ($this->oldToNewClasses as $oldClass => $newClass) { + $oldClassType = new ObjectType($oldClass); + $newClassType = new FullyQualifiedObjectType($newClass); + + $this->docBlockManipulator->changeType($node, $oldClassType, $newClassType); } - return true; + $this->phpDocClassRenamer->changeTypeInAnnotationTypes($node, $this->oldToNewClasses); } - private function isValidUseImportChange(string $newName, UseUse $useUse): bool + private function refactorName(Name $name): ?Name { - /** @var Use_[]|null $useNodes */ - $useNodes = $useUse->getAttribute(AttributeKey::USE_NODES); - if ($useNodes === null) { - return true; + $stringName = $this->getName($name); + if ($stringName === null) { + return null; } - foreach ($useNodes as $useNode) { - if ($this->isName($useNode, $newName)) { - // name already exists - return false; - } + $newName = $this->oldToNewClasses[$stringName] ?? null; + if (! $newName) { + return null; } - return true; - } + if (! $this->isClassToInterfaceValidChange($name, $newName)) { + return null; + } - private function isValidClassNameChange(Node $node, string $newName, Class_ $classNode): bool - { - if ($classNode->extends === $node && interface_exists($newName)) { - return false; + $parentNode = $name->getAttribute(AttributeKey::PARENT_NODE); + // no need to preslash "use \SomeNamespace" of imported namespace + if ($parentNode instanceof UseUse) { + if ($parentNode->type === Use_::TYPE_NORMAL || $parentNode->type === Use_::TYPE_UNKNOWN) { + return new Name($newName); + } } - return ! (in_array($node, $classNode->implements, true) && class_exists($newName)); + + return new FullyQualified($newName); } private function refactorNamespaceNode(Namespace_ $namespace): ?Node @@ -228,21 +222,6 @@ private function refactorNamespaceNode(Namespace_ $namespace): ?Node return $namespace; } - private function getClassOfNamespaceToRefactor(Namespace_ $namespace): ?ClassLike - { - $foundClass = $this->betterNodeFinder->findFirst($namespace, function (Node $node): bool { - if (! $node instanceof ClassLike) { - return false; - } - - $classLikeName = $this->getName($node); - - return isset($this->oldToNewClasses[$classLikeName]); - }); - - return $foundClass instanceof ClassLike ? $foundClass : null; - } - private function refactorClassLikeNode(ClassLike $classLike): ?Node { $name = $this->getName($classLike); @@ -281,56 +260,51 @@ private function refactorClassLikeNode(ClassLike $classLike): ?Node return $classLike; } - private function refactorName(Name $name): ?Name + /** + * Checks validity: + * + * - extends SomeClass + * - extends SomeInterface + * + * - new SomeClass + * - new SomeInterface + * + * - implements SomeInterface + * - implements SomeClass + */ + private function isClassToInterfaceValidChange(Node $node, string $newName): bool { - $stringName = $this->getName($name); - if ($stringName === null) { - return null; - } - - $newName = $this->oldToNewClasses[$stringName] ?? null; - if (! $newName) { - return null; + // ensure new is not with interface + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + if ($parentNode instanceof New_ && interface_exists($newName)) { + return false; } - if (! $this->isClassToInterfaceValidChange($name, $newName)) { - return null; + if ($parentNode instanceof Class_) { + return $this->isValidClassNameChange($node, $newName, $parentNode); } - $parentNode = $name->getAttribute(AttributeKey::PARENT_NODE); - // no need to preslash "use \SomeNamespace" of imported namespace + // prevent to change to import, that already exists if ($parentNode instanceof UseUse) { - if ($parentNode->type === Use_::TYPE_NORMAL || $parentNode->type === Use_::TYPE_UNKNOWN) { - return new Name($newName); - } + return $this->isValidUseImportChange($newName, $parentNode); } - return new FullyQualified($newName); + return true; } - /** - * Replace types in @var/@param/@return/@throws, - * Doctrine @ORM entity targetClass, Serialize, Assert etc. - */ - private function refactorPhpDoc(Node $node): void + private function getClassOfNamespaceToRefactor(Namespace_ $namespace): ?ClassLike { - $nodePhpDocInfo = $this->getPhpDocInfo($node); - if ($nodePhpDocInfo === null) { - return; - } - - if (! $this->docBlockManipulator->hasNodeTypeTags($node)) { - return; - } + $foundClass = $this->betterNodeFinder->findFirst($namespace, function (Node $node): bool { + if (! $node instanceof ClassLike) { + return false; + } - foreach ($this->oldToNewClasses as $oldClass => $newClass) { - $oldClassType = new ObjectType($oldClass); - $newClassType = new FullyQualifiedObjectType($newClass); + $classLikeName = $this->getName($node); - $this->docBlockManipulator->changeType($node, $oldClassType, $newClassType); - } + return isset($this->oldToNewClasses[$classLikeName]); + }); - $this->phpDocClassRenamer->changeTypeInAnnotationTypes($node, $this->oldToNewClasses); + return $foundClass instanceof ClassLike ? $foundClass : null; } private function ensureClassWillNotBeDuplicate(string $newName, string $oldName): void @@ -360,4 +334,30 @@ private function changeNameToFullyQualifiedName(ClassLike $classLike): void $node->setAttribute(AttributeKey::ORIGINAL_NODE, null); }); } + + private function isValidClassNameChange(Node $node, string $newName, Class_ $classNode): bool + { + if ($classNode->extends === $node && interface_exists($newName)) { + return false; + } + return ! (in_array($node, $classNode->implements, true) && class_exists($newName)); + } + + private function isValidUseImportChange(string $newName, UseUse $useUse): bool + { + /** @var Use_[]|null $useNodes */ + $useNodes = $useUse->getAttribute(AttributeKey::USE_NODES); + if ($useNodes === null) { + return true; + } + + foreach ($useNodes as $useNode) { + if ($this->isName($useNode, $newName)) { + // name already exists + return false; + } + } + + return true; + } } diff --git a/packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php b/packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php index a683903999e0..d6c0da9f7253 100644 --- a/packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php +++ b/packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php @@ -121,6 +121,19 @@ public function refactor(Node $node): ?Node return $this->changeConstantVisibility($node, $useClasses, $parentConstIsProtected, $class); } + private function shouldSkip(ClassConst $classConst): bool + { + if ($classConst->getAttribute(self::HAS_NEW_ACCESS_LEVEL)) { + return true; + } + + if (! $this->isAtLeastPhpVersion('7.1')) { + return true; + } + + return count($classConst->consts) !== 1; + } + private function findParentClassConstant(string $class, string $constant): ?ClassConst { $classNode = $this->parsedNodesByType->findClass($class); @@ -143,34 +156,6 @@ private function findParentClassConstant(string $class, string $constant): ?Clas return null; } - private function makePrivateOrWeaker(ClassConst $classConst, bool $protectedRequired): void - { - if ($protectedRequired) { - $this->makeProtected($classConst); - } else { - $this->makePrivate($classConst); - } - } - - /** - * @param string[] $useClasses - */ - private function isUsedByChildrenOnly(array $useClasses, string $class): bool - { - $isChild = false; - - foreach ($useClasses as $useClass) { - if (is_a($useClass, $class, true)) { - $isChild = true; - } else { - // not a child, must be public - return false; - } - } - - return $isChild; - } - private function findClassConstantFetches(string $className, string $constantName): ?array { $classConstantFetchByClassAndName = $this->classConstantFetchAnalyzer->provideClassConstantFetchByClassAndName(); @@ -209,16 +194,31 @@ private function changeConstantVisibility( return $classConst; } - private function shouldSkip(ClassConst $classConst): bool + private function makePrivateOrWeaker(ClassConst $classConst, bool $protectedRequired): void { - if ($classConst->getAttribute(self::HAS_NEW_ACCESS_LEVEL)) { - return true; + if ($protectedRequired) { + $this->makeProtected($classConst); + } else { + $this->makePrivate($classConst); } + } - if (! $this->isAtLeastPhpVersion('7.1')) { - return true; + /** + * @param string[] $useClasses + */ + private function isUsedByChildrenOnly(array $useClasses, string $class): bool + { + $isChild = false; + + foreach ($useClasses as $useClass) { + if (is_a($useClass, $class, true)) { + $isChild = true; + } else { + // not a child, must be public + return false; + } } - return count($classConst->consts) !== 1; + return $isChild; } } diff --git a/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php b/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php index 666778f8b484..c1f130497ba3 100644 --- a/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php +++ b/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php @@ -101,34 +101,31 @@ public function refactor(Node $node): ?Node return $this->refactorAlreadyCreatedNode($node, $phpDocInfo, $variable); } - private function createFuncCallBasedOnType(Type $type, Variable $variable): ?FuncCall + private function getVarDocVariableName(PhpDocInfo $phpDocInfo): ?string { - if ($type instanceof ObjectType) { - $instanceOf = new Instanceof_($variable, new FullyQualified($type->getClassName())); - return $this->createFunction('assert', [$instanceOf]); - } - - if ($type instanceof IntegerType) { - $isInt = $this->createFunction('is_int', [$variable]); - return $this->createFunction('assert', [$isInt]); + $varTagValueNode = $phpDocInfo->getVarTagValue(); + if ($varTagValueNode === null) { + return null; } - if ($type instanceof FloatType) { - $isFloat = $this->createFunction('is_float', [$variable]); - return $this->createFunction('assert', [$isFloat]); + $variableName = $varTagValueNode->variableName; + // no variable + if (empty($variableName)) { + return null; } - if ($type instanceof StringType) { - $isString = $this->createFunction('is_string', [$variable]); - return $this->createFunction('assert', [$isString]); - } + return ltrim($variableName, '$'); + } - if ($type instanceof BooleanType) { - $isInt = $this->createFunction('is_bool', [$variable]); - return $this->createFunction('assert', [$isInt]); - } + private function findVariableByName(Stmt $stmt, string $docVariableName): ?Variable + { + return $this->betterNodeFinder->findFirst($stmt, function (Node $stmt) use ($docVariableName): bool { + if (! $stmt instanceof Variable) { + return false; + } - return null; + return $this->isName($stmt, $docVariableName); + }); } private function isVariableJustCreated(Node $node, string $docVariableName): bool @@ -150,41 +147,6 @@ private function isVariableJustCreated(Node $node, string $docVariableName): boo return $this->isName($assign->var, $docVariableName); } - private function getVarDocVariableName(PhpDocInfo $phpDocInfo): ?string - { - $varTagValueNode = $phpDocInfo->getVarTagValue(); - if ($varTagValueNode === null) { - return null; - } - - $variableName = $varTagValueNode->variableName; - // no variable - if (empty($variableName)) { - return null; - } - - return ltrim($variableName, '$'); - } - - private function removeVarAnnotation(Node $node, PhpDocInfo $phpDocInfo): void - { - $varTagValueNode = $phpDocInfo->getByType(VarTagValueNode::class); - $phpDocInfo->removeTagValueNodeFromNode($varTagValueNode); - - $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $phpDocInfo); - } - - private function findVariableByName(Stmt $stmt, string $docVariableName): ?Variable - { - return $this->betterNodeFinder->findFirst($stmt, function (Node $stmt) use ($docVariableName): bool { - if (! $stmt instanceof Variable) { - return false; - } - - return $this->isName($stmt, $docVariableName); - }); - } - private function refactorFreshlyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, Variable $variable): ?Node { $node->setAttribute('comments', []); @@ -220,4 +182,42 @@ private function refactorAlreadyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, return $node; } + + private function createFuncCallBasedOnType(Type $type, Variable $variable): ?FuncCall + { + if ($type instanceof ObjectType) { + $instanceOf = new Instanceof_($variable, new FullyQualified($type->getClassName())); + return $this->createFunction('assert', [$instanceOf]); + } + + if ($type instanceof IntegerType) { + $isInt = $this->createFunction('is_int', [$variable]); + return $this->createFunction('assert', [$isInt]); + } + + if ($type instanceof FloatType) { + $isFloat = $this->createFunction('is_float', [$variable]); + return $this->createFunction('assert', [$isFloat]); + } + + if ($type instanceof StringType) { + $isString = $this->createFunction('is_string', [$variable]); + return $this->createFunction('assert', [$isString]); + } + + if ($type instanceof BooleanType) { + $isInt = $this->createFunction('is_bool', [$variable]); + return $this->createFunction('assert', [$isInt]); + } + + return null; + } + + private function removeVarAnnotation(Node $node, PhpDocInfo $phpDocInfo): void + { + $varTagValueNode = $phpDocInfo->getByType(VarTagValueNode::class); + $phpDocInfo->removeTagValueNodeFromNode($varTagValueNode); + + $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $phpDocInfo); + } } diff --git a/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php b/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php index 6ed384354570..fdfe828d4343 100644 --- a/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php +++ b/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php @@ -139,6 +139,16 @@ private function getContainer(string $requestServiceName): Container return $container; } + private function resolveKernelClass(): ?string + { + $kernelClassParameter = $this->parameterProvider->provideParameter(Option::KERNEL_CLASS_PARAMETER); + if ($kernelClassParameter) { + return $kernelClassParameter; + } + + return $this->getDefaultKernelClass(); + } + private function getDefaultKernelClass(): ?string { $possibleKernelClasses = ['App\Kernel', 'Kernel', 'AppKernel']; @@ -151,14 +161,4 @@ private function getDefaultKernelClass(): ?string return null; } - - private function resolveKernelClass(): ?string - { - $kernelClassParameter = $this->parameterProvider->provideParameter(Option::KERNEL_CLASS_PARAMETER); - if ($kernelClassParameter) { - return $kernelClassParameter; - } - - return $this->getDefaultKernelClass(); - } } diff --git a/packages/Symfony/src/Bridge/DependencyInjection/SymfonyContainerFactory.php b/packages/Symfony/src/Bridge/DependencyInjection/SymfonyContainerFactory.php index 34770ae12adb..5e9991938f30 100644 --- a/packages/Symfony/src/Bridge/DependencyInjection/SymfonyContainerFactory.php +++ b/packages/Symfony/src/Bridge/DependencyInjection/SymfonyContainerFactory.php @@ -67,6 +67,17 @@ public function createFromKernelClass(string $kernelClass): Container return $container; } + private function resolveEnvironment(): string + { + /** @var string|null $kernelEnvironment */ + $kernelEnvironment = $this->parameterProvider->provideParameter(Option::KERNEL_ENVIRONMENT_PARAMETER); + if ($kernelEnvironment) { + return $kernelEnvironment; + } + + return $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? self::FALLBACK_ENVIRONMENT; + } + /** * Mimics https://github.com/symfony/symfony/blob/f834c9262b411aa5793fcea23694e3ad3b5acbb4/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php#L200-L203 */ @@ -113,15 +124,4 @@ public function process(ContainerBuilder $containerBuilder): void return $containerBuilder; } - - private function resolveEnvironment(): string - { - /** @var string|null $kernelEnvironment */ - $kernelEnvironment = $this->parameterProvider->provideParameter(Option::KERNEL_ENVIRONMENT_PARAMETER); - if ($kernelEnvironment) { - return $kernelEnvironment; - } - - return $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? self::FALLBACK_ENVIRONMENT; - } } diff --git a/packages/Symfony/src/Rector/Class_/MakeCommandLazyRector.php b/packages/Symfony/src/Rector/Class_/MakeCommandLazyRector.php index 430b1bcf10ee..670fee9793ea 100644 --- a/packages/Symfony/src/Rector/Class_/MakeCommandLazyRector.php +++ b/packages/Symfony/src/Rector/Class_/MakeCommandLazyRector.php @@ -84,16 +84,6 @@ public function refactor(Node $node): ?Node return $node; } - private function createDefaultNameProperty(Node $commandNameNode): Property - { - $propertyBuilder = $this->builderFactory->property('defaultName'); - $propertyBuilder->makeProtected(); - $propertyBuilder->makeStatic(); - $propertyBuilder->setDefault($commandNameNode); - - return $propertyBuilder->getNode(); - } - private function resolveCommandNameAndRemove(Class_ $class): ?Node { $commandName = null; @@ -136,6 +126,16 @@ private function resolveCommandNameAndRemove(Class_ $class): ?Node return $commandName; } + private function createDefaultNameProperty(Node $commandNameNode): Property + { + $propertyBuilder = $this->builderFactory->property('defaultName'); + $propertyBuilder->makeProtected(); + $propertyBuilder->makeStatic(); + $propertyBuilder->setDefault($commandNameNode); + + return $propertyBuilder->getNode(); + } + private function matchCommandNameNodeInConstruct(Expr $expr): ?Node { if (! $expr instanceof MethodCall && ! $expr instanceof StaticCall) { diff --git a/packages/Symfony/src/Rector/Console/ConsoleExecuteReturnIntRector.php b/packages/Symfony/src/Rector/Console/ConsoleExecuteReturnIntRector.php index c9c5e4ce5833..9490f0ee1841 100644 --- a/packages/Symfony/src/Rector/Console/ConsoleExecuteReturnIntRector.php +++ b/packages/Symfony/src/Rector/Console/ConsoleExecuteReturnIntRector.php @@ -85,6 +85,18 @@ public function refactor(Node $node): ?Node return $node; } + private function refactorReturnTypeDeclaration(ClassMethod $classMethod): void + { + if ($classMethod->returnType) { + // already set + if ($this->isName($classMethod->returnType, 'int')) { + return; + } + } + + $classMethod->returnType = new Identifier('int'); + } + private function addReturn0ToMethod(ClassMethod $classMethod): void { $hasReturn = false; @@ -143,18 +155,6 @@ private function setReturnTo0InsteadOfNull(Return_ $return): void } } - private function refactorReturnTypeDeclaration(ClassMethod $classMethod): void - { - if ($classMethod->returnType) { - // already set - if ($this->isName($classMethod->returnType, 'int')) { - return; - } - } - - $classMethod->returnType = new Identifier('int'); - } - private function refactorTernaryReturn(Ternary $ternary): bool { $hasChanged = false; diff --git a/packages/Symfony/src/Rector/MethodCall/MakeDispatchFirstArgumentEventRector.php b/packages/Symfony/src/Rector/MethodCall/MakeDispatchFirstArgumentEventRector.php index a61ef0ac46a6..b6be6db72212 100644 --- a/packages/Symfony/src/Rector/MethodCall/MakeDispatchFirstArgumentEventRector.php +++ b/packages/Symfony/src/Rector/MethodCall/MakeDispatchFirstArgumentEventRector.php @@ -82,26 +82,6 @@ public function refactor(Node $node): ?Node return null; } - /** - * Is the event name just `::class`? - * We can remove it - */ - private function isEventNameSameAsEventObjectClass(MethodCall $methodCall): bool - { - if (! $methodCall->args[1]->value instanceof ClassConstFetch) { - return false; - } - - $classConst = $this->getValue($methodCall->args[1]->value); - $eventStaticType = $this->getStaticType($methodCall->args[0]->value); - - if (! $eventStaticType instanceof ObjectType) { - return false; - } - - return $classConst === $eventStaticType->getClassName(); - } - private function shouldSkip(MethodCall $methodCall): bool { if (! $this->isObjectType($methodCall->var, EventDispatcherInterface::class)) { @@ -148,4 +128,24 @@ private function refactorGetCallFuncCall( return null; } + + /** + * Is the event name just `::class`? + * We can remove it + */ + private function isEventNameSameAsEventObjectClass(MethodCall $methodCall): bool + { + if (! $methodCall->args[1]->value instanceof ClassConstFetch) { + return false; + } + + $classConst = $this->getValue($methodCall->args[1]->value); + $eventStaticType = $this->getStaticType($methodCall->args[0]->value); + + if (! $eventStaticType instanceof ObjectType) { + return false; + } + + return $classConst === $eventStaticType->getClassName(); + } } diff --git a/packages/Symfony/src/Rector/MethodCall/SimplifyWebTestCaseAssertionsRector.php b/packages/Symfony/src/Rector/MethodCall/SimplifyWebTestCaseAssertionsRector.php index 536b99a5aab4..424a61f81dc2 100644 --- a/packages/Symfony/src/Rector/MethodCall/SimplifyWebTestCaseAssertionsRector.php +++ b/packages/Symfony/src/Rector/MethodCall/SimplifyWebTestCaseAssertionsRector.php @@ -140,6 +140,30 @@ private function isInWebTestCase(Node $node): bool return $this->isObjectType($class, WebTestCase::class); } + private function processAssertResponseStatusCodeSame(Node $node): ?MethodCall + { + if (! $node instanceof MethodCall) { + return null; + } + + if (! $this->isName($node->name, 'assertSame')) { + return null; + } + + if (! $this->areNodesEqual($node->args[1]->value, $this->getStatusCodeMethodCall)) { + return null; + } + + $statusCode = $this->getValue($node->args[0]->value); + + // handled by another methods + if (in_array($statusCode, [200, 301], true)) { + return null; + } + + return new MethodCall(new Variable('this'), 'assertResponseStatusCodeSame', [$node->args[0]]); + } + /** * @return Arg[]|null */ @@ -219,28 +243,4 @@ private function processAssertResponseRedirects(MethodCall $methodCall): ?Node return null; } - - private function processAssertResponseStatusCodeSame(Node $node): ?MethodCall - { - if (! $node instanceof MethodCall) { - return null; - } - - if (! $this->isName($node->name, 'assertSame')) { - return null; - } - - if (! $this->areNodesEqual($node->args[1]->value, $this->getStatusCodeMethodCall)) { - return null; - } - - $statusCode = $this->getValue($node->args[0]->value); - - // handled by another methods - if (in_array($statusCode, [200, 301], true)) { - return null; - } - - return new MethodCall(new Variable('this'), 'assertResponseStatusCodeSame', [$node->args[0]]); - } } diff --git a/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php b/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php index 4232d69a7b58..69c17bb0d832 100644 --- a/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php +++ b/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php @@ -129,6 +129,15 @@ private function processStringType(Node $node, int $argumentPosition, Node $firs $this->processPreviousAssign($node, $firstArgument); } + private function isFunctionNamed(Node $node, string $name): bool + { + if (! $node instanceof FuncCall) { + return false; + } + + return $this->isName($node, $name); + } + private function processPreviousAssign(Node $node, Node $firstArgument): void { /** @var Assign|null $createdNode */ @@ -162,13 +171,4 @@ private function findPreviousNodeAssign(Node $node, Node $firstArgument): ?Assig return $checkedNode; }); } - - private function isFunctionNamed(Node $node, string $name): bool - { - if (! $node instanceof FuncCall) { - return false; - } - - return $this->isName($node, $name); - } } diff --git a/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php b/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php index 272d7b4085b8..8a2340d9ebf1 100644 --- a/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php +++ b/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php @@ -243,25 +243,6 @@ private function changeListenerToSubscriberWithMethods(Class_ $class, array $eve return $class; } - /** - * @return String_|ClassConstFetch - */ - private function createEventName(string $eventName): Node - { - if (class_exists($eventName)) { - return $this->createClassConstantReference($eventName); - } - - // is string a that could be caught in constant, e.g. KernelEvents? - if (isset($this->eventNamesToClassConstants[$eventName])) { - [$class, $constant] = $this->eventNamesToClassConstants[$eventName]; - - return $this->createClassConstant($class, $constant); - } - - return new String_($eventName); - } - /** * @param mixed[][] $eventsToMethods */ @@ -289,14 +270,23 @@ private function createGetSubscribedEventsClassMethod(array $eventsToMethods): C return $getSubscribedEventsMethod; } - private function decorateClassMethodWithReturnType(ClassMethod $classMethod): void + /** + * @return String_|ClassConstFetch + */ + private function createEventName(string $eventName): Node { - if ($this->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) { - $classMethod->returnType = new Identifier('array'); + if (class_exists($eventName)) { + return $this->createClassConstantReference($eventName); } - $arrayMixedType = new ArrayType(new MixedType(), new MixedType(true)); - $this->docBlockManipulator->addReturnTag($classMethod, $arrayMixedType); + // is string a that could be caught in constant, e.g. KernelEvents? + if (isset($this->eventNamesToClassConstants[$eventName])) { + [$class, $constant] = $this->eventNamesToClassConstants[$eventName]; + + return $this->createClassConstant($class, $constant); + } + + return new String_($eventName); } /** @@ -348,4 +338,14 @@ private function createMultipleMethods( $eventsToMethodsArray->items[] = new ArrayItem($multipleMethodsArray, $expr); } + + private function decorateClassMethodWithReturnType(ClassMethod $classMethod): void + { + if ($this->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) { + $classMethod->returnType = new Identifier('array'); + } + + $arrayMixedType = new ArrayType(new MixedType(), new MixedType(true)); + $this->docBlockManipulator->addReturnTag($classMethod, $arrayMixedType); + } } diff --git a/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php b/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php index 5c13d06b684e..1dea095da146 100644 --- a/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php +++ b/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php @@ -148,6 +148,19 @@ private function shouldSkipType(Type $newType, ClassMethod $classMethod): bool return false; } + private function shouldSkipArrayType(ArrayType $arrayType, ClassMethod $classMethod): bool + { + if ($this->isNewAndCurrentTypeBothCallable($arrayType, $classMethod)) { + return true; + } + + if ($this->isMixedOfSpecificOverride($arrayType, $classMethod)) { + return true; + } + + return false; + } + private function isNewAndCurrentTypeBothCallable(ArrayType $newArrayType, ClassMethod $classMethod): bool { $currentPhpDocInfo = $this->getPhpDocInfo($classMethod); @@ -189,17 +202,4 @@ private function isMixedOfSpecificOverride(ArrayType $arrayType, ClassMethod $cl return true; } - - private function shouldSkipArrayType(ArrayType $arrayType, ClassMethod $classMethod): bool - { - if ($this->isNewAndCurrentTypeBothCallable($arrayType, $classMethod)) { - return true; - } - - if ($this->isMixedOfSpecificOverride($arrayType, $classMethod)) { - return true; - } - - return false; - } } diff --git a/packages/TypeDeclaration/src/Rector/ClassMethod/AddMethodCallBasedParamTypeRector.php b/packages/TypeDeclaration/src/Rector/ClassMethod/AddMethodCallBasedParamTypeRector.php index 25541b22c631..71507bb812b1 100644 --- a/packages/TypeDeclaration/src/Rector/ClassMethod/AddMethodCallBasedParamTypeRector.php +++ b/packages/TypeDeclaration/src/Rector/ClassMethod/AddMethodCallBasedParamTypeRector.php @@ -116,31 +116,6 @@ public function refactor(Node $node): ?Node return $node; } - private function skipArgumentStaticType(Node $node, Type $argumentStaticType, int $position): bool - { - if ($argumentStaticType instanceof MixedType) { - return true; - } - - if (! isset($node->params[$position])) { - return true; - } - - $parameter = $node->params[$position]; - if ($parameter->type === null) { - return false; - } - - $parameterStaticType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($parameter->type); - - // already completed → skip - if ($parameterStaticType->equals($argumentStaticType)) { - return true; - } - - return false; - } - /** * @param MethodCall[]|StaticCall[]|Array_[] $classMethodCalls * @return Type[] @@ -166,4 +141,29 @@ private function getCallTypesByPosition(array $classMethodCalls): array return $staticTypeByArgumentPosition; } + + private function skipArgumentStaticType(Node $node, Type $argumentStaticType, int $position): bool + { + if ($argumentStaticType instanceof MixedType) { + return true; + } + + if (! isset($node->params[$position])) { + return true; + } + + $parameter = $node->params[$position]; + if ($parameter->type === null) { + return false; + } + + $parameterStaticType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($parameter->type); + + // already completed → skip + if ($parameterStaticType->equals($argumentStaticType)) { + return true; + } + + return false; + } } diff --git a/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php b/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php index 66e330a17802..c7357feba3f3 100644 --- a/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php +++ b/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php @@ -162,6 +162,55 @@ public function refactor(Node $node): ?Node return $node; } + /** + * @param ClassMethod|Function_ $node + */ + private function shouldSkip(Node $node): bool + { + if ($this->overrideExistingReturnTypes === false) { + if ($node->returnType) { + return true; + } + } + + if (! $node instanceof ClassMethod) { + return false; + } + + return $this->isNames($node, self::EXCLUDED_METHOD_NAMES); + } + + /** + * @param ClassMethod|Function_ $node + */ + private function isReturnTypeAlreadyAdded(Node $node, Type $returnType): bool + { + $returnNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType); + + if ($node->returnType === null) { + return false; + } + + if ($this->areNodesEqual($node->returnType, $returnNode)) { + return true; + } + + // is array <=> iterable <=> Iterator co-type? → skip + if ($this->isArrayIterableIteratorCoType($node, $returnType)) { + return true; + } + + // prevent overriding self with itself + if ($this->print($node->returnType) === 'self') { + $className = $node->getAttribute(AttributeKey::CLASS_NAME); + if (ltrim($this->print($returnNode), '\\') === $className) { + return true; + } + } + + return false; + } + /** * Add typehint to all children class methods */ @@ -193,6 +242,15 @@ private function populateChildren(ClassMethod $classMethod, Type $returnType): v } } + private function isArrayIterableIteratorCoType(Node $node, Type $returnType): bool + { + if (! $this->isNames($node->returnType, ['iterable', 'Iterator', 'array'])) { + return false; + } + + return $this->isStaticTypeIterable($returnType); + } + private function addReturnTypeToChildMethod( ClassLike $classLike, ClassMethod $classMethod, @@ -218,55 +276,6 @@ private function addReturnTypeToChildMethod( $this->notifyNodeChangeFileInfo($currentClassMethod); } - /** - * @param ClassMethod|Function_ $node - */ - private function shouldSkip(Node $node): bool - { - if ($this->overrideExistingReturnTypes === false) { - if ($node->returnType) { - return true; - } - } - - if (! $node instanceof ClassMethod) { - return false; - } - - return $this->isNames($node, self::EXCLUDED_METHOD_NAMES); - } - - /** - * @param ClassMethod|Function_ $node - */ - private function isReturnTypeAlreadyAdded(Node $node, Type $returnType): bool - { - $returnNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType); - - if ($node->returnType === null) { - return false; - } - - if ($this->areNodesEqual($node->returnType, $returnNode)) { - return true; - } - - // is array <=> iterable <=> Iterator co-type? → skip - if ($this->isArrayIterableIteratorCoType($node, $returnType)) { - return true; - } - - // prevent overriding self with itself - if ($this->print($node->returnType) === 'self') { - $className = $node->getAttribute(AttributeKey::CLASS_NAME); - if (ltrim($this->print($returnNode), '\\') === $className) { - return true; - } - } - - return false; - } - private function isStaticTypeIterable(Type $type): bool { if ($type instanceof ArrayType) { @@ -295,13 +304,4 @@ private function isStaticTypeIterable(Type $type): bool return false; } - - private function isArrayIterableIteratorCoType(Node $node, Type $returnType): bool - { - if (! $this->isNames($node->returnType, ['iterable', 'Iterator', 'array'])) { - return false; - } - - return $this->isStaticTypeIterable($returnType); - } } diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php index b51603e289c8..ca56b67888f8 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php @@ -80,30 +80,6 @@ public function getPriority(): int return 800; } - private function getResolveParamStaticTypeAsPHPStanType(ClassMethod $classMethod, string $propertyName): Type - { - $paramStaticType = new ArrayType(new MixedType(), new MixedType()); - - $this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use ( - $propertyName, - &$paramStaticType - ): ?int { - if (! $node instanceof Variable) { - return null; - } - - if (! $this->nameResolver->isName($node, $propertyName)) { - return null; - } - - $paramStaticType = $this->nodeTypeResolver->getStaticType($node); - - return NodeTraverser::STOP_TRAVERSAL; - }); - - return $paramStaticType; - } - /** * In case the property name is different to param name: * @@ -151,22 +127,6 @@ private function resolveParamForPropertyFetch(ClassMethod $classMethod, string $ return null; } - private function isParamNullable(Param $param): bool - { - if ($param->type instanceof NullableType) { - return true; - } - - if ($param->default) { - $defaultValueStaticType = $this->nodeTypeResolver->getStaticType($param->default); - if ($defaultValueStaticType instanceof NullType) { - return true; - } - } - - return false; - } - private function resolveParamTypeToPHPStanType(Param $param): Type { if ($param->type === null) { @@ -192,6 +152,46 @@ private function resolveParamTypeToPHPStanType(Param $param): Type return $this->staticTypeMapper->mapPhpParserNodePHPStanType($param->type); } + private function getResolveParamStaticTypeAsPHPStanType(ClassMethod $classMethod, string $propertyName): Type + { + $paramStaticType = new ArrayType(new MixedType(), new MixedType()); + + $this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use ( + $propertyName, + &$paramStaticType + ): ?int { + if (! $node instanceof Variable) { + return null; + } + + if (! $this->nameResolver->isName($node, $propertyName)) { + return null; + } + + $paramStaticType = $this->nodeTypeResolver->getStaticType($node); + + return NodeTraverser::STOP_TRAVERSAL; + }); + + return $paramStaticType; + } + + private function isParamNullable(Param $param): bool + { + if ($param->type instanceof NullableType) { + return true; + } + + if ($param->default) { + $defaultValueStaticType = $this->nodeTypeResolver->getStaticType($param->default); + if ($defaultValueStaticType instanceof NullType) { + return true; + } + } + + return false; + } + private function resolveFullyQualifiedOrAlaisedObjectType(Param $param): ?Type { if ($param->type === null) { diff --git a/packages/TypeDeclaration/src/TypeNormalizer.php b/packages/TypeDeclaration/src/TypeNormalizer.php index d7b1ec1d3193..bdf6b3a1f9e7 100644 --- a/packages/TypeDeclaration/src/TypeNormalizer.php +++ b/packages/TypeDeclaration/src/TypeNormalizer.php @@ -137,6 +137,18 @@ public function normalizeArrayTypeAndArrayNever(Type $type): Type return $this->typeFactory->createMixedPassedOrUnionType($nonNeverTypes); } + private function collectNestedArrayTypeFromUnionType(UnionType $unionType, int $arrayNesting): void + { + foreach ($unionType->getTypes() as $unionedType) { + if ($unionedType instanceof ArrayType) { + ++$arrayNesting; + $this->normalizeArrayOfUnionToUnionArray($unionedType, $arrayNesting); + } else { + $this->collectedNestedArrayTypes[] = new NestedArrayTypeValueObject($unionedType, $arrayNesting); + } + } + } + /** * @param NestedArrayTypeValueObject[] $collectedNestedArrayTypes */ @@ -159,16 +171,4 @@ private function createUnionedTypesFromArrayTypes(array $collectedNestedArrayTyp return $unionedTypes[0]; } - - private function collectNestedArrayTypeFromUnionType(UnionType $unionType, int $arrayNesting): void - { - foreach ($unionType->getTypes() as $unionedType) { - if ($unionedType instanceof ArrayType) { - ++$arrayNesting; - $this->normalizeArrayOfUnionToUnionArray($unionedType, $arrayNesting); - } else { - $this->collectedNestedArrayTypes[] = new NestedArrayTypeValueObject($unionedType, $arrayNesting); - } - } - } } diff --git a/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php b/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php index 5618b4dd5f88..72770675ae23 100644 --- a/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php +++ b/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php @@ -120,23 +120,26 @@ public function refactor(Node $node): ?Node return $node; } - private function removeGetParamAssignIfNotUseful(?Node $getParamParentNode): void + /** + * @param Param[] $paramNamesToParentNodes + * @return string[] + */ + private function addClassMethodParameters(ClassMethod $classMethod, array $paramNamesToParentNodes): array { - // e.g. $value = (int) $this->getParam('value'); - if ($getParamParentNode instanceof Cast) { - $getParamParentNode = $getParamParentNode->getAttribute(AttributeKey::PARENT_NODE); - } + $addedParamNames = []; + foreach ($paramNamesToParentNodes as $paramName => $parentNode) { + $this->removeGetParamAssignIfNotUseful($parentNode); - if (! $getParamParentNode instanceof Assign) { - return; - } + if (in_array($paramName, $addedParamNames, true)) { + continue; + } - if (! $getParamParentNode->var instanceof Variable) { - return; - } + $paramName = $this->correctParamNameBasedOnAssign($parentNode, $paramName); - // matches: "$x = $this->getParam('x');" - $this->removeNode($getParamParentNode); + $classMethod->params[] = new Param(new Variable($paramName)); + $addedParamNames[] = $paramName; + } + return $addedParamNames; } private function addRouteAnnotation(ClassMethod $classMethod, RouteValueObject $routeValueObject): void @@ -152,26 +155,23 @@ private function addRouteAnnotation(ClassMethod $classMethod, RouteValueObject $ $this->addUseType(new FullyQualifiedObjectType(SymfonyRouteTagValueNode::CLASS_NAME), $classMethod); } - /** - * @param Param[] $paramNamesToParentNodes - * @return string[] - */ - private function addClassMethodParameters(ClassMethod $classMethod, array $paramNamesToParentNodes): array + private function removeGetParamAssignIfNotUseful(?Node $getParamParentNode): void { - $addedParamNames = []; - foreach ($paramNamesToParentNodes as $paramName => $parentNode) { - $this->removeGetParamAssignIfNotUseful($parentNode); - - if (in_array($paramName, $addedParamNames, true)) { - continue; - } + // e.g. $value = (int) $this->getParam('value'); + if ($getParamParentNode instanceof Cast) { + $getParamParentNode = $getParamParentNode->getAttribute(AttributeKey::PARENT_NODE); + } - $paramName = $this->correctParamNameBasedOnAssign($parentNode, $paramName); + if (! $getParamParentNode instanceof Assign) { + return; + } - $classMethod->params[] = new Param(new Variable($paramName)); - $addedParamNames[] = $paramName; + if (! $getParamParentNode->var instanceof Variable) { + return; } - return $addedParamNames; + + // matches: "$x = $this->getParam('x');" + $this->removeNode($getParamParentNode); } private function correctParamNameBasedOnAssign(Node $parentNode, string $currentParamName): string diff --git a/packages/ZendToSymfony/src/Rector/MethodCall/ThisHelperToServiceMethodCallRector.php b/packages/ZendToSymfony/src/Rector/MethodCall/ThisHelperToServiceMethodCallRector.php index be6bd0816d7d..0bb1cf86e2bc 100644 --- a/packages/ZendToSymfony/src/Rector/MethodCall/ThisHelperToServiceMethodCallRector.php +++ b/packages/ZendToSymfony/src/Rector/MethodCall/ThisHelperToServiceMethodCallRector.php @@ -132,6 +132,19 @@ public function refactor(Node $node): ?Node return new MethodCall($propertyFetch, 'direct', $node->args); } + private function resolveHelperName(MethodCall $methodCall): string + { + /** @var string $methodName */ + $methodName = $this->getName($methodCall->name); + + // special case handled by another path + if ($methodName === 'getHelper') { + return $this->getValue($methodCall->args[0]->value); + } + + return $methodName; + } + private function resolveHelperClassType(string $helperName): Type { // @todo make configurable/adaptable for custom helper override in own namespace @@ -148,17 +161,4 @@ private function resolveHelperClassType(string $helperName): Type $helperName )); } - - private function resolveHelperName(MethodCall $methodCall): string - { - /** @var string $methodName */ - $methodName = $this->getName($methodCall->name); - - // special case handled by another path - if ($methodName === 'getHelper') { - return $this->getValue($methodCall->args[0]->value); - } - - return $methodName; - } } diff --git a/src/Application/RectorApplication.php b/src/Application/RectorApplication.php index 810f6cd01014..7aa5190dc6a9 100644 --- a/src/Application/RectorApplication.php +++ b/src/Application/RectorApplication.php @@ -174,6 +174,40 @@ public function runOnFileInfos(array $fileInfos): void $this->finishingExtensionRunner->run(); } + /** + * This prevent CI report flood with 1 file = 1 line in progress bar + */ + private function configureStepCount(SymfonyStyle $symfonyStyle): void + { + if ($this->ciDetector->isCiDetected() === false) { + return; + } + + $privatesAccessor = new PrivatesAccessor(); + + /** @var ProgressBar $progressBar */ + $progressBar = $privatesAccessor->getPrivateProperty($symfonyStyle, 'progressBar'); + if ($progressBar->getMaxSteps() < 10) { + return; + } + + $redrawFrequency = (int) ($progressBar->getMaxSteps() / 20); + $progressBar->setRedrawFrequency($redrawFrequency); + } + + /** + * @param SmartFileInfo[] $fileInfos + */ + private function configurePHPStanNodeScopeResolver(array $fileInfos): void + { + $filePaths = []; + foreach ($fileInfos as $fileInfo) { + $filePaths[] = $fileInfo->getRealPath(); + } + + $this->nodeScopeResolver->setAnalysedFiles($filePaths); + } + private function tryCatchWrapper(SmartFileInfo $smartFileInfo, callable $callback, string $phase): void { $this->advance($smartFileInfo, $phase); @@ -229,38 +263,4 @@ private function advance(SmartFileInfo $smartFileInfo, string $phase): void $this->symfonyStyle->progressAdvance(); } } - - /** - * @param SmartFileInfo[] $fileInfos - */ - private function configurePHPStanNodeScopeResolver(array $fileInfos): void - { - $filePaths = []; - foreach ($fileInfos as $fileInfo) { - $filePaths[] = $fileInfo->getRealPath(); - } - - $this->nodeScopeResolver->setAnalysedFiles($filePaths); - } - - /** - * This prevent CI report flood with 1 file = 1 line in progress bar - */ - private function configureStepCount(SymfonyStyle $symfonyStyle): void - { - if ($this->ciDetector->isCiDetected() === false) { - return; - } - - $privatesAccessor = new PrivatesAccessor(); - - /** @var ProgressBar $progressBar */ - $progressBar = $privatesAccessor->getPrivateProperty($symfonyStyle, 'progressBar'); - if ($progressBar->getMaxSteps() < 10) { - return; - } - - $redrawFrequency = (int) ($progressBar->getMaxSteps() / 20); - $progressBar->setRedrawFrequency($redrawFrequency); - } } diff --git a/src/Console/Application.php b/src/Console/Application.php index 70b421cee0ac..4c0b35060701 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -121,13 +121,16 @@ private function filterCommandsByScope(array $commands): array return array_values($filteredCommands); } - private function removeUnusedOptions(InputDefinition $inputDefinition): void + private function getNewWorkingDir(InputInterface $input): string { - $options = $inputDefinition->getOptions(); - - unset($options['quiet'], $options['no-interaction']); + $workingDir = $input->getParameterOption(['--working-dir', '-d']); + if ($workingDir !== false && ! is_dir($workingDir)) { + throw new InvalidConfigurationException( + 'Invalid working directory specified, ' . $workingDir . ' does not exist.' + ); + } - $inputDefinition->setOptions($options); + return (string) $workingDir; } private function shouldPrintMetaInformation(InputInterface $input): bool @@ -143,6 +146,15 @@ private function shouldPrintMetaInformation(InputInterface $input): bool return ! ($hasVersionOption || $hasNoArguments || $hasJsonOutput); } + private function removeUnusedOptions(InputDefinition $inputDefinition): void + { + $options = $inputDefinition->getOptions(); + + unset($options['quiet'], $options['no-interaction']); + + $inputDefinition->setOptions($options); + } + private function addCustomOptions(InputDefinition $inputDefinition): void { $inputDefinition->addOption(new InputOption( @@ -186,16 +198,4 @@ private function getDefaultConfigPath(): string { return getcwd() . '/rector.yaml'; } - - private function getNewWorkingDir(InputInterface $input): string - { - $workingDir = $input->getParameterOption(['--working-dir', '-d']); - if ($workingDir !== false && ! is_dir($workingDir)) { - throw new InvalidConfigurationException( - 'Invalid working directory specified, ' . $workingDir . ' does not exist.' - ); - } - - return (string) $workingDir; - } } diff --git a/src/Console/Command/ScreenFileCommand.php b/src/Console/Command/ScreenFileCommand.php index b4a8419d4b09..e5c955df063a 100644 --- a/src/Console/Command/ScreenFileCommand.php +++ b/src/Console/Command/ScreenFileCommand.php @@ -128,34 +128,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Shell::CODE_SUCCESS; } - /** - * @param mixed[] $data - */ - private function createDocBlockFromArrayData(array $data, string $indent = ''): string - { - $comments = ''; - $comments .= PHP_EOL; - - foreach ($data as $name => $value) { - $wrapInQuotes = true; - if (is_array($value)) { - $wrapInQuotes = false; - $value = $this->createDocBlockFromArrayData($value, ' * '); - } - - $comments .= sprintf( - '// %s%s: %s%s%s', - $indent, - $name, - $wrapInQuotes ? '"' : '', - $value, - $wrapInQuotes ? '"' : '' - ) . PHP_EOL; - } - - return $comments; - } - /** * @param Node[] $nodes */ @@ -186,67 +158,18 @@ private function decorateNodes(array $nodes): void } /** - * @param mixed[] $data - * @return mixed[] - */ - private function decorateClassLike(ClassLike $classLike, array $data): array - { - $data['name'] = $this->nameResolver->getName($classLike); - - $parentClassName = $classLike->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName) { - $data['parent_class_name'] = $parentClassName; - } - - return $data; - } - - /** - * @param mixed[] $data - * @return mixed[] - */ - private function decorateMethodCall(MethodCall $methodCall, array $data): array - { - $data['method_call_variable'] = $this->decorateNodeData($methodCall->var); - $data['method_call_name'] = $this->nameResolver->getName($methodCall->name); - - return $data; - } - - /** - * @param mixed[] $data - * @return mixed[] + * @param Node[] $nodes */ - private function decorateWithNodeType(Node $node, array $data): array + private function outputDecoratedFileContent(InputInterface $input, array $nodes): void { - $data['node_type'] = $this->getObjectShortClass($node); - - return $data; - } + $decoratedFileContent = 'betterStandardPrinter->prettyPrint($nodes); - /** - * @param mixed[] $data - * @return mixed[] - */ - private function decorateReturn(Return_ $return, array $data): array - { - if ($return->expr === null) { - return $data; + $outputOption = (string) $input->getOption(self::OUTPUT_OPTION); + if ($outputOption) { + FileSystem::write($outputOption, $decoratedFileContent); + } else { + $this->symfonyStyle->writeln($decoratedFileContent); } - - $data['returned_node'] = $this->decorateNodeData($return->expr); - - return $data; - } - - /** - * @param object $object - */ - private function getObjectShortClass($object): string - { - $classNode = get_class($object); - - return (string) Strings::after($classNode, '\\', -1); } /** @@ -312,6 +235,61 @@ private function decorateNodeData(Node $node, array $data = []): array return $data; } + /** + * @param mixed[] $data + */ + private function createDocBlockFromArrayData(array $data, string $indent = ''): string + { + $comments = ''; + $comments .= PHP_EOL; + + foreach ($data as $name => $value) { + $wrapInQuotes = true; + if (is_array($value)) { + $wrapInQuotes = false; + $value = $this->createDocBlockFromArrayData($value, ' * '); + } + + $comments .= sprintf( + '// %s%s: %s%s%s', + $indent, + $name, + $wrapInQuotes ? '"' : '', + $value, + $wrapInQuotes ? '"' : '' + ) . PHP_EOL; + } + + return $comments; + } + + /** + * @param mixed[] $data + * @return mixed[] + */ + private function decorateWithNodeType(Node $node, array $data): array + { + $data['node_type'] = $this->getObjectShortClass($node); + + return $data; + } + + /** + * @param mixed[] $data + * @return mixed[] + */ + private function decorateClassLike(ClassLike $classLike, array $data): array + { + $data['name'] = $this->nameResolver->getName($classLike); + + $parentClassName = $classLike->getAttribute(AttributeKey::PARENT_CLASS_NAME); + if ($parentClassName) { + $data['parent_class_name'] = $parentClassName; + } + + return $data; + } + /** * @param mixed[] $data * @return mixed[] @@ -325,17 +303,39 @@ private function decorateAssign(Assign $assign, array $data): array } /** - * @param Node[] $nodes + * @param mixed[] $data + * @return mixed[] */ - private function outputDecoratedFileContent(InputInterface $input, array $nodes): void + private function decorateReturn(Return_ $return, array $data): array { - $decoratedFileContent = 'betterStandardPrinter->prettyPrint($nodes); - - $outputOption = (string) $input->getOption(self::OUTPUT_OPTION); - if ($outputOption) { - FileSystem::write($outputOption, $decoratedFileContent); - } else { - $this->symfonyStyle->writeln($decoratedFileContent); + if ($return->expr === null) { + return $data; } + + $data['returned_node'] = $this->decorateNodeData($return->expr); + + return $data; + } + + /** + * @param object $object + */ + private function getObjectShortClass($object): string + { + $classNode = get_class($object); + + return (string) Strings::after($classNode, '\\', -1); + } + + /** + * @param mixed[] $data + * @return mixed[] + */ + private function decorateMethodCall(MethodCall $methodCall, array $data): array + { + $data['method_call_variable'] = $this->decorateNodeData($methodCall->var); + $data['method_call_name'] = $this->nameResolver->getName($methodCall->name); + + return $data; } } diff --git a/src/Console/Option/SetOptionResolver.php b/src/Console/Option/SetOptionResolver.php index 6021134ccaa2..9bc256bf4bf7 100644 --- a/src/Console/Option/SetOptionResolver.php +++ b/src/Console/Option/SetOptionResolver.php @@ -56,48 +56,6 @@ public function detectFromNameAndDirectory(string $setName, string $configDirect return $nearestMatch->getRealPath(); } - private function reportSetNotFound(string $configDirectory, string $setName): void - { - $allSets = $this->findAllSetsInDirectory($configDirectory); - - $suggestedSet = ObjectHelpers::getSuggestion($allSets, $setName); - - [$versionedSets, $unversionedSets] = $this->separateVersionedAndUnversionedSets($allSets); - - $setsListInString = $this->createSetListInString($unversionedSets, $versionedSets); - - $setNotFoundMessage = sprintf( - '%s "%s" was not found.%s%s', - ucfirst($this->keyName), - $setName, - PHP_EOL, - $suggestedSet ? sprintf('Did you mean "%s"?', $suggestedSet) . PHP_EOL : '' - ); - - $pickOneOfMessage = sprintf('Pick "--%s" of:%s%s', $this->keyName, PHP_EOL . PHP_EOL, $setsListInString); - - throw new SetNotFoundException($setNotFoundMessage . PHP_EOL . $pickOneOfMessage); - } - - /** - * @return string[] - */ - private function findAllSetsInDirectory(string $configDirectory): array - { - $finder = Finder::create() - ->files() - ->in($configDirectory); - - $sets = []; - foreach ($finder->getIterator() as $fileInfo) { - $sets[] = $fileInfo->getBasename('.' . $fileInfo->getExtension()); - } - - sort($sets); - - return array_unique($sets); - } - /** * @return SplFileInfo[] */ @@ -140,6 +98,29 @@ private function findNearestMatchingFiles(string $configDirectory, string $setNa return $nearestMatches; } + private function reportSetNotFound(string $configDirectory, string $setName): void + { + $allSets = $this->findAllSetsInDirectory($configDirectory); + + $suggestedSet = ObjectHelpers::getSuggestion($allSets, $setName); + + [$versionedSets, $unversionedSets] = $this->separateVersionedAndUnversionedSets($allSets); + + $setsListInString = $this->createSetListInString($unversionedSets, $versionedSets); + + $setNotFoundMessage = sprintf( + '%s "%s" was not found.%s%s', + ucfirst($this->keyName), + $setName, + PHP_EOL, + $suggestedSet ? sprintf('Did you mean "%s"?', $suggestedSet) . PHP_EOL : '' + ); + + $pickOneOfMessage = sprintf('Pick "--%s" of:%s%s', $this->keyName, PHP_EOL . PHP_EOL, $setsListInString); + + throw new SetNotFoundException($setNotFoundMessage . PHP_EOL . $pickOneOfMessage); + } + private function matchVersionInTheEnd(string $setName): ?string { $match = Strings::match($setName, '#(?[\d\.]+$)#'); @@ -152,22 +133,22 @@ private function matchVersionInTheEnd(string $setName): ?string } /** - * @param string[] $unversionedSets - * @param string[] $versionedSets + * @return string[] */ - private function createSetListInString(array $unversionedSets, array $versionedSets): string + private function findAllSetsInDirectory(string $configDirectory): array { - $setsListInString = ''; + $finder = Finder::create() + ->files() + ->in($configDirectory); - foreach ($unversionedSets as $unversionedSet) { - $setsListInString .= ' * ' . $unversionedSet . PHP_EOL; + $sets = []; + foreach ($finder->getIterator() as $fileInfo) { + $sets[] = $fileInfo->getBasename('.' . $fileInfo->getExtension()); } - foreach ($versionedSets as $groupName => $configName) { - $setsListInString .= ' * ' . $groupName . ': ' . implode(', ', $configName) . PHP_EOL; - } + sort($sets); - return $setsListInString; + return array_unique($sets); } /** @@ -197,4 +178,23 @@ private function separateVersionedAndUnversionedSets(array $allSets): array return [$versionedSets, $unversionedSets]; } + + /** + * @param string[] $unversionedSets + * @param string[] $versionedSets + */ + private function createSetListInString(array $unversionedSets, array $versionedSets): string + { + $setsListInString = ''; + + foreach ($unversionedSets as $unversionedSet) { + $setsListInString .= ' * ' . $unversionedSet . PHP_EOL; + } + + foreach ($versionedSets as $groupName => $configName) { + $setsListInString .= ' * ' . $groupName . ': ' . implode(', ', $configName) . PHP_EOL; + } + + return $setsListInString; + } } diff --git a/src/NodeContainer/ParsedNodesByType.php b/src/NodeContainer/ParsedNodesByType.php index c9c796f44b5a..b5d6fdb81d90 100644 --- a/src/NodeContainer/ParsedNodesByType.php +++ b/src/NodeContainer/ParsedNodesByType.php @@ -562,14 +562,44 @@ private function addMethod(ClassMethod $classMethod): void $this->methodsByType[$className][$methodName] = $classMethod; } - private function isClassAnonymous(Class_ $classNode): bool + /** + * Matches array like: "[$this, 'methodName']" → ['ClassName', 'methodName'] + * @return string[]|null + */ + private function matchArrayCallableClassAndMethod(Array_ $array): ?array { - if ($classNode->isAnonymous() || $classNode->name === null) { - return true; + if (count($array->items) !== 2) { + return null; } - // PHPStan polution - return Strings::startsWith($classNode->name->toString(), 'AnonymousClass'); + if ($array->items[0] === null) { + return null; + } + + // $this, self, static, FQN + if (! $this->isThisVariable($array->items[0]->value)) { + return null; + } + + if ($array->items[1] === null) { + return null; + } + + if (! $array->items[1]->value instanceof String_) { + return null; + } + + /** @var String_ $string */ + $string = $array->items[1]->value; + + $methodName = $string->value; + $className = $array->getAttribute(AttributeKey::CLASS_NAME); + + if ($className === null) { + return null; + } + + return [$className, $methodName]; } /** @@ -612,44 +642,14 @@ private function addCall(Node $node): void } } - /** - * Matches array like: "[$this, 'methodName']" → ['ClassName', 'methodName'] - * @return string[]|null - */ - private function matchArrayCallableClassAndMethod(Array_ $array): ?array + private function isClassAnonymous(Class_ $classNode): bool { - if (count($array->items) !== 2) { - return null; - } - - if ($array->items[0] === null) { - return null; - } - - // $this, self, static, FQN - if (! $this->isThisVariable($array->items[0]->value)) { - return null; - } - - if ($array->items[1] === null) { - return null; - } - - if (! $array->items[1]->value instanceof String_) { - return null; - } - - /** @var String_ $string */ - $string = $array->items[1]->value; - - $methodName = $string->value; - $className = $array->getAttribute(AttributeKey::CLASS_NAME); - - if ($className === null) { - return null; + if ($classNode->isAnonymous() || $classNode->name === null) { + return true; } - return [$className, $methodName]; + // PHPStan polution + return Strings::startsWith($classNode->name->toString(), 'AnonymousClass'); } private function isThisVariable(Node $node): bool diff --git a/src/PhpParser/Node/Manipulator/CallManipulator.php b/src/PhpParser/Node/Manipulator/CallManipulator.php index 27ad08261113..ae3e967bd8e4 100644 --- a/src/PhpParser/Node/Manipulator/CallManipulator.php +++ b/src/PhpParser/Node/Manipulator/CallManipulator.php @@ -104,6 +104,22 @@ public function isVariadic(ReflectionFunctionAbstract $reflectionFunctionAbstrac return $this->isExternalScopeVariadic($reflectionFunctionAbstract, $callNode); } + /** + * native PHP bug fix + */ + private function isVariadicByName(ReflectionFunctionAbstract $reflectionFunctionAbstract): bool + { + if (! $reflectionFunctionAbstract instanceof ReflectionMethod) { + return false; + } + + if ($reflectionFunctionAbstract->getDeclaringClass()->getName() !== 'ReflectionMethod') { + return false; + } + + return $reflectionFunctionAbstract->getName() === 'invoke'; + } + private function containsFuncGetArgsFuncCall(Node $node): bool { return (bool) $this->betterNodeFinder->findFirst($node, function (Node $node): ?bool { @@ -166,20 +182,4 @@ private function resolveMotherType(Node $callNode): string { return $callNode instanceof FuncCall ? Function_::class : ClassMethod::class; } - - /** - * native PHP bug fix - */ - private function isVariadicByName(ReflectionFunctionAbstract $reflectionFunctionAbstract): bool - { - if (! $reflectionFunctionAbstract instanceof ReflectionMethod) { - return false; - } - - if ($reflectionFunctionAbstract->getDeclaringClass()->getName() !== 'ReflectionMethod') { - return false; - } - - return $reflectionFunctionAbstract->getName() === 'invoke'; - } } diff --git a/src/PhpParser/Node/Manipulator/ChildAndParentClassManipulator.php b/src/PhpParser/Node/Manipulator/ChildAndParentClassManipulator.php index 19ae5c2cc232..e63d63a3c382 100644 --- a/src/PhpParser/Node/Manipulator/ChildAndParentClassManipulator.php +++ b/src/PhpParser/Node/Manipulator/ChildAndParentClassManipulator.php @@ -101,26 +101,6 @@ public function completeChildConstructors(Class_ $classNode, ClassMethod $constr } } - private function findFirstParentConstructor(Class_ $classNode): ?ClassMethod - { - while ($classNode !== null) { - $constructMethodNode = $classNode->getMethod('__construct'); - if ($constructMethodNode !== null) { - return $constructMethodNode; - } - - /** @var string|null $parentClassName */ - $parentClassName = $classNode->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName === null) { - return null; - } - - $classNode = $this->parsedNodesByType->findClass($parentClassName); - } - - return null; - } - private function completeParentConstructorBasedOnParentNode(Class_ $parentClassNode, ClassMethod $classMethod): void { // iterate up? @@ -141,4 +121,24 @@ private function completeParentConstructorBasedOnParentNode(Class_ $parentClassN ); $classMethod->stmts[] = new Expression($parentConstructCallNode); } + + private function findFirstParentConstructor(Class_ $classNode): ?ClassMethod + { + while ($classNode !== null) { + $constructMethodNode = $classNode->getMethod('__construct'); + if ($constructMethodNode !== null) { + return $constructMethodNode; + } + + /** @var string|null $parentClassName */ + $parentClassName = $classNode->getAttribute(AttributeKey::PARENT_CLASS_NAME); + if ($parentClassName === null) { + return null; + } + + $classNode = $this->parsedNodesByType->findClass($parentClassName); + } + + return null; + } } diff --git a/src/PhpParser/Node/Manipulator/ClassManipulator.php b/src/PhpParser/Node/Manipulator/ClassManipulator.php index 738c246bfe70..86752a6ef9b9 100644 --- a/src/PhpParser/Node/Manipulator/ClassManipulator.php +++ b/src/PhpParser/Node/Manipulator/ClassManipulator.php @@ -481,17 +481,6 @@ private function getClassMethodNames(Class_ $classNode): array return $classMethodNames; } - private function hasMethodParameter(ClassMethod $classMethod, string $name): bool - { - foreach ($classMethod->params as $constructorParameter) { - if ($this->nameResolver->isName($constructorParameter->var, $name)) { - return true; - } - } - - return false; - } - /** * @param PropertyFetch|StaticPropertyFetch $node */ @@ -515,13 +504,6 @@ private function isNonAssignPropertyFetch(Node $node): bool return ! $this->isNodeLeftPartOfAssign($node); } - private function isNodeLeftPartOfAssign(Node $node): bool - { - $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); - - return $parentNode instanceof Assign && $parentNode->var === $node; - } - /** * @return string[] */ @@ -545,6 +527,23 @@ private function getSerializablePropertyNames(Class_ $node): array return $serializablePropertyNames; } + private function hasClassParentClassMethod(Class_ $class, string $methodName): bool + { + $parentClassName = $class->getAttribute(AttributeKey::PARENT_CLASS_NAME); + if ($parentClassName === null) { + return false; + } + + return method_exists($parentClassName, $methodName); + } + + private function createParentClassMethodCall(string $methodName): Expression + { + $staticCall = new StaticCall(new Name('parent'), $methodName); + + return new Expression($staticCall); + } + /** * @param Stmt[] $stmts * @return Stmt[] @@ -568,20 +567,21 @@ private function filterOutExistingStmts(ClassMethod $classMethod, array $stmts): return $stmts; } - private function hasClassParentClassMethod(Class_ $class, string $methodName): bool + private function hasMethodParameter(ClassMethod $classMethod, string $name): bool { - $parentClassName = $class->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName === null) { - return false; + foreach ($classMethod->params as $constructorParameter) { + if ($this->nameResolver->isName($constructorParameter->var, $name)) { + return true; + } } - return method_exists($parentClassName, $methodName); + return false; } - private function createParentClassMethodCall(string $methodName): Expression + private function isNodeLeftPartOfAssign(Node $node): bool { - $staticCall = new StaticCall(new Name('parent'), $methodName); + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); - return new Expression($staticCall); + return $parentNode instanceof Assign && $parentNode->var === $node; } } diff --git a/src/PhpParser/Node/Manipulator/ClassMethodManipulator.php b/src/PhpParser/Node/Manipulator/ClassMethodManipulator.php index 6d19f6dff2d0..d8ba86392de6 100644 --- a/src/PhpParser/Node/Manipulator/ClassMethodManipulator.php +++ b/src/PhpParser/Node/Manipulator/ClassMethodManipulator.php @@ -173,6 +173,26 @@ public function removeUnusedParameters(ClassMethod $classMethod): void } } + /** + * @param FuncCall[] $compactFuncCalls + * @return string[] + */ + private function extractArgumentsFromCompactFuncCalls(array $compactFuncCalls): array + { + $arguments = []; + foreach ($compactFuncCalls as $compactFuncCall) { + foreach ($compactFuncCall->args as $arg) { + $value = $this->valueResolver->getValue($arg->value); + + if ($value) { + $arguments[] = $value; + } + } + } + + return $arguments; + } + private function isMethodInParent(string $class, string $method): bool { foreach (class_parents($class) as $parentClass) { @@ -201,24 +221,4 @@ private function resolveName(ClassMethod $classMethod, array $possibleNames): st throw new ShouldNotHappenException(); } - - /** - * @param FuncCall[] $compactFuncCalls - * @return string[] - */ - private function extractArgumentsFromCompactFuncCalls(array $compactFuncCalls): array - { - $arguments = []; - foreach ($compactFuncCalls as $compactFuncCall) { - foreach ($compactFuncCall->args as $arg) { - $value = $this->valueResolver->getValue($arg->value); - - if ($value) { - $arguments[] = $value; - } - } - } - - return $arguments; - } } diff --git a/src/PhpParser/Node/Manipulator/MethodCallManipulator.php b/src/PhpParser/Node/Manipulator/MethodCallManipulator.php index 5d1acc3ee39e..f1c9a0bdc3fa 100644 --- a/src/PhpParser/Node/Manipulator/MethodCallManipulator.php +++ b/src/PhpParser/Node/Manipulator/MethodCallManipulator.php @@ -139,37 +139,6 @@ private function findMethodCallsOnVariable(Variable $variable): array return $previousMethodCalls; } - /** - * @return MethodCall[] - */ - private function collectMethodCallsOnVariableName(Node $node, string $variableName): array - { - $methodCalls = []; - - $this->callableNodeTraverser->traverseNodesWithCallable($node, function (Node $node) use ( - $variableName, - &$methodCalls - ) { - if (! $node instanceof MethodCall) { - return null; - } - - if (! $node->var instanceof Variable) { - return null; - } - - if (! $this->nameResolver->isName($node->var, $variableName)) { - return null; - } - - $methodCalls[] = $node; - - return null; - }); - - return $methodCalls; - } - /** * @see https://stackoverflow.com/a/4507991/1348344 * @param object[] $objects @@ -220,4 +189,35 @@ private function resolvePreviousNodeInSameScope(Node $parentNode): ?Node return $parentNode; } + + /** + * @return MethodCall[] + */ + private function collectMethodCallsOnVariableName(Node $node, string $variableName): array + { + $methodCalls = []; + + $this->callableNodeTraverser->traverseNodesWithCallable($node, function (Node $node) use ( + $variableName, + &$methodCalls + ) { + if (! $node instanceof MethodCall) { + return null; + } + + if (! $node->var instanceof Variable) { + return null; + } + + if (! $this->nameResolver->isName($node->var, $variableName)) { + return null; + } + + $methodCalls[] = $node; + + return null; + }); + + return $methodCalls; + } } diff --git a/src/PhpParser/Node/Manipulator/VisibilityManipulator.php b/src/PhpParser/Node/Manipulator/VisibilityManipulator.php index 3806d0e28e99..1add52503467 100644 --- a/src/PhpParser/Node/Manipulator/VisibilityManipulator.php +++ b/src/PhpParser/Node/Manipulator/VisibilityManipulator.php @@ -56,33 +56,6 @@ public function replaceVisibilityFlag(Node $node, string $visibility): void $this->addVisibilityFlag($node, $visibility); } - /** - * This way "abstract", "static", "final" are kept - * - * @param ClassMethod|Property|ClassConst $node - */ - private function removeOriginalVisibilityFromFlags(Node $node): void - { - $this->ensureIsClassMethodOrProperty($node, __METHOD__); - - // no modifier - if ($node->flags === 0) { - return; - } - - if ($node->isPublic()) { - $node->flags -= Class_::MODIFIER_PUBLIC; - } - - if ($node->isProtected()) { - $node->flags -= Class_::MODIFIER_PROTECTED; - } - - if ($node->isPrivate()) { - $node->flags -= Class_::MODIFIER_PRIVATE; - } - } - /** * @param Class_|ClassMethod|Property|ClassConst $node */ @@ -115,6 +88,33 @@ private function addVisibilityFlag(Node $node, string $visibility): void } } + /** + * This way "abstract", "static", "final" are kept + * + * @param ClassMethod|Property|ClassConst $node + */ + private function removeOriginalVisibilityFromFlags(Node $node): void + { + $this->ensureIsClassMethodOrProperty($node, __METHOD__); + + // no modifier + if ($node->flags === 0) { + return; + } + + if ($node->isPublic()) { + $node->flags -= Class_::MODIFIER_PUBLIC; + } + + if ($node->isProtected()) { + $node->flags -= Class_::MODIFIER_PROTECTED; + } + + if ($node->isPrivate()) { + $node->flags -= Class_::MODIFIER_PRIVATE; + } + } + private function ensureIsClassMethodOrProperty(Node $node, string $location): void { foreach ($this->allowedNodeTypes as $allowedNodeType) { diff --git a/src/PhpParser/Node/Value/ValueResolver.php b/src/PhpParser/Node/Value/ValueResolver.php index 3cad3207bbc2..dbb17bcabf5b 100644 --- a/src/PhpParser/Node/Value/ValueResolver.php +++ b/src/PhpParser/Node/Value/ValueResolver.php @@ -115,6 +115,34 @@ private function getConstExprEvaluator(): ConstExprEvaluator return $this->constExprEvaluator; } + /** + * @return mixed[] + */ + private function extractConstantArrayTypeValue(ConstantArrayType $constantArrayType): array + { + $keys = []; + foreach ($constantArrayType->getKeyTypes() as $i => $keyType) { + /** @var ConstantScalarType $keyType */ + $keys[$i] = $keyType->getValue(); + } + + $values = []; + foreach ($constantArrayType->getValueTypes() as $i => $valueType) { + if ($valueType instanceof ConstantArrayType) { + $value = $this->extractConstantArrayTypeValue($valueType); + } elseif ($valueType instanceof ConstantScalarType) { + $value = $valueType->getValue(); + } else { + // not sure about value + continue; + } + + $values[$keys[$i]] = $value; + } + + return $values; + } + private function resolveDirConstant(Dir $dir): string { $fileInfo = $dir->getAttribute(AttributeKey::FILE_INFO); @@ -168,32 +196,4 @@ private function resolveClassConstFetch(ClassConstFetch $classConstFetch) return $this->constExprEvaluator->evaluateDirectly($classConstNode->consts[0]->value); } - - /** - * @return mixed[] - */ - private function extractConstantArrayTypeValue(ConstantArrayType $constantArrayType): array - { - $keys = []; - foreach ($constantArrayType->getKeyTypes() as $i => $keyType) { - /** @var ConstantScalarType $keyType */ - $keys[$i] = $keyType->getValue(); - } - - $values = []; - foreach ($constantArrayType->getValueTypes() as $i => $valueType) { - if ($valueType instanceof ConstantArrayType) { - $value = $this->extractConstantArrayTypeValue($valueType); - } elseif ($valueType instanceof ConstantScalarType) { - $value = $valueType->getValue(); - } else { - // not sure about value - continue; - } - - $values[$keys[$i]] = $value; - } - - return $values; - } } diff --git a/src/PhpParser/Printer/BetterStandardPrinter.php b/src/PhpParser/Printer/BetterStandardPrinter.php index df20abe4a395..7dc02b1ed5e8 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -298,20 +298,6 @@ protected function pStmt_Declare(Declare_ $declare): string return Strings::replace($declareString, '#\s+#'); } - /** - * @param Node[] $nodes - */ - private function containsNop(array $nodes): bool - { - foreach ($nodes as $node) { - if ($node instanceof Nop) { - return true; - } - } - - return false; - } - /** * Solves https://github.com/rectorphp/rector/issues/1964 * @@ -343,4 +329,18 @@ private function detectTabOrSpaceIndentCharacter(array $stmts): void } } } + + /** + * @param Node[] $nodes + */ + private function containsNop(array $nodes): bool + { + foreach ($nodes as $node) { + if ($node instanceof Nop) { + return true; + } + } + + return false; + } } diff --git a/src/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector.php b/src/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector.php index e5399b1a2a39..fbc33e5a9785 100644 --- a/src/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector.php +++ b/src/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector.php @@ -87,6 +87,20 @@ public function refactor(Node $node): ?Node return $node; } + private function shouldSkipProperty(Node $node): bool + { + if (! $this->docBlockManipulator->hasTag($node, self::INJECT_ANNOTATION)) { + return true; + } + + // it needs @var tag as well, to get the type + if (! $this->docBlockManipulator->hasTag($node, 'var')) { + return true; + } + + return false; + } + private function addPropertyToCollector(Property $property): void { $classNode = $property->getAttribute(AttributeKey::CLASS_NODE); @@ -105,18 +119,4 @@ private function addPropertyToCollector(Property $property): void $this->addPropertyToClass($classNode, $propertyType, $propertyName); } - - private function shouldSkipProperty(Node $node): bool - { - if (! $this->docBlockManipulator->hasTag($node, self::INJECT_ANNOTATION)) { - return true; - } - - // it needs @var tag as well, to get the type - if (! $this->docBlockManipulator->hasTag($node, 'var')) { - return true; - } - - return false; - } } diff --git a/src/Rector/Argument/ArgumentAdderRector.php b/src/Rector/Argument/ArgumentAdderRector.php index efdb54485437..70413172c451 100644 --- a/src/Rector/Argument/ArgumentAdderRector.php +++ b/src/Rector/Argument/ArgumentAdderRector.php @@ -123,6 +123,31 @@ public function refactor(Node $node): ?Node return $node; } + /** + * @param MethodCall|StaticCall|ClassMethod $node + */ + private function isObjectTypeMatch(Node $node, string $type): bool + { + if ($node instanceof MethodCall) { + return $this->isObjectType($node->var, $type); + } + + if ($node instanceof StaticCall) { + return $this->isObjectType($node->class, $type); + } + + // ClassMethod + /** @var Class_|null $class */ + $class = $node->getAttribute(AttributeKey::CLASS_NODE); + + // anonymous class + if ($class === null) { + return false; + } + + return $this->isObjectType($class, $type); + } + /** * @param ClassMethod|MethodCall|StaticCall $node * @param mixed[] $positionWithDefaultValues @@ -154,24 +179,6 @@ private function processPositionWithDefaultValues(Node $node, array $positionWit } } - /** - * @param mixed $defaultValue - */ - private function addClassMethodParam( - ClassMethod $classMethod, - string $name, - $defaultValue, - ?string $type, - int $position - ): void { - $param = new Param(new Variable($name), BuilderHelpers::normalizeValue($defaultValue)); - if ($type) { - $param->type = ctype_upper($type[0]) ? new FullyQualified($type) : new Identifier($type); - } - - $classMethod->params[$position] = $param; - } - /** * @param ClassMethod|MethodCall|StaticCall $node */ @@ -216,27 +223,20 @@ private function isInCorrectScope(Node $node, array $parameterConfiguration): bo } /** - * @param MethodCall|StaticCall|ClassMethod $node + * @param mixed $defaultValue */ - private function isObjectTypeMatch(Node $node, string $type): bool - { - if ($node instanceof MethodCall) { - return $this->isObjectType($node->var, $type); - } - - if ($node instanceof StaticCall) { - return $this->isObjectType($node->class, $type); - } - - // ClassMethod - /** @var Class_|null $class */ - $class = $node->getAttribute(AttributeKey::CLASS_NODE); - - // anonymous class - if ($class === null) { - return false; + private function addClassMethodParam( + ClassMethod $classMethod, + string $name, + $defaultValue, + ?string $type, + int $position + ): void { + $param = new Param(new Variable($name), BuilderHelpers::normalizeValue($defaultValue)); + if ($type) { + $param->type = ctype_upper($type[0]) ? new FullyQualified($type) : new Identifier($type); } - return $this->isObjectType($class, $type); + $classMethod->params[$position] = $param; } } diff --git a/src/Rector/Argument/ArgumentRemoverRector.php b/src/Rector/Argument/ArgumentRemoverRector.php index d656cc30c066..5eccab1212ae 100644 --- a/src/Rector/Argument/ArgumentRemoverRector.php +++ b/src/Rector/Argument/ArgumentRemoverRector.php @@ -124,16 +124,6 @@ private function processPosition(Node $node, int $position, ?array $match): void } } - /** - * @param mixed[] $values - */ - private function isArgumentValueMatch(Arg $arg, array $values): bool - { - $nodeValue = $this->getValue($arg->value); - - return in_array($nodeValue, $values, true); - } - /** * @param ClassMethod|StaticCall|MethodCall $node */ @@ -155,4 +145,14 @@ private function removeByName(Node $node, int $position, string $name): void return; } } + + /** + * @param mixed[] $values + */ + private function isArgumentValueMatch(Arg $arg, array $values): bool + { + $nodeValue = $this->getValue($arg->value); + + return in_array($nodeValue, $values, true); + } } diff --git a/src/Rector/ClassMethod/AddMethodParentCallRector.php b/src/Rector/ClassMethod/AddMethodParentCallRector.php index 7a233d1d0125..cc551c8b8ffd 100644 --- a/src/Rector/ClassMethod/AddMethodParentCallRector.php +++ b/src/Rector/ClassMethod/AddMethodParentCallRector.php @@ -107,6 +107,22 @@ public function refactor(Node $node): ?Node return null; } + private function shouldSkipMethod(ClassMethod $classMethod, string $method): bool + { + if (! $this->isName($classMethod, $method)) { + return true; + } + + return $this->hasParentCallOfMethod($classMethod, $method); + } + + private function createParentStaticCall(string $method): Expression + { + $parentStaticCall = new StaticCall(new Name('parent'), new Identifier($method)); + + return new Expression($parentStaticCall); + } + /** * Looks for "parent:: */ @@ -126,20 +142,4 @@ private function hasParentCallOfMethod(ClassMethod $classMethod, string $method) return $this->isName($node, $method); }); } - - private function createParentStaticCall(string $method): Expression - { - $parentStaticCall = new StaticCall(new Name('parent'), new Identifier($method)); - - return new Expression($parentStaticCall); - } - - private function shouldSkipMethod(ClassMethod $classMethod, string $method): bool - { - if (! $this->isName($classMethod, $method)) { - return true; - } - - return $this->hasParentCallOfMethod($classMethod, $method); - } } diff --git a/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php b/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php index b18265d314b2..12855e4922a6 100644 --- a/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php +++ b/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php @@ -131,6 +131,29 @@ private function processMagicSet(Assign $assign): ?Node return null; } + private function processPropertyFetch(PropertyFetch $propertyFetch): ?MethodCall + { + foreach ($this->typeToMethodCalls as $type => $transformation) { + $objectType = new ObjectType($type); + if ($this->shouldSkipPropertyFetch($propertyFetch, $objectType)) { + continue; + } + + // setter, skip + $parentNode = $propertyFetch->getAttribute(AttributeKey::PARENT_NODE); + + if ($parentNode instanceof Assign) { + if ($parentNode->var === $propertyFetch) { + continue; + } + } + + return $this->createMethodCallNodeFromPropertyFetchNode($propertyFetch, $transformation['get']); + } + + return null; + } + private function shouldSkipPropertyFetch(PropertyFetch $propertyFetch, ObjectType $objectType): bool { if (! $this->isObjectType($propertyFetch->var, $objectType)) { @@ -145,47 +168,24 @@ private function shouldSkipPropertyFetch(PropertyFetch $propertyFetch, ObjectTyp return $this->propertyFetchManipulator->isPropertyToSelf($propertyFetch); } - private function createMethodCallNodeFromPropertyFetchNode( + private function createMethodCallNodeFromAssignNode( PropertyFetch $propertyFetch, + Node $node, string $method ): MethodCall { /** @var Variable $variableNode */ $variableNode = $propertyFetch->var; - return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetch)]); + return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetch), $node]); } - private function createMethodCallNodeFromAssignNode( + private function createMethodCallNodeFromPropertyFetchNode( PropertyFetch $propertyFetch, - Node $node, string $method ): MethodCall { /** @var Variable $variableNode */ $variableNode = $propertyFetch->var; - return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetch), $node]); - } - - private function processPropertyFetch(PropertyFetch $propertyFetch): ?MethodCall - { - foreach ($this->typeToMethodCalls as $type => $transformation) { - $objectType = new ObjectType($type); - if ($this->shouldSkipPropertyFetch($propertyFetch, $objectType)) { - continue; - } - - // setter, skip - $parentNode = $propertyFetch->getAttribute(AttributeKey::PARENT_NODE); - - if ($parentNode instanceof Assign) { - if ($parentNode->var === $propertyFetch) { - continue; - } - } - - return $this->createMethodCallNodeFromPropertyFetchNode($propertyFetch, $transformation['get']); - } - - return null; + return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetch)]); } } diff --git a/src/Rector/MethodBody/FluentReplaceRector.php b/src/Rector/MethodBody/FluentReplaceRector.php index bb89aa3c3cd5..455d93d6b5c0 100644 --- a/src/Rector/MethodBody/FluentReplaceRector.php +++ b/src/Rector/MethodBody/FluentReplaceRector.php @@ -95,36 +95,6 @@ public function refactor(Node $node): ?Node return $currentOne; } - private function isMatchingMethodCall(MethodCall $methodCall): bool - { - foreach ($this->classesToDefluent as $classToDefluent) { - if ($this->isObjectType($methodCall, $classToDefluent)) { - return true; - } - } - - return false; - } - - /** - * @param MethodCall[] $methodCalls - * @return Variable|PropertyFetch - */ - private function extractRootVariable(array $methodCalls): Expr - { - foreach ($methodCalls as $methodCall) { - if ($methodCall->var instanceof Variable) { - return $methodCall->var; - } - - if ($methodCall->var instanceof PropertyFetch) { - return $methodCall->var; - } - } - - throw new ShouldNotHappenException(); - } - private function isLastMethodCallInChainCall(MethodCall $methodCall): bool { // is chain method call @@ -185,4 +155,34 @@ private function createNonFluentMethodCalls(array $chainMethodCalls): array return $decoupledMethodCalls; } + + private function isMatchingMethodCall(MethodCall $methodCall): bool + { + foreach ($this->classesToDefluent as $classToDefluent) { + if ($this->isObjectType($methodCall, $classToDefluent)) { + return true; + } + } + + return false; + } + + /** + * @param MethodCall[] $methodCalls + * @return Variable|PropertyFetch + */ + private function extractRootVariable(array $methodCalls): Expr + { + foreach ($methodCalls as $methodCall) { + if ($methodCall->var instanceof Variable) { + return $methodCall->var; + } + + if ($methodCall->var instanceof PropertyFetch) { + return $methodCall->var; + } + } + + throw new ShouldNotHappenException(); + } } diff --git a/src/Rector/Namespace_/PseudoNamespaceToNamespaceRector.php b/src/Rector/Namespace_/PseudoNamespaceToNamespaceRector.php index 6ee73c9aaed5..17f13c0f3e8e 100644 --- a/src/Rector/Namespace_/PseudoNamespaceToNamespaceRector.php +++ b/src/Rector/Namespace_/PseudoNamespaceToNamespaceRector.php @@ -134,6 +134,36 @@ public function afterTraverse(array $nodes): array return $nodes; } + /** + * @param Name|Identifier $node + * @return Name|Identifier + */ + private function processNameOrIdentifier(Node $node): ?Node + { + // no name → skip + if ($node->toString() === '') { + return null; + } + + foreach ($this->namespacePrefixesWithExcludedClasses as $namespacePrefix => $excludedClasses) { + if (! $this->isName($node, $namespacePrefix . '*')) { + continue; + } + + if (is_array($excludedClasses) && $this->isNames($node, $excludedClasses)) { + return null; + } + + if ($node instanceof Name) { + return $this->processName($node); + } + + return $this->processIdentifier($node); + } + + return null; + } + private function processName(Name $name): Name { $nodeName = $this->getName($name); @@ -175,34 +205,4 @@ private function processIdentifier(Identifier $identifier): ?Identifier return $identifier; } - - /** - * @param Name|Identifier $node - * @return Name|Identifier - */ - private function processNameOrIdentifier(Node $node): ?Node - { - // no name → skip - if ($node->toString() === '') { - return null; - } - - foreach ($this->namespacePrefixesWithExcludedClasses as $namespacePrefix => $excludedClasses) { - if (! $this->isName($node, $namespacePrefix . '*')) { - continue; - } - - if (is_array($excludedClasses) && $this->isNames($node, $excludedClasses)) { - return null; - } - - if ($node instanceof Name) { - return $this->processName($node); - } - - return $this->processIdentifier($node); - } - - return null; - } } diff --git a/src/Rector/Property/InjectAnnotationClassRector.php b/src/Rector/Property/InjectAnnotationClassRector.php index 3f40f6d207d8..c7800106ae0f 100644 --- a/src/Rector/Property/InjectAnnotationClassRector.php +++ b/src/Rector/Property/InjectAnnotationClassRector.php @@ -147,6 +147,32 @@ public function refactor(Node $node): ?Node return null; } + private function ensureAnnotationClassIsSupported(string $annotationClass): void + { + if (isset($this->annotationToTagClass[$annotationClass])) { + return; + } + + throw new NotImplementedException(sprintf( + 'Annotation class "%s" is not implemented yet. Use one of "%s" or add custom tag for it to Rector.', + $annotationClass, + implode('", "', array_keys($this->annotationToTagClass)) + )); + } + + private function resolveType(Node $node, PhpDocTagValueNode $phpDocTagValueNode): Type + { + if ($phpDocTagValueNode instanceof JMSInjectTagValueNode) { + return $this->resolveJMSDIInjectType($node, $phpDocTagValueNode); + } + + if ($phpDocTagValueNode instanceof PHPDIInjectTagValueNode) { + return $this->docBlockManipulator->getVarType($node); + } + + throw new ShouldNotHappenException(); + } + private function refactorPropertyWithAnnotation(Property $property, Type $type, string $tagClass): ?Property { if ($type instanceof MixedType) { @@ -203,30 +229,4 @@ private function resolveJMSDIInjectType(Node $node, JMSInjectTagValueNode $jmsIn return new MixedType(); } - - private function ensureAnnotationClassIsSupported(string $annotationClass): void - { - if (isset($this->annotationToTagClass[$annotationClass])) { - return; - } - - throw new NotImplementedException(sprintf( - 'Annotation class "%s" is not implemented yet. Use one of "%s" or add custom tag for it to Rector.', - $annotationClass, - implode('", "', array_keys($this->annotationToTagClass)) - )); - } - - private function resolveType(Node $node, PhpDocTagValueNode $phpDocTagValueNode): Type - { - if ($phpDocTagValueNode instanceof JMSInjectTagValueNode) { - return $this->resolveJMSDIInjectType($node, $phpDocTagValueNode); - } - - if ($phpDocTagValueNode instanceof PHPDIInjectTagValueNode) { - return $this->docBlockManipulator->getVarType($node); - } - - throw new ShouldNotHappenException(); - } } diff --git a/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php b/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php index 0bb0af7f791b..dab87754cc58 100644 --- a/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php +++ b/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php @@ -99,34 +99,24 @@ public function refactor(SmartFileInfo $smartFileInfo): void } /** - * @param Node[] $nodes - * @return Node[] + * @param Stmt[] $nodes */ - private function removeAllOtherNamespaces(array $nodes, Namespace_ $namespaceNode): array + private function shouldDeleteFileInfo(SmartFileInfo $smartFileInfo, array $nodes): bool { - foreach ($nodes as $key => $stmt) { - if ($stmt instanceof Namespace_ && $stmt !== $namespaceNode) { - unset($nodes[$key]); + $classLikes = $this->betterNodeFinder->findClassLikes($nodes); + foreach ($classLikes as $classLike) { + $className = $this->getName($classLike); + if ($className === null) { + continue; } - } - - return $nodes; - } - private function removeAllClassLikesFromNamespaceNode(Namespace_ $namespaceNode): void - { - foreach ($namespaceNode->stmts as $key => $namespaceStatement) { - if ($namespaceStatement instanceof ClassLike) { - unset($namespaceNode->stmts[$key]); + $classShortName = $this->classNaming->getShortName($className); + if ($smartFileInfo->getBasenameWithoutSuffix() === $classShortName) { + return false; } } - } - private function createClassLikeFileDestination(ClassLike $classLike, SmartFileInfo $smartFileInfo): string - { - $currentDirectory = dirname($smartFileInfo->getRealPath()); - - return $currentDirectory . DIRECTORY_SEPARATOR . $classLike->name . '.php'; + return true; } /** @@ -170,27 +160,6 @@ private function processNamespaceNodes( } } - /** - * @param Stmt[] $nodes - */ - private function shouldDeleteFileInfo(SmartFileInfo $smartFileInfo, array $nodes): bool - { - $classLikes = $this->betterNodeFinder->findClassLikes($nodes); - foreach ($classLikes as $classLike) { - $className = $this->getName($classLike); - if ($className === null) { - continue; - } - - $classShortName = $this->classNaming->getShortName($className); - if ($smartFileInfo->getBasenameWithoutSuffix() === $classShortName) { - return false; - } - } - - return true; - } - /** * @param Stmt[] $nodes */ @@ -229,4 +198,35 @@ private function processNodesWithoutNamespace(array $nodes, SmartFileInfo $smart } } } + + /** + * @param Node[] $nodes + * @return Node[] + */ + private function removeAllOtherNamespaces(array $nodes, Namespace_ $namespaceNode): array + { + foreach ($nodes as $key => $stmt) { + if ($stmt instanceof Namespace_ && $stmt !== $namespaceNode) { + unset($nodes[$key]); + } + } + + return $nodes; + } + + private function removeAllClassLikesFromNamespaceNode(Namespace_ $namespaceNode): void + { + foreach ($namespaceNode->stmts as $key => $namespaceStatement) { + if ($namespaceStatement instanceof ClassLike) { + unset($namespaceNode->stmts[$key]); + } + } + } + + private function createClassLikeFileDestination(ClassLike $classLike, SmartFileInfo $smartFileInfo): string + { + $currentDirectory = dirname($smartFileInfo->getRealPath()); + + return $currentDirectory . DIRECTORY_SEPARATOR . $classLike->name . '.php'; + } } diff --git a/src/Rector/Typehint/ParentTypehintedArgumentRector.php b/src/Rector/Typehint/ParentTypehintedArgumentRector.php index a96159e9a9b8..220925b72e5b 100644 --- a/src/Rector/Typehint/ParentTypehintedArgumentRector.php +++ b/src/Rector/Typehint/ParentTypehintedArgumentRector.php @@ -107,6 +107,21 @@ public function refactor(Node $node): ?Node return null; } + /** + * @param string[][] $methodToArgumentToTypes + */ + private function processArgumentToTypes(ClassMethod $classMethod, array $methodToArgumentToTypes): void + { + foreach ($methodToArgumentToTypes as $method => $argumentToTypes) { + if (! $this->isName($classMethod, $method)) { + continue; + } + + $this->processClassMethodNodeWithTypehints($classMethod, $argumentToTypes); + return; + } + } + /** * @param string[] $parametersToTypes */ @@ -129,19 +144,4 @@ private function processClassMethodNodeWithTypehints(ClassMethod $classMethod, a } } } - - /** - * @param string[][] $methodToArgumentToTypes - */ - private function processArgumentToTypes(ClassMethod $classMethod, array $methodToArgumentToTypes): void - { - foreach ($methodToArgumentToTypes as $method => $argumentToTypes) { - if (! $this->isName($classMethod, $method)) { - continue; - } - - $this->processClassMethodNodeWithTypehints($classMethod, $argumentToTypes); - return; - } - } } diff --git a/src/Standalone/RectorStandaloneRunner.php b/src/Standalone/RectorStandaloneRunner.php index af0d34c7800a..7908ce68f38e 100644 --- a/src/Standalone/RectorStandaloneRunner.php +++ b/src/Standalone/RectorStandaloneRunner.php @@ -84,6 +84,26 @@ public function processSourceWithSet( return $this->container->get(ErrorAndDiffCollector::class); } + /** + * @param string[] $source + * @return string[] + */ + private function absolutizeSource(array $source): array + { + foreach ($source as $key => $singleSource) { + /** @var string $singleSource */ + if (! file_exists($singleSource)) { + throw new FileNotFoundException($singleSource); + } + + /** @var string $realpath */ + $realpath = realpath($singleSource); + $source[$key] = $realpath; + } + + return $source; + } + /** * Mostly copied from: https://github.com/rectorphp/rector/blob/master/src/Console/Command/ProcessCommand.php. * @param string[] $source @@ -114,34 +134,36 @@ private function prepare(array $source, bool $isDryRun): void $stubLoader->loadStubs(); } - private function reportErrors(): void + /** + * @param string[] $source + * @return SmartFileInfo[] + */ + private function findFilesInSource(array $source): array { - /** @var ErrorAndDiffCollector $errorAndDiffCollector */ - $errorAndDiffCollector = $this->container->get(ErrorAndDiffCollector::class); + /** @var FilesFinder $filesFinder */ + $filesFinder = $this->container->get(FilesFinder::class); - /** @var ConsoleOutputFormatter $consoleOutputFormatter */ - $consoleOutputFormatter = $this->container->get(ConsoleOutputFormatter::class); - $consoleOutputFormatter->report($errorAndDiffCollector); + return $filesFinder->findInDirectoriesAndFiles($source, ['php']); } /** - * @param string[] $source - * @return string[] + * @param SmartFileInfo[] $phpFileInfos */ - private function absolutizeSource(array $source): array + private function runRectorOnFileInfos(array $phpFileInfos): void { - foreach ($source as $key => $singleSource) { - /** @var string $singleSource */ - if (! file_exists($singleSource)) { - throw new FileNotFoundException($singleSource); - } + /** @var RectorApplication $rectorApplication */ + $rectorApplication = $this->container->get(RectorApplication::class); + $rectorApplication->runOnFileInfos($phpFileInfos); + } - /** @var string $realpath */ - $realpath = realpath($singleSource); - $source[$key] = $realpath; - } + private function reportErrors(): void + { + /** @var ErrorAndDiffCollector $errorAndDiffCollector */ + $errorAndDiffCollector = $this->container->get(ErrorAndDiffCollector::class); - return $source; + /** @var ConsoleOutputFormatter $consoleOutputFormatter */ + $consoleOutputFormatter = $this->container->get(ConsoleOutputFormatter::class); + $consoleOutputFormatter->report($errorAndDiffCollector); } private function finish(): void @@ -175,26 +197,4 @@ private function prepareConfiguration(RectorNodeTraverser $rectorNodeTraverser, '--' . Option::OPTION_OUTPUT_FORMAT => 'console', ], $definition)); } - - /** - * @param string[] $source - * @return SmartFileInfo[] - */ - private function findFilesInSource(array $source): array - { - /** @var FilesFinder $filesFinder */ - $filesFinder = $this->container->get(FilesFinder::class); - - return $filesFinder->findInDirectoriesAndFiles($source, ['php']); - } - - /** - * @param SmartFileInfo[] $phpFileInfos - */ - private function runRectorOnFileInfos(array $phpFileInfos): void - { - /** @var RectorApplication $rectorApplication */ - $rectorApplication = $this->container->get(RectorApplication::class); - $rectorApplication->runOnFileInfos($phpFileInfos); - } } diff --git a/src/Testing/PHPUnit/AbstractRectorTestCase.php b/src/Testing/PHPUnit/AbstractRectorTestCase.php index cabcaf2f54a3..ca6e7414f610 100644 --- a/src/Testing/PHPUnit/AbstractRectorTestCase.php +++ b/src/Testing/PHPUnit/AbstractRectorTestCase.php @@ -165,28 +165,6 @@ protected function setParameter(string $name, $value): void $parameterProvider->changeParameter($name, $value); } - private function doTestFileMatchesExpectedContent( - string $originalFile, - string $expectedFile, - string $fixtureFile - ): void { - $this->setParameter(Option::SOURCE, [$originalFile]); - - $smartFileInfo = new SmartFileInfo($originalFile); - - // life-cycle trio :) - $this->fileProcessor->parseFileInfoToLocalCache($smartFileInfo); - $this->fileProcessor->refactor($smartFileInfo); - $changedContent = $this->fileProcessor->printToString($smartFileInfo); - - try { - $this->assertStringEqualsFile($expectedFile, $changedContent, 'Caused by ' . $fixtureFile); - } catch (ExpectationFailedException $expectationFailedException) { - $expectedFileContent = FileSystem::read($expectedFile); - $this->assertStringMatchesFormat($expectedFileContent, $changedContent, 'Caused by ' . $fixtureFile); - } - } - private function ensureConfigFileExists(): void { if (file_exists($this->provideConfig())) { @@ -223,6 +201,23 @@ private function createContainerWithAllRectors(): void $this->bootKernelWithConfigs(RectorKernel::class, [$configFileTempPath]); } + private function getConfigFor3rdPartyTest(): string + { + if ($this->provideConfig() !== '') { + return $this->provideConfig(); + } + + $rectorClassWithConfiguration = $this->getCurrentTestRectorClassesWithConfiguration(); + $yamlContent = Yaml::dump([ + 'services' => $rectorClassWithConfiguration, + ], Yaml::DUMP_OBJECT_AS_MAP); + + $configFileTempPath = sprintf(sys_get_temp_dir() . '/rector_temp_tests/current_test.yaml'); + FileSystem::write($configFileTempPath, $yamlContent); + + return $configFileTempPath; + } + private function configureEnabledRectors(EnabledRectorsProvider $enabledRectorsProvider): void { foreach ($this->getCurrentTestRectorClassesWithConfiguration() as $rectorClass => $configuration) { @@ -239,20 +234,25 @@ private function configurePhpVersionFeatures(): void $this->setParameter(Option::PHP_VERSION_FEATURES, $this->getPhpVersion()); } - private function getConfigFor3rdPartyTest(): string - { - if ($this->provideConfig() !== '') { - return $this->provideConfig(); - } + private function doTestFileMatchesExpectedContent( + string $originalFile, + string $expectedFile, + string $fixtureFile + ): void { + $this->setParameter(Option::SOURCE, [$originalFile]); - $rectorClassWithConfiguration = $this->getCurrentTestRectorClassesWithConfiguration(); - $yamlContent = Yaml::dump([ - 'services' => $rectorClassWithConfiguration, - ], Yaml::DUMP_OBJECT_AS_MAP); + $smartFileInfo = new SmartFileInfo($originalFile); - $configFileTempPath = sprintf(sys_get_temp_dir() . '/rector_temp_tests/current_test.yaml'); - FileSystem::write($configFileTempPath, $yamlContent); + // life-cycle trio :) + $this->fileProcessor->parseFileInfoToLocalCache($smartFileInfo); + $this->fileProcessor->refactor($smartFileInfo); + $changedContent = $this->fileProcessor->printToString($smartFileInfo); - return $configFileTempPath; + try { + $this->assertStringEqualsFile($expectedFile, $changedContent, 'Caused by ' . $fixtureFile); + } catch (ExpectationFailedException $expectationFailedException) { + $expectedFileContent = FileSystem::read($expectedFile); + $this->assertStringMatchesFormat($expectedFileContent, $changedContent, 'Caused by ' . $fixtureFile); + } } } diff --git a/utils/DocumentationGenerator/src/OutputFormatter/DumpRectors/MarkdownDumpRectorsOutputFormatter.php b/utils/DocumentationGenerator/src/OutputFormatter/DumpRectors/MarkdownDumpRectorsOutputFormatter.php index 82d870023f98..eca92cff4c0d 100644 --- a/utils/DocumentationGenerator/src/OutputFormatter/DumpRectors/MarkdownDumpRectorsOutputFormatter.php +++ b/utils/DocumentationGenerator/src/OutputFormatter/DumpRectors/MarkdownDumpRectorsOutputFormatter.php @@ -92,6 +92,39 @@ private function printRectors(array $rectors): void } } + /** + * @param RectorInterface[] $rectors + * @return RectorInterface[][] + */ + private function groupRectorsByPackage(array $rectors): array + { + $rectorsByPackage = []; + foreach ($rectors as $rector) { + $package = $this->rectorMetadataResolver->resolvePackageFromRectorClass(get_class($rector)); + $rectorsByPackage[$package][] = $rector; + } + + // sort groups by name to make them more readable + ksort($rectorsByPackage); + + return $rectorsByPackage; + } + + /** + * @param RectorInterface[][] $rectorsByGroup + */ + private function printGroupsMenu(array $rectorsByGroup): void + { + foreach (array_keys($rectorsByGroup) as $group) { + $escapedGroup = str_replace('\\', '', $group); + $escapedGroup = Strings::webalize($escapedGroup, '_'); + + $this->symfonyStyle->writeln(sprintf('- [%s](#%s)', $group, $escapedGroup)); + } + + $this->symfonyStyle->newLine(); + } + private function printRector(RectorInterface $rector): void { $headline = $this->getRectorClassWithoutNamespace($rector); @@ -126,44 +159,6 @@ private function getRectorClassWithoutNamespace(RectorInterface $rector): string return $rectorClassParts[count($rectorClassParts) - 1]; } - private function printCodeWrapped(string $content, string $format): void - { - $this->symfonyStyle->writeln(sprintf('```%s%s%s%s```', $format, PHP_EOL, rtrim($content), PHP_EOL)); - } - - /** - * @param RectorInterface[][] $rectorsByGroup - */ - private function printGroupsMenu(array $rectorsByGroup): void - { - foreach (array_keys($rectorsByGroup) as $group) { - $escapedGroup = str_replace('\\', '', $group); - $escapedGroup = Strings::webalize($escapedGroup, '_'); - - $this->symfonyStyle->writeln(sprintf('- [%s](#%s)', $group, $escapedGroup)); - } - - $this->symfonyStyle->newLine(); - } - - /** - * @param RectorInterface[] $rectors - * @return RectorInterface[][] - */ - private function groupRectorsByPackage(array $rectors): array - { - $rectorsByPackage = []; - foreach ($rectors as $rector) { - $package = $this->rectorMetadataResolver->resolvePackageFromRectorClass(get_class($rector)); - $rectorsByPackage[$package][] = $rector; - } - - // sort groups by name to make them more readable - ksort($rectorsByPackage); - - return $rectorsByPackage; - } - private function printConfiguration(RectorInterface $rector, CodeSampleInterface $codeSample): void { if (! $codeSample instanceof ConfiguredCodeSample) { @@ -193,4 +188,9 @@ private function printCodeSample(CodeSampleInterface $codeSample): void $this->printCodeWrapped($diff, 'diff'); } + + private function printCodeWrapped(string $content, string $format): void + { + $this->symfonyStyle->writeln(sprintf('```%s%s%s%s```', $format, PHP_EOL, rtrim($content), PHP_EOL)); + } } diff --git a/utils/RectorGenerator/src/Command/CreateRectorCommand.php b/utils/RectorGenerator/src/Command/CreateRectorCommand.php index fd157fb288a9..7f2a87fc3726 100644 --- a/utils/RectorGenerator/src/Command/CreateRectorCommand.php +++ b/utils/RectorGenerator/src/Command/CreateRectorCommand.php @@ -121,6 +121,39 @@ protected function execute(InputInterface $input, OutputInterface $output): int return ShellCode::SUCCESS; } + /** + * @param mixed[] $templateVariables + */ + private function processComposerAutoload(array $templateVariables): void + { + $composerJsonFilePath = getcwd() . '/composer.json'; + $composerJson = $this->loadFileToJson($composerJsonFilePath); + + $package = $templateVariables['_Package_']; + + // skip core, already autoloaded + if ($package === 'Rector') { + return; + } + + $namespace = 'Rector\\' . $package . '\\'; + $namespaceTest = 'Rector\\' . $package . '\\Tests\\'; + + // already autoloaded? + if (isset($composerJson['autoload']['psr-4'][$namespace])) { + return; + } + + $composerJson['autoload']['psr-4'][$namespace] = 'packages/' . $package . '/src'; + $composerJson['autoload-dev']['psr-4'][$namespaceTest] = 'packages/' . $package . '/tests'; + + $this->saveJsonToFile($composerJsonFilePath, $composerJson); + + // rebuild new namespace + $composerDumpProcess = new Process(['composer', 'dump']); + $composerDumpProcess->run(); + } + /** * @return SmartFileInfo[] */ @@ -209,44 +242,31 @@ private function printSuccess(string $name): void } /** - * @param mixed[] $variables + * @return mixed[] */ - private function applyVariables(string $content, array $variables): string + private function loadFileToJson(string $filePath): array { - return str_replace(array_keys($variables), array_values($variables), $content); + $fileContent = FileSystem::read($filePath); + return Json::decode($fileContent, Json::FORCE_ARRAY); } /** - * @param mixed[] $templateVariables + * @param mixed[] $json */ - private function processComposerAutoload(array $templateVariables): void + private function saveJsonToFile(string $filePath, array $json): void { - $composerJsonFilePath = getcwd() . '/composer.json'; - $composerJson = $this->loadFileToJson($composerJsonFilePath); - - $package = $templateVariables['_Package_']; - - // skip core, already autoloaded - if ($package === 'Rector') { - return; - } - - $namespace = 'Rector\\' . $package . '\\'; - $namespaceTest = 'Rector\\' . $package . '\\Tests\\'; - - // already autoloaded? - if (isset($composerJson['autoload']['psr-4'][$namespace])) { - return; - } - - $composerJson['autoload']['psr-4'][$namespace] = 'packages/' . $package . '/src'; - $composerJson['autoload-dev']['psr-4'][$namespaceTest] = 'packages/' . $package . '/tests'; - - $this->saveJsonToFile($composerJsonFilePath, $composerJson); + $content = Json::encode($json, Json::PRETTY); + $content = $this->inlineSections($content, ['keywords', 'bin']); + $content = $this->inlineAuthors($content); + FileSystem::write($filePath, $content); + } - // rebuild new namespace - $composerDumpProcess = new Process(['composer', 'dump']); - $composerDumpProcess->run(); + /** + * @param mixed[] $variables + */ + private function applyVariables(string $content, array $variables): string + { + return str_replace(array_keys($variables), array_values($variables), $content); } /** @@ -267,26 +287,6 @@ private function inlineSections(string $jsonContent, array $sections): string return $jsonContent; } - /** - * @return mixed[] - */ - private function loadFileToJson(string $filePath): array - { - $fileContent = FileSystem::read($filePath); - return Json::decode($fileContent, Json::FORCE_ARRAY); - } - - /** - * @param mixed[] $json - */ - private function saveJsonToFile(string $filePath, array $json): void - { - $content = Json::encode($json, Json::PRETTY); - $content = $this->inlineSections($content, ['keywords', 'bin']); - $content = $this->inlineAuthors($content); - FileSystem::write($filePath, $content); - } - private function inlineAuthors(string $jsonContent): string { $pattern = '#(?"authors": \[\s+)(?.*?)(?\s+\](,))#ms';