Merge branch 'master' of github.com:rectorphp/rector into previousStatementRewrite

This commit is contained in:
Jeroen Smit 2019-11-01 15:55:46 +01:00
commit c41737e3a2
210 changed files with 4634 additions and 4227 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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';

View File

@ -47,8 +47,7 @@ if ($errors === []) {
}
foreach ($errors as $error) {
echo $error;
echo PHP_EOL;
echo $error . PHP_EOL;
}
exit(ShellCode::ERROR);

View File

@ -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"

View File

@ -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'

View File

@ -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) {

View File

@ -14,3 +14,6 @@ services:
PHPStan\PhpDocParser\Parser\PhpDocParser:
alias: 'Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser'
Doctrine\Common\Annotations\Reader:
alias: 'Doctrine\Common\Annotations\AnnotationReader'

View File

@ -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');

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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');
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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');
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\Exception;
use Exception;
final class TooLongException extends Exception
{
}

View File

@ -123,18 +123,15 @@ PHP
return $this->createAnonymousFunction($classMethod, $objectVariable);
}
/**
* @param Param[] $params
* @return Arg[]
*/
private function convertParamsToArgs(array $params): array
private function shouldSkipArray(Array_ $array): bool
{
$args = [];
foreach ($params as $key => $param) {
$args[$key] = new Arg($param->var);
// callback is exactly "[$two, 'items']"
if (count($array->items) !== 2) {
return true;
}
return $args;
// can be totally empty in case of "[, $value]"
return $array->items[0] === null;
}
/**
@ -171,17 +168,6 @@ 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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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'];

View File

@ -0,0 +1,49 @@
<?php
namespace Rector\CodeQuality\Tests\Rector\Class_\CompleteDynamicPropertiesRector\Fixture;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor;
use PhpParser\NodeVisitorAbstract;
class SkipAnonymousClass
{
/**
* @param Node|Node[] $nodes
*/
public function traverseNodesWithCallable($nodes, callable $callable): void
{
if (! is_array($nodes)) {
$nodes = $nodes ? [$nodes] : [];
}
$nodeTraverser = new NodeTraverser();
$nodeTraverser->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);
}
};
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Rector\CodeQuality\Tests\Rector\Concat\JoinStringConcatRector\Fixture;
class SkipLongerThan120
{
public function run()
{
$name = 'Adds $uuid property to entities, that already have $id with integer type.' .
'Require for step-by-step migration from int to uuid. ' .
'In following step it should be renamed to $id and replace it';
}
}

View File

@ -21,6 +21,7 @@ final class JoinStringConcatRectorTest extends AbstractRectorTestCase
public function provideDataForTest(): Iterator
{
yield [__DIR__ . '/Fixture/fixture.php.inc'];
yield [__DIR__ . '/Fixture/skip_longer_than_120.php.inc'];
}
protected function getRectorClass(): string

View File

@ -0,0 +1,45 @@
<?php
namespace Rector\CodeQuality\Tests\Rector\If_\RemoveAlwaysTrueConditionSetInConstructorRector\Fixture;
final class FixStaticArray
{
private $value;
public function __construct()
{
$this->value = [5];
}
public function go()
{
if ($this->value) {
$maybe = 'yes';
return 'she says ' . $maybe;
}
}
}
?>
-----
<?php
namespace Rector\CodeQuality\Tests\Rector\If_\RemoveAlwaysTrueConditionSetInConstructorRector\Fixture;
final class FixStaticArray
{
private $value;
public function __construct()
{
$this->value = [5];
}
public function go()
{
$maybe = 'yes';
return 'she says ' . $maybe;
}
}
?>

View File

@ -0,0 +1,26 @@
<?php
namespace Rector\CodeQuality\Tests\Rector\If_\RemoveAlwaysTrueConditionSetInConstructorRector\Fixture;
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
final class SkipAfterOverriden
{
/**
* @var bool
*/
private $areListenerClassesLoaded = false;
public function __construct()
{
}
public function getListenerClassesToEventsToMethods(): array
{
if ($this->areListenerClassesLoaded) {
return $this->listenerClassesToEvents;
}
$this->areListenerClassesLoaded = true;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Rector\CodeQuality\Tests\Rector\If_\RemoveAlwaysTrueConditionSetInConstructorRector\Fixture;
final class SkipArray
{
private $value;
public function __construct(array $value)
{
$this->value = $value;
}
public function go()
{
if ($this->value) {
$maybe = 'yes';
return 'she says ' . $maybe;
}
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\CodeQuality\Tests\Rector\If_\RemoveAlwaysTrueConditionSetInConstructorRector\Fixture;
final class SkipNullableSet
{
/**
* @var mixed[]|null
*/
private $callback;
/**
* @param mixed[]|null $callback
*/
public function __construct(?array $callback)
{
$this->callback = $callback;
}
public function __toString()
{
$contentItems = [];
if ($this->callback) {
$contentItems['callback'] = 5;
}
return '...';
}
}

View File

@ -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

View File

@ -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);
});

View File

@ -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] ?? [];
}
}

View File

@ -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, '\\');
}
}

View File

@ -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 = [];
}
}

View File

@ -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;
}
}

View File

@ -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');
});
}
}

View File

@ -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');
}
}

View File

@ -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);
}
}

View File

@ -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;
);
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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, '#(?<start>[^\"])(?<hash>____\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, '#(?<start>[^\"])(?<hash>____\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);
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;
use PhpParser\Node;
class PreventDuplication
{
public function getNodeTypes(): array
{
return [Node\Stmt\Expression::class];
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node;
class PreventDuplication
{
public function getNodeTypes(): array
{
return [Expression::class];
}
}
?>

View File

@ -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'];

View File

@ -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;
}
}

View File

@ -126,7 +126,7 @@ final class DoctrineEntityManipulator
}
}
if ($shouldUpdate === false) {
if (! $shouldUpdate) {
return;
}

View File

@ -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)
);
}
}

View File

@ -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');
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -76,7 +76,7 @@ PHP
return null;
}
if ($conditionStaticType->getValue() !== true) {
if (! $conditionStaticType->getValue()) {
return null;
}

View File

@ -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)]);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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_;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -78,7 +78,7 @@ PHP
}
$hasEntityGetIdMethodCall = $this->hasEntityGetIdMethodCall($node);
if ($hasEntityGetIdMethodCall === false) {
if (! $hasEntityGetIdMethodCall) {
return null;
}

View File

@ -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();
}
}
}

View File

@ -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);
});
}
}

View File

@ -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;
});
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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:(?<ruleClass>\w+),(?<ruleAttribute>\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:(?<ruleClass>\w+),(?<ruleAttribute>\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;
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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') {

View File

@ -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. <ClassType>::$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. <ClassType>::$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;
}
}

View File

@ -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+#');
}
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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]);
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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, ''));
}
}

View File

@ -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);
}
}

Some files were not shown because too many files have changed in this diff Show More