diff --git a/.dockerignore b/.dockerignore index 325c9692d26..1a834540de6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,7 +7,15 @@ .editorconfig .phpstorm.meta.php +.coveralls.yml + +phpstan.neon rector.yaml +create-rector.yaml.dist +phpunit.xml +ecs.yaml +ecs-after-rector.yaml +changelog-linker.yaml LICENSE @@ -24,3 +32,5 @@ docker-compose.dist.yml /vendor /docs +/sample +/var diff --git a/.travis.yml b/.travis.yml index 597580295ef..097e42b1347 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,6 @@ matrix: env: CODING_STANDARD=true - php: '7.2' env: STANDALONE=true - - php: '7.3' - env: DOG_FOOD=true - - php: '7.4snapshot' install: - composer update $COMPOSER_FLAGS @@ -47,14 +44,14 @@ script: - | if [[ $STATIC_ANALYSIS == true ]]; then composer docs - php bin/check_services_in_yaml_configs.php - php bin/run_all_sets.php + php ci/check_services_in_yaml_configs.php + php ci/run_all_sets.php fi # Eat your own dog food - | if [[ $DOG_FOOD == true ]]; then - bin/rector process src --set dead-code --dry-run + composer rector fi # Run standalone install in non-root package, ref https://github.com/rectorphp/rector/issues/732 diff --git a/bin/container.php b/bin/container.php index f56fee5df3d..c926e1e5446 100644 --- a/bin/container.php +++ b/bin/container.php @@ -2,7 +2,8 @@ declare(strict_types=1); -use Rector\Console\Option\SetOptionResolver; +use Rector\Bootstrap\ConfigResolver; +use Rector\Bootstrap\SetOptionResolver; use Rector\DependencyInjection\RectorContainerFactory; use Rector\Exception\Configuration\SetNotFoundException; use Rector\Set\Set; @@ -31,6 +32,11 @@ $configs[] = ConfigFileFinder::provide('rector', ['rector.yml', 'rector.yaml']); // remove empty values $configs = array_filter($configs); +// resolve: parameters > sets +$configResolver = new ConfigResolver(); +$parameterSetsConfigs = $configResolver->resolveFromParameterSetsFromConfigFiles($configs); +$configs = array_merge($configs, $parameterSetsConfigs); + // Build DI container $rectorContainerFactory = new RectorContainerFactory(); return $rectorContainerFactory->createFromConfigs($configs); diff --git a/bin/rector b/bin/rector index 1c071f1edca..a4f29aa3769 100755 --- a/bin/rector +++ b/bin/rector @@ -3,7 +3,6 @@ declare(strict_types=1); -use Composer\XdebugHandler\XdebugHandler; use Psr\Container\ContainerInterface; use Rector\Console\Application; @@ -15,10 +14,6 @@ gc_disable(); // Require Composer autoload.php require_once __DIR__ . '/bootstrap.php'; -$xdebug = new XdebugHandler('rector', '--ansi'); -$xdebug->check(); -unset($xdebug); - /** @var ContainerInterface $container */ $container = require_once __DIR__ . '/container.php'; diff --git a/bin/check_services_in_yaml_configs.php b/ci/check_services_in_yaml_configs.php similarity index 100% rename from bin/check_services_in_yaml_configs.php rename to ci/check_services_in_yaml_configs.php diff --git a/bin/clean_trailing_spaces.sh b/ci/clean_trailing_spaces.sh similarity index 100% rename from bin/clean_trailing_spaces.sh rename to ci/clean_trailing_spaces.sh diff --git a/bin/run_all_sets.php b/ci/run_all_sets.php similarity index 97% rename from bin/run_all_sets.php rename to ci/run_all_sets.php index cde2f7483e8..2dcca950ee7 100644 --- a/bin/run_all_sets.php +++ b/ci/run_all_sets.php @@ -47,8 +47,7 @@ if ($errors === []) { } foreach ($errors as $error) { - echo $error; - echo PHP_EOL; + echo $error . PHP_EOL; } exit(ShellCode::ERROR); diff --git a/composer.json b/composer.json index 2d56a7c93a8..c10f151ceac 100644 --- a/composer.json +++ b/composer.json @@ -188,7 +188,7 @@ "check-cs": "vendor/bin/ecs check bin packages src tests utils --ansi", "fix-cs": [ "vendor/bin/ecs check bin packages src tests utils --fix --ansi", - "bin/clean_trailing_spaces.sh" + "ci/clean_trailing_spaces.sh" ], "phpstan": "vendor/bin/phpstan analyse packages src tests --error-format symplify --ansi", "changelog": [ @@ -199,7 +199,8 @@ "docs": [ "bin/rector dump-rectors -o markdown > docs/AllRectorsOverview.md", "bin/rector dump-nodes -o markdown > docs/NodesOverview.md" - ] + ], + "rector": "bin/rector process packages src tests --config rector-ci.yaml --dry-run" }, "scripts-descriptions": { "docs": "Regenerate descriptions of all Rectors to docs/AllRectorsOverview.md file" diff --git a/ecs.yaml b/ecs.yaml index 6870625c4f4..6fcdbf0271b 100644 --- a/ecs.yaml +++ b/ecs.yaml @@ -24,29 +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*' - Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer: extra_skipped_classes: - 'PhpParser\PrettyPrinter\Standard' @@ -70,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 @@ -183,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 13d59c01a29..075707c74b8 100644 --- a/packages/Architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php +++ b/packages/Architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php @@ -169,6 +169,48 @@ PHP 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 @@ PHP } } - 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 @@ PHP 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/config/config.yaml b/packages/BetterPhpDocParser/config/config.yaml index 7052e96f2fe..e83f994a95b 100644 --- a/packages/BetterPhpDocParser/config/config.yaml +++ b/packages/BetterPhpDocParser/config/config.yaml @@ -14,3 +14,6 @@ services: PHPStan\PhpDocParser\Parser\PhpDocParser: alias: 'Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser' + + Doctrine\Common\Annotations\Reader: + alias: 'Doctrine\Common\Annotations\AnnotationReader' diff --git a/packages/BetterPhpDocParser/src/AnnotationReader/AnnotationReaderFactory.php b/packages/BetterPhpDocParser/src/AnnotationReader/AnnotationReaderFactory.php index ec90bb42d1a..38223d77b4d 100644 --- a/packages/BetterPhpDocParser/src/AnnotationReader/AnnotationReaderFactory.php +++ b/packages/BetterPhpDocParser/src/AnnotationReader/AnnotationReaderFactory.php @@ -6,10 +6,11 @@ namespace Rector\BetterPhpDocParser\AnnotationReader; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; +use Doctrine\Common\Annotations\Reader; final class AnnotationReaderFactory { - public function create(): AnnotationReader + public function create(): Reader { AnnotationRegistry::registerLoader('class_exists'); diff --git a/packages/BetterPhpDocParser/src/AnnotationReader/NodeAnnotationReader.php b/packages/BetterPhpDocParser/src/AnnotationReader/NodeAnnotationReader.php index 25321c55347..7d49d2e7713 100644 --- a/packages/BetterPhpDocParser/src/AnnotationReader/NodeAnnotationReader.php +++ b/packages/BetterPhpDocParser/src/AnnotationReader/NodeAnnotationReader.php @@ -5,8 +5,7 @@ declare(strict_types=1); namespace Rector\BetterPhpDocParser\AnnotationReader; use Doctrine\Common\Annotations\AnnotationException; -use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\ORM\Mapping\Annotation; +use Doctrine\Common\Annotations\Reader; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; @@ -16,24 +15,23 @@ use Rector\PhpParser\Node\Resolver\NameResolver; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; -use Symfony\Component\Validator\Constraint; use Throwable; final class NodeAnnotationReader { /** - * @var AnnotationReader + * @var Reader */ - private $annotationReader; + private $reader; /** * @var NameResolver */ private $nameResolver; - public function __construct(AnnotationReader $annotationReader, NameResolver $nameResolver) + public function __construct(Reader $reader, NameResolver $nameResolver) { - $this->annotationReader = $annotationReader; + $this->reader = $reader; $this->nameResolver = $nameResolver; } @@ -51,7 +49,7 @@ final class NodeAnnotationReader $reflectionMethod = new ReflectionMethod($className, $methodName); try { - return $this->annotationReader->getMethodAnnotation($reflectionMethod, $annotationClassName); + return $this->reader->getMethodAnnotation($reflectionMethod, $annotationClassName); } catch (AnnotationException $annotationException) { // unable to laod return null; @@ -65,11 +63,11 @@ final class NodeAnnotationReader { $classReflection = $this->createClassReflectionFromNode($class); - return $this->annotationReader->getClassAnnotation($classReflection, $annotationClassName); + return $this->reader->getClassAnnotation($classReflection, $annotationClassName); } /** - * @return Annotation|Constraint|null + * @return object|null */ public function readPropertyAnnotation(Property $property, string $annotationClassName) { @@ -78,13 +76,15 @@ final class NodeAnnotationReader return null; } - /** @var Annotation|null $propertyAnnotation */ - $propertyAnnotation = $this->annotationReader->getPropertyAnnotation($propertyReflection, $annotationClassName); - if ($propertyAnnotation === null) { - return null; - } + return $this->reader->getPropertyAnnotation($propertyReflection, $annotationClassName); + } - 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 @@ -107,12 +107,4 @@ final class NodeAnnotationReader 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 3815ee7c46c..cbc44bbd2fc 100644 --- a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php +++ b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php @@ -79,6 +79,17 @@ final class PhpDocInfoFactory 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 @@ final class PhpDocInfoFactory 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/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php index 01c3047c434..897c0d62f0d 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php @@ -100,19 +100,19 @@ final class SymfonyRouteTagValueNode extends AbstractTagValueNode $contentItems['name'] = sprintf('name="%s"', $this->name); } - if ($this->methods) { + if ($this->methods !== []) { $contentItems['methods'] = $this->printArrayItem($this->methods, 'methods'); } - if ($this->options) { + if ($this->options !== []) { $contentItems['options'] = $this->printArrayItem($this->options, 'options'); } - if ($this->defaults) { + if ($this->defaults !== []) { $contentItems['defaults'] = $this->printArrayItem($this->defaults, 'defaults'); } - if ($this->requirements) { + if ($this->requirements !== []) { $contentItems['requirements'] = $this->printArrayItem($this->requirements, 'requirements'); } diff --git a/packages/BetterPhpDocParser/src/PhpDocNodeFactory/JMS/JMSInjectPhpDocNodeFactory.php b/packages/BetterPhpDocParser/src/PhpDocNodeFactory/JMS/JMSInjectPhpDocNodeFactory.php index f0e817bba5b..56286e39aed 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNodeFactory/JMS/JMSInjectPhpDocNodeFactory.php +++ b/packages/BetterPhpDocParser/src/PhpDocNodeFactory/JMS/JMSInjectPhpDocNodeFactory.php @@ -45,11 +45,7 @@ final class JMSInjectPhpDocNodeFactory extends AbstractPhpDocNodeFactory return null; } - if ($inject->value === null) { - $serviceName = $this->nameResolver->getName($node); - } else { - $serviceName = $inject->value; - } + $serviceName = $inject->value === null ? $this->nameResolver->getName($node) : $inject->value; // needed for proper doc block formatting $this->resolveContentFromTokenIterator($tokenIterator); diff --git a/packages/BetterPhpDocParser/src/PhpDocParser/AnnotationContentResolver.php b/packages/BetterPhpDocParser/src/PhpDocParser/AnnotationContentResolver.php index b529f0f0204..01df04079d6 100644 --- a/packages/BetterPhpDocParser/src/PhpDocParser/AnnotationContentResolver.php +++ b/packages/BetterPhpDocParser/src/PhpDocParser/AnnotationContentResolver.php @@ -74,7 +74,7 @@ final class AnnotationContentResolver } $start = $this->tryStartWithKey($name, $start, $tokenIterator); - if ($start === false) { + if (! $start) { $tokenIterator->next(); continue; } @@ -112,7 +112,7 @@ final class AnnotationContentResolver private function tryStartWithKey(string $name, bool $start, TokenIterator $localTokenIterator): bool { - if ($start === true) { + if ($start) { return true; } diff --git a/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php b/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php index b784028e895..72518d1d21a 100644 --- a/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php +++ b/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php @@ -206,38 +206,6 @@ final class BetterPhpDocParser extends PhpDocParser 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 @@ final class BetterPhpDocParser extends PhpDocParser 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, @@ -289,11 +245,7 @@ final class BetterPhpDocParser extends PhpDocParser $tag = ltrim($tag, '@'); if ($phpDocNodeFactory instanceof NameAwarePhpDocNodeFactoryInterface) { - if (Strings::lower($phpDocNodeFactory->getName()) === Strings::lower($tag)) { - return true; - } - - return false; + return Strings::lower($phpDocNodeFactory->getName()) === Strings::lower($tag); } if ($phpDocNodeFactory instanceof ClassAwarePhpDocNodeFactoryInterface) { @@ -307,6 +259,11 @@ final class BetterPhpDocParser extends PhpDocParser 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 +289,43 @@ final class BetterPhpDocParser extends PhpDocParser 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 5f20fce027d..a2f73a16df5 100644 --- a/packages/BetterPhpDocParser/src/PhpDocParser/ClassAnnotationMatcher.php +++ b/packages/BetterPhpDocParser/src/PhpDocParser/ClassAnnotationMatcher.php @@ -33,14 +33,6 @@ final class ClassAnnotationMatcher 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 */ @@ -52,7 +44,7 @@ final class ClassAnnotationMatcher continue; } - if ($useUse->alias) { + if ($useUse->alias !== null) { $unaliasedShortClass = Strings::substring($tag, Strings::length($useUse->alias->toString())); if (Strings::startsWith($unaliasedShortClass, '\\')) { return $useUse->name . $unaliasedShortClass; @@ -67,4 +59,12 @@ final class ClassAnnotationMatcher return null; } + + private function isUseMatchingName(string $tag, UseUse $useUse): bool + { + $shortName = $useUse->alias !== null ? $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 dd1b1957cfa..082ca7bdffb 100644 --- a/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php +++ b/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php @@ -148,7 +148,7 @@ final class PhpDocInfoPrinter $startEndValueObject = $attributeAwareNode->getAttribute(Attribute::PHP_DOC_NODE_INFO) ?: $startEndValueObject; $attributeAwareNode = $this->multilineSpaceFormatPreserver->fixMultilineDescriptions($attributeAwareNode); - if ($startEndValueObject) { + if ($startEndValueObject !== null) { $isLastToken = ($nodeCount === $i); $output = $this->addTokensFromTo( @@ -162,7 +162,7 @@ final class PhpDocInfoPrinter } if ($attributeAwareNode instanceof PhpDocTagNode) { - if ($startEndValueObject) { + if ($startEndValueObject !== null) { return $this->printPhpDocTagNode($attributeAwareNode, $startEndValueObject, $output); } @@ -246,7 +246,7 @@ final class PhpDocInfoPrinter $phpDocTagNode->value->description ); - if (substr_count($nodeOutput, "\n")) { + if (substr_count($nodeOutput, "\n") !== 0) { $nodeOutput = Strings::replace($nodeOutput, "#\n#", PHP_EOL . ' * '); } } @@ -255,6 +255,14 @@ final class PhpDocInfoPrinter 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 @@ final class PhpDocInfoPrinter 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/Exception/TooLongException.php b/packages/CodeQuality/src/Exception/TooLongException.php new file mode 100644 index 00000000000..c74076d1abe --- /dev/null +++ b/packages/CodeQuality/src/Exception/TooLongException.php @@ -0,0 +1,11 @@ +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 @@ PHP 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 */ @@ -200,7 +186,7 @@ PHP } // does method return something? - if ($hasClassMethodReturn) { + if ($hasClassMethodReturn !== []) { $anonymousFunction->stmts[] = new Return_($innerMethodCall); } else { $anonymousFunction->stmts[] = new Expression($innerMethodCall); @@ -214,4 +200,18 @@ PHP 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 c27844641af..6db6559a493 100644 --- a/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php +++ b/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php @@ -12,6 +12,7 @@ use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; +use PhpParser\NodeTraverser; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use Rector\NodeTypeResolver\Node\AttributeKey; @@ -125,6 +126,77 @@ PHP return $node; } + /** + * @return Type[] + */ + private function resolveFetchedLocalPropertyNameToType(Class_ $class): array + { + $fetchedLocalPropertyNameToTypes = []; + + $this->traverseNodesWithCallable($class->stmts, function (Node $node) use ( + &$fetchedLocalPropertyNameToTypes + ): ?int { + // skip anonymous class scope + if ($this->isAnonymousClass($node)) { + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + if (! $node instanceof PropertyFetch) { + return null; + } + + if (! $node->var instanceof Variable) { + return null; + } + + if (! $this->isName($node->var, 'this')) { + return null; + } + + // special Laravel collection scope + if ($this->shouldSkipForLaravelCollection($node)) { + return null; + } + + if ($node->name instanceof Variable) { + return null; + } + + $propertyName = $this->getName($node->name); + if ($propertyName === null) { + return null; + } + + $propertyFetchType = $this->resolvePropertyFetchType($node); + + $fetchedLocalPropertyNameToTypes[$propertyName][] = $propertyFetchType; + + return null; + }); + + // normalize types to union + $fetchedLocalPropertyNameToType = []; + foreach ($fetchedLocalPropertyNameToTypes as $name => $types) { + $fetchedLocalPropertyNameToType[$name] = $this->typeFactory->createMixedPassedOrUnionType($types); + } + + return $fetchedLocalPropertyNameToType; + } + + /** + * @return string[] + */ + private function getClassPropertyNames(Class_ $class): array + { + $propertyNames = []; + + foreach ($class->getProperties() as $property) { + $propertyNames[] = $this->getName($property); + } + + return $propertyNames; + } + /** * @param Type[] $fetchedLocalPropertyNameToTypes * @param string[] $propertiesToComplete @@ -160,78 +232,6 @@ PHP return $newProperties; } - /** - * @return Type[] - */ - private function resolveFetchedLocalPropertyNameToType(Class_ $class): array - { - $fetchedLocalPropertyNameToTypes = []; - - $this->traverseNodesWithCallable($class->stmts, function (Node $node) use ( - &$fetchedLocalPropertyNameToTypes - ) { - if (! $node instanceof PropertyFetch) { - return null; - } - - if (! $this->isName($node->var, 'this')) { - return null; - } - - // special Laravel collection scope - if ($this->shouldSkipForLaravelCollection($node)) { - return null; - } - - if ($node->name instanceof Variable) { - return null; - } - - $propertyName = $this->getName($node->name); - if ($propertyName === null) { - return null; - } - - $propertyFetchType = $this->resolvePropertyFetchType($node); - - $fetchedLocalPropertyNameToTypes[$propertyName][] = $propertyFetchType; - }); - - // normalize types to union - $fetchedLocalPropertyNameToType = []; - foreach ($fetchedLocalPropertyNameToTypes as $name => $types) { - $fetchedLocalPropertyNameToType[$name] = $this->typeFactory->createMixedPassedOrUnionType($types); - } - - return $fetchedLocalPropertyNameToType; - } - - /** - * @return string[] - */ - private function getClassPropertyNames(Class_ $class): array - { - $propertyNames = []; - - foreach ($class->getProperties() as $property) { - $propertyNames[] = $this->getName($property); - } - - return $propertyNames; - } - - 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(); - } - private function shouldSkipForLaravelCollection(Node $node): bool { $staticCallOrClassMethod = $this->betterNodeFinder->findFirstAncestorInstancesOf( @@ -245,4 +245,16 @@ PHP 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/Concat/JoinStringConcatRector.php b/packages/CodeQuality/src/Rector/Concat/JoinStringConcatRector.php index c2c18f05a9a..acf21be35d2 100644 --- a/packages/CodeQuality/src/Rector/Concat/JoinStringConcatRector.php +++ b/packages/CodeQuality/src/Rector/Concat/JoinStringConcatRector.php @@ -4,9 +4,11 @@ declare(strict_types=1); namespace Rector\CodeQuality\Rector\Concat; +use Nette\Utils\Strings; use PhpParser\Node; use PhpParser\Node\Expr\BinaryOp\Concat; use PhpParser\Node\Scalar\String_; +use Rector\CodeQuality\Exception\TooLongException; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -16,6 +18,11 @@ use Rector\RectorDefinition\RectorDefinition; */ final class JoinStringConcatRector extends AbstractRector { + /** + * @var int + */ + private const LINE_BREAK_POINT = 80; + public function getDefinition(): RectorDefinition { return new RectorDefinition('Joins concat of 2 strings', [ @@ -56,7 +63,11 @@ PHP */ public function refactor(Node $node): ?Node { - return $this->joinConcatIfStrings($node); + try { + return $this->joinConcatIfStrings($node); + } catch (TooLongException $tooLongException) { + return null; + } } /** @@ -80,6 +91,11 @@ PHP return $concat; } + $value = $concat->left->value . $concat->right->value; + if (Strings::length($value) >= self::LINE_BREAK_POINT) { + throw new TooLongException(); + } + return new String_($concat->left->value . $concat->right->value); } } diff --git a/packages/CodeQuality/src/Rector/For_/ForToForeachRector.php b/packages/CodeQuality/src/Rector/For_/ForToForeachRector.php index 2d43867db9d..66b02c04720 100644 --- a/packages/CodeQuality/src/Rector/For_/ForToForeachRector.php +++ b/packages/CodeQuality/src/Rector/For_/ForToForeachRector.php @@ -138,6 +138,13 @@ PHP return $foreach; } + private function reset(): void + { + $this->keyValueName = null; + $this->countValueName = null; + $this->iteratedExpr = null; + } + /** * @param Expr[] $initExprs */ @@ -209,39 +216,6 @@ PHP 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 @@ PHP }); } + /** + * @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 10c194163a9..78022cf88c8 100644 --- a/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php +++ b/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\If_; @@ -22,6 +23,7 @@ use Rector\RectorDefinition\RectorDefinition; /** * @see https://phpstan.org/r/e909844a-084e-427e-92ac-fed3c2aeabab + * * @see \Rector\CodeQuality\Tests\Rector\If_\RemoveAlwaysTrueConditionSetInConstructorRector\RemoveAlwaysTrueConditionSetInConstructorRectorTest */ final class RemoveAlwaysTrueConditionSetInConstructorRector extends AbstractRector @@ -124,6 +126,32 @@ PHP 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[] */ @@ -155,7 +183,26 @@ PHP $resolvedTypes = []; - $this->traverseNodesWithCallable($class->stmts, function (Node $node) use ( + // add dfeault vlaue @todo + $defaultValue = $property->props[0]->default; + if ($defaultValue !== null) { + $defaultValueStaticType = $this->getStaticType($defaultValue); + $resolvedTypes[] = $defaultValueStaticType; + } + + $assignTypes = $this->resolveAssignTypes($class, $propertyName); + + return array_merge($resolvedTypes, $assignTypes); + } + + /** + * @return Type[] + */ + private function resolveAssignTypes(ClassLike $classLike, string $propertyName): array + { + $resolvedTypes = []; + + $this->traverseNodesWithCallable($classLike->stmts, function (Node $node) use ( $propertyName, &$resolvedTypes ) { @@ -181,30 +228,4 @@ PHP 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_/ShortenElseIfRector.php b/packages/CodeQuality/src/Rector/If_/ShortenElseIfRector.php index cef6429309d..eba94b87653 100644 --- a/packages/CodeQuality/src/Rector/If_/ShortenElseIfRector.php +++ b/packages/CodeQuality/src/Rector/If_/ShortenElseIfRector.php @@ -88,7 +88,7 @@ PHP // Try to shorten the nested if before transforming it to elseif $refactored = $this->shortenElseIf($if); - if ($refactored) { + if ($refactored !== null) { $if = $refactored; } diff --git a/packages/CodeQuality/src/Rector/If_/SimplifyIfElseToTernaryRector.php b/packages/CodeQuality/src/Rector/If_/SimplifyIfElseToTernaryRector.php index 9576f76fe69..c2064e30935 100644 --- a/packages/CodeQuality/src/Rector/If_/SimplifyIfElseToTernaryRector.php +++ b/packages/CodeQuality/src/Rector/If_/SimplifyIfElseToTernaryRector.php @@ -145,11 +145,6 @@ PHP return $onlyStmt->expr; } - private function unwrapExpression(Node $node): Node - { - return $node instanceof Expression ? $node->expr : $node; - } - /** * @param Node[] $nodes */ @@ -168,4 +163,9 @@ PHP { 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 23a92c3ce4a..61737de468b 100644 --- a/packages/CodeQuality/src/Rector/If_/SimplifyIfIssetToNullCoalescingRector.php +++ b/packages/CodeQuality/src/Rector/If_/SimplifyIfIssetToNullCoalescingRector.php @@ -108,22 +108,6 @@ PHP 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 @@ PHP } 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/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php index 10bff5f8303..a809869ac27 100644 --- a/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php +++ b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php @@ -22,6 +22,7 @@ final class CompleteDynamicPropertiesRectorTest extends AbstractRectorTestCase { yield [__DIR__ . '/Fixture/fixture.php.inc']; yield [__DIR__ . '/Fixture/multiple_types.php.inc']; + yield [__DIR__ . '/Fixture/skip_anonymous_class.php.inc']; yield [__DIR__ . '/Fixture/skip_defined.php.inc']; yield [__DIR__ . '/Fixture/skip_parent_property.php.inc']; yield [__DIR__ . '/Fixture/skip_trait_used.php.inc']; diff --git a/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/Fixture/skip_anonymous_class.php.inc b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/Fixture/skip_anonymous_class.php.inc new file mode 100644 index 00000000000..6bdeeafd107 --- /dev/null +++ b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/Fixture/skip_anonymous_class.php.inc @@ -0,0 +1,49 @@ +addVisitor($this->createNodeVisitor($callable)); + $nodeTraverser->traverse($nodes); + } + + private function createNodeVisitor(callable $callable): NodeVisitor + { + return new class($callable) extends NodeVisitorAbstract { + /** + * @var callable + */ + private $callable; + + public function __construct(callable $callable) + { + $this->callable = $callable; + } + + /** + * @return int|Node|null + */ + public function enterNode(Node $node) + { + $callable = $this->callable; + return $callable($node); + } + }; + } +} diff --git a/packages/CodeQuality/tests/Rector/Concat/JoinStringConcatRector/Fixture/skip_longer_than_120.php.inc b/packages/CodeQuality/tests/Rector/Concat/JoinStringConcatRector/Fixture/skip_longer_than_120.php.inc new file mode 100644 index 00000000000..9ada4f4597a --- /dev/null +++ b/packages/CodeQuality/tests/Rector/Concat/JoinStringConcatRector/Fixture/skip_longer_than_120.php.inc @@ -0,0 +1,13 @@ +value = [5]; + } + + public function go() + { + if ($this->value) { + $maybe = 'yes'; + return 'she says ' . $maybe; + } + } +} + +?> +----- +value = [5]; + } + + public function go() + { + $maybe = 'yes'; + return 'she says ' . $maybe; + } +} + +?> diff --git a/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/Fixture/skip_after_overridden.php.inc b/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/Fixture/skip_after_overridden.php.inc new file mode 100644 index 00000000000..4a1ba2fb7ea --- /dev/null +++ b/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/Fixture/skip_after_overridden.php.inc @@ -0,0 +1,26 @@ +areListenerClassesLoaded) { + return $this->listenerClassesToEvents; + } + + $this->areListenerClassesLoaded = true; + } +} diff --git a/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/Fixture/skip_array.php.inc b/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/Fixture/skip_array.php.inc new file mode 100644 index 00000000000..22865dafd9c --- /dev/null +++ b/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/Fixture/skip_array.php.inc @@ -0,0 +1,21 @@ +value = $value; + } + + public function go() + { + if ($this->value) { + $maybe = 'yes'; + return 'she says ' . $maybe; + } + } +} diff --git a/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/Fixture/skip_nullable_set.php.inc b/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/Fixture/skip_nullable_set.php.inc new file mode 100644 index 00000000000..f18887276e4 --- /dev/null +++ b/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/Fixture/skip_nullable_set.php.inc @@ -0,0 +1,30 @@ +callback = $callback; + } + + public function __toString() + { + $contentItems = []; + + if ($this->callback) { + $contentItems['callback'] = 5; + } + + return '...'; + } +} diff --git a/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/RemoveAlwaysTrueConditionSetInConstructorRectorTest.php b/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/RemoveAlwaysTrueConditionSetInConstructorRectorTest.php index c619530d67d..07a3196d4d2 100644 --- a/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/RemoveAlwaysTrueConditionSetInConstructorRectorTest.php +++ b/packages/CodeQuality/tests/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector/RemoveAlwaysTrueConditionSetInConstructorRectorTest.php @@ -27,11 +27,17 @@ final class RemoveAlwaysTrueConditionSetInConstructorRectorTest extends Abstract yield [__DIR__ . '/Fixture/multiple_lines.php.inc']; yield [__DIR__ . '/Fixture/multiple_lines_in_callable.php.inc']; yield [__DIR__ . '/Fixture/multiple_lines_removed.php.inc']; + yield [__DIR__ . '/Fixture/fix_static_array.php.inc']; + + // skip + yield [__DIR__ . '/Fixture/skip_after_overridden.php.inc']; + yield [__DIR__ . '/Fixture/skip_array.php.inc']; yield [__DIR__ . '/Fixture/skip_changed_value.php.inc']; yield [__DIR__ . '/Fixture/skip_scalars.php.inc']; yield [__DIR__ . '/Fixture/skip_unknown.php.inc']; yield [__DIR__ . '/Fixture/skip_optional_argument_value.php.inc']; yield [__DIR__ . '/Fixture/skip_trait.php.inc']; + yield [__DIR__ . '/Fixture/skip_nullable_set.php.inc']; } protected function getRectorClass(): string diff --git a/packages/CodingStyle/src/Application/NameImportingCommander.php b/packages/CodingStyle/src/Application/NameImportingCommander.php index fb1303b7bba..f8ee17db8ec 100644 --- a/packages/CodingStyle/src/Application/NameImportingCommander.php +++ b/packages/CodingStyle/src/Application/NameImportingCommander.php @@ -6,9 +6,11 @@ namespace Rector\CodingStyle\Application; use PhpParser\Node; use PhpParser\Node\Name; +use PhpParser\Node\Stmt\UseUse; use Rector\CodingStyle\Node\NameImporter; use Rector\Configuration\Option; use Rector\Contract\PhpParser\Node\CommanderInterface; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\NodeTraverser\CallableNodeTraverser; use Symplify\PackageBuilder\Parameter\ParameterProvider; @@ -55,6 +57,11 @@ final class NameImportingCommander implements CommanderInterface return null; } + // skip name of UseUse + if ($node->getAttribute(AttributeKey::PARENT_NODE) instanceof UseUse) { + return null; + } + return $this->nameImporter->importName($node); }); diff --git a/packages/CodingStyle/src/Application/UseAddingCommander.php b/packages/CodingStyle/src/Application/UseAddingCommander.php index d049b95a7e6..ceff26d8660 100644 --- a/packages/CodingStyle/src/Application/UseAddingCommander.php +++ b/packages/CodingStyle/src/Application/UseAddingCommander.php @@ -260,16 +260,6 @@ final class UseAddingCommander implements CommanderInterface 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 @@ final class UseAddingCommander implements CommanderInterface 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 de2eb6d8efa..4ad8a7a89d6 100644 --- a/packages/CodingStyle/src/Application/UseImportsAdder.php +++ b/packages/CodingStyle/src/Application/UseImportsAdder.php @@ -80,29 +80,22 @@ final class UseImportsAdder $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; + foreach ($mainTypes as $key => $mainType) { + foreach ($typesToRemove as $typeToRemove) { + if ($mainType->equals($typeToRemove)) { + unset($mainTypes[$key]); + } + } } - 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, '\\'); + return array_values($mainTypes); } /** @@ -134,21 +127,28 @@ final class UseImportsAdder 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 152441d98a4..e4ccd449f84 100644 --- a/packages/CodingStyle/src/Node/ConcatJoiner.php +++ b/packages/CodingStyle/src/Node/ConcatJoiner.php @@ -40,6 +40,12 @@ final class ConcatJoiner 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 @@ final class ConcatJoiner $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 500154f6bd6..322b237813f 100644 --- a/packages/CodingStyle/src/Node/NameImporter.php +++ b/packages/CodingStyle/src/Node/NameImporter.php @@ -57,7 +57,12 @@ final class NameImporter public function importName(Name $name): ?Name { + if ($name->getAttribute('virtual_node')) { + return null; + } + $staticType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($name); + if (! $staticType instanceof FullyQualifiedObjectType) { return null; } @@ -90,6 +95,16 @@ final class NameImporter 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 +147,4 @@ final class NameImporter $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 646c839af84..16858e7627a 100644 --- a/packages/CodingStyle/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php +++ b/packages/CodingStyle/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php @@ -122,32 +122,7 @@ PHP if ($reflectionMethod->isProtected() && $classMethod->isProtected()) { return true; } - - if ($reflectionMethod->isPrivate() && $classMethod->isPrivate()) { - return true; - } - - 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; - } + return $reflectionMethod->isPrivate() && $classMethod->isPrivate(); } /** @@ -181,7 +156,7 @@ PHP $isStaticSelfFactory = $this->isStaticNamedConstructor($iteratedClassMethod); - if ($isStaticSelfFactory === false) { + if (! $isStaticSelfFactory) { continue; } @@ -191,6 +166,26 @@ PHP 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(); } @@ -219,12 +214,7 @@ PHP if ($this->isName($node->expr->class, 'self')) { return true; } - - if ($this->isName($node->expr->class, 'static')) { - return true; - } - - return false; + return $this->isName($node->expr->class, 'static'); }); } } diff --git a/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php b/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php index 1b8206a89a7..29e62eff320 100644 --- a/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php +++ b/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php @@ -118,6 +118,19 @@ PHP $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 */ @@ -126,13 +139,8 @@ PHP if (! $this->isNewVariableThanBefore($currentStmtVariableName)) { return false; } - // this is already empty line before - if ($this->isPreceededByEmptyLine($node, $key)) { - return false; - } - - return true; + return ! $this->isPreceededByEmptyLine($node, $key); } private function isNewVariableThanBefore(?string $currentStmtVariableName): bool @@ -170,17 +178,4 @@ PHP 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/ClassMethod/ReturnArrayClassMethodToYieldRector.php b/packages/CodingStyle/src/Rector/ClassMethod/ReturnArrayClassMethodToYieldRector.php index 62918afa412..6ab1ba7b788 100644 --- a/packages/CodingStyle/src/Rector/ClassMethod/ReturnArrayClassMethodToYieldRector.php +++ b/packages/CodingStyle/src/Rector/ClassMethod/ReturnArrayClassMethodToYieldRector.php @@ -167,9 +167,9 @@ PHP private function completeComments(Node $node): void { - if ($this->returnDocComment) { + if ($this->returnDocComment !== null) { $node->setDocComment($this->returnDocComment); - } elseif ($this->returnComments) { + } elseif ($this->returnComments !== []) { $node->setAttribute('comments', $this->returnComments); } } diff --git a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php index f7f87becd3d..88da20eae93 100644 --- a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php +++ b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php @@ -115,7 +115,7 @@ PHP return null; } - if ($node->default) { + if ($node->default !== null) { return null; } @@ -161,35 +161,6 @@ PHP }); } - /** - * @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 */ @@ -228,7 +199,7 @@ PHP return $this->isNames($countedArgument, $propertyNames); }); - if ($isNextNodeCountingProperty === false) { + if (! $isNextNodeCountingProperty) { return null; } @@ -236,6 +207,35 @@ PHP }); } + /** + * @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 */ @@ -250,13 +250,8 @@ PHP )) { return true; } - - if ($this->propertyFetchManipulator->isLocalPropertyOfNames($expr->right, $propertyNames) && $this->isNull( + return $this->propertyFetchManipulator->isLocalPropertyOfNames($expr->right, $propertyNames) && $this->isNull( $expr->left - )) { - return true; - } - - return false; + ); } } diff --git a/packages/CodingStyle/src/Rector/FuncCall/ConsistentPregDelimiterRector.php b/packages/CodingStyle/src/Rector/FuncCall/ConsistentPregDelimiterRector.php index d125277c863..fa359fac72e 100644 --- a/packages/CodingStyle/src/Rector/FuncCall/ConsistentPregDelimiterRector.php +++ b/packages/CodingStyle/src/Rector/FuncCall/ConsistentPregDelimiterRector.php @@ -126,6 +126,19 @@ PHP 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 @@ PHP 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/FuncCall/VersionCompareFuncCallToConstantRector.php b/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php index 8e32487867c..b766b600ffc 100644 --- a/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php +++ b/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php @@ -113,11 +113,7 @@ EOS private function isPhpVersionConstant(Expr $expr): bool { - if ($expr instanceof ConstFetch && $expr->name->toString() === 'PHP_VERSION') { - return true; - } - - return false; + return $expr instanceof ConstFetch && $expr->name->toString() === 'PHP_VERSION'; } private function getNewNodeForArg(Expr $expr): Node diff --git a/packages/CodingStyle/src/Rector/String_/ManualJsonStringToJsonEncodeArrayRector.php b/packages/CodingStyle/src/Rector/String_/ManualJsonStringToJsonEncodeArrayRector.php index 385e8e90127..b967555c3d8 100644 --- a/packages/CodingStyle/src/Rector/String_/ManualJsonStringToJsonEncodeArrayRector.php +++ b/packages/CodingStyle/src/Rector/String_/ManualJsonStringToJsonEncodeArrayRector.php @@ -143,13 +143,6 @@ PHP 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 @@ PHP } } + 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 @@ PHP 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 @@ PHP return null; } - private function createArrayNodeFromJsonString(string $stringValue): Array_ + /** + * @param Expr[] $placeholderNodes + */ + private function replaceNodeObjectHashPlaceholdersWithNodes(Array_ $array, array $placeholderNodes): void { - $array = Json::decode($stringValue, Json::FORCE_ARRAY); + // 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); - return $this->createArray($array); + if ($placeholderNode && $this->isImplodeToJson($placeholderNode)) { + /** @var FuncCall $placeholderNode */ + return $placeholderNode->args[1]->value; + } + } + + return $this->matchPlaceholderNode($node, $placeholderNodes); + }); } - private function collectContentAndPlaceholderNodesFromNextExpressions(Assign $assign): ConcatExpressionJoinData + /** + * @param Expr[] $placeholderNodes + */ + private function matchPlaceholderNode(Node $node, array $placeholderNodes): ?Expr { - $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; - } + if (! $node instanceof String_) { + return null; } - return $concatExpressionJoinData; + return $placeholderNodes[$node->value] ?? null; } /** @@ -325,40 +361,4 @@ PHP 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 a2526b1355b..e124916f9eb 100644 --- a/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php +++ b/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php @@ -152,6 +152,37 @@ PHP $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 */ @@ -236,17 +267,11 @@ PHP private function resolveSearchNode(Use_ $node): ?Node { $searchNode = $node->getAttribute(AttributeKey::PARENT_NODE); - if ($searchNode) { + if ($searchNode !== null) { return $searchNode; } - $searchNode = $node->getAttribute(AttributeKey::NEXT_NODE); - if ($searchNode) { - return $searchNode; - } - - // skip - return null; + return $node->getAttribute(AttributeKey::NEXT_NODE); } private function resolveUsedNames(Node $searchNode): void @@ -337,35 +362,4 @@ PHP 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/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Fixture/prevent_duplication.php.inc b/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Fixture/prevent_duplication.php.inc new file mode 100644 index 00000000000..bbe1b511fbc --- /dev/null +++ b/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Fixture/prevent_duplication.php.inc @@ -0,0 +1,32 @@ + +----- + diff --git a/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/ImportFullyQualifiedNamesRectorTest.php b/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/ImportFullyQualifiedNamesRectorTest.php index 9a54e4ec155..bd77b9bc1a1 100644 --- a/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/ImportFullyQualifiedNamesRectorTest.php +++ b/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/ImportFullyQualifiedNamesRectorTest.php @@ -13,6 +13,7 @@ final class ImportFullyQualifiedNamesRectorTest extends AbstractRectorTestCase /** * @dataProvider provideNamespacedClasses() * @dataProvider provideFunctions() + * @dataProvider providerPartials() */ public function test(string $file): void { @@ -21,7 +22,12 @@ final class ImportFullyQualifiedNamesRectorTest extends AbstractRectorTestCase public function providerPartials(): Iterator { - // @todo fix later, details + yield [__DIR__ . '/Fixture/prevent_duplication.php.inc']; + } + + public function skippedProviderPartials(): Iterator + { +// @todo fix later yield [__DIR__ . '/Fixture/doc_combined.php.inc']; yield [__DIR__ . '/Fixture/conflicting_endings.php.inc']; yield [__DIR__ . '/Fixture/import_return_doc.php.inc']; diff --git a/packages/DeadCode/src/Analyzer/SetterOnlyMethodAnalyzer.php b/packages/DeadCode/src/Analyzer/SetterOnlyMethodAnalyzer.php index e57b86bdfda..edb918a1735 100644 --- a/packages/DeadCode/src/Analyzer/SetterOnlyMethodAnalyzer.php +++ b/packages/DeadCode/src/Analyzer/SetterOnlyMethodAnalyzer.php @@ -94,13 +94,13 @@ final class SetterOnlyMethodAnalyzer $relationPropertyNames = $this->doctrineEntityManipulator->resolveRelationPropertyNames($class); $assignOnlyPrivatePropertyNames = array_diff($assignOnlyPrivatePropertyNames, $relationPropertyNames); - if ($assignOnlyPrivatePropertyNames) { + if ($assignOnlyPrivatePropertyNames !== []) { $this->propertiesAndMethodsToRemoveByType[$type]['properties'] = $assignOnlyPrivatePropertyNames; } // 2. setter only methods by class $setterOnlyMethodNames = $this->resolveSetterOnlyMethodNames($class, $assignOnlyPrivatePropertyNames); - if ($setterOnlyMethodNames) { + if ($setterOnlyMethodNames !== []) { $this->propertiesAndMethodsToRemoveByType[$type]['methods'] = $setterOnlyMethodNames; } } diff --git a/packages/DeadCode/src/Doctrine/DoctrineEntityManipulator.php b/packages/DeadCode/src/Doctrine/DoctrineEntityManipulator.php index 20b9e150dfd..497e63b3091 100644 --- a/packages/DeadCode/src/Doctrine/DoctrineEntityManipulator.php +++ b/packages/DeadCode/src/Doctrine/DoctrineEntityManipulator.php @@ -126,7 +126,7 @@ final class DoctrineEntityManipulator } } - if ($shouldUpdate === false) { + if (! $shouldUpdate) { return; } diff --git a/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php b/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php index 44b7bc69c30..26813f5107b 100644 --- a/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php +++ b/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php @@ -103,6 +103,27 @@ PHP 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? @@ -127,29 +148,8 @@ PHP 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 56c2152dbe2..d858c45c193 100644 --- a/packages/DeadCode/src/Rector/ClassMethod/RemoveDelegatingParentCallRector.php +++ b/packages/DeadCode/src/Rector/ClassMethod/RemoveDelegatingParentCallRector.php @@ -104,6 +104,35 @@ PHP return null; } + private function shouldSkipClass(?ClassLike $classLike): bool + { + if (! $classLike instanceof Class_) { + return true; + } + return $classLike->extends === null; + } + + /** + * @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 @@ -139,12 +168,18 @@ PHP if (! $this->areArgsAndParamsEqual($staticCall->args, $classMethod->params)) { return false; } + return ! $this->isParentClassMethodVisibilityOverride($classMethod, $staticCall); + } - if ($this->isParentClassMethodVisibilityOverride($classMethod, $staticCall)) { + private function hasRequiredAnnotation(Node $node): bool + { + if ($node->getDocComment() === null) { return false; } - return true; + $docCommentText = $node->getDocComment()->getText(); + + return (bool) Strings::match($docCommentText, '#\s\@required\s#si'); } /** @@ -200,49 +235,4 @@ PHP 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 082a0045126..897485855fc 100644 --- a/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php +++ b/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php @@ -130,6 +130,23 @@ PHP }); } + /** + * @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 @@ PHP }); } - /** - * @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 @@ PHP 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 @@ PHP 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 06d105d1f72..d6d6f518486 100644 --- a/packages/DeadCode/src/Rector/ClassMethod/RemoveUnusedParameterRector.php +++ b/packages/DeadCode/src/Rector/ClassMethod/RemoveUnusedParameterRector.php @@ -153,45 +153,6 @@ PHP return $node; } - /** - * @return Param[] - */ - private function resolveUnusedParameters(ClassMethod $classMethod): 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 = []; - } - - continue; - } - - $unusedParameters[$i] = $param; - } - - return $unusedParameters; - } - - /** - * @param Param[] $parameters1 - * @param Param[] $parameters2 - * @return Param[] - */ - private function getParameterOverlap(array $parameters1, array $parameters2): array - { - return array_uintersect( - $parameters1, - $parameters2, - function (Param $a, Param $b): int { - return $this->betterStandardPrinter->areNodesEqual($a, $b) ? 0 : 1; - } - ); - } - /** * @param ClassMethod $classMethod * @param string $methodName @@ -216,4 +177,43 @@ PHP } return $unusedParameters; } + + /** + * @param Param[] $parameters1 + * @param Param[] $parameters2 + * @return Param[] + */ + private function getParameterOverlap(array $parameters1, array $parameters2): array + { + return array_uintersect( + $parameters1, + $parameters2, + function (Param $a, Param $b): int { + return $this->betterStandardPrinter->areNodesEqual($a, $b) ? 0 : 1; + } + ); + } + + /** + * @return Param[] + */ + private function resolveUnusedParameters(ClassMethod $classMethod): 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 = []; + } + + 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 d325557980d..a384a5e422f 100644 --- a/packages/DeadCode/src/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector.php +++ b/packages/DeadCode/src/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector.php @@ -190,62 +190,6 @@ PHP return $class; } - private function getOtherRelationProperty(Property $property): ?Property - { - $targetEntity = $this->docBlockManipulator->getDoctrineFqnTargetEntity($property); - if ($targetEntity === null) { - return null; - } - - $otherProperty = $this->doctrineEntityManipulator->resolveOtherProperty($property); - if ($otherProperty === null) { - return null; - } - - // get the class property and remove "mappedBy/inversedBy" from annotation - $relatedEntityClass = $this->parsedNodesByType->findClass($targetEntity); - if (! $relatedEntityClass instanceof Class_) { - return null; - } - - foreach ($relatedEntityClass->getProperties() as $relatedEntityClassStmt) { - if (! $this->isName($relatedEntityClassStmt, $otherProperty)) { - continue; - } - - return $relatedEntityClassStmt; - } - - return null; - } - - private function removeInversedByOrMappedByOnRelatedProperty(Property $property): void - { - $otherRelationProperty = $this->getOtherRelationProperty($property); - if ($otherRelationProperty === null) { - return; - } - - $this->doctrineEntityManipulator->removeMappedByOrInversedByFromProperty($otherRelationProperty); - } - - private function isPropertyFetchAssignOfArrayCollection(PropertyFetch $propertyFetch): bool - { - $parentNode = $propertyFetch->getAttribute(AttributeKey::PARENT_NODE); - if (! $parentNode instanceof Assign) { - return false; - } - - if (! $parentNode->expr instanceof New_) { - return false; - } - - /** @var New_ $new */ - $new = $parentNode->expr; - - return $this->isName($new->class, ArrayCollection::class); - } - /** * @return string[] */ @@ -280,4 +224,60 @@ PHP return $usedPropertyNames; } + + private function removeInversedByOrMappedByOnRelatedProperty(Property $property): void + { + $otherRelationProperty = $this->getOtherRelationProperty($property); + if ($otherRelationProperty === null) { + return; + } + + $this->doctrineEntityManipulator->removeMappedByOrInversedByFromProperty($otherRelationProperty); + } + + private function isPropertyFetchAssignOfArrayCollection(PropertyFetch $propertyFetch): bool + { + $parentNode = $propertyFetch->getAttribute(AttributeKey::PARENT_NODE); + if (! $parentNode instanceof Assign) { + return false; + } + + if (! $parentNode->expr instanceof New_) { + return false; + } + + /** @var New_ $new */ + $new = $parentNode->expr; + + return $this->isName($new->class, ArrayCollection::class); + } + + private function getOtherRelationProperty(Property $property): ?Property + { + $targetEntity = $this->docBlockManipulator->getDoctrineFqnTargetEntity($property); + if ($targetEntity === null) { + return null; + } + + $otherProperty = $this->doctrineEntityManipulator->resolveOtherProperty($property); + if ($otherProperty === null) { + return null; + } + + // get the class property and remove "mappedBy/inversedBy" from annotation + $relatedEntityClass = $this->parsedNodesByType->findClass($targetEntity); + if (! $relatedEntityClass instanceof Class_) { + return null; + } + + foreach ($relatedEntityClass->getProperties() as $relatedEntityClassStmt) { + if (! $this->isName($relatedEntityClassStmt, $otherProperty)) { + continue; + } + + return $relatedEntityClassStmt; + } + + return null; + } } diff --git a/packages/DeadCode/src/Rector/If_/RemoveAlwaysTrueIfConditionRector.php b/packages/DeadCode/src/Rector/If_/RemoveAlwaysTrueIfConditionRector.php index 1a663f5059f..9fa3ce1a06b 100644 --- a/packages/DeadCode/src/Rector/If_/RemoveAlwaysTrueIfConditionRector.php +++ b/packages/DeadCode/src/Rector/If_/RemoveAlwaysTrueIfConditionRector.php @@ -76,7 +76,7 @@ PHP return null; } - if ($conditionStaticType->getValue() !== true) { + if (! $conditionStaticType->getValue()) { return null; } diff --git a/packages/DeadCode/src/Rector/Instanceof_/RemoveDuplicatedInstanceOfRector.php b/packages/DeadCode/src/Rector/Instanceof_/RemoveDuplicatedInstanceOfRector.php index 5bcc8095aee..5b860dbaf29 100644 --- a/packages/DeadCode/src/Rector/Instanceof_/RemoveDuplicatedInstanceOfRector.php +++ b/packages/DeadCode/src/Rector/Instanceof_/RemoveDuplicatedInstanceOfRector.php @@ -99,24 +99,6 @@ PHP $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 @@ PHP 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 @@ PHP // 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 4611c5bc1bf..936c76166e2 100644 --- a/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php +++ b/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php @@ -109,6 +109,64 @@ PHP return $node; } + /** + * @param MethodCall|StaticCall|FuncCall $node + */ + private function shouldSkip(Node $node): bool + { + if ($node->args === []) { + return true; + } + + if (! $node instanceof FuncCall) { + return false; + } + + $functionName = $this->getName($node->name); + if ($functionName === null) { + return false; + } + + if (! function_exists($functionName)) { + return false; + } + + $reflectionFunction = new ReflectionFunction($functionName); + + // skip native functions, hard to analyze without stubs (stubs would make working with IDE non-practical) + return $reflectionFunction->isInternal(); + } + + /** + * @param StaticCall|FuncCall|MethodCall $node + * @return Expr[] + */ + private function resolveDefaultValuesFromCall(Node $node): array + { + /** @var string|null $nodeName */ + $nodeName = $this->getName($node); + if ($nodeName === null) { + return []; + } + + if ($node instanceof FuncCall) { + return $this->resolveFuncCallDefaultParamValues($nodeName); + } + + /** @var string|null $className */ + $className = $node->getAttribute(AttributeKey::CLASS_NAME); + if ($className === null) { // anonymous class + return []; + } + + $classMethodNode = $this->parsedNodesByType->findMethod($nodeName, $className); + if ($classMethodNode !== null) { + return $this->resolveDefaultParamValuesFromFunctionLike($classMethodNode); + } + + return []; + } + /** * @param StaticCall|MethodCall|FuncCall $node * @param Expr[]|mixed[] $defaultValues @@ -146,53 +204,6 @@ PHP return $keysToRemove; } - /** - * @param StaticCall|FuncCall|MethodCall $node - * @return Expr[] - */ - private function resolveDefaultValuesFromCall(Node $node): array - { - /** @var string|null $nodeName */ - $nodeName = $this->getName($node); - if ($nodeName === null) { - return []; - } - - if ($node instanceof FuncCall) { - return $this->resolveFuncCallDefaultParamValues($nodeName); - } - - /** @var string|null $className */ - $className = $node->getAttribute(AttributeKey::CLASS_NAME); - if ($className === null) { // anonymous class - return []; - } - - $classMethodNode = $this->parsedNodesByType->findMethod($nodeName, $className); - if ($classMethodNode !== null) { - return $this->resolveDefaultParamValuesFromFunctionLike($classMethodNode); - } - - return []; - } - - /** - * @return Node[] - */ - private function resolveDefaultParamValuesFromFunctionLike(FunctionLike $functionLike): array - { - $defaultValues = []; - foreach ($functionLike->getParams() as $key => $param) { - if ($param->default === null) { - continue; - } - - $defaultValues[$key] = $param->default; - } - - return $defaultValues; - } - /** * @return Expr[] */ @@ -224,30 +235,19 @@ PHP } /** - * @param MethodCall|StaticCall|FuncCall $node + * @return Node[] */ - private function shouldSkip(Node $node): bool + private function resolveDefaultParamValuesFromFunctionLike(FunctionLike $functionLike): array { - if ($node->args === []) { - return true; + $defaultValues = []; + foreach ($functionLike->getParams() as $key => $param) { + if ($param->default === null) { + continue; + } + + $defaultValues[$key] = $param->default; } - if (! $node instanceof FuncCall) { - return false; - } - - $functionName = $this->getName($node->name); - if ($functionName === null) { - return false; - } - - if (! function_exists($functionName)) { - return false; - } - - $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 db06bb7262a..6421683e53d 100644 --- a/packages/DeadCode/src/Rector/Plus/RemoveDeadZeroAndOneOperationRector.php +++ b/packages/DeadCode/src/Rector/Plus/RemoveDeadZeroAndOneOperationRector.php @@ -102,46 +102,6 @@ PHP 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 @@ PHP 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 210313697b8..38496f463a9 100644 --- a/packages/DeadCode/src/Rector/Plus/RemoveZeroAndOneBinaryRector.php +++ b/packages/DeadCode/src/Rector/Plus/RemoveZeroAndOneBinaryRector.php @@ -83,6 +83,46 @@ PHP } } + 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 @@ PHP 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 44d76444370..9a3fd2b4681 100644 --- a/packages/DeadCode/src/Rector/Property/RemoveUnusedPrivatePropertyRector.php +++ b/packages/DeadCode/src/Rector/Property/RemoveUnusedPrivatePropertyRector.php @@ -89,31 +89,6 @@ PHP 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()) { @@ -143,18 +118,37 @@ PHP /** @var Node\Stmt\ClassLike $class */ $class = $property->getAttribute(AttributeKey::CLASS_NODE); - $hasMagicPropertyFetch = (bool) $this->betterNodeFinder->findFirst($class->stmts, function (Node $node): bool { + return (bool) $this->betterNodeFinder->findFirst($class->stmts, function (Node $node): bool { if (! $node instanceof PropertyFetch) { return false; } return $node->name instanceof Expr; }); + } - if ($hasMagicPropertyFetch) { - return true; + /** + * 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 false; + return $uselessAssigns; } } diff --git a/packages/DeadCode/src/Rector/Stmt/RemoveUnreachableStatementRector.php b/packages/DeadCode/src/Rector/Stmt/RemoveUnreachableStatementRector.php index f71d57b5b4e..bb9e0820070 100644 --- a/packages/DeadCode/src/Rector/Stmt/RemoveUnreachableStatementRector.php +++ b/packages/DeadCode/src/Rector/Stmt/RemoveUnreachableStatementRector.php @@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\Else_; use PhpParser\Node\Stmt\If_; use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Nop; +use PhpParser\Node\Stmt\While_; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -91,6 +92,11 @@ PHP return null; } + if ($previousNode instanceof While_) { + $node->setAttribute(AttributeKey::IS_UNREACHABLE, false); + return null; + } + if ($this->isAfterMarkTestSkippedMethodCall($node)) { $node->setAttribute(AttributeKey::IS_UNREACHABLE, false); return null; @@ -154,10 +160,6 @@ PHP return true; } - if ($node instanceof Else_) { - return true; - } - - return false; + return $node instanceof Else_; } } diff --git a/packages/Doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php b/packages/Doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php index 2129cd4e5df..6a1ce4da628 100644 --- a/packages/Doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php +++ b/packages/Doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php @@ -56,7 +56,7 @@ final class DoctrineDocBlockResolver if (is_string($class)) { if (ClassExistenceStaticHelper::doesClassLikeExist($class)) { $classNode = $this->parsedNodesByType->findClass($class); - if ($classNode) { + if ($classNode !== null) { return $this->isDoctrineEntityClass($classNode); } @@ -128,7 +128,7 @@ final class DoctrineDocBlockResolver return false; } - if ($propertyPhpDocInfo->getByType(ColumnTagValueNode::class)) { + if ($propertyPhpDocInfo->getByType(ColumnTagValueNode::class) !== null) { return true; } diff --git a/packages/Doctrine/src/Provider/EntityWithMissingUuidProvider.php b/packages/Doctrine/src/Provider/EntityWithMissingUuidProvider.php index e1d1a9b85cd..fa6306da00e 100644 --- a/packages/Doctrine/src/Provider/EntityWithMissingUuidProvider.php +++ b/packages/Doctrine/src/Provider/EntityWithMissingUuidProvider.php @@ -77,7 +77,7 @@ final class EntityWithMissingUuidProvider } // already has $uuid property - if ($this->classManipulator->getProperty($class, 'uuid')) { + if ($this->classManipulator->getProperty($class, 'uuid') !== null) { continue; } diff --git a/packages/Doctrine/src/Rector/ClassMethod/ChangeReturnTypeOfClassMethodWithGetIdRector.php b/packages/Doctrine/src/Rector/ClassMethod/ChangeReturnTypeOfClassMethodWithGetIdRector.php index 56efaae2ef9..00479cc9b29 100644 --- a/packages/Doctrine/src/Rector/ClassMethod/ChangeReturnTypeOfClassMethodWithGetIdRector.php +++ b/packages/Doctrine/src/Rector/ClassMethod/ChangeReturnTypeOfClassMethodWithGetIdRector.php @@ -78,7 +78,7 @@ PHP } $hasEntityGetIdMethodCall = $this->hasEntityGetIdMethodCall($node); - if ($hasEntityGetIdMethodCall === false) { + if (! $hasEntityGetIdMethodCall) { return null; } diff --git a/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php b/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php index 7432925cef7..09737610156 100644 --- a/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php +++ b/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php @@ -83,86 +83,6 @@ final class AddUuidMirrorForRelationPropertyRector extends AbstractRector return $node; } - /** - * Creates duplicated property, that has "*uuidSuffix" - * and nullable join column, so we cna complete them manually - */ - private function createMirrorNullable(Property $property): Property - { - $oldPropertyName = $this->getName($property); - - $propertyWithUuid = clone $property; - - // this is needed to keep old property name - $this->updateDocComment($propertyWithUuid); - - // name must be changed after the doc comment update, because the reflection annotation needed for update of doc comment - // would miss non existing *Uuid property - $uuidPropertyName = $oldPropertyName . 'Uuid'; - $newPropertyProperty = new PropertyProperty(new VarLikeIdentifier($uuidPropertyName)); - $propertyWithUuid->props = [$newPropertyProperty]; - - $this->addNewPropertyToCollector($property, $oldPropertyName, $uuidPropertyName); - - return $propertyWithUuid; - } - - private function updateDocComment(Property $property): void - { - /** @var PhpDocInfo $propertyPhpDocInfo */ - $propertyPhpDocInfo = $this->getPhpDocInfo($property); - - /** @var DoctrineRelationTagValueNodeInterface $doctrineRelationTagValueNode */ - $doctrineRelationTagValueNode = $this->getDoctrineRelationTagValueNode($property); - - if ($doctrineRelationTagValueNode instanceof ToManyTagNodeInterface) { - $this->refactorToManyPropertyPhpDocInfo($propertyPhpDocInfo, $property); - } elseif ($doctrineRelationTagValueNode instanceof ToOneTagNodeInterface) { - $this->refactorToOnePropertyPhpDocInfo($propertyPhpDocInfo); - } - - $this->docBlockManipulator->updateNodeWithPhpDocInfo($property, $propertyPhpDocInfo); - } - - private function refactorToManyPropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo, Property $property): void - { - $doctrineJoinColumnTagValueNode = $propertyPhpDocInfo->getByType(JoinColumnTagValueNode::class); - if ($doctrineJoinColumnTagValueNode) { - // replace @ORM\JoinColumn with @ORM\JoinTable - $propertyPhpDocInfo->removeTagValueNodeFromNode($doctrineJoinColumnTagValueNode); - } - - $joinTableTagNode = $this->phpDocTagNodeFactory->createJoinTableTagNode($property); - $propertyPhpDocInfo->getPhpDocNode()->children[] = $joinTableTagNode; - } - - private function refactorToOnePropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo): void - { - /** @var JoinColumnTagValueNode $joinColumnTagValueNode */ - $joinColumnTagValueNode = $propertyPhpDocInfo->getByType(JoinColumnTagValueNode::class); - - if ($joinColumnTagValueNode) { - $joinColumnTagValueNode->changeName(''); - $joinColumnTagValueNode->changeNullable(true); - $joinColumnTagValueNode->changeReferencedColumnName('uuid'); - } else { - $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 @@ -191,7 +111,7 @@ final class AddUuidMirrorForRelationPropertyRector extends AbstractRector } $oneToOneTagValueNode = $propertyPhpDocInfo->getByType(OneToOneTagValueNode::class); - if ($oneToOneTagValueNode) { + if ($oneToOneTagValueNode !== null) { // skip mappedBy oneToOne, as the column doesn't really exist if ($oneToOneTagValueNode->getMappedBy()) { return true; @@ -201,6 +121,60 @@ final class AddUuidMirrorForRelationPropertyRector extends AbstractRector return false; } + /** + * Creates duplicated property, that has "*uuidSuffix" + * and nullable join column, so we cna complete them manually + */ + private function createMirrorNullable(Property $property): Property + { + $oldPropertyName = $this->getName($property); + + $propertyWithUuid = clone $property; + + // this is needed to keep old property name + $this->updateDocComment($propertyWithUuid); + + // name must be changed after the doc comment update, because the reflection annotation needed for update of doc comment + // would miss non existing *Uuid property + $uuidPropertyName = $oldPropertyName . 'Uuid'; + $newPropertyProperty = new PropertyProperty(new VarLikeIdentifier($uuidPropertyName)); + $propertyWithUuid->props = [$newPropertyProperty]; + + $this->addNewPropertyToCollector($property, $oldPropertyName, $uuidPropertyName); + + 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 */ + $propertyPhpDocInfo = $this->getPhpDocInfo($property); + + /** @var DoctrineRelationTagValueNodeInterface $doctrineRelationTagValueNode */ + $doctrineRelationTagValueNode = $this->getDoctrineRelationTagValueNode($property); + + if ($doctrineRelationTagValueNode instanceof ToManyTagNodeInterface) { + $this->refactorToManyPropertyPhpDocInfo($propertyPhpDocInfo, $property); + } elseif ($doctrineRelationTagValueNode instanceof ToOneTagNodeInterface) { + $this->refactorToOnePropertyPhpDocInfo($propertyPhpDocInfo); + } + + $this->docBlockManipulator->updateNodeWithPhpDocInfo($property, $propertyPhpDocInfo); + } + private function addNewPropertyToCollector( Property $property, string $oldPropertyName, @@ -219,4 +193,30 @@ final class AddUuidMirrorForRelationPropertyRector extends AbstractRector $doctrineRelationTagValueNode ); } + + private function refactorToManyPropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo, Property $property): void + { + $doctrineJoinColumnTagValueNode = $propertyPhpDocInfo->getByType(JoinColumnTagValueNode::class); + if ($doctrineJoinColumnTagValueNode !== null) { + // replace @ORM\JoinColumn with @ORM\JoinTable + $propertyPhpDocInfo->removeTagValueNodeFromNode($doctrineJoinColumnTagValueNode); + } + + $joinTableTagNode = $this->phpDocTagNodeFactory->createJoinTableTagNode($property); + $propertyPhpDocInfo->getPhpDocNode()->children[] = $joinTableTagNode; + } + + private function refactorToOnePropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo): void + { + /** @var JoinColumnTagValueNode $joinColumnTagValueNode */ + $joinColumnTagValueNode = $propertyPhpDocInfo->getByType(JoinColumnTagValueNode::class); + + if ($joinColumnTagValueNode) { + $joinColumnTagValueNode->changeName(''); + $joinColumnTagValueNode->changeNullable(true); + $joinColumnTagValueNode->changeReferencedColumnName('uuid'); + } else { + $propertyPhpDocInfo->getPhpDocNode()->children[] = $this->phpDocTagNodeFactory->createJoinColumnTagNode(); + } + } } diff --git a/packages/Doctrine/src/Rector/Class_/AlwaysInitializeUuidInEntityRector.php b/packages/Doctrine/src/Rector/Class_/AlwaysInitializeUuidInEntityRector.php index 618e2408d6e..0c979ac86ef 100644 --- a/packages/Doctrine/src/Rector/Class_/AlwaysInitializeUuidInEntityRector.php +++ b/packages/Doctrine/src/Rector/Class_/AlwaysInitializeUuidInEntityRector.php @@ -128,12 +128,7 @@ final class AlwaysInitializeUuidInEntityRector extends AbstractRector if (! $this->isName($staticCall->name, 'uuid4')) { return false; } - - if (! $this->isName($node->var, $uuidPropertyName)) { - return false; - } - - return true; + return $this->isName($node->var, $uuidPropertyName); }); } } diff --git a/packages/Doctrine/src/Rector/Class_/ManagerRegistryGetManagerToEntityManagerRector.php b/packages/Doctrine/src/Rector/Class_/ManagerRegistryGetManagerToEntityManagerRector.php index c97d0a12e1f..3e9837ce84c 100644 --- a/packages/Doctrine/src/Rector/Class_/ManagerRegistryGetManagerToEntityManagerRector.php +++ b/packages/Doctrine/src/Rector/Class_/ManagerRegistryGetManagerToEntityManagerRector.php @@ -132,46 +132,6 @@ PHP 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,6 +158,25 @@ PHP return array_unique($registryCalledMethods); } + private function resolveManagerRegistryParam(ClassMethod $classMethod): ?Param + { + foreach ($classMethod->params as $param) { + if ($param->type === null) { + continue; + } + + if (! $this->isName($param->type, ManagerRegistry::class)) { + continue; + } + + $classMethod->params[] = $this->createEntityManagerParam(); + + return $param; + } + + return null; + } + private function removeManagerRegistryDependency( Class_ $class, ClassMethod $classMethod, @@ -219,6 +198,46 @@ PHP $this->removeRegistryDependencyAssign($class, $classMethod, $registryParam); } + /** + * Before: + * $entityRegistry-> + * + * After: + * $this->entityManager-> + */ + private function replaceEntityRegistryVariableWithEntityManagerProperty(Class_ $node): void + { + $this->traverseNodesWithCallable($node->stmts, function (Node $node): ?PropertyFetch { + if (! $node instanceof Variable) { + return null; + } + + if (! $this->isObjectType($node, ObjectManager::class)) { + return null; + } + + 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; + } + + if (! $this->isRegistryGetManagerMethodCall($node)) { + return null; + } + + $this->removeNode($node); + }); + } + private function addConstructorDependencyWithProperty( Class_ $class, ClassMethod $classMethod, @@ -231,44 +250,9 @@ PHP $this->addPropertyToClass($class, $objectType, $name); } - private function resolveManagerRegistryParam(ClassMethod $classMethod): ?Param + private function createEntityManagerParam(): Param { - foreach ($classMethod->params as $param) { - if ($param->type === null) { - continue; - } - - if (! $this->isName($param->type, ManagerRegistry::class)) { - continue; - } - - $classMethod->params[] = $this->createEntityManagerParam(); - - return $param; - } - - return null; - } - - private function removeManagerRegistryProperty(Class_ $class, Assign $assign): void - { - $managerRegistryPropertyName = $this->getName($assign->var); - - $this->traverseNodesWithCallable($class->stmts, function (Node $node) use ( - $managerRegistryPropertyName - ): ?int { - if (! $node instanceof Property) { - return null; - } - - if (! $this->isName($node, $managerRegistryPropertyName)) { - return null; - } - - $this->removeNode($node); - - return NodeTraverser::STOP_TRAVERSAL; - }); + return new Param(new Variable('entityManager'), null, new FullyQualified(EntityManagerInterface::class)); } private function removeRegistryDependencyAssign(Class_ $class, ClassMethod $classMethod, Param $registryParam): void @@ -293,6 +277,18 @@ PHP } } + private function isRegistryGetManagerMethodCall(Assign $assign): bool + { + if (! $assign->expr instanceof MethodCall) { + return false; + } + + if (! $this->isObjectType($assign->expr->var, ManagerRegistry::class)) { + return false; + } + return $this->isName($assign->expr->name, 'getManager'); + } + /** * Creates: "$this->value = $value;" */ @@ -303,25 +299,24 @@ PHP 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 2c2ebbc709f..906c587c301 100644 --- a/packages/Doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php +++ b/packages/Doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php @@ -86,16 +86,6 @@ PHP 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 @@ PHP 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 849d97af753..50e29f814ca 100644 --- a/packages/Doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php +++ b/packages/Doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php @@ -114,7 +114,7 @@ PHP // A. try find "setUuid()" call on the same object later $setUuidCallOnSameVariable = $this->getSetUuidMethodCallOnSameVariable($node); - if ($setUuidCallOnSameVariable) { + if ($setUuidCallOnSameVariable !== null) { $node->args = $setUuidCallOnSameVariable->args; $this->removeNode($setUuidCallOnSameVariable); return $node; @@ -195,15 +195,18 @@ PHP if (! $this->isObjectType($node->var, $variableType)) { return false; } - - if (! $this->isName($node->name, 'setUuid')) { - return false; - } - - return true; + return $this->isName($node->name, 'setUuid'); }); } + 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 +218,4 @@ PHP return $argumentStaticType->getClassName() === Uuid::class; } - - private function createUuidStringNode(): String_ - { - $uuidValue = Uuid::uuid4(); - $uuidValueString = $uuidValue->toString(); - - return new String_($uuidValueString); - } } diff --git a/packages/DoctrineCodeQuality/src/Rector/Class_/InitializeDefaultEntityCollectionRector.php b/packages/DoctrineCodeQuality/src/Rector/Class_/InitializeDefaultEntityCollectionRector.php index d8d744b82f7..86eb41207e0 100644 --- a/packages/DoctrineCodeQuality/src/Rector/Class_/InitializeDefaultEntityCollectionRector.php +++ b/packages/DoctrineCodeQuality/src/Rector/Class_/InitializeDefaultEntityCollectionRector.php @@ -95,7 +95,7 @@ PHP return null; } - if (! $classPhpDocInfo->getByType(EntityTagValueNode::class)) { + if ($classPhpDocInfo->getByType(EntityTagValueNode::class) === null) { return null; } @@ -128,7 +128,7 @@ PHP continue; } - if (! $propertyPhpDocInfo->getByType(ToManyTagNodeInterface::class)) { + if ($propertyPhpDocInfo->getByType(ToManyTagNodeInterface::class) === null) { continue; } diff --git a/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php b/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php index bca95b7557f..6d8c390a6b6 100644 --- a/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php +++ b/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php @@ -199,6 +199,22 @@ abstract class AbstractFileSystemRector implements FileSystemRectorInterface 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 @@ abstract class AbstractFileSystemRector implements FileSystemRectorInterface 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 56c88091036..e6aa998fb34 100644 --- a/packages/Laravel/src/Rector/Class_/InlineValidationRulesToArrayDefinitionRector.php +++ b/packages/Laravel/src/Rector/Class_/InlineValidationRulesToArrayDefinitionRector.php @@ -85,6 +85,62 @@ PHP return $node; } + private function shouldSkipArrayItem(ArrayItem $arrayItem): bool + { + $classNode = $arrayItem->getAttribute(AttributeKey::CLASS_NODE); + if ($classNode === null) { + return true; + } + + if (! $this->isObjectType($classNode, self::FORM_REQUEST_CLASS)) { + return true; + } + + $methodNode = $arrayItem->getAttribute(AttributeKey::METHOD_NODE); + if ($methodNode === null) { + return true; + } + + if (! $this->isName($methodNode, 'rules')) { + return true; + } + return ! $arrayItem->value instanceof String_ && ! $arrayItem->value instanceof Concat; + } + + /** + * @return Expr[] + */ + private function createNewRules(ArrayItem $arrayItem): array + { + $newRules = $this->transformRulesSetToExpressionsArray($arrayItem->value); + + foreach ($newRules as $key => $newRule) { + if (! $newRule instanceof Concat) { + continue; + } + + $fullString = $this->transformConcatExpressionToSingleString($newRule); + if ($fullString === null) { + return []; + } + + $matches = Strings::match($fullString, '#^exists:(?\w+),(?\w+)$#'); + if ($matches === null) { + continue; + } + + $ruleClass = $matches['ruleClass']; + $ruleAttribute = $matches['ruleAttribute']; + + $arguments = [new ClassConstFetch(new Name($ruleClass), 'class'), new String_($ruleAttribute)]; + + $ruleExistsStaticCall = $this->createStaticCall('Illuminate\Validation\Rule', 'exists', $arguments); + $newRules[$key] = $ruleExistsStaticCall; + } + + return $newRules; + } + /** * @return Expr[] */ @@ -135,65 +191,4 @@ PHP return $output; } - - /** - * @return Expr[] - */ - private function createNewRules(ArrayItem $arrayItem): array - { - $newRules = $this->transformRulesSetToExpressionsArray($arrayItem->value); - - foreach ($newRules as $key => $newRule) { - if (! $newRule instanceof Concat) { - continue; - } - - $fullString = $this->transformConcatExpressionToSingleString($newRule); - if ($fullString === null) { - return []; - } - - $matches = Strings::match($fullString, '#^exists:(?\w+),(?\w+)$#'); - if ($matches === null) { - continue; - } - - $ruleClass = $matches['ruleClass']; - $ruleAttribute = $matches['ruleAttribute']; - - $arguments = [new ClassConstFetch(new Name($ruleClass), 'class'), new String_($ruleAttribute)]; - - $ruleExistsStaticCall = $this->createStaticCall('Illuminate\Validation\Rule', 'exists', $arguments); - $newRules[$key] = $ruleExistsStaticCall; - } - - return $newRules; - } - - private function shouldSkipArrayItem(ArrayItem $arrayItem): bool - { - $classNode = $arrayItem->getAttribute(AttributeKey::CLASS_NODE); - if ($classNode === null) { - return true; - } - - if (! $this->isObjectType($classNode, self::FORM_REQUEST_CLASS)) { - return true; - } - - $methodNode = $arrayItem->getAttribute(AttributeKey::METHOD_NODE); - if ($methodNode === null) { - return true; - } - - if (! $this->isName($methodNode, 'rules')) { - return true; - } - - if (! $arrayItem->value instanceof String_ && ! $arrayItem->value instanceof Concat) { - return true; - } - - return false; - } } diff --git a/packages/Laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php b/packages/Laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php index 6e433a78e3e..2c62231df08 100644 --- a/packages/Laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php +++ b/packages/Laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php @@ -87,26 +87,6 @@ PHP 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 @@ PHP ], ]; } + + /** + * @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 67c7f2e6a1e..e1525185909 100644 --- a/packages/NetteTesterToPHPUnit/src/AssertManipulator.php +++ b/packages/NetteTesterToPHPUnit/src/AssertManipulator.php @@ -133,6 +133,20 @@ final class AssertManipulator 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 @@ final class AssertManipulator $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 @@ final class AssertManipulator 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 89040c6ed8c..ea4d7e6641a 100644 --- a/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php +++ b/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php @@ -95,11 +95,6 @@ PHP 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 @@ PHP } } + 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 92b104ab4ff..df641f40940 100644 --- a/packages/NetteToSymfony/src/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector.php +++ b/packages/NetteToSymfony/src/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector.php @@ -182,6 +182,20 @@ PHP 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 @@ PHP 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 3976b1e2efc..9c990075432 100644 --- a/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php +++ b/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php @@ -235,6 +235,31 @@ PHP 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 @@ PHP 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 25c71e1def2..6858952ad3a 100644 --- a/packages/NetteToSymfony/src/Rector/Class_/NetteFormToSymfonyFormRector.php +++ b/packages/NetteToSymfony/src/Rector/Class_/NetteFormToSymfonyFormRector.php @@ -152,6 +152,31 @@ PHP 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 @@ PHP ); } - 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 70a877c9c42..35d2a83819e 100644 --- a/packages/NodeTypeResolver/src/NodeTypeResolver.php +++ b/packages/NodeTypeResolver/src/NodeTypeResolver.php @@ -269,12 +269,7 @@ final class NodeTypeResolver if (is_a($nodeType->getClassName(), 'SimpleXMLElement', true)) { return true; } - - if (is_a($nodeType->getClassName(), 'ResourceBundle', true)) { - return true; - } - - return false; + return is_a($nodeType->getClassName(), 'ResourceBundle', true); } return $this->isArrayType($node); @@ -291,7 +286,7 @@ final class NodeTypeResolver if ($node instanceof PropertyFetch || $node instanceof StaticPropertyFetch) { // PHPStan false positive, when variable has type[] docblock, but default array is missing - if ($this->isPropertyFetchWithArrayDefault($node) === false) { + if (! $this->isPropertyFetchWithArrayDefault($node)) { return false; } } @@ -420,6 +415,67 @@ final class NodeTypeResolver } } + /** + * @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 */ @@ -434,6 +490,34 @@ final class NodeTypeResolver 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 @@ -471,32 +555,29 @@ final class NodeTypeResolver 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); } /** @@ -606,33 +687,6 @@ final class NodeTypeResolver 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); @@ -664,42 +718,15 @@ final class NodeTypeResolver return $paramStaticType; } - private function isUnionNullTypeOfRequiredType(ObjectType $objectType, Type $resolvedType): bool + private function isAnonymousClass(Node $node): bool { - if (! $resolvedType instanceof UnionType) { + if (! $node instanceof Class_) { return false; } - if (count($resolvedType->getTypes()) !== 2) { - return false; - } + $className = $this->nameResolver->getName($node); - $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); - } - - 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; + return $className === null || Strings::contains($className, 'AnonymousClass'); } private function isObjectTypeFnMatch(string $className, string $requiredType): bool @@ -707,54 +734,6 @@ final class NodeTypeResolver 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 - */ - 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 - )); - } - /** * @return string[] */ @@ -766,4 +745,20 @@ final class NodeTypeResolver return array_unique($classLikeTypes); } + + /** + * @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; + } } diff --git a/packages/NodeTypeResolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php b/packages/NodeTypeResolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php index aea8d594ede..88bcd22eff2 100644 --- a/packages/NodeTypeResolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php +++ b/packages/NodeTypeResolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php @@ -83,6 +83,24 @@ final class FunctionMethodAndClassNodeVisitor extends NodeVisitorAbstract 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) { @@ -133,22 +151,4 @@ final class FunctionMethodAndClassNodeVisitor extends NodeVisitorAbstract $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/PHPStan/Type/StaticTypeAnalyzer.php b/packages/NodeTypeResolver/src/PHPStan/Type/StaticTypeAnalyzer.php index 2523604b36f..5aadc75a677 100644 --- a/packages/NodeTypeResolver/src/PHPStan/Type/StaticTypeAnalyzer.php +++ b/packages/NodeTypeResolver/src/PHPStan/Type/StaticTypeAnalyzer.php @@ -4,7 +4,9 @@ declare(strict_types=1); namespace Rector\NodeTypeResolver\PHPStan\Type; +use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; @@ -13,6 +15,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\UnionType; final class StaticTypeAnalyzer { @@ -26,6 +29,18 @@ final class StaticTypeAnalyzer } foreach ($types as $type) { + if ($type instanceof ConstantArrayType) { + continue; + } + + if ($type instanceof ArrayType) { + return false; + } + + if ($this->isNullable($type)) { + return false; + } + if ($type instanceof MixedType) { return false; } @@ -51,6 +66,21 @@ final class StaticTypeAnalyzer return true; } + public function isNullable(Type $type): bool + { + if (! $type instanceof UnionType) { + return false; + } + + foreach ($type->getTypes() as $unionedType) { + if ($unionedType instanceof NullType) { + return true; + } + } + + return false; + } + private function isScalarType(Type $type): bool { if ($type instanceof NullType) { diff --git a/packages/NodeTypeResolver/src/PerNodeTypeResolver/NameTypeResolver.php b/packages/NodeTypeResolver/src/PerNodeTypeResolver/NameTypeResolver.php index dad8e7c520b..9432e2ba272 100644 --- a/packages/NodeTypeResolver/src/PerNodeTypeResolver/NameTypeResolver.php +++ b/packages/NodeTypeResolver/src/PerNodeTypeResolver/NameTypeResolver.php @@ -46,28 +46,6 @@ final class NameTypeResolver implements PerNodeTypeResolverInterface 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 @@ final class NameTypeResolver implements PerNodeTypeResolverInterface 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 1e0ca2b362a..924c4a1eaac 100644 --- a/packages/NodeTypeResolver/src/PerNodeTypeResolver/VariableTypeResolver.php +++ b/packages/NodeTypeResolver/src/PerNodeTypeResolver/VariableTypeResolver.php @@ -91,45 +91,6 @@ final class VariableTypeResolver implements PerNodeTypeResolverInterface, NodeTy $this->nodeTypeResolver = $nodeTypeResolver; } - private function resolveNodeScope(Node $node): ?Scope - { - /** @var Scope|null $nodeScope */ - $nodeScope = $node->getAttribute(AttributeKey::SCOPE); - if ($nodeScope) { - return $nodeScope; - } - - // is node in trait - $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); - if ($classNode instanceof Trait_) { - /** @var string $traitName */ - $traitName = $node->getAttribute(AttributeKey::CLASS_NAME); - $traitNodeScope = $this->traitNodeScopeCollector->getScopeForTraitAndNode($traitName, $node); - if ($traitNodeScope) { - return $traitNodeScope; - } - } - - $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); - if ($parentNode instanceof Node) { - $parentNodeScope = $parentNode->getAttribute(AttributeKey::SCOPE); - if ($parentNodeScope) { - return $parentNodeScope; - } - } - - // get nearest variable scope - $method = $node->getAttribute(AttributeKey::METHOD_NODE); - if ($method instanceof Node) { - $methodNodeScope = $method->getAttribute(AttributeKey::SCOPE); - if ($methodNodeScope) { - return $methodNodeScope; - } - } - - return null; - } - private function resolveTypesFromScope(Variable $variable, string $variableName): Type { $nodeScope = $this->resolveNodeScope($variable); @@ -144,4 +105,43 @@ final class VariableTypeResolver implements PerNodeTypeResolverInterface, NodeTy // 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 */ + $nodeScope = $node->getAttribute(AttributeKey::SCOPE); + if ($nodeScope !== null) { + return $nodeScope; + } + + // is node in trait + $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); + if ($classNode instanceof Trait_) { + /** @var string $traitName */ + $traitName = $node->getAttribute(AttributeKey::CLASS_NAME); + $traitNodeScope = $this->traitNodeScopeCollector->getScopeForTraitAndNode($traitName, $node); + if ($traitNodeScope !== null) { + return $traitNodeScope; + } + } + + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + if ($parentNode instanceof Node) { + $parentNodeScope = $parentNode->getAttribute(AttributeKey::SCOPE); + if ($parentNodeScope !== null) { + return $parentNodeScope; + } + } + + // get nearest variable scope + $method = $node->getAttribute(AttributeKey::METHOD_NODE); + if ($method instanceof Node) { + $methodNodeScope = $method->getAttribute(AttributeKey::SCOPE); + if ($methodNodeScope !== null) { + return $methodNodeScope; + } + } + + return null; + } } diff --git a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php index f66ffd5ee01..c7a1b9885e5 100644 --- a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php +++ b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php @@ -412,7 +412,7 @@ final class DocBlockManipulator return $node; }); - if ($this->hasPhpDocChanged === false) { + if (! $this->hasPhpDocChanged) { return; } @@ -512,6 +512,20 @@ final class DocBlockManipulator 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; + } + return $this->areArrayTypeWithSingleObjectChildToParent($firstType, $secondType); + } + /** * 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 +555,6 @@ final class DocBlockManipulator } } - /** - * @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 +568,6 @@ final class DocBlockManipulator )); } - 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 +595,13 @@ final class DocBlockManipulator 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 09380ae55f4..4e659aaac79 100644 --- a/packages/NodeTypeResolver/src/StaticTypeMapper.php +++ b/packages/NodeTypeResolver/src/StaticTypeMapper.php @@ -244,7 +244,7 @@ final class StaticTypeMapper if ($phpStanType instanceof UnionType) { // match array types $arrayNode = $this->matchArrayTypes($phpStanType); - if ($arrayNode) { + if ($arrayNode !== null) { return $arrayNode; } @@ -443,6 +443,7 @@ final class StaticTypeMapper if ($node instanceof Name) { $name = $node->toString(); + if (ClassExistenceStaticHelper::doesClassLikeExist($name)) { return new FullyQualifiedObjectType($node->toString()); } @@ -628,6 +629,44 @@ final class StaticTypeMapper 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 +706,6 @@ final class StaticTypeMapper 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/NodeTypeResolver/src/Type/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php b/packages/NodeTypeResolver/src/Type/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php index eae67b3eeda..e08ea040d7b 100644 --- a/packages/NodeTypeResolver/src/Type/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php +++ b/packages/NodeTypeResolver/src/Type/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php @@ -34,7 +34,7 @@ final class KernelGetContainerAfterBootReturnTypeExtension implements DynamicMet ): Type { $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - if ($this->isCalledAfterBoot($scope, $methodCall) === false) { + if (! $this->isCalledAfterBoot($scope, $methodCall)) { return $returnType; } diff --git a/packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php b/packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php index 7ceb49a0131..8814c1ab337 100644 --- a/packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php +++ b/packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php @@ -13,7 +13,6 @@ use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use Psr\Container\ContainerInterface; -use Rector\Exception\ShouldNotHappenException; final class StaticContainerGetDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { @@ -32,7 +31,8 @@ final class StaticContainerGetDynamicMethodReturnTypeExtension implements Dynami MethodCall $methodCall, Scope $scope ): Type { - $valueType = $scope->getType($methodCall->args[0]->value); + $value = $methodCall->args[0]->value; + $valueType = $scope->getType($value); // we don't know what it is if ($valueType instanceof MixedType) { @@ -43,10 +43,7 @@ final class StaticContainerGetDynamicMethodReturnTypeExtension implements Dynami return new ObjectType($valueType->getValue()); } - throw new ShouldNotHappenException(sprintf( - '%s type given, only "%s" is supported', - get_class($valueType), - ConstantStringType::class - )); + // unknown, probably variable + return new MixedType(); } } diff --git a/packages/PHPStan/src/Type/FullyQualifiedObjectType.php b/packages/PHPStan/src/Type/FullyQualifiedObjectType.php index fed6b032577..83d7582b0b2 100644 --- a/packages/PHPStan/src/Type/FullyQualifiedObjectType.php +++ b/packages/PHPStan/src/Type/FullyQualifiedObjectType.php @@ -9,6 +9,7 @@ use PhpParser\Node\Name; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\UseUse; use PHPStan\Type\ObjectType; +use Rector\NodeTypeResolver\Node\AttributeKey; final class FullyQualifiedObjectType extends ObjectType { @@ -33,19 +34,28 @@ final class FullyQualifiedObjectType extends ObjectType public function getShortNameNode(): Name { - return new Name($this->getShortName()); + $name = new Name($this->getShortName()); + $name->setAttribute('virtual_node', true); + + return $name; } public function getUseNode(): Use_ { - $useUse = new UseUse(new Name($this->getClassName())); + $name = new Name($this->getClassName()); + $useUse = new UseUse($name); + + $name->setAttribute(AttributeKey::PARENT_NODE, $useUse); return new Use_([$useUse]); } public function getFunctionUseNode(): Use_ { - $useUse = new UseUse(new Name($this->getClassName()), null, Use_::TYPE_FUNCTION); + $name = new Name($this->getClassName()); + $useUse = new UseUse($name, null, Use_::TYPE_FUNCTION); + + $name->setAttribute(AttributeKey::PARENT_NODE, $useUse); return new Use_([$useUse]); } diff --git a/packages/PHPStan/src/TypeFactoryStaticHelper.php b/packages/PHPStan/src/TypeFactoryStaticHelper.php index 125134ad4ca..bfd9d3bd56c 100644 --- a/packages/PHPStan/src/TypeFactoryStaticHelper.php +++ b/packages/PHPStan/src/TypeFactoryStaticHelper.php @@ -19,11 +19,7 @@ final class TypeFactoryStaticHelper { $objectTypes = []; foreach ($types as $type) { - if ($type instanceof Type) { - $objectTypes[] = $type; - } else { - $objectTypes[] = new ObjectType($type); - } + $objectTypes[] = $type instanceof Type ? $type : new ObjectType($type); } // this is needed to prevent missing broker static fatal error, for tests with missing class diff --git a/packages/PHPUnit/src/Composer/ComposerAutoloadedDirectoryProvider.php b/packages/PHPUnit/src/Composer/ComposerAutoloadedDirectoryProvider.php index 0634aba5b6b..5243047f350 100644 --- a/packages/PHPUnit/src/Composer/ComposerAutoloadedDirectoryProvider.php +++ b/packages/PHPUnit/src/Composer/ComposerAutoloadedDirectoryProvider.php @@ -49,6 +49,20 @@ final class ComposerAutoloadedDirectoryProvider 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 @@ final class ComposerAutoloadedDirectoryProvider 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 d8135237eb5..06fa1e311d3 100644 --- a/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php +++ b/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php @@ -119,6 +119,25 @@ PHP return $node; } + private function shouldSkipClassMethod(ClassMethod $classMethod): bool + { + if (! $this->isInTestClass($classMethod)) { + return true; + } + + if (! $this->isTestClassMethod($classMethod)) { + return true; + } + + if ($classMethod->getDocComment() !== null) { + $text = $classMethod->getDocComment(); + if (Strings::match($text->getText(), '#@expectedException\b#')) { + return true; + } + } + return $this->containsAssertCall($classMethod); + } + private function addDoesNotPerformAssertion(ClassMethod $classMethod): void { // A. create new doc @@ -140,30 +159,6 @@ PHP $this->docBlockManipulator->updateNodeWithPhpDocInfo($classMethod, $phpDocInfo); } - private function shouldSkipClassMethod(ClassMethod $classMethod): bool - { - if (! $this->isInTestClass($classMethod)) { - return true; - } - - if (! $this->isTestClassMethod($classMethod)) { - return true; - } - - if ($classMethod->getDocComment()) { - $text = $classMethod->getDocComment(); - if (Strings::match($text->getText(), '#@expectedException\b#')) { - return true; - } - } - - if ($this->containsAssertCall($classMethod)) { - return true; - } - - return false; - } - private function containsAssertCall(ClassMethod $classMethod): bool { $cacheHash = md5($this->print($classMethod)); @@ -185,31 +180,42 @@ PHP 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]; - } - - $smartFileInfo = new SmartFileInfo($fileName); - $examinedMethodNodes = $this->fileInfoParser->parseFileInfoToNodesAndDecorate($smartFileInfo); - - /** @var ClassMethod|null $examinedClassMethod */ - $examinedClassMethod = $this->betterNodeFinder->findFirst( - $examinedMethodNodes, - function (Node $node) use ($methodName): bool { - if (! $node instanceof ClassMethod) { - return false; - } - - return $this->isName($node, $methodName); + return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool { + if (! $node instanceof MethodCall && ! $node instanceof StaticCall) { + return false; } - ); - $this->analyzedMethodsInFileName[$fileName][$methodName] = $examinedClassMethod; + return $this->isNames($node->name, ['assert*', 'expectException*', 'setExpectedException*']); + }); + } - 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 !== null) { + return $this->containsAssertCall($classMethod); + } + + return false; + }); } /** @@ -219,12 +225,12 @@ PHP { if ($node instanceof MethodCall) { $classMethod = $this->parsedNodesByType->findClassMethodByMethodCall($node); - if ($classMethod) { + if ($classMethod !== null) { return $classMethod; } } elseif ($node instanceof StaticCall) { $classMethod = $this->parsedNodesByType->findClassMethodByStaticCall($node); - if ($classMethod) { + if ($classMethod !== null) { return $classMethod; } } @@ -267,50 +273,30 @@ PHP return $this->findClassMethodInFile($fileName, $methodName); } - private function hasDirectAssertCall(ClassMethod $classMethod): bool + private function findClassMethodInFile(string $fileName, string $methodName): ?ClassMethod { - return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool { - if (! $node instanceof MethodCall && ! $node instanceof StaticCall) { - return false; + // skip already anayzed method to prevent cycling + if (isset($this->analyzedMethodsInFileName[$fileName][$methodName])) { + return $this->analyzedMethodsInFileName[$fileName][$methodName]; + } + + $smartFileInfo = new SmartFileInfo($fileName); + $examinedMethodNodes = $this->fileInfoParser->parseFileInfoToNodesAndDecorate($smartFileInfo); + + /** @var ClassMethod|null $examinedClassMethod */ + $examinedClassMethod = $this->betterNodeFinder->findFirst( + $examinedMethodNodes, + function (Node $node) use ($methodName): bool { + if (! $node instanceof ClassMethod) { + return false; + } + + return $this->isName($node, $methodName); } + ); - if ($this->isName($node->name, 'assert*')) { - return true; - } + $this->analyzedMethodsInFileName[$fileName][$methodName] = $examinedClassMethod; - // expectException(...) - if ($this->isName($node->name, 'expectException*')) { - return true; - } - - return false; - }); - } - - 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; - }); + return $examinedClassMethod; } } diff --git a/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php b/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php index ddf9910917e..6ff63847bcb 100644 --- a/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php +++ b/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php @@ -98,6 +98,39 @@ PHP return $node; } + private function shouldSkipClass(Class_ $class): bool + { + if ($class->isAnonymous()) { + return true; + } + + $className = $this->getName($class); + if ($className === null) { + return true; + } + + // is a test case + if (Strings::endsWith($className, 'Test')) { + return true; + } + + // is the @see annotation already added + if ($class->getDocComment() !== null) { + /** @var string $docCommentText */ + $docCommentText = $class->getDocComment()->getText(); + + /** @var string $shortClassName */ + $shortClassName = Strings::after($className, '\\', -1); + $seeClassPattern = '#@see (.*?)' . preg_quote($shortClassName, '#') . 'Test#m'; + + if (Strings::match($docCommentText, $seeClassPattern)) { + return true; + } + } + + return false; + } + private function resolveTestCaseClassName(string $className): ?string { if (class_exists($className . 'Test')) { @@ -122,45 +155,12 @@ PHP return new PhpDocTagNode('@see', new AttributeAwareGenericTagValueNode('\\' . $className)); } - private function shouldSkipClass(Class_ $class): bool - { - if ($class->isAnonymous()) { - return true; - } - - $className = $this->getName($class); - if ($className === null) { - return true; - } - - // is a test case - if (Strings::endsWith($className, 'Test')) { - return true; - } - - // is the @see annotation already added - if ($class->getDocComment()) { - /** @var string $docCommentText */ - $docCommentText = $class->getDocComment()->getText(); - - /** @var string $shortClassName */ - $shortClassName = Strings::after($className, '\\', -1); - $seeClassPattern = '#@see (.*?)' . preg_quote($shortClassName, '#') . 'Test#m'; - - if (Strings::match($docCommentText, $seeClassPattern)) { - return true; - } - } - - return false; - } - /** * @return string[] */ private function getPhpUnitTestCaseClasses(): array { - if ($this->phpUnitTestCaseClasses) { + if ($this->phpUnitTestCaseClasses !== []) { return $this->phpUnitTestCaseClasses; } diff --git a/packages/PHPUnit/src/Rector/Class_/ArrayArgumentInTestToDataProviderRector.php b/packages/PHPUnit/src/Rector/Class_/ArrayArgumentInTestToDataProviderRector.php index 57fc10ce35b..bd91ddeae8d 100644 --- a/packages/PHPUnit/src/Rector/Class_/ArrayArgumentInTestToDataProviderRector.php +++ b/packages/PHPUnit/src/Rector/Class_/ArrayArgumentInTestToDataProviderRector.php @@ -207,29 +207,32 @@ PHP return $node; } - private function createDataProviderTagNode(string $dataProviderMethodName): PhpDocTagNode + /** + * @param mixed[] $configuration + */ + private function ensureConfigurationIsSet(array $configuration): void { - return new PhpDocTagNode('@dataProvider', new GenericTagValueNode($dataProviderMethodName . '()')); - } - - private function createParamTagNode(string $name, TypeNode $typeNode): PhpDocTagNode - { - return new PhpDocTagNode('@param', new ParamTagValueNode($typeNode, false, '$' . $name, '')); - } - - private function resolveUniqueArrayStaticTypes(Array_ $array): Type - { - $itemStaticTypes = []; - foreach ($array->items as $arrayItem) { - $arrayItemStaticType = $this->getStaticType($arrayItem->value); - if ($arrayItemStaticType instanceof MixedType) { - continue; - } - - $itemStaticTypes[] = new ArrayType(new MixedType(), $arrayItemStaticType); + if ($configuration !== []) { + return; } - return $this->typeFactory->createMixedPassedOrUnionType($itemStaticTypes); + throw new ShouldNotHappenException(sprintf( + 'Add configuration via "%s" argument for "%s"', + '$configuration', + self::class + )); + } + + /** + * @param string[] $singleConfiguration + */ + private function isMethodCallMatch(MethodCall $methodCall, array $singleConfiguration): bool + { + if (! $this->isObjectType($methodCall->var, $singleConfiguration['class'])) { + return false; + } + + return $this->isName($methodCall->name, $singleConfiguration['old_method']); } private function createDataProviderMethodName(Node $node): string @@ -240,20 +243,18 @@ PHP 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; } /** @@ -270,7 +271,7 @@ PHP $itemsStaticType = $this->resolveItemStaticType($array, $isNestedArray); - if ($isNestedArray === false) { + if (! $isNestedArray) { foreach ($array->items as $arrayItem) { $variable = new Variable($variableName . ($i === 1 ? '' : $i)); @@ -298,83 +299,6 @@ PHP return $paramAndArgs; } - /** - * @param ParamAndArgValueObject[] $paramAndArgs - * @return Param[] - */ - private function createParams(array $paramAndArgs): array - { - $params = []; - foreach ($paramAndArgs as $paramAndArg) { - $param = new Param($paramAndArg->getVariable()); - - $staticType = $paramAndArg->getType(); - - if ($staticType !== null && ! $staticType instanceof UnionType) { - $phpNodeType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($staticType); - if ($phpNodeType !== null) { - $param->type = $phpNodeType; - } - } - - $params[] = $param; - } - - return $params; - } - - private function resolveItemStaticType(Array_ $array, bool $isNestedArray): Type - { - $staticTypes = []; - if ($isNestedArray === false) { - foreach ($array->items as $arrayItem) { - $arrayItemStaticType = $this->getStaticType($arrayItem->value); - if ($arrayItemStaticType) { - $staticTypes[] = $arrayItemStaticType; - } - } - } - - return $this->typeFactory->createMixedPassedOrUnionType($staticTypes); - } - - private function isNestedArray(Array_ $array): bool - { - foreach ($array->items as $arrayItem) { - if ($arrayItem->value instanceof Array_) { - return true; - } - } - - return false; - } - - /** - * @param string[] $singleConfiguration - */ - private function isMethodCallMatch(MethodCall $methodCall, array $singleConfiguration): bool - { - if (! $this->isObjectType($methodCall->var, $singleConfiguration['class'])) { - return false; - } - - return $this->isName($methodCall->name, $singleConfiguration['old_method']); - } - - private function resolveUniqueArrayStaticType(Array_ $array): Type - { - $isNestedArray = $this->isNestedArray($array); - - $uniqueArrayStaticType = $this->resolveUniqueArrayStaticTypes($array); - - if ($isNestedArray && $uniqueArrayStaticType instanceof ArrayType) { - // unwrap one level up - return $uniqueArrayStaticType->getItemType(); - } - - return $uniqueArrayStaticType; - } - /** * @param ParamAndArgValueObject[] $paramAndArgs */ @@ -400,19 +324,95 @@ PHP } } - /** - * @param mixed[] $configuration - */ - private function ensureConfigurationIsSet(array $configuration): void + private function createDataProviderTagNode(string $dataProviderMethodName): PhpDocTagNode { - if ($configuration !== []) { - return; + return new PhpDocTagNode('@dataProvider', new GenericTagValueNode($dataProviderMethodName . '()')); + } + + /** + * @return ClassMethod[] + */ + private function createDataProviderClassMethodsFromRecipes(): array + { + $dataProviderClassMethods = []; + + foreach ($this->dataProviderClassMethodRecipes as $dataProviderClassMethodRecipe) { + $dataProviderClassMethods[] = $this->dataProviderClassMethodFactory->createFromRecipe( + $dataProviderClassMethodRecipe + ); } - throw new ShouldNotHappenException(sprintf( - 'Add configuration via "%s" argument for "%s"', - '$configuration', - self::class - )); + return $dataProviderClassMethods; + } + + private function isNestedArray(Array_ $array): bool + { + foreach ($array->items as $arrayItem) { + if ($arrayItem->value instanceof Array_) { + return true; + } + } + + return false; + } + + private function resolveUniqueArrayStaticTypes(Array_ $array): Type + { + $itemStaticTypes = []; + foreach ($array->items as $arrayItem) { + $arrayItemStaticType = $this->getStaticType($arrayItem->value); + if ($arrayItemStaticType instanceof MixedType) { + continue; + } + + $itemStaticTypes[] = new ArrayType(new MixedType(), $arrayItemStaticType); + } + + return $this->typeFactory->createMixedPassedOrUnionType($itemStaticTypes); + } + + private function resolveItemStaticType(Array_ $array, bool $isNestedArray): Type + { + $staticTypes = []; + if (! $isNestedArray) { + foreach ($array->items as $arrayItem) { + $arrayItemStaticType = $this->getStaticType($arrayItem->value); + if ($arrayItemStaticType) { + $staticTypes[] = $arrayItemStaticType; + } + } + } + + return $this->typeFactory->createMixedPassedOrUnionType($staticTypes); + } + + /** + * @param ParamAndArgValueObject[] $paramAndArgs + * @return Param[] + */ + private function createParams(array $paramAndArgs): array + { + $params = []; + foreach ($paramAndArgs as $paramAndArg) { + $param = new Param($paramAndArg->getVariable()); + + $staticType = $paramAndArg->getType(); + + if ($staticType !== null && ! $staticType instanceof UnionType) { + $phpNodeType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($staticType); + if ($phpNodeType !== null) { + $param->type = $phpNodeType; + } + } + + $params[] = $param; + } + + return $params; + } + + private function createParamTagNode(string $name, TypeNode $typeNode): PhpDocTagNode + { + 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 172890de705..2c92cefa353 100644 --- a/packages/PHPUnit/src/Rector/MethodCall/ReplaceAssertArraySubsetRector.php +++ b/packages/PHPUnit/src/Rector/MethodCall/ReplaceAssertArraySubsetRector.php @@ -124,6 +124,37 @@ PHP $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 @@ PHP $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 a030c0fd961..412f26478ea 100644 --- a/packages/PHPUnit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php +++ b/packages/PHPUnit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Rector\PHPUnit\Rector\MethodCall; use PhpParser\Node; +use PhpParser\Node\Arg; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Scalar\String_; @@ -140,29 +141,24 @@ PHP // split into chunks of X parameters $valueChunks = array_chunk($values, $numberOfParameters); foreach ($valueChunks as $valueChunk) { - $node->args[] = new Node\Arg($this->createArray($valueChunk)); + $node->args[] = new Arg($this->createArray($valueChunk)); } 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')) { + foreach ($methodCall->args as $arg) { + $argumentStaticType = $this->getStaticType($arg->value); + if ($argumentStaticType instanceof ArrayType) { continue; } - $firstArgumentValue = $previousMethodCall->args[0]->value; - if (! $firstArgumentValue instanceof String_) { - continue; - } - - return $firstArgumentValue->value; + return false; } - throw new ShouldNotHappenException(); + return true; } private function inferMockedClassName(MethodCall $methodCall): ?string @@ -193,6 +189,25 @@ PHP return null; } + private function inferMockedMethodName(MethodCall $methodCall): string + { + $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; + } + + throw new ShouldNotHappenException(); + } + private function findRootVariableOfChainCall(MethodCall $methodCall): ?Variable { $currentMethodCallee = $methodCall->var; @@ -202,18 +217,4 @@ PHP return $currentMethodCallee; } - - private function areAllArgArrayTypes(MethodCall $methodCall): bool - { - foreach ($methodCall->args as $arg) { - $argumentStaticType = $this->getStaticType($arg->value); - if ($argumentStaticType instanceof ArrayType) { - continue; - } - - return false; - } - - return true; - } } diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_expected_exception.php.inc b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_expected_exception.php.inc index d3187cd32af..983e6ea7db6 100644 --- a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_expected_exception.php.inc +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_expected_exception.php.inc @@ -18,4 +18,9 @@ class KeepExpectedException extends \PHPUnit\Framework\TestCase $this->expectException('Throwable'); throw new InvalidArgumentException(); } + + public function testSetExpectedException() + { + $this->setExpectedException('Throwable'); + } } diff --git a/packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/Fixture/skip_already_array.php.inc b/packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/Fixture/skip_already_array.php.inc index cca5f83f6e4..9fcc73dd5ab 100644 --- a/packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/Fixture/skip_already_array.php.inc +++ b/packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/Fixture/skip_already_array.php.inc @@ -3,7 +3,7 @@ namespace Rector\PHPUnit\Tests\Rector\MethodCall\WithConsecutiveArgToArrayRector\Fixture; use PHPUnit\Framework\TestCase; -use Rector\PHPUnit\Tests\Rector\MethodCall\WithConsecutiveArgToArrayRector\ClassWithMethodOfTwoArguments; +use Rector\PHPUnit\Tests\Rector\MethodCall\WithConsecutiveArgToArrayRector\Source\ClassWithMethodOfTwoArguments; class SkipAlreadyArrayTestCase extends TestCase { diff --git a/packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/ClassWithMethodOfTwoArguments.php b/packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/Source/ClassWithMethodOfTwoArguments.php similarity index 79% rename from packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/ClassWithMethodOfTwoArguments.php rename to packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/Source/ClassWithMethodOfTwoArguments.php index a13786063d5..083d6251185 100644 --- a/packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/ClassWithMethodOfTwoArguments.php +++ b/packages/PHPUnit/tests/Rector/MethodCall/WithConsecutiveArgToArrayRector/Source/ClassWithMethodOfTwoArguments.php @@ -2,11 +2,12 @@ declare(strict_types=1); -namespace Rector\PHPUnit\Tests\Rector\MethodCall\WithConsecutiveArgToArrayRector; +namespace Rector\PHPUnit\Tests\Rector\MethodCall\WithConsecutiveArgToArrayRector\Source; final class ClassWithMethodOfTwoArguments { public function go(int $one, string $two): void { + $three = $one + $two; } } diff --git a/packages/PSR4/src/Composer/PSR4AutoloadPathsProvider.php b/packages/PSR4/src/Composer/PSR4AutoloadPathsProvider.php index 110ccec829a..6c760cfdb04 100644 --- a/packages/PSR4/src/Composer/PSR4AutoloadPathsProvider.php +++ b/packages/PSR4/src/Composer/PSR4AutoloadPathsProvider.php @@ -34,12 +34,6 @@ final class PSR4AutoloadPathsProvider 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 @@ final class PSR4AutoloadPathsProvider 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 263135a89fe..ca62bc14d46 100644 --- a/packages/PSR4/src/Extension/RenamedClassesReportExtension.php +++ b/packages/PSR4/src/Extension/RenamedClassesReportExtension.php @@ -65,24 +65,6 @@ final class RenamedClassesReportExtension implements ReportingExtensionInterface ); } - /** - * @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 @@ final class RenamedClassesReportExtension implements ReportingExtensionInterface 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/FileRelocationResolver.php b/packages/PSR4/src/FileRelocationResolver.php index b9e98e3d312..427c0cf058d 100644 --- a/packages/PSR4/src/FileRelocationResolver.php +++ b/packages/PSR4/src/FileRelocationResolver.php @@ -111,14 +111,14 @@ final class FileRelocationResolver $removedParts[] = $reversedNamePart; } - if ($hasStopped === false) { + if (! $hasStopped) { $rootNameParts = $nameParts; $rootNameParts[] = $suffixName; } else { $rootNameParts = array_reverse($reversedNameParts); $rootNameParts[] = $suffixName; - if ($removedParts) { + if ($removedParts !== []) { $rootNameParts = array_merge($rootNameParts, $removedParts); } } diff --git a/packages/PSR4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php b/packages/PSR4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php index c8da00ddfb3..dc461748093 100644 --- a/packages/PSR4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php +++ b/packages/PSR4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php @@ -94,7 +94,7 @@ final class NormalizeNamespaceByPSR4ComposerAutoloadRector extends AbstractRecto $newUseImports = array_unique($newUseImports); - if ($newUseImports) { + if ($newUseImports !== []) { $useImports = $this->createUses($newUseImports); $node->stmts = array_merge($useImports, $node->stmts); } @@ -128,17 +128,6 @@ final class NormalizeNamespaceByPSR4ComposerAutoloadRector extends AbstractRecto 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 @@ final class NormalizeNamespaceByPSR4ComposerAutoloadRector extends AbstractRecto 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 6b0e5f45ecf..40d4159cff6 100644 --- a/packages/Php56/src/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector.php +++ b/packages/Php56/src/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector.php @@ -144,17 +144,26 @@ PHP 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 @@ -188,32 +197,19 @@ PHP } $variableName = $this->getName($variable); - if ($variableName === null) { - return true; + return $variableName === null; + } + + private function isStaticVariable(Node $parentNode): bool + { + // definition of static variable + if ($parentNode instanceof StaticVar) { + $parentParentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE); + if ($parentParentNode instanceof Static_) { + return true; + } } return false; } - - private function collectDefinedVariablesFromForeach(Node $node): void - { - if (! $node instanceof Foreach_) { - return; - } - - $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; - } - }); - } } diff --git a/packages/Php70/src/EregToPcreTransformer.php b/packages/Php70/src/EregToPcreTransformer.php index 0b7393b2011..5dd6c97fab3 100644 --- a/packages/Php70/src/EregToPcreTransformer.php +++ b/packages/Php70/src/EregToPcreTransformer.php @@ -46,10 +46,8 @@ final class EregToPcreTransformer if (isset($icache[$s])) { return $icache[$s]; } - } else { - if (isset($cache[$s])) { - return $cache[$s]; - } + } elseif (isset($cache[$s])) { + return $cache[$s]; } [$r, $i] = $this->_ere2pcre($s, 0); if ($i !== strlen($s)) { diff --git a/packages/Php71/src/Rector/FuncCall/RemoveExtraParametersRector.php b/packages/Php71/src/Rector/FuncCall/RemoveExtraParametersRector.php index 5ecf2b4cb4e..fc4dc387840 100644 --- a/packages/Php71/src/Rector/FuncCall/RemoveExtraParametersRector.php +++ b/packages/Php71/src/Rector/FuncCall/RemoveExtraParametersRector.php @@ -99,12 +99,7 @@ final class RemoveExtraParametersRector extends AbstractRector if ($this->callManipulator->isVariadic($reflectionFunctionLike, $node)) { return true; } - - if ($reflectionFunctionLike->getNumberOfParameters() >= count($node->args)) { - return true; - } - - return false; + return $reflectionFunctionLike->getNumberOfParameters() >= count($node->args); } /** diff --git a/packages/Php73/src/Rector/FuncCall/ArrayKeyFirstLastRector.php b/packages/Php73/src/Rector/FuncCall/ArrayKeyFirstLastRector.php index c3001a71bf3..acbee72a069 100644 --- a/packages/Php73/src/Rector/FuncCall/ArrayKeyFirstLastRector.php +++ b/packages/Php73/src/Rector/FuncCall/ArrayKeyFirstLastRector.php @@ -128,11 +128,6 @@ PHP if (function_exists(self::ARRAY_KEY_FIRST) && function_exists(self::ARRAY_KEY_LAST)) { return false; } - - if ($this->isNames($funcCall, ['reset', 'end'])) { - return true; - } - - return false; + return $this->isNames($funcCall, ['reset', 'end']); } } diff --git a/packages/Php74/src/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php b/packages/Php74/src/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php index f1ffd95fa08..625e5713ba5 100644 --- a/packages/Php74/src/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php +++ b/packages/Php74/src/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php @@ -87,9 +87,51 @@ PHP return null; } - private function isIteratorToArrayFuncCall(Expr $expr): bool + private function refactorArray(FuncCall $funcCall): ?Array_ { - return $expr instanceof FuncCall && $this->isName($expr, 'iterator_to_array'); + $array = new Array_(); + + foreach ($funcCall->args as $arg) { + $value = $arg->value; + + if ($this->shouldSkipArrayForInvalidTypeOrKeys($value)) { + return null; + } + + $value = $this->resolveValue($value); + + $array->items[] = $this->createUnpackedArrayItem($value); + } + + return $array; + } + + private function refactorIteratorToArray(FuncCall $funcCall): Array_ + { + $array = new Array_(); + $array->items[] = $this->createUnpackedArrayItem($funcCall->args[0]->value); + + return $array; + } + + private function shouldSkipArrayForInvalidTypeOrKeys(Expr $expr): bool + { + // we have no idea what it is → cannot change it + if (! $this->isArrayType($expr)) { + return true; + } + + $arrayStaticType = $this->getStaticType($expr); + if ($arrayStaticType instanceof ConstantArrayType) { + foreach ($arrayStaticType->getKeyTypes() as $keyType) { + // key cannot be string + if ($keyType instanceof ConstantStringType) { + return true; + } + } + } + + return false; } private function resolveValue(Expr $expr): Expr @@ -120,55 +162,13 @@ PHP return $expr; } - private function refactorArray(FuncCall $funcCall): ?Array_ - { - $array = new Array_(); - - foreach ($funcCall->args as $arg) { - $value = $arg->value; - - if ($this->shouldSkipArrayForInvalidTypeOrKeys($value)) { - return null; - } - - $value = $this->resolveValue($value); - - $array->items[] = $this->createUnpackedArrayItem($value); - } - - return $array; - } - - private function refactorIteratorToArray(FuncCall $funcCall): Array_ - { - $array = new Array_(); - $array->items[] = $this->createUnpackedArrayItem($funcCall->args[0]->value); - - return $array; - } - private function createUnpackedArrayItem(Expr $expr): ArrayItem { return new ArrayItem($expr, null, false, [], true); } - private function shouldSkipArrayForInvalidTypeOrKeys(Expr $expr): bool + private function isIteratorToArrayFuncCall(Expr $expr): bool { - // we have no idea what it is → cannot change it - if (! $this->isArrayType($expr)) { - return true; - } - - $arrayStaticType = $this->getStaticType($expr); - if ($arrayStaticType instanceof ConstantArrayType) { - foreach ($arrayStaticType->getKeyTypes() as $keyType) { - // key cannot be string - if ($keyType instanceof ConstantStringType) { - return true; - } - } - } - - return false; + 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 ee149f86b38..b443555f121 100644 --- a/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php +++ b/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php @@ -93,24 +93,6 @@ PHP 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 */ @@ -130,12 +112,25 @@ PHP if (Strings::match($numericValueAsString, '#e#i')) { return true; } - // too short - if (Strings::length($numericValueAsString) <= self::GROUP_SIZE) { - return true; + return Strings::length($numericValueAsString) <= self::GROUP_SIZE; + } + + /** + * @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 false; + return $chunks; } } diff --git a/packages/Php74/src/Rector/MethodCall/ChangeReflectionTypeToStringToGetNameRector.php b/packages/Php74/src/Rector/MethodCall/ChangeReflectionTypeToStringToGetNameRector.php index 4f961ffed6a..d4c87e2fb60 100644 --- a/packages/Php74/src/Rector/MethodCall/ChangeReflectionTypeToStringToGetNameRector.php +++ b/packages/Php74/src/Rector/MethodCall/ChangeReflectionTypeToStringToGetNameRector.php @@ -108,6 +108,80 @@ PHP return null; } + private function refactorMethodCall(MethodCall $methodCall): ?Node + { + $this->collectCallByVariable($methodCall); + + if ($this->shouldSkipMethodCall($methodCall)) { + return null; + } + + if ($this->isReflectionParameterGetTypeMethodCall($methodCall)) { + return $this->refactorReflectionParameterGetName($methodCall); + } + + if ($this->isReflectionFunctionAbstractGetReturnTypeMethodCall($methodCall)) { + return $this->refactorReflectionFunctionGetReturnType($methodCall); + } + + return null; + } + + private function refactorIfHasReturnTypeWasCalled(MethodCall $methodCall): ?Node + { + if (! $methodCall->var instanceof Variable) { + return null; + } + + $variableName = $this->getName($methodCall->var); + + $callsByVariable = $this->callsByVariable[$variableName] ?? []; + + // we already know it has return type + if (in_array('hasReturnType', $callsByVariable, true)) { + return $this->createMethodCall($methodCall, 'getName'); + } + + return null; + } + + 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); + + if ($variableName && $methodName) { + $this->callsByVariable[$variableName][] = $methodName; + } + } + } + + private function shouldSkipMethodCall(MethodCall $methodCall): bool + { + // just added node → skip it + if ($methodCall->getAttribute(AttributeKey::SCOPE) === null) { + return true; + } + + // is to string retype? + $parentNode = $methodCall->getAttribute(AttributeKey::PARENT_NODE); + if ($parentNode instanceof String_) { + return false; + } + + if ($parentNode instanceof Concat) { + return false; + } + + // probably already converted + if ($parentNode instanceof Ternary) { + return true; + } + return $parentNode instanceof Return_; + } + private function isReflectionParameterGetTypeMethodCall(MethodCall $methodCall): bool { if (! $this->isObjectType($methodCall->var, ReflectionParameter::class)) { @@ -140,7 +214,7 @@ PHP private function refactorReflectionFunctionGetReturnType(MethodCall $methodCall): Node { $refactoredMethodCall = $this->refactorIfHasReturnTypeWasCalled($methodCall); - if ($refactoredMethodCall) { + if ($refactoredMethodCall !== null) { return $refactoredMethodCall; } @@ -152,83 +226,4 @@ PHP return $ternary; } - - private function shouldSkipMethodCall(MethodCall $methodCall): bool - { - // just added node → skip it - if ($methodCall->getAttribute(AttributeKey::SCOPE) === null) { - return true; - } - - // is to string retype? - $parentNode = $methodCall->getAttribute(AttributeKey::PARENT_NODE); - if ($parentNode instanceof String_) { - return false; - } - - if ($parentNode instanceof Concat) { - return false; - } - - // probably already converted - if ($parentNode instanceof Ternary) { - return true; - } - - if ($parentNode instanceof Return_) { - return true; - } - - return false; - } - - 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); - - if ($variableName && $methodName) { - $this->callsByVariable[$variableName][] = $methodName; - } - } - } - - private function refactorIfHasReturnTypeWasCalled(MethodCall $methodCall): ?Node - { - if (! $methodCall->var instanceof Variable) { - return null; - } - - $variableName = $this->getName($methodCall->var); - - $callsByVariable = $this->callsByVariable[$variableName] ?? []; - - // we already know it has return type - if (in_array('hasReturnType', $callsByVariable, true)) { - return $this->createMethodCall($methodCall, 'getName'); - } - - return null; - } - - private function refactorMethodCall(MethodCall $methodCall): ?Node - { - $this->collectCallByVariable($methodCall); - - if ($this->shouldSkipMethodCall($methodCall)) { - return null; - } - - if ($this->isReflectionParameterGetTypeMethodCall($methodCall)) { - return $this->refactorReflectionParameterGetName($methodCall); - } - - if ($this->isReflectionFunctionAbstractGetReturnTypeMethodCall($methodCall)) { - return $this->refactorReflectionFunctionGetReturnType($methodCall); - } - - return null; - } } diff --git a/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecMocksToPHPUnitMocksRector.php b/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecMocksToPHPUnitMocksRector.php index f8230ec2e34..3bc858d90a2 100644 --- a/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecMocksToPHPUnitMocksRector.php +++ b/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecMocksToPHPUnitMocksRector.php @@ -79,53 +79,6 @@ final class PhpSpecMocksToPHPUnitMocksRector extends AbstractPhpSpecToPHPUnitRec 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 @@ final class PhpSpecMocksToPHPUnitMocksRector extends AbstractPhpSpecToPHPUnitRec 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 @@ final class PhpSpecMocksToPHPUnitMocksRector extends AbstractPhpSpecToPHPUnitRec 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 7dd47b7a7d0..8fec10cf88d 100644 --- a/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php +++ b/packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php @@ -204,56 +204,56 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni return $node; } - private function thisToTestedObjectPropertyFetch(Expr $expr): Expr + private function processDuring(MethodCall $methodCall): MethodCall { - if (! $expr instanceof Variable) { - return $expr; + if (! isset($methodCall->args[0])) { + throw new ShouldNotHappenException(); } - if (! $this->isName($expr, 'this')) { - return $expr; + $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); + } } - return $this->testedObjectPropertyFetch; + /** @var MethodCall $parentMethodCall */ + $parentMethodCall = $methodCall->var; + $parentMethodCall->name = new Identifier('expectException'); + + // add $this->object->someCall($withArgs) + $this->addNodeAfterNode($thisObjectPropertyMethodCall, $methodCall); + + return $parentMethodCall; } - private function createAssertMethod(string $name, Expr $value, ?Expr $expected): MethodCall + private function processDuringInstantiation(MethodCall $methodCall): MethodCall { - $this->isBoolAssert = false; + /** @var MethodCall $parentMethodCall */ + $parentMethodCall = $methodCall->var; + $parentMethodCall->name = new Identifier('expectException'); - // 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; + 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 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni 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 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni } } + 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 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni 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); - - $this->isPrepared = true; - } - - private function processDuring(MethodCall $methodCall): MethodCall - { - if (! isset($methodCall->args[0])) { - throw new ShouldNotHappenException(); + if (! $methodCall->args[1]->value instanceof Array_) { + return; } - $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); - } + /** @var Array_ $array */ + $array = $methodCall->args[1]->value; + foreach ($array->items as $arrayItem) { + $staticCall->args[] = new Arg($arrayItem->value); } - - /** @var MethodCall $parentMethodCall */ - $parentMethodCall = $methodCall->var; - $parentMethodCall->name = new Identifier('expectException'); - - // add $this->object->someCall($withArgs) - $this->addNodeAfterNode($thisObjectPropertyMethodCall, $methodCall); - - return $parentMethodCall; } - private function processDuringInstantiation(MethodCall $methodCall): MethodCall + private function resolveBoolMethodName(string $name, Expr $expr): string { - /** @var MethodCall $parentMethodCall */ - $parentMethodCall = $methodCall->var; - $parentMethodCall->name = new Identifier('expectException'); + if (! $this->isBool($expr)) { + return $name; + } - return $parentMethodCall; + if ($name === 'assertSame') { + $this->isBoolAssert = true; + return $this->isFalse($expr) ? 'assertFalse' : 'assertTrue'; + } + + if ($name === 'assertNotSame') { + $this->isBoolAssert = true; + return $this->isFalse($expr) ? 'assertNotFalse' : 'assertNotTrue'; + } + + return $name; + } + + private function thisToTestedObjectPropertyFetch(Expr $expr): Expr + { + if (! $expr instanceof Variable) { + return $expr; + } + + 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 2acb23e7b34..4bc58a4f339 100644 --- a/packages/RemovingStatic/src/Printer/FactoryClassPrinter.php +++ b/packages/RemovingStatic/src/Printer/FactoryClassPrinter.php @@ -60,14 +60,6 @@ final class FactoryClassPrinter $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 @@ final class FactoryClassPrinter 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 cff2073b120..9a78b382d56 100644 --- a/packages/RemovingStatic/src/Rector/Class_/PHPUnitStaticToKernelTestCaseGetRector.php +++ b/packages/RemovingStatic/src/Rector/Class_/PHPUnitStaticToKernelTestCaseGetRector.php @@ -192,126 +192,6 @@ PHP return null; } - /** - * @return ObjectType[] - */ - private function collectNewProperties(Class_ $class): array - { - $this->newProperties = []; - - $this->traverseNodesWithCallable($class->stmts, function (Node $node): void { - if (! $node instanceof StaticCall) { - return; - } - - foreach ($this->staticClassTypes as $type) { - $objectType = new ObjectType($type); - if (! $this->isObjectType($node->class, $objectType)) { - continue; - } - - $this->newProperties[] = $objectType; - } - }); - - $this->newProperties = array_unique($this->newProperties); - - 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 - $propertyName = $this->propertyNaming->fqnToVariableName($objectType); - $propertyFetch = new PropertyFetch(new Variable('this'), $propertyName); - - // turn static call to method on property call - $methodCall = new MethodCall($propertyFetch, $staticCall->name); - $methodCall->args = $staticCall->args; - - 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 - */ - private function addNewPropertiesToClass(Class_ $class, array $newProperties): Class_ - { - $properties = []; - foreach ($newProperties as $objectType) { - $properties[] = $this->createPropertyFromType($objectType); - } - - // add property to the start of the class - $class->stmts = array_merge($properties, $class->stmts); - - return $class; - } - - private function createContainerGetTypeToPropertyAssign(ObjectType $objectType): Expression - { - $getMethodCall = $this->createContainerGetTypeMethodCall($objectType); - - $propertyName = $this->propertyNaming->fqnToVariableName($objectType); - $propertyFetch = new PropertyFetch(new Variable('this'), $propertyName); - - $assign = new Assign($propertyFetch, $getMethodCall); - - return new Expression($assign); - } - - private function getParentSetUpStaticCallPosition(ClassMethod $setupClassMethod): ?int - { - foreach ((array) $setupClassMethod->stmts as $position => $methodStmt) { - if ($methodStmt instanceof Expression) { - $methodStmt = $methodStmt->expr; - } - - if (! $methodStmt instanceof StaticCall) { - continue; - } - - if (! $this->isName($methodStmt->class, 'parent')) { - continue; - } - - if (! $this->isName($methodStmt->name, 'setUp')) { - continue; - } - - return $position; - } - - 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 @@ -347,17 +227,77 @@ PHP return $class; } - private function createSetUpMethod(Expression $parentSetupStaticCall, Expression $assign): ClassMethod + /** + * @return ObjectType[] + */ + private function collectNewProperties(Class_ $class): array { - $classMethodBuilder = $this->builderFactory->method('setUp'); - $classMethodBuilder->makeProtected(); - $classMethodBuilder->addStmt($parentSetupStaticCall); - $classMethodBuilder->addStmt($assign); + $this->newProperties = []; - $classMethod = $classMethodBuilder->getNode(); + $this->traverseNodesWithCallable($class->stmts, function (Node $node): void { + if (! $node instanceof StaticCall) { + return; + } - $this->phpUnitTypeDeclarationDecorator->decorate($classMethod); - return $classMethod; + foreach ($this->staticClassTypes as $type) { + $objectType = new ObjectType($type); + if (! $this->isObjectType($node->class, $objectType)) { + continue; + } + + $this->newProperties[] = $objectType; + } + }); + + $this->newProperties = array_unique($this->newProperties); + + return $this->newProperties; + } + + private function convertStaticCallToPropertyMethodCall(StaticCall $staticCall, ObjectType $objectType): MethodCall + { + // create "$this->someService" instead + $propertyName = $this->propertyNaming->fqnToVariableName($objectType); + $propertyFetch = new PropertyFetch(new Variable('this'), $propertyName); + + // turn static call to method on property call + $methodCall = new MethodCall($propertyFetch, $staticCall->name); + $methodCall->args = $staticCall->args; + + return $methodCall; + } + + /** + * @param ObjectType[] $newProperties + */ + private function addNewPropertiesToClass(Class_ $class, array $newProperties): Class_ + { + $properties = []; + foreach ($newProperties as $objectType) { + $properties[] = $this->createPropertyFromType($objectType); + } + + // add property to the start of the class + $class->stmts = array_merge($properties, $class->stmts); + + 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); + + $propertyName = $this->propertyNaming->fqnToVariableName($objectType); + $propertyFetch = new PropertyFetch(new Variable('this'), $propertyName); + + $assign = new Assign($propertyFetch, $getMethodCall); + + return new Expression($assign); } private function updateSetUpMethod( @@ -373,4 +313,64 @@ PHP 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) { + if ($methodStmt instanceof Expression) { + $methodStmt = $methodStmt->expr; + } + + if (! $methodStmt instanceof StaticCall) { + continue; + } + + if (! $this->isName($methodStmt->class, 'parent')) { + continue; + } + + if (! $this->isName($methodStmt->name, 'setUp')) { + continue; + } + + return $position; + } + + return null; + } } diff --git a/packages/RemovingStatic/src/Rector/Class_/StaticTypeToSetterInjectionRector.php b/packages/RemovingStatic/src/Rector/Class_/StaticTypeToSetterInjectionRector.php index 98dd20d963d..4eafbde8a94 100644 --- a/packages/RemovingStatic/src/Rector/Class_/StaticTypeToSetterInjectionRector.php +++ b/packages/RemovingStatic/src/Rector/Class_/StaticTypeToSetterInjectionRector.php @@ -122,15 +122,6 @@ PHP 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 @@ PHP 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 4245ffb3e84..9e561ff618a 100644 --- a/packages/RemovingStatic/src/UniqueObjectFactoryFactory.php +++ b/packages/RemovingStatic/src/UniqueObjectFactoryFactory.php @@ -93,22 +93,33 @@ final class UniqueObjectFactoryFactory return $factoryClassBuilder->getNode(); } - /** - * @param Param[] $params - * - * @return Assign[] - */ - private function createAssignsFromParams(array $params): array + private function resolveClassShortName(string $name): string { - $assigns = []; - - /** @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)); + if (Strings::contains($name, '\\')) { + return (string) Strings::after($name, '\\', -1); } - return $assigns; + return $name; + } + + /** + * @return Property[] + */ + private function createPropertiesFromTypes(ObjectType $objectType): 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; } private function createConstructMethod(ObjectType $objectType): ClassMethod @@ -166,31 +177,20 @@ final class UniqueObjectFactoryFactory } /** - * @return Property[] + * @param Param[] $params + * + * @return Assign[] */ - private function createPropertiesFromTypes(ObjectType $objectType): array + private function createAssignsFromParams(array $params): array { - $properties = []; + $assigns = []; - $propertyName = $this->propertyNaming->fqnToVariableName($objectType); - $propertyBuilder = $this->builderFactory->property($propertyName); - $propertyBuilder->makePrivate(); - - $property = $propertyBuilder->getNode(); - - $this->docBlockManipulator->changeVarTag($property, $objectType); - - $properties[] = $property; - - return $properties; - } - - 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 4f333d11b49..137ed9407f9 100644 --- a/packages/Renaming/src/Rector/Class_/RenameClassRector.php +++ b/packages/Renaming/src/Rector/Class_/RenameClassRector.php @@ -144,61 +144,55 @@ PHP } /** - * 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; + } + + if (! $this->isClassToInterfaceValidChange($name, $newName)) { + return null; + } + + $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 true; - } - - 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)); + return new FullyQualified($newName); } private function refactorNamespaceNode(Namespace_ $namespace): ?Node @@ -228,21 +222,6 @@ PHP 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 @@ PHP 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; + // ensure new is not with interface + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + if ($parentNode instanceof New_ && interface_exists($newName)) { + return false; } - $newName = $this->oldToNewClasses[$stringName] ?? null; - if (! $newName) { - return null; + if ($parentNode instanceof Class_) { + return $this->isValidClassNameChange($node, $newName, $parentNode); } - if (! $this->isClassToInterfaceValidChange($name, $newName)) { - return null; - } - - $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; - } + $foundClass = $this->betterNodeFinder->findFirst($namespace, function (Node $node): bool { + if (! $node instanceof ClassLike) { + return false; + } - if (! $this->docBlockManipulator->hasNodeTypeTags($node)) { - return; - } + $classLikeName = $this->getName($node); - foreach ($this->oldToNewClasses as $oldClass => $newClass) { - $oldClassType = new ObjectType($oldClass); - $newClassType = new FullyQualifiedObjectType($newClass); + return isset($this->oldToNewClasses[$classLikeName]); + }); - $this->docBlockManipulator->changeType($node, $oldClassType, $newClassType); - } - - $this->phpDocClassRenamer->changeTypeInAnnotationTypes($node, $this->oldToNewClasses); + return $foundClass instanceof ClassLike ? $foundClass : null; } private function ensureClassWillNotBeDuplicate(string $newName, string $oldName): void @@ -360,4 +334,30 @@ PHP $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/Renaming/tests/Rector/Class_/RenameClassRector/NamePostImportTest.php b/packages/Renaming/tests/Rector/Class_/RenameClassRector/AutoImportNamesParameterTest.php similarity index 68% rename from packages/Renaming/tests/Rector/Class_/RenameClassRector/NamePostImportTest.php rename to packages/Renaming/tests/Rector/Class_/RenameClassRector/AutoImportNamesParameterTest.php index 9af61b05d10..b3dfac270ce 100644 --- a/packages/Renaming/tests/Rector/Class_/RenameClassRector/NamePostImportTest.php +++ b/packages/Renaming/tests/Rector/Class_/RenameClassRector/AutoImportNamesParameterTest.php @@ -5,13 +5,14 @@ declare(strict_types=1); namespace Rector\Renaming\Tests\Rector\Class_\RenameClassRector; use Iterator; +use Rector\CodeQuality\Rector\BooleanAnd\SimplifyEmptyArrayCheckRector; use Rector\Configuration\Option; use Rector\Renaming\Rector\Class_\RenameClassRector; use Rector\Renaming\Tests\Rector\Class_\RenameClassRector\Source\NewClass; use Rector\Renaming\Tests\Rector\Class_\RenameClassRector\Source\OldClass; use Rector\Testing\PHPUnit\AbstractRectorTestCase; -final class NamePostImportTest extends AbstractRectorTestCase +final class AutoImportNamesParameterTest extends AbstractRectorTestCase { protected function tearDown(): void { @@ -30,7 +31,9 @@ final class NamePostImportTest extends AbstractRectorTestCase public function provideDataForTest(): Iterator { - yield [__DIR__ . '/Fixture/class_to_new_with_post_import.php.inc']; + yield [__DIR__ . '/Fixture/AutoImportNamesParameter/class_to_new_with_post_import.php.inc']; + yield [__DIR__ . '/Fixture/AutoImportNamesParameter/partial_expression.php.inc']; + yield [__DIR__ . '/Fixture/AutoImportNamesParameter/skip_closure_me.php.inc']; } /** @@ -39,6 +42,8 @@ final class NamePostImportTest extends AbstractRectorTestCase protected function getRectorsWithConfiguration(): array { return [ + # this class causes to "partial_expression.php.inc" to fail + SimplifyEmptyArrayCheckRector::class => [], RenameClassRector::class => [ '$oldToNewClasses' => [ OldClass::class => NewClass::class, diff --git a/packages/Renaming/tests/Rector/Class_/RenameClassRector/Fixture/class_to_new_with_post_import.php.inc b/packages/Renaming/tests/Rector/Class_/RenameClassRector/Fixture/AutoImportNamesParameter/class_to_new_with_post_import.php.inc similarity index 88% rename from packages/Renaming/tests/Rector/Class_/RenameClassRector/Fixture/class_to_new_with_post_import.php.inc rename to packages/Renaming/tests/Rector/Class_/RenameClassRector/Fixture/AutoImportNamesParameter/class_to_new_with_post_import.php.inc index aea9f805e40..86746b4c3dd 100644 --- a/packages/Renaming/tests/Rector/Class_/RenameClassRector/Fixture/class_to_new_with_post_import.php.inc +++ b/packages/Renaming/tests/Rector/Class_/RenameClassRector/Fixture/AutoImportNamesParameter/class_to_new_with_post_import.php.inc @@ -1,6 +1,6 @@ +----- + diff --git a/packages/Renaming/tests/Rector/Class_/RenameClassRector/Fixture/AutoImportNamesParameter/skip_closure_me.php.inc b/packages/Renaming/tests/Rector/Class_/RenameClassRector/Fixture/AutoImportNamesParameter/skip_closure_me.php.inc new file mode 100644 index 00000000000..113f7a2c7b2 --- /dev/null +++ b/packages/Renaming/tests/Rector/Class_/RenameClassRector/Fixture/AutoImportNamesParameter/skip_closure_me.php.inc @@ -0,0 +1,12 @@ +builderFactory->use($name); - if ($alias) { + if ($alias !== '') { $useBuilder->as($alias); } diff --git a/packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php b/packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php index a683903999e..5d272e829f9 100644 --- a/packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php +++ b/packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php @@ -121,15 +121,28 @@ PHP 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); if ($classNode !== null && $classNode->hasAttribute(AttributeKey::PARENT_CLASS_NAME)) { /** @var string $parentClassName */ $parentClassName = $classNode->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName) { + if ($parentClassName !== '') { $parentClassConstant = $this->parsedNodesByType->findClassConstant($parentClassName, $constant); - if ($parentClassConstant) { + if ($parentClassConstant !== null) { // Make sure the parent's constant has been refactored $this->refactor($parentClassConstant); @@ -143,34 +156,6 @@ PHP 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 @@ PHP 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); + } + } + + /** + * @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; + } } - if (! $this->isAtLeastPhpVersion('7.1')) { - return true; - } - - return count($classConst->consts) !== 1; + return $isChild; } } diff --git a/packages/SOLID/src/Rector/Class_/MakeUnusedClassesWithChildrenAbstractRector.php b/packages/SOLID/src/Rector/Class_/MakeUnusedClassesWithChildrenAbstractRector.php index 760590edb95..a660ccec458 100644 --- a/packages/SOLID/src/Rector/Class_/MakeUnusedClassesWithChildrenAbstractRector.php +++ b/packages/SOLID/src/Rector/Class_/MakeUnusedClassesWithChildrenAbstractRector.php @@ -72,17 +72,17 @@ PHP } // 1. is in static call? - if ($this->parsedNodesByType->findMethodCallsOnClass($className)) { + if ($this->parsedNodesByType->findMethodCallsOnClass($className) !== []) { return null; } // 2. is in new? - if ($this->parsedNodesByType->findNewNodesByClass($className)) { + if ($this->parsedNodesByType->findNewNodesByClass($className) !== []) { return null; } // 3. does it have any children - if (! $this->parsedNodesByType->findChildrenOfClass($className)) { + if ($this->parsedNodesByType->findChildrenOfClass($className) === []) { return null; } diff --git a/packages/Sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php b/packages/Sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php old mode 100644 new mode 100755 index be2a123439e..17d5e7c0b78 --- a/packages/Sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php +++ b/packages/Sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php @@ -8,6 +8,8 @@ use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Return_; use Rector\BetterPhpDocParser\PhpDocNode\Sensio\SensioTemplateTagValueNode; @@ -15,6 +17,7 @@ use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; use Rector\Sensio\Helper\TemplateGuesser; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; final class TemplateAnnotationRector extends AbstractRector { @@ -65,15 +68,57 @@ PHP */ public function getNodeTypes(): array { - return [ClassMethod::class]; + return [ClassMethod::class, Class_::class]; } - /** - * @param ClassMethod $node - */ public function refactor(Node $node): ?Node { - $phpDocInfo = $this->getPhpDocInfo($node); + if ($node instanceof Class_) { + return $this->addBaseClassIfMissing($node); + } + + if ($node instanceof ClassMethod) { + return $this->replaceTemplateAnnotation($node); + } + + return null; + } + + private function addBaseClassIfMissing(Class_ $node): ?Node + { + if ($node->extends !== null) { + return null; + } + + if (! $this->classHasTemplateAnnotations($node)) { + return null; + } + + $node->extends = new FullyQualified(AbstractController::class); + + return $node; + } + + private function classHasTemplateAnnotations(Class_ $node): bool + { + foreach ($node->stmts as $stmtNode) { + $phpDocInfo = $this->getPhpDocInfo($stmtNode); + if ($phpDocInfo === null) { + continue; + } + + $templateTagValueNode = $phpDocInfo->getByType(SensioTemplateTagValueNode::class); + if ($templateTagValueNode !== null) { + return true; + } + } + + return false; + } + + private function replaceTemplateAnnotation(ClassMethod $classMethod): ?Node + { + $phpDocInfo = $this->getPhpDocInfo($classMethod); if ($phpDocInfo === null) { return null; } @@ -84,15 +129,15 @@ PHP } /** @var Return_|null $returnNode */ - $returnNode = $this->betterNodeFinder->findLastInstanceOf((array) $node->stmts, Return_::class); + $returnNode = $this->betterNodeFinder->findLastInstanceOf((array) $classMethod->stmts, Return_::class); // create "$this->render('template.file.twig.html', ['key' => 'value']);" method call - $renderArguments = $this->resolveRenderArguments($node, $returnNode, $templateTagValueNode); + $renderArguments = $this->resolveRenderArguments($classMethod, $returnNode, $templateTagValueNode); $thisRenderMethodCall = $this->createMethodCall('this', 'render', $renderArguments); if ($returnNode === null) { // or add as last statement in the method - $node->stmts[] = new Return_($thisRenderMethodCall); + $classMethod->stmts[] = new Return_($thisRenderMethodCall); } // replace Return_ node value if exists and is not already in correct format @@ -101,9 +146,9 @@ PHP } // remove annotation - $this->docBlockManipulator->removeTagFromNode($node, SensioTemplateTagValueNode::class); + $this->docBlockManipulator->removeTagFromNode($classMethod, SensioTemplateTagValueNode::class); - return $node; + return $classMethod; } /** @@ -142,6 +187,7 @@ PHP /** * Already existing method call + * * @return Array_[] */ private function resolveArrayArgumentsFromMethodCall(Return_ $returnNode): array diff --git a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture.php.inc b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture.php.inc old mode 100644 new mode 100755 index 20497cd4278..c7a5607be13 --- a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture.php.inc +++ b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture.php.inc @@ -3,8 +3,9 @@ namespace AppBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -class ClassWithNamedService13Controller +class ClassWithNamedService13Controller extends AbstractController { /** * @Template @@ -38,8 +39,9 @@ class ClassWithNamedService13Controller namespace AppBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -class ClassWithNamedService13Controller +class ClassWithNamedService13Controller extends AbstractController { public function indexAction() { diff --git a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture2.php.inc b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture2.php.inc old mode 100644 new mode 100755 index a64103cfc60..5bd3dce8241 --- a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture2.php.inc +++ b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture2.php.inc @@ -3,8 +3,9 @@ namespace AppBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -class ClassWithNamedService23Controller +class ClassWithNamedService23Controller extends AbstractController { /** * @Template() @@ -32,8 +33,9 @@ class ClassWithNamedService23Controller namespace AppBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -class ClassWithNamedService23Controller +class ClassWithNamedService23Controller extends AbstractController { public function indexAction() { diff --git a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture3.php.inc b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture3.php.inc old mode 100644 new mode 100755 index 02fb2e999b1..f02ac63c754 --- a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture3.php.inc +++ b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture3.php.inc @@ -3,8 +3,9 @@ namespace AppBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -class ClassWithNamedService33Controller +class ClassWithNamedService33Controller extends AbstractController { /** * @Template() @@ -38,8 +39,9 @@ class ClassWithNamedService33Controller namespace AppBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -class ClassWithNamedService33Controller +class ClassWithNamedService33Controller extends AbstractController { public function indexAction() { diff --git a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture4.php.inc b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture4.php.inc old mode 100644 new mode 100755 index 44758218807..39ae1543ea9 --- a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture4.php.inc +++ b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/Fixture/Version3/fixture4.php.inc @@ -1,8 +1,9 @@ +----- +render('AppBundle:ClassWithNamedService25:index.html.twig'); + } +} + +?> diff --git a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/TemplateAnnotationVersion5RectorTest.php b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/TemplateAnnotationVersion5RectorTest.php old mode 100644 new mode 100755 index cc8161056b1..4aa193610ce --- a/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/TemplateAnnotationVersion5RectorTest.php +++ b/packages/Sensio/tests/Rector/FrameworkExtraBundle/TemplateAnnotationRector/TemplateAnnotationVersion5RectorTest.php @@ -27,6 +27,8 @@ final class TemplateAnnotationVersion5RectorTest extends AbstractRectorTestCase yield [__DIR__ . '/Fixture/Version5/fixture5.php.inc']; yield [__DIR__ . '/Fixture/Version5/with_route_too.php.inc']; yield [__DIR__ . '/Fixture/Version5/with_route_options.php.inc']; + yield [__DIR__ . '/Fixture/Version5/without_base_class.php.inc']; + yield [__DIR__ . '/Fixture/Version5/skip_without_template.php.inc']; } /** diff --git a/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php b/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php index 666778f8b48..938fda7302d 100644 --- a/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php +++ b/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php @@ -94,41 +94,38 @@ PHP } $isVariableJustCreated = $this->isVariableJustCreated($node, $docVariableName); - if ($isVariableJustCreated === false) { + if (! $isVariableJustCreated) { return $this->refactorFreshlyCreatedNode($node, $phpDocInfo, $variable); } 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]); + $varTagValueNode = $phpDocInfo->getVarTagValue(); + if ($varTagValueNode === null) { + return null; } - if ($type instanceof IntegerType) { - $isInt = $this->createFunction('is_int', [$variable]); - return $this->createFunction('assert', [$isInt]); + $variableName = $varTagValueNode->variableName; + // no variable + if (empty($variableName)) { + return null; } - if ($type instanceof FloatType) { - $isFloat = $this->createFunction('is_float', [$variable]); - return $this->createFunction('assert', [$isFloat]); - } + return ltrim($variableName, '$'); + } - if ($type instanceof StringType) { - $isString = $this->createFunction('is_string', [$variable]); - return $this->createFunction('assert', [$isString]); - } + 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; + } - if ($type instanceof BooleanType) { - $isInt = $this->createFunction('is_bool', [$variable]); - return $this->createFunction('assert', [$isInt]); - } - - return null; + return $this->isName($stmt, $docVariableName); + }); } private function isVariableJustCreated(Node $node, string $docVariableName): bool @@ -150,41 +147,6 @@ PHP 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 @@ PHP 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 6ed38435457..fdfe828d434 100644 --- a/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php +++ b/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php @@ -139,6 +139,16 @@ final class DefaultAnalyzedSymfonyApplicationContainer implements AnalyzedApplic 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 @@ final class DefaultAnalyzedSymfonyApplicationContainer implements AnalyzedApplic 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 34770ae12ad..5e9991938f3 100644 --- a/packages/Symfony/src/Bridge/DependencyInjection/SymfonyContainerFactory.php +++ b/packages/Symfony/src/Bridge/DependencyInjection/SymfonyContainerFactory.php @@ -67,6 +67,17 @@ final class SymfonyContainerFactory 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 @@ final class SymfonyContainerFactory 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 430b1bcf10e..670fee9793e 100644 --- a/packages/Symfony/src/Rector/Class_/MakeCommandLazyRector.php +++ b/packages/Symfony/src/Rector/Class_/MakeCommandLazyRector.php @@ -84,16 +84,6 @@ PHP 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 @@ PHP 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 c9c5e4ce583..7ff863b5c22 100644 --- a/packages/Symfony/src/Rector/Console/ConsoleExecuteReturnIntRector.php +++ b/packages/Symfony/src/Rector/Console/ConsoleExecuteReturnIntRector.php @@ -85,6 +85,18 @@ PHP 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; @@ -114,7 +126,7 @@ PHP private function setReturnTo0InsteadOfNull(Return_ $return): void { - if (! $return->expr) { + if ($return->expr === null) { $return->expr = new LNumber(0); return; } @@ -143,18 +155,6 @@ PHP } } - 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/Form/FormIsValidRector.php b/packages/Symfony/src/Rector/Form/FormIsValidRector.php index 8bd91ad3220..80b69f01b2e 100644 --- a/packages/Symfony/src/Rector/Form/FormIsValidRector.php +++ b/packages/Symfony/src/Rector/Form/FormIsValidRector.php @@ -102,11 +102,7 @@ PHP } $variableName = $this->getName($methodCall->var); - if ($variableName === null) { - return true; - } - - return false; + return $variableName === null; } private function isIsSubmittedByAlreadyCalledOnVariable(Variable $variable): bool diff --git a/packages/Symfony/src/Rector/MethodCall/MakeDispatchFirstArgumentEventRector.php b/packages/Symfony/src/Rector/MethodCall/MakeDispatchFirstArgumentEventRector.php index a61ef0ac46a..e5809c14df6 100644 --- a/packages/Symfony/src/Rector/MethodCall/MakeDispatchFirstArgumentEventRector.php +++ b/packages/Symfony/src/Rector/MethodCall/MakeDispatchFirstArgumentEventRector.php @@ -82,26 +82,6 @@ PHP 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)) { @@ -111,12 +91,7 @@ PHP if (! $this->isName($methodCall->name, 'dispatch')) { return true; } - - if (! isset($methodCall->args[1])) { - return true; - } - - return false; + return ! isset($methodCall->args[1]); } private function refactorStringArgument(MethodCall $methodCall): Node @@ -148,4 +123,24 @@ PHP 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 cfa14f5708f..78348e0381f 100644 --- a/packages/Symfony/src/Rector/MethodCall/SimplifyWebTestCaseAssertionsRector.php +++ b/packages/Symfony/src/Rector/MethodCall/SimplifyWebTestCaseAssertionsRector.php @@ -140,6 +140,30 @@ PHP 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 @@ PHP 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 4232d69a7b5..69c17bb0d83 100644 --- a/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php +++ b/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php @@ -129,6 +129,15 @@ PHP $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 @@ PHP 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 272d7b4085b..4f77a86be09 100644 --- a/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php +++ b/packages/SymfonyCodeQuality/src/Rector/Class_/EventListenerToEventSubscriberRector.php @@ -120,7 +120,7 @@ class SomeEventSubscriber implements EventSubscriberInterface */ public static function getSubscribedEvents(): array { - return ['some_event' => 'methodToBeCalled']; + return ['some_event' => 'methodToBeCalled']; } public function methodToBeCalled() @@ -243,25 +243,6 @@ PHP 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 @@ PHP 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 @@ PHP $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/PHPStan/Type/ObjectTypeSpecifier.php b/packages/TypeDeclaration/src/PHPStan/Type/ObjectTypeSpecifier.php index 0324d260293..f01dcd5515f 100644 --- a/packages/TypeDeclaration/src/PHPStan/Type/ObjectTypeSpecifier.php +++ b/packages/TypeDeclaration/src/PHPStan/Type/ObjectTypeSpecifier.php @@ -30,17 +30,17 @@ final class ObjectTypeSpecifier } $aliasedObjectType = $this->matchAliasedObjectType($node, $objectType); - if ($aliasedObjectType) { + if ($aliasedObjectType !== null) { return $aliasedObjectType; } $shortenedObjectType = $this->matchShortenedObjectType($node, $objectType); - if ($shortenedObjectType) { + if ($shortenedObjectType !== null) { return $shortenedObjectType; } $sameNamespacedObjectType = $this->matchSameNamespacedObjectType($node, $objectType); - if ($sameNamespacedObjectType) { + if ($sameNamespacedObjectType !== null) { return $sameNamespacedObjectType; } @@ -89,17 +89,17 @@ final class ObjectTypeSpecifier foreach ($uses as $use) { foreach ($use->uses as $useUse) { - if ($useUse->alias) { + if ($useUse->alias !== null) { continue; } $partialNamespaceObjectType = $this->matchPartialNamespaceObjectType($objectType, $useUse); - if ($partialNamespaceObjectType) { + if ($partialNamespaceObjectType !== null) { return $partialNamespaceObjectType; } $partialNamespaceObjectType = $this->matchClassWithLastUseImportPart($objectType, $useUse); - if ($partialNamespaceObjectType) { + if ($partialNamespaceObjectType !== null) { return $partialNamespaceObjectType; } } diff --git a/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php b/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php index 5c13d06b684..abcf858a87e 100644 --- a/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php +++ b/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php @@ -148,6 +148,14 @@ PHP return false; } + private function shouldSkipArrayType(ArrayType $arrayType, ClassMethod $classMethod): bool + { + if ($this->isNewAndCurrentTypeBothCallable($arrayType, $classMethod)) { + return true; + } + return $this->isMixedOfSpecificOverride($arrayType, $classMethod); + } + private function isNewAndCurrentTypeBothCallable(ArrayType $newArrayType, ClassMethod $classMethod): bool { $currentPhpDocInfo = $this->getPhpDocInfo($classMethod); @@ -163,12 +171,7 @@ PHP if (! $newArrayType->getItemType()->isCallable()->yes()) { return false; } - - if (! $currentReturnType->getItemType()->isCallable()->yes()) { - return false; - } - - return true; + return $currentReturnType->getItemType()->isCallable()->yes(); } private function isMixedOfSpecificOverride(ArrayType $arrayType, ClassMethod $classMethod): bool @@ -183,23 +186,6 @@ PHP } $currentReturnType = $currentPhpDocInfo->getReturnType(); - if (! $currentReturnType instanceof ArrayType) { - return false; - } - - 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; + return $currentReturnType instanceof ArrayType; } } diff --git a/packages/TypeDeclaration/src/Rector/ClassMethod/AddMethodCallBasedParamTypeRector.php b/packages/TypeDeclaration/src/Rector/ClassMethod/AddMethodCallBasedParamTypeRector.php index 25541b22c63..0405f5160a6 100644 --- a/packages/TypeDeclaration/src/Rector/ClassMethod/AddMethodCallBasedParamTypeRector.php +++ b/packages/TypeDeclaration/src/Rector/ClassMethod/AddMethodCallBasedParamTypeRector.php @@ -116,31 +116,6 @@ PHP 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,24 @@ PHP 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 + return $parameterStaticType->equals($argumentStaticType); + } } diff --git a/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php b/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php index 66e330a1780..599e7b7b8a6 100644 --- a/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php +++ b/packages/TypeDeclaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php @@ -148,7 +148,7 @@ PHP // @see https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters if ($this->isAtLeastPhpVersion('7.4') && $isSubtype) { $node->returnType = $inferredReturnNode; - } elseif ($isSubtype === false) { // type override + } elseif (! $isSubtype) { // type override $node->returnType = $inferredReturnNode; } } else { @@ -162,68 +162,12 @@ PHP return $node; } - /** - * Add typehint to all children class methods - */ - private function populateChildren(ClassMethod $classMethod, Type $returnType): void - { - $methodName = $this->getName($classMethod); - if ($methodName === null) { - throw new ShouldNotHappenException(); - } - - $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - if (! is_string($className)) { - throw new ShouldNotHappenException(); - } - - $childrenClassLikes = $this->parsedNodesByType->findChildrenOfClass($className); - if ($childrenClassLikes === []) { - return; - } - - // update their methods as well - foreach ($childrenClassLikes as $childClassLike) { - $usedTraits = $this->parsedNodesByType->findUsedTraitsInClass($childClassLike); - foreach ($usedTraits as $trait) { - $this->addReturnTypeToChildMethod($trait, $classMethod, $returnType); - } - - $this->addReturnTypeToChildMethod($childClassLike, $classMethod, $returnType); - } - } - - private function addReturnTypeToChildMethod( - ClassLike $classLike, - ClassMethod $classMethod, - Type $returnType - ): void { - $methodName = $this->getName($classMethod); - - $currentClassMethod = $classLike->getMethod($methodName); - if ($currentClassMethod === null) { - return; - } - - $resolvedChildTypeNode = $this->resolveChildTypeNode($returnType); - if ($resolvedChildTypeNode === null) { - return; - } - - $currentClassMethod->returnType = $resolvedChildTypeNode; - - // make sure the type is not overridden - $currentClassMethod->returnType->setAttribute(self::DO_NOT_CHANGE, true); - - $this->notifyNodeChangeFileInfo($currentClassMethod); - } - /** * @param ClassMethod|Function_ $node */ private function shouldSkip(Node $node): bool { - if ($this->overrideExistingReturnTypes === false) { + if (! $this->overrideExistingReturnTypes) { if ($node->returnType) { return true; } @@ -267,6 +211,71 @@ PHP return false; } + /** + * Add typehint to all children class methods + */ + private function populateChildren(ClassMethod $classMethod, Type $returnType): void + { + $methodName = $this->getName($classMethod); + if ($methodName === null) { + throw new ShouldNotHappenException(); + } + + $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); + if (! is_string($className)) { + throw new ShouldNotHappenException(); + } + + $childrenClassLikes = $this->parsedNodesByType->findChildrenOfClass($className); + if ($childrenClassLikes === []) { + return; + } + + // update their methods as well + foreach ($childrenClassLikes as $childClassLike) { + $usedTraits = $this->parsedNodesByType->findUsedTraitsInClass($childClassLike); + foreach ($usedTraits as $trait) { + $this->addReturnTypeToChildMethod($trait, $classMethod, $returnType); + } + + $this->addReturnTypeToChildMethod($childClassLike, $classMethod, $returnType); + } + } + + 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, + Type $returnType + ): void { + $methodName = $this->getName($classMethod); + + $currentClassMethod = $classLike->getMethod($methodName); + if ($currentClassMethod === null) { + return; + } + + $resolvedChildTypeNode = $this->resolveChildTypeNode($returnType); + if ($resolvedChildTypeNode === null) { + return; + } + + $currentClassMethod->returnType = $resolvedChildTypeNode; + + // make sure the type is not overridden + $currentClassMethod->returnType->setAttribute(self::DO_NOT_CHANGE, true); + + $this->notifyNodeChangeFileInfo($currentClassMethod); + } + private function isStaticTypeIterable(Type $type): bool { if ($type instanceof ArrayType) { @@ -295,13 +304,4 @@ PHP 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 b51603e289c..1fc251202c3 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php @@ -80,30 +80,6 @@ final class ConstructorPropertyTypeInferer extends AbstractTypeInferer implement 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 @@ final class ConstructorPropertyTypeInferer extends AbstractTypeInferer implement 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 @@ final class ConstructorPropertyTypeInferer extends AbstractTypeInferer implement 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 !== null) { + $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/TypeInferer/ReturnTypeInferer/YieldNodesReturnTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer/YieldNodesReturnTypeInferer.php index b3c249d0565..b7ba4ebdc48 100644 --- a/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer/YieldNodesReturnTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer/YieldNodesReturnTypeInferer.php @@ -47,7 +47,7 @@ final class YieldNodesReturnTypeInferer extends AbstractTypeInferer implements R $yieldNodes = $this->betterNodeFinder->findInstanceOf((array) $functionLike->stmts, Yield_::class); $types = []; - if (count($yieldNodes)) { + if (count($yieldNodes) > 0) { foreach ($yieldNodes as $yieldNode) { if ($yieldNode->value === null) { continue; diff --git a/packages/TypeDeclaration/src/TypeNormalizer.php b/packages/TypeDeclaration/src/TypeNormalizer.php index d7b1ec1d319..bdf6b3a1f9e 100644 --- a/packages/TypeDeclaration/src/TypeNormalizer.php +++ b/packages/TypeDeclaration/src/TypeNormalizer.php @@ -137,6 +137,18 @@ final class TypeNormalizer 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 @@ final class TypeNormalizer 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/Extension/ReportRoutesExtension.php b/packages/ZendToSymfony/src/Extension/ReportRoutesExtension.php index 872a15e1651..e14bb0d9ed4 100644 --- a/packages/ZendToSymfony/src/Extension/ReportRoutesExtension.php +++ b/packages/ZendToSymfony/src/Extension/ReportRoutesExtension.php @@ -36,11 +36,10 @@ final class ReportRoutesExtension implements ReportingExtensionInterface $tableLines = []; foreach ($this->routeCollector->getRouteValueObjects() as $routeValueObject) { - if ($routeValueObject->getParams()) { - $paramsAsString = '$' . implode(', $', $routeValueObject->getParams()); - } else { - $paramsAsString = ''; - } + $paramsAsString = $routeValueObject->getParams() !== [] ? '$' . implode( + ', $', + $routeValueObject->getParams() + ) : ''; $tableLines[] = [ $routeValueObject->getControllerClass(), diff --git a/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php b/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php index 5618b4dd5f8..72770675ae2 100644 --- a/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php +++ b/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php @@ -120,38 +120,6 @@ PHP return $node; } - private function removeGetParamAssignIfNotUseful(?Node $getParamParentNode): void - { - // e.g. $value = (int) $this->getParam('value'); - if ($getParamParentNode instanceof Cast) { - $getParamParentNode = $getParamParentNode->getAttribute(AttributeKey::PARENT_NODE); - } - - if (! $getParamParentNode instanceof Assign) { - return; - } - - if (! $getParamParentNode->var instanceof Variable) { - return; - } - - // matches: "$x = $this->getParam('x');" - $this->removeNode($getParamParentNode); - } - - private function addRouteAnnotation(ClassMethod $classMethod, RouteValueObject $routeValueObject): void - { - $symfonyRoutePhpDocTagNode = $routeValueObject->getSymfonyRoutePhpDocTagNode(); - $symfonyRoutePhpDocNode = new SpacelessPhpDocTagNode( - SymfonyRouteTagValueNode::SHORT_NAME, - $symfonyRoutePhpDocTagNode - ); - - $this->docBlockManipulator->addTag($classMethod, $symfonyRoutePhpDocNode); - - $this->addUseType(new FullyQualifiedObjectType(SymfonyRouteTagValueNode::CLASS_NAME), $classMethod); - } - /** * @param Param[] $paramNamesToParentNodes * @return string[] @@ -174,6 +142,38 @@ PHP return $addedParamNames; } + private function addRouteAnnotation(ClassMethod $classMethod, RouteValueObject $routeValueObject): void + { + $symfonyRoutePhpDocTagNode = $routeValueObject->getSymfonyRoutePhpDocTagNode(); + $symfonyRoutePhpDocNode = new SpacelessPhpDocTagNode( + SymfonyRouteTagValueNode::SHORT_NAME, + $symfonyRoutePhpDocTagNode + ); + + $this->docBlockManipulator->addTag($classMethod, $symfonyRoutePhpDocNode); + + $this->addUseType(new FullyQualifiedObjectType(SymfonyRouteTagValueNode::CLASS_NAME), $classMethod); + } + + private function removeGetParamAssignIfNotUseful(?Node $getParamParentNode): void + { + // e.g. $value = (int) $this->getParam('value'); + if ($getParamParentNode instanceof Cast) { + $getParamParentNode = $getParamParentNode->getAttribute(AttributeKey::PARENT_NODE); + } + + if (! $getParamParentNode instanceof Assign) { + return; + } + + if (! $getParamParentNode->var instanceof Variable) { + return; + } + + // matches: "$x = $this->getParam('x');" + $this->removeNode($getParamParentNode); + } + private function correctParamNameBasedOnAssign(Node $parentNode, string $currentParamName): string { if (! $parentNode instanceof Assign) { diff --git a/packages/ZendToSymfony/src/Rector/ClassMethod/ThisRequestToRequestParameterRector.php b/packages/ZendToSymfony/src/Rector/ClassMethod/ThisRequestToRequestParameterRector.php index 471cb7385d0..9ff2b9c7b9b 100644 --- a/packages/ZendToSymfony/src/Rector/ClassMethod/ThisRequestToRequestParameterRector.php +++ b/packages/ZendToSymfony/src/Rector/ClassMethod/ThisRequestToRequestParameterRector.php @@ -94,7 +94,7 @@ PHP }); // add request argument - if ($hasRequest === false) { + if (! $hasRequest) { return null; } diff --git a/packages/ZendToSymfony/src/Rector/ClassMethod/ThisViewToThisRenderResponseRector.php b/packages/ZendToSymfony/src/Rector/ClassMethod/ThisViewToThisRenderResponseRector.php index de0a33387f5..adbe5ffbbd5 100644 --- a/packages/ZendToSymfony/src/Rector/ClassMethod/ThisViewToThisRenderResponseRector.php +++ b/packages/ZendToSymfony/src/Rector/ClassMethod/ThisViewToThisRenderResponseRector.php @@ -109,7 +109,7 @@ PHP return $node; }); - if ($hasRender === false) { + if (! $hasRender) { return null; } @@ -127,13 +127,8 @@ PHP if ($this->isObjectType($propertyFetch, ZendClass::ZEND_VIEW)) { return true; } - // fallback if checked property is missing @var doc - if ($this->isNames($propertyFetch->name, ['_view', 'view'])) { - return true; - } - - return false; + return $this->isNames($propertyFetch->name, ['_view', 'view']); } /** diff --git a/packages/ZendToSymfony/src/Rector/MethodCall/ThisHelperToServiceMethodCallRector.php b/packages/ZendToSymfony/src/Rector/MethodCall/ThisHelperToServiceMethodCallRector.php index be6bd0816d7..0bb1cf86e2b 100644 --- a/packages/ZendToSymfony/src/Rector/MethodCall/ThisHelperToServiceMethodCallRector.php +++ b/packages/ZendToSymfony/src/Rector/MethodCall/ThisHelperToServiceMethodCallRector.php @@ -132,6 +132,19 @@ PHP 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 @@ PHP $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/phpstan.neon b/phpstan.neon index 0c51b1b3a4f..eab4b55cb04 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -169,7 +169,7 @@ parameters: - '#Method Rector\\Symfony\\Bridge\\DefaultAnalyzedSymfonyApplicationContainer\:\:getService\(\) should return object but returns object\|null#' - '#Call to function property_exists\(\) with string and (.*?) will always evaluate to false#' - - '#Method Rector\\Console\\Option\\SetOptionResolver\:\:separateVersionedAndUnversionedSets\(\) should return array\> but returns array\|string\>\>#' + - '#Method Rector\\Bootstrap\\SetOptionResolver\:\:separateVersionedAndUnversionedSets\(\) should return array\> but returns array\|string\>\>#' - '#Access to an undefined property PhpParser\\Node\\FunctionLike\|PhpParser\\Node\\Stmt\\ClassLike\:\:\$stmts#' - '#Property Rector\\TypeDeclaration\\TypeInferer\\(.*?)\:\:\$(.*?)TypeInferers \(array\) does not accept array#' diff --git a/rector-ci.yaml b/rector-ci.yaml new file mode 100644 index 00000000000..cdada396d1b --- /dev/null +++ b/rector-ci.yaml @@ -0,0 +1,11 @@ +parameters: + sets: + - "code-quality" + - "dead-code" + + auto_import_names: true + + exclude_paths: + - "/Fixture/" + - "/Source/" + - "/Expected/" diff --git a/rector.yaml b/rector.yaml index 740d5205c23..7b98c8de8fd 100644 --- a/rector.yaml +++ b/rector.yaml @@ -4,7 +4,6 @@ parameters: exclude_paths: - "/Fixture/" - - "/Fixtures/" - "/Expected/" - "/Source/" - "packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php" @@ -18,5 +17,3 @@ parameters: php_version_features: '7.1' services: - Rector\CodeQuality\Rector\FuncCall\ArrayMergeOfNonArraysToSimpleArrayRector: ~ -# Rector\PHPUnit\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector: ~ diff --git a/sample/ScreenSample.php b/sample/ScreenSample.php deleted file mode 100644 index 02532557d71..00000000000 --- a/sample/ScreenSample.php +++ /dev/null @@ -1,14 +0,0 @@ -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()) { + 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 @@ final class RectorApplication $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/Bootstrap/ConfigResolver.php b/src/Bootstrap/ConfigResolver.php new file mode 100644 index 00000000000..2d03511054f --- /dev/null +++ b/src/Bootstrap/ConfigResolver.php @@ -0,0 +1,52 @@ +setOptionResolver = new SetOptionResolver(); + $this->setsResolver = new SetsResolver(); + } + + /** + * @param string[] $configFiles + * @return string[] + */ + public function resolveFromParameterSetsFromConfigFiles(array $configFiles): array + { + $configs = []; + + $sets = $this->setsResolver->resolveFromConfigFiles($configFiles); + return array_merge($configs, $this->resolveFromSets($sets)); + } + + /** + * @param string[] $sets + * @return string[] + */ + private function resolveFromSets(array $sets): array + { + $configs = []; + foreach ($sets as $set) { + $configs[] = $this->setOptionResolver->detectFromNameAndDirectory($set, Set::SET_DIRECTORY); + } + + return $configs; + } +} diff --git a/src/Console/Option/SetOptionResolver.php b/src/Bootstrap/SetOptionResolver.php similarity index 98% rename from src/Console/Option/SetOptionResolver.php rename to src/Bootstrap/SetOptionResolver.php index 6021134ccaa..62b9219e45a 100644 --- a/src/Console/Option/SetOptionResolver.php +++ b/src/Bootstrap/SetOptionResolver.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\Console\Option; +namespace Rector\Bootstrap; use Nette\Utils\ObjectHelpers; use Nette\Utils\Strings; @@ -43,7 +43,7 @@ final class SetOptionResolver return $this->detectFromNameAndDirectory($setName, $configDirectory); } - public function detectFromNameAndDirectory(string $setName, string $configDirectory): ?string + public function detectFromNameAndDirectory(string $setName, string $configDirectory): string { $nearestMatches = $this->findNearestMatchingFiles($configDirectory, $setName); if (count($nearestMatches) === 0) { @@ -56,48 +56,6 @@ final class SetOptionResolver 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 @@ final class SetOptionResolver 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\.]+$)#'); @@ -151,6 +132,53 @@ final class SetOptionResolver return Strings::replace($version, '#\.#'); } + /** + * @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); + } + + /** + * @param string[] $allSets + * @return string[][] + */ + private function separateVersionedAndUnversionedSets(array $allSets): array + { + $versionedSets = []; + $unversionedSets = []; + + foreach ($allSets as $set) { + $hasVersion = (bool) Strings::match($set, '#\d#'); + + if (! $hasVersion) { + $unversionedSets[] = $set; + continue; + } + + $match = Strings::match($set, '#^(?[A-Za-z\-]+)#'); + $setWithoutVersion = $match['set']; + + if ($setWithoutVersion !== $set) { + $versionedSets[$setWithoutVersion][] = $set; + } + } + + return [$versionedSets, $unversionedSets]; + } + /** * @param string[] $unversionedSets * @param string[] $versionedSets @@ -169,32 +197,4 @@ final class SetOptionResolver return $setsListInString; } - - /** - * @param string[] $allSets - * @return string[][] - */ - private function separateVersionedAndUnversionedSets(array $allSets): array - { - $versionedSets = []; - $unversionedSets = []; - - foreach ($allSets as $set) { - $hasVersion = (bool) Strings::match($set, '#\d#'); - - if ($hasVersion === false) { - $unversionedSets[] = $set; - continue; - } - - $match = Strings::match($set, '#^(?[A-Za-z\-]+)#'); - $setWithoutVersion = $match['set']; - - if ($setWithoutVersion !== $set) { - $versionedSets[$setWithoutVersion][] = $set; - } - } - - return [$versionedSets, $unversionedSets]; - } } diff --git a/src/Bootstrap/SetsResolver.php b/src/Bootstrap/SetsResolver.php new file mode 100644 index 00000000000..d4f0576322b --- /dev/null +++ b/src/Bootstrap/SetsResolver.php @@ -0,0 +1,26 @@ +hasParameterOption('--xdebug'); + if (! $isXdebugAllowed) { + $xdebug = new XdebugHandler('rector', '--ansi'); + $xdebug->check(); + unset($xdebug); + } + $this->configuration->setConfigFilePathFromInput($input); $shouldFollowByNewline = false; // switch working dir $newWorkDir = $this->getNewWorkingDir($input); - if ($newWorkDir) { + if ($newWorkDir !== '') { $oldWorkingDir = getcwd(); chdir($newWorkDir); $output->isDebug() && $output->writeln('Changed CWD form ' . $oldWorkingDir . ' to ' . getcwd()); @@ -81,7 +91,10 @@ final class Application extends SymfonyApplication $configPath = $this->configuration->getConfigFilePath(); if ($configPath) { - $output->writeln('Config file: ' . realpath($configPath)); + $configFileInfo = new SmartFileInfo($configPath); + $relativeConfigPath = $configFileInfo->getRelativeFilePathFromDirectory(getcwd()); + + $output->writeln('Config file: ' . $relativeConfigPath); $shouldFollowByNewline = true; } } @@ -121,13 +134,16 @@ final class Application extends SymfonyApplication return array_values($filteredCommands); } - private function removeUnusedOptions(InputDefinition $inputDefinition): void + private function getNewWorkingDir(InputInterface $input): string { - $options = $inputDefinition->getOptions(); + $workingDir = $input->getParameterOption(['--working-dir', '-d']); + if ($workingDir !== false && ! is_dir($workingDir)) { + throw new InvalidConfigurationException( + 'Invalid working directory specified, ' . $workingDir . ' does not exist.' + ); + } - unset($options['quiet'], $options['no-interaction']); - - $inputDefinition->setOptions($options); + return (string) $workingDir; } private function shouldPrintMetaInformation(InputInterface $input): bool @@ -143,6 +159,15 @@ final class Application extends SymfonyApplication 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( @@ -167,6 +192,13 @@ final class Application extends SymfonyApplication 'Enable debug verbosity (-vvv)' )); + $inputDefinition->addOption(new InputOption( + 'xdebug', + null, + InputOption::VALUE_NONE, + 'Allow running xdebug' + )); + $inputDefinition->addOption(new InputOption( '--working-dir', '-d', @@ -179,16 +211,4 @@ final class Application extends SymfonyApplication { 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 b4a8419d4b0..0ad9aa5f984 100644 --- a/src/Console/Command/ScreenFileCommand.php +++ b/src/Console/Command/ScreenFileCommand.php @@ -28,7 +28,6 @@ use Rector\PhpParser\NodeTraverser\CallableNodeTraverser; use Rector\PhpParser\Printer\BetterStandardPrinter; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symplify\PackageBuilder\Console\Command\CommandNaming; @@ -41,11 +40,6 @@ final class ScreenFileCommand extends AbstractCommand */ private const FILE_ARGUMENT = 'file'; - /** - * @var string - */ - private const OUTPUT_OPTION = 'output'; - /** * @var SymfonyStyle */ @@ -107,7 +101,6 @@ final class ScreenFileCommand extends AbstractCommand $this->setDescription('Load file and print nodes meta data - super helpful to learn to build rules'); $this->addArgument(self::FILE_ARGUMENT, InputArgument::REQUIRED, 'Path to file to be screened'); - $this->addOption(self::OUTPUT_OPTION, 'o', InputOption::VALUE_REQUIRED, 'Path to print decorated file into'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -123,51 +116,18 @@ final class ScreenFileCommand extends AbstractCommand $this->decorateNodes($nodes); // 4. print decorated nodes to output/file - $this->outputDecoratedFileContent($input, $nodes); + $this->outputDecoratedFileContent($nodes, $smartFileInfo); 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 */ private function decorateNodes(array $nodes): void { $this->callableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node): Node { - // not useful - if ($node instanceof Expression) { - $infoNode = $node->expr; - } else { - $infoNode = $node; - } + $infoNode = $node instanceof Expression ? $node->expr : $node; $data = $this->decorateNodeData($infoNode); @@ -186,67 +146,16 @@ final class ScreenFileCommand extends AbstractCommand } /** - * @param mixed[] $data - * @return mixed[] + * @param Node[] $nodes */ - private function decorateClassLike(ClassLike $classLike, array $data): array + private function outputDecoratedFileContent(array $nodes, SmartFileInfo $fileInfo): void { - $data['name'] = $this->nameResolver->getName($classLike); + $decoratedFileContent = 'betterStandardPrinter->prettyPrint($nodes); - $parentClassName = $classLike->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName) { - $data['parent_class_name'] = $parentClassName; - } + $outputFileName = 'rector_vision_' . $fileInfo->getFilename(); + FileSystem::write($outputFileName, $decoratedFileContent); - 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[] - */ - private function decorateWithNodeType(Node $node, array $data): array - { - $data['node_type'] = $this->getObjectShortClass($node); - - return $data; - } - - /** - * @param mixed[] $data - * @return mixed[] - */ - private function decorateReturn(Return_ $return, array $data): array - { - 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); + $this->symfonyStyle->writeln(sprintf('See: %s', $outputFileName)); } /** @@ -312,6 +221,61 @@ final class ScreenFileCommand extends AbstractCommand 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 +289,39 @@ final class ScreenFileCommand extends AbstractCommand } /** - * @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/Output/ConsoleOutputFormatter.php b/src/Console/Output/ConsoleOutputFormatter.php index 5de080636fc..3f399b58cf7 100644 --- a/src/Console/Output/ConsoleOutputFormatter.php +++ b/src/Console/Output/ConsoleOutputFormatter.php @@ -116,7 +116,7 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface private function reportRemovedFilesAndNodes(ErrorAndDiffCollector $errorAndDiffCollector): void { - if ($errorAndDiffCollector->getRemovedAndAddedFilesCount()) { + if ($errorAndDiffCollector->getRemovedAndAddedFilesCount() !== 0) { $this->symfonyStyle->note( sprintf('%d files were added/removed', $errorAndDiffCollector->getRemovedAndAddedFilesCount()) ); diff --git a/src/DependencyInjection/RectorContainerFactory.php b/src/DependencyInjection/RectorContainerFactory.php index 588dafcf8c9..573a2c04018 100644 --- a/src/DependencyInjection/RectorContainerFactory.php +++ b/src/DependencyInjection/RectorContainerFactory.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Rector\DependencyInjection; use Psr\Container\ContainerInterface; -use Rector\Console\Option\SetOptionResolver; +use Rector\Bootstrap\SetOptionResolver; use Rector\Exception\ShouldNotHappenException; use Rector\HttpKernel\RectorKernel; use Rector\Set\Set; @@ -41,7 +41,7 @@ final class RectorContainerFactory $isDebug = InputDetector::isDebug(); $rectorKernel = new RectorKernel($environment, $isDebug); - if ($configFiles) { + if ($configFiles !== []) { $rectorKernel->setConfigs($configFiles); } diff --git a/src/NodeContainer/ParsedNodesByType.php b/src/NodeContainer/ParsedNodesByType.php index c9c796f44b5..59f05410944 100644 --- a/src/NodeContainer/ParsedNodesByType.php +++ b/src/NodeContainer/ParsedNodesByType.php @@ -337,7 +337,7 @@ final class ParsedNodesByType $classNames = TypeUtils::getDirectClassNames($objectType); foreach ($classNames as $className) { $foundMethod = $this->findMethod($methodName, $className); - if ($foundMethod) { + if ($foundMethod !== null) { return $foundMethod; } } @@ -562,14 +562,44 @@ final class ParsedNodesByType $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 @@ final class ParsedNodesByType } } - /** - * 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 ($classNode->isAnonymous() || $classNode->name === null) { + return true; } - 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]; + // PHPStan polution + return Strings::startsWith($classNode->name->toString(), 'AnonymousClass'); } private function isThisVariable(Node $node): bool diff --git a/src/PhpDoc/PhpDocClassRenamer.php b/src/PhpDoc/PhpDocClassRenamer.php index d48803d8549..c657e02fdf1 100644 --- a/src/PhpDoc/PhpDocClassRenamer.php +++ b/src/PhpDoc/PhpDocClassRenamer.php @@ -56,7 +56,7 @@ final class PhpDocClassRenamer $this->processDoctrineRelationTagValueNode($oldToNewClasses, $phpDocInfo); $this->processSerializerTypeTagValueNode($oldToNewClasses, $phpDocInfo); - if ($this->shouldUpdate === false) { + if (! $this->shouldUpdate) { return; } diff --git a/src/PhpParser/Node/BetterNodeFinder.php b/src/PhpParser/Node/BetterNodeFinder.php index d3ddea5b650..30732d85eb1 100644 --- a/src/PhpParser/Node/BetterNodeFinder.php +++ b/src/PhpParser/Node/BetterNodeFinder.php @@ -141,13 +141,8 @@ final class BetterNodeFinder if (! $node instanceof ClassLike) { return false; } - // skip anonymous classes - if ($node instanceof Class_ && $node->isAnonymous()) { - return false; - } - - return true; + return ! ($node instanceof Class_ && $node->isAnonymous()); }); } diff --git a/src/PhpParser/Node/Manipulator/AssignManipulator.php b/src/PhpParser/Node/Manipulator/AssignManipulator.php index 8757d6bbf82..132ae6c2343 100644 --- a/src/PhpParser/Node/Manipulator/AssignManipulator.php +++ b/src/PhpParser/Node/Manipulator/AssignManipulator.php @@ -37,11 +37,7 @@ final class AssignManipulator return false; } - if ($node->var instanceof ArrayDimFetch) { - $potentialPropertyFetch = $node->var->var; - } else { - $potentialPropertyFetch = $node->var; - } + $potentialPropertyFetch = $node->var instanceof ArrayDimFetch ? $node->var->var : $node->var; return $potentialPropertyFetch instanceof PropertyFetch || $potentialPropertyFetch instanceof StaticPropertyFetch; } @@ -57,14 +53,7 @@ final class AssignManipulator return false; } - /** @var Assign $node */ - if ($node->var instanceof ArrayDimFetch) { - /** @var PropertyFetch|StaticPropertyFetch $propertyFetch */ - $propertyFetch = $node->var->var; - } else { - /** @var PropertyFetch|StaticPropertyFetch $propertyFetch */ - $propertyFetch = $node->var; - } + $propertyFetch = $node->var instanceof ArrayDimFetch ? $node->var->var : $node->var; return $this->nameResolver->isNames($propertyFetch, $propertyNames); } diff --git a/src/PhpParser/Node/Manipulator/CallManipulator.php b/src/PhpParser/Node/Manipulator/CallManipulator.php index 27ad0826111..ae3e967bd8e 100644 --- a/src/PhpParser/Node/Manipulator/CallManipulator.php +++ b/src/PhpParser/Node/Manipulator/CallManipulator.php @@ -104,6 +104,22 @@ final class CallManipulator 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 @@ final class CallManipulator { 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 19ae5c2cc23..508e196e4ac 100644 --- a/src/PhpParser/Node/Manipulator/ChildAndParentClassManipulator.php +++ b/src/PhpParser/Node/Manipulator/ChildAndParentClassManipulator.php @@ -52,13 +52,13 @@ final class ChildAndParentClassManipulator // not in analyzed scope, nothing we can do $parentClassNode = $this->parsedNodesByType->findClass($parentClassName); - if ($parentClassNode) { + if ($parentClassNode !== null) { $this->completeParentConstructorBasedOnParentNode($parentClassNode, $classMethod); return; } // complete parent call for __construct() - if ($parentClassName) { + if ($parentClassName !== '') { if (method_exists($parentClassName, '__construct')) { $parentConstructCallNode = $this->nodeFactory->createParentConstructWithParams([]); $classMethod->stmts[] = new Expression($parentConstructCallNode); @@ -101,26 +101,6 @@ final class ChildAndParentClassManipulator } } - 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 @@ final class ChildAndParentClassManipulator ); $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 738c246bfe7..6b043b776e8 100644 --- a/src/PhpParser/Node/Manipulator/ClassManipulator.php +++ b/src/PhpParser/Node/Manipulator/ClassManipulator.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\StaticPropertyFetch; +use PhpParser\Node\Expr\Variable; use PhpParser\Node\Name; use PhpParser\Node\Param; use PhpParser\Node\Stmt; @@ -481,23 +482,16 @@ final class ClassManipulator 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 */ private function isNonAssignPropertyFetch(Node $node): bool { if ($node instanceof PropertyFetch) { + if (! $node->var instanceof Variable) { + return false; + } + if (! $this->nameResolver->isName($node->var, 'this')) { return false; } @@ -515,13 +509,6 @@ final class ClassManipulator 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 +532,23 @@ final class ClassManipulator 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 +572,21 @@ final class ClassManipulator 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 6d19f6dff2d..d8ba86392de 100644 --- a/src/PhpParser/Node/Manipulator/ClassMethodManipulator.php +++ b/src/PhpParser/Node/Manipulator/ClassMethodManipulator.php @@ -173,6 +173,26 @@ final class ClassMethodManipulator } } + /** + * @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 @@ final class ClassMethodManipulator 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 88299f2a600..bfbc819370f 100644 --- a/src/PhpParser/Node/Manipulator/MethodCallManipulator.php +++ b/src/PhpParser/Node/Manipulator/MethodCallManipulator.php @@ -139,37 +139,6 @@ final class MethodCallManipulator 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 @@ final class MethodCallManipulator 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/PropertyFetchManipulator.php b/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php index fe8d9c7dd74..4414cb00979 100644 --- a/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php +++ b/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php @@ -168,13 +168,8 @@ final class PropertyFetchManipulator if (! $node->var instanceof PropertyFetch) { return false; } - // must be local property - if (! $this->nameResolver->isName($node->var->var, 'this')) { - return false; - } - - return true; + return $this->nameResolver->isName($node->var->var, 'this'); } /** diff --git a/src/PhpParser/Node/Manipulator/VisibilityManipulator.php b/src/PhpParser/Node/Manipulator/VisibilityManipulator.php index 3806d0e28e9..1add5250346 100644 --- a/src/PhpParser/Node/Manipulator/VisibilityManipulator.php +++ b/src/PhpParser/Node/Manipulator/VisibilityManipulator.php @@ -56,33 +56,6 @@ final class VisibilityManipulator $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 @@ final class VisibilityManipulator } } + /** + * 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/Resolver/NameResolver.php b/src/PhpParser/Node/Resolver/NameResolver.php index 3a5d0f49f4c..f9aec7f462b 100644 --- a/src/PhpParser/Node/Resolver/NameResolver.php +++ b/src/PhpParser/Node/Resolver/NameResolver.php @@ -24,6 +24,7 @@ use PhpParser\Node\Stmt\Trait_; use PhpParser\Node\Stmt\Use_; use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\Node\AttributeKey; +use Symplify\PackageBuilder\FileSystem\SmartFileInfo; final class NameResolver { @@ -44,8 +45,16 @@ final class NameResolver public function isName(Node $node, string $name): bool { if ($node instanceof MethodCall) { + $debugBacktrace = debug_backtrace(); + + $previousCaller = $debugBacktrace[0]; + $fileInfo = new SmartFileInfo($previousCaller['file']); + $location = $fileInfo->getRelativeFilePathFromDirectory(getcwd()) . ':' . $previousCaller['line']; + throw new ShouldNotHappenException(sprintf( - 'Cannot get name on "%s" node. Use $node->name instead', MethodCall::class + 'Cannot get name on "%s" node. Use $node->name instead. Called in: %s', + MethodCall::class, + $location )); } diff --git a/src/PhpParser/Node/Value/ValueResolver.php b/src/PhpParser/Node/Value/ValueResolver.php index 3cad3207bbc..dbb17bcabf5 100644 --- a/src/PhpParser/Node/Value/ValueResolver.php +++ b/src/PhpParser/Node/Value/ValueResolver.php @@ -115,6 +115,34 @@ final class ValueResolver 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 @@ final class ValueResolver 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 df20abe4a39..ca95d14ed03 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -105,13 +105,7 @@ final class BetterStandardPrinter extends Standard */ protected function indent(): void { - if ($this->tabOrSpaceIndentCharacter === ' ') { - // 4 spaces - $multiplier = 4; - } else { - // 1 tab - $multiplier = 1; - } + $multiplier = $this->tabOrSpaceIndentCharacter === ' ' ? 4 : 1; $this->indentLevel += $multiplier; $this->nl .= str_repeat($this->tabOrSpaceIndentCharacter, $multiplier); @@ -298,20 +292,6 @@ final class BetterStandardPrinter extends Standard 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 +323,18 @@ final class BetterStandardPrinter extends Standard } } } + + /** + * @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/AbstractPHPUnitRector.php b/src/Rector/AbstractPHPUnitRector.php index ad94d4ce50e..5cb15f86e0e 100644 --- a/src/Rector/AbstractPHPUnitRector.php +++ b/src/Rector/AbstractPHPUnitRector.php @@ -24,7 +24,7 @@ abstract class AbstractPHPUnitRector extends AbstractRector } $docComment = $classMethod->getDocComment(); - if ($docComment) { + if ($docComment !== null) { return (bool) Strings::match($docComment->getText(), '#@test\b#'); } diff --git a/src/Rector/AbstractRector.php b/src/Rector/AbstractRector.php index cd18d1dea11..677bc73f5f5 100644 --- a/src/Rector/AbstractRector.php +++ b/src/Rector/AbstractRector.php @@ -4,11 +4,13 @@ declare(strict_types=1); namespace Rector\Rector; +use Nette\Utils\Strings; use PhpParser\BuilderFactory; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Name; use PhpParser\Node\Stmt; +use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Expression; use PhpParser\NodeVisitorAbstract; use Rector\Commander\CommanderCollector; @@ -219,7 +221,7 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn if ($originalNode->getAttribute(AttributeKey::FILE_INFO) !== null) { $node->setAttribute(AttributeKey::FILE_INFO, $originalNode->getAttribute(AttributeKey::FILE_INFO)); - } elseif ($originalNode->getAttribute(AttributeKey::PARENT_NODE)) { + } elseif ($originalNode->getAttribute(AttributeKey::PARENT_NODE) !== null) { /** @var Node $parentOriginalNode */ $parentOriginalNode = $originalNode->getAttribute(AttributeKey::PARENT_NODE); $node->setAttribute(AttributeKey::FILE_INFO, $parentOriginalNode->getAttribute(AttributeKey::FILE_INFO)); @@ -251,6 +253,17 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn } } + protected 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 updateAttributes(Node $node): void { // update Resolved name attribute if name is changed diff --git a/src/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector.php b/src/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector.php index e5399b1a2a3..b46de1ab8ae 100644 --- a/src/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector.php +++ b/src/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector.php @@ -87,6 +87,15 @@ PHP 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 + return ! $this->docBlockManipulator->hasTag($node, 'var'); + } + private function addPropertyToCollector(Property $property): void { $classNode = $property->getAttribute(AttributeKey::CLASS_NODE); @@ -105,18 +114,4 @@ PHP $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 efdb5448543..70413172c45 100644 --- a/src/Rector/Argument/ArgumentAdderRector.php +++ b/src/Rector/Argument/ArgumentAdderRector.php @@ -123,6 +123,31 @@ PHP 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 @@ PHP } } - /** - * @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 @@ PHP } /** - * @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); + 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); } - 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); + $classMethod->params[$position] = $param; } } diff --git a/src/Rector/Argument/ArgumentRemoverRector.php b/src/Rector/Argument/ArgumentRemoverRector.php index d656cc30c06..5eccab1212a 100644 --- a/src/Rector/Argument/ArgumentRemoverRector.php +++ b/src/Rector/Argument/ArgumentRemoverRector.php @@ -124,16 +124,6 @@ PHP } } - /** - * @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 @@ PHP 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 7a233d1d012..cc551c8b8ff 100644 --- a/src/Rector/ClassMethod/AddMethodParentCallRector.php +++ b/src/Rector/ClassMethod/AddMethodParentCallRector.php @@ -107,6 +107,22 @@ PHP 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 @@ PHP 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 b18265d314b..12855e4922a 100644 --- a/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php +++ b/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php @@ -131,41 +131,6 @@ PHP return null; } - private function shouldSkipPropertyFetch(PropertyFetch $propertyFetch, ObjectType $objectType): bool - { - if (! $this->isObjectType($propertyFetch->var, $objectType)) { - return true; - } - - if (! $this->propertyFetchManipulator->isMagicOnType($propertyFetch, $objectType)) { - return true; - } - - // $this->value = $value - return $this->propertyFetchManipulator->isPropertyToSelf($propertyFetch); - } - - private function createMethodCallNodeFromPropertyFetchNode( - PropertyFetch $propertyFetch, - string $method - ): MethodCall { - /** @var Variable $variableNode */ - $variableNode = $propertyFetch->var; - - return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetch)]); - } - - private function createMethodCallNodeFromAssignNode( - 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) { @@ -188,4 +153,39 @@ PHP return null; } + + private function shouldSkipPropertyFetch(PropertyFetch $propertyFetch, ObjectType $objectType): bool + { + if (! $this->isObjectType($propertyFetch->var, $objectType)) { + return true; + } + + if (! $this->propertyFetchManipulator->isMagicOnType($propertyFetch, $objectType)) { + return true; + } + + // $this->value = $value + return $this->propertyFetchManipulator->isPropertyToSelf($propertyFetch); + } + + private function createMethodCallNodeFromAssignNode( + PropertyFetch $propertyFetch, + Node $node, + string $method + ): MethodCall { + /** @var Variable $variableNode */ + $variableNode = $propertyFetch->var; + + return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetch), $node]); + } + + private function createMethodCallNodeFromPropertyFetchNode( + PropertyFetch $propertyFetch, + string $method + ): MethodCall { + /** @var Variable $variableNode */ + $variableNode = $propertyFetch->var; + + return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetch)]); + } } diff --git a/src/Rector/MethodBody/FluentReplaceRector.php b/src/Rector/MethodBody/FluentReplaceRector.php index bb89aa3c3cd..455d93d6b5c 100644 --- a/src/Rector/MethodBody/FluentReplaceRector.php +++ b/src/Rector/MethodBody/FluentReplaceRector.php @@ -95,36 +95,6 @@ PHP 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 @@ PHP 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/MethodCall/MethodCallToReturnRector.php b/src/Rector/MethodCall/MethodCallToReturnRector.php index e6b1bfe5be7..aec56e4e0a2 100644 --- a/src/Rector/MethodCall/MethodCallToReturnRector.php +++ b/src/Rector/MethodCall/MethodCallToReturnRector.php @@ -6,6 +6,7 @@ namespace Rector\Rector\MethodCall; use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Return_; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; @@ -77,7 +78,7 @@ PHP */ public function getNodeTypes(): array { - return [Node\Stmt\Expression::class]; + return [Expression::class]; } /** diff --git a/src/Rector/Namespace_/PseudoNamespaceToNamespaceRector.php b/src/Rector/Namespace_/PseudoNamespaceToNamespaceRector.php index 6ee73c9aaed..17f13c0f3e8 100644 --- a/src/Rector/Namespace_/PseudoNamespaceToNamespaceRector.php +++ b/src/Rector/Namespace_/PseudoNamespaceToNamespaceRector.php @@ -134,6 +134,36 @@ PHP 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 @@ PHP 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 3f40f6d207d..c7800106ae0 100644 --- a/src/Rector/Property/InjectAnnotationClassRector.php +++ b/src/Rector/Property/InjectAnnotationClassRector.php @@ -147,6 +147,32 @@ PHP 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 @@ PHP 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 0bb0af7f791..23dd9a670bb 100644 --- a/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php +++ b/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php @@ -87,7 +87,7 @@ PHP /** @var Namespace_[] $namespaceNodes */ $namespaceNodes = $this->betterNodeFinder->findInstanceOf($nodes, Namespace_::class); - if (count($namespaceNodes)) { + if (count($namespaceNodes) > 0) { $this->processNamespaceNodes($smartFileInfo, $namespaceNodes, $nodes, $shouldDelete); } else { $this->processNodesWithoutNamespace($nodes, $smartFileInfo, $shouldDelete); @@ -99,34 +99,24 @@ PHP } /** - * @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; + } + + $classShortName = $this->classNaming->getShortName($className); + if ($smartFileInfo->getBasenameWithoutSuffix() === $classShortName) { + return false; } } - 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'; + return true; } /** @@ -170,27 +160,6 @@ PHP } } - /** - * @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 */ @@ -215,11 +184,7 @@ PHP $fileDestination = $this->createClassLikeFileDestination($node, $smartFileInfo); - if ($declareNode) { - $nodes = [$declareNode, $node]; - } else { - $nodes = [$node]; - } + $nodes = $declareNode !== null ? [$declareNode, $node] : [$node]; // has file changed? if ($shouldDelete) { @@ -229,4 +194,35 @@ PHP } } } + + /** + * @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 a96159e9a9b..09ae71c65bc 100644 --- a/src/Rector/Typehint/ParentTypehintedArgumentRector.php +++ b/src/Rector/Typehint/ParentTypehintedArgumentRector.php @@ -107,29 +107,6 @@ PHP return null; } - /** - * @param string[] $parametersToTypes - */ - private function processClassMethodNodeWithTypehints(ClassMethod $classMethod, array $parametersToTypes): void - { - /** @var Param $param */ - foreach ($classMethod->params as $param) { - foreach ($parametersToTypes as $parameter => $type) { - $parameter = ltrim($parameter, '$'); - - if (! $this->isName($param, $parameter)) { - continue; - } - - if ($type === '') { // remove type - $param->type = null; - } else { - $param->type = $this->staticTypeMapper->mapStringToPhpParserNode($type); - } - } - } - } - /** * @param string[][] $methodToArgumentToTypes */ @@ -144,4 +121,23 @@ PHP return; } } + + /** + * @param string[] $parametersToTypes + */ + private function processClassMethodNodeWithTypehints(ClassMethod $classMethod, array $parametersToTypes): void + { + /** @var Param $param */ + foreach ($classMethod->params as $param) { + foreach ($parametersToTypes as $parameter => $type) { + $parameter = ltrim($parameter, '$'); + + if (! $this->isName($param, $parameter)) { + continue; + } + + $param->type = $type === '' ? null : $this->staticTypeMapper->mapStringToPhpParserNode($type); + } + } + } } diff --git a/src/Standalone/RectorStandaloneRunner.php b/src/Standalone/RectorStandaloneRunner.php index af0d34c7800..cb699b14455 100644 --- a/src/Standalone/RectorStandaloneRunner.php +++ b/src/Standalone/RectorStandaloneRunner.php @@ -75,7 +75,7 @@ final class RectorStandaloneRunner $phpFileInfos = $this->findFilesInSource($source); $this->runRectorOnFileInfos($phpFileInfos); - if ($isQuietMode === false) { + if (! $isQuietMode) { $this->reportErrors(); } @@ -84,6 +84,26 @@ final class RectorStandaloneRunner 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,6 +134,28 @@ final class RectorStandaloneRunner $stubLoader->loadStubs(); } + /** + * @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); + } + private function reportErrors(): void { /** @var ErrorAndDiffCollector $errorAndDiffCollector */ @@ -124,26 +166,6 @@ final class RectorStandaloneRunner $consoleOutputFormatter->report($errorAndDiffCollector); } - /** - * @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; - } - private function finish(): void { /** @var FinishingExtensionRunner $finishingExtensionRunner */ @@ -175,26 +197,4 @@ final class RectorStandaloneRunner '--' . 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 cabcaf2f54a..ca6e7414f61 100644 --- a/src/Testing/PHPUnit/AbstractRectorTestCase.php +++ b/src/Testing/PHPUnit/AbstractRectorTestCase.php @@ -165,28 +165,6 @@ abstract class AbstractRectorTestCase extends AbstractGenericRectorTestCase $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,22 +201,6 @@ abstract class AbstractRectorTestCase extends AbstractGenericRectorTestCase $this->bootKernelWithConfigs(RectorKernel::class, [$configFileTempPath]); } - private function configureEnabledRectors(EnabledRectorsProvider $enabledRectorsProvider): void - { - foreach ($this->getCurrentTestRectorClassesWithConfiguration() as $rectorClass => $configuration) { - $enabledRectorsProvider->addEnabledRector($rectorClass, (array) $configuration); - } - } - - private function configurePhpVersionFeatures(): void - { - if ($this->getPhpVersion() === '') { - return; - } - - $this->setParameter(Option::PHP_VERSION_FEATURES, $this->getPhpVersion()); - } - private function getConfigFor3rdPartyTest(): string { if ($this->provideConfig() !== '') { @@ -255,4 +217,42 @@ abstract class AbstractRectorTestCase extends AbstractGenericRectorTestCase return $configFileTempPath; } + + private function configureEnabledRectors(EnabledRectorsProvider $enabledRectorsProvider): void + { + foreach ($this->getCurrentTestRectorClassesWithConfiguration() as $rectorClass => $configuration) { + $enabledRectorsProvider->addEnabledRector($rectorClass, (array) $configuration); + } + } + + private function configurePhpVersionFeatures(): void + { + if ($this->getPhpVersion() === '') { + return; + } + + $this->setParameter(Option::PHP_VERSION_FEATURES, $this->getPhpVersion()); + } + + 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); + } + } } diff --git a/utils/DocumentationGenerator/src/OutputFormatter/DumpRectors/MarkdownDumpRectorsOutputFormatter.php b/utils/DocumentationGenerator/src/OutputFormatter/DumpRectors/MarkdownDumpRectorsOutputFormatter.php index 82d870023f9..eca92cff4c0 100644 --- a/utils/DocumentationGenerator/src/OutputFormatter/DumpRectors/MarkdownDumpRectorsOutputFormatter.php +++ b/utils/DocumentationGenerator/src/OutputFormatter/DumpRectors/MarkdownDumpRectorsOutputFormatter.php @@ -92,6 +92,39 @@ final class MarkdownDumpRectorsOutputFormatter implements DumpRectorsOutputForma } } + /** + * @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 @@ final class MarkdownDumpRectorsOutputFormatter implements DumpRectorsOutputForma 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 @@ final class MarkdownDumpRectorsOutputFormatter implements DumpRectorsOutputForma $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 fd157fb288a..7f2a87fc372 100644 --- a/utils/RectorGenerator/src/Command/CreateRectorCommand.php +++ b/utils/RectorGenerator/src/Command/CreateRectorCommand.php @@ -121,6 +121,39 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt 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[] */ @@ -208,6 +241,26 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt )); } + /** + * @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); + } + /** * @param mixed[] $variables */ @@ -216,39 +269,6 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt return str_replace(array_keys($variables), array_values($variables), $content); } - /** - * @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(); - } - /** * @param string[] $sections */ @@ -267,26 +287,6 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt 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';