mirror of
https://github.com/rectorphp/rector.git
synced 2025-03-19 23:09:43 +01:00
Merge branch 'master' of github.com:rectorphp/rector into previousStatementRewrite
This commit is contained in:
commit
c41737e3a2
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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';
|
||||
|
||||
|
@ -47,8 +47,7 @@ if ($errors === []) {
|
||||
}
|
||||
|
||||
foreach ($errors as $error) {
|
||||
echo $error;
|
||||
echo PHP_EOL;
|
||||
echo $error . PHP_EOL;
|
||||
}
|
||||
|
||||
exit(ShellCode::ERROR);
|
@ -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"
|
||||
|
34
ecs.yaml
34
ecs.yaml
@ -24,29 +24,6 @@ services:
|
||||
- 'getNodeTypes'
|
||||
- 'refactor'
|
||||
|
||||
Symplify\CodingStandard\Sniffs\DependencyInjection\NoClassInstantiationSniff:
|
||||
extra_allowed_classes:
|
||||
- 'PHPStan\Type\*'
|
||||
- '*Type'
|
||||
- 'PHPStan\Analyser\Scope'
|
||||
- 'PhpParser\NodeVisitor\NameResolver'
|
||||
- 'PhpParser\Node\*'
|
||||
- '*Data'
|
||||
- '*Recipe'
|
||||
- '*ValueObject'
|
||||
- 'PhpParser\Comment'
|
||||
- 'PhpParser\Lexer'
|
||||
- 'PhpParser\Comment\Doc'
|
||||
- 'PhpParser\NodeTraverser'
|
||||
- 'Rector\Reporting\FileDiff'
|
||||
- 'Rector\RectorDefinition\*'
|
||||
- 'Rector\Application\Error'
|
||||
- 'Rector\DependencyInjection\Loader\*'
|
||||
- 'Symplify\PackageBuilder\*'
|
||||
- 'Symfony\Component\Console\Input\*Input'
|
||||
- 'PHPStan\Analyser\NameScope'
|
||||
- 'PHPStan\Rules\RuleErrors\RuleError*'
|
||||
|
||||
Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer:
|
||||
extra_skipped_classes:
|
||||
- 'PhpParser\PrettyPrinter\Standard'
|
||||
@ -70,6 +47,9 @@ parameters:
|
||||
- 'src/Rector/AbstractRector.php'
|
||||
|
||||
skip:
|
||||
# rather useless
|
||||
Symplify\CodingStandard\Sniffs\DependencyInjection\NoClassInstantiationSniff: ~
|
||||
|
||||
PHP_CodeSniffer\Standards\PSR2\Sniffs\Methods\MethodDeclarationSniff.Underscore: ~
|
||||
Symplify\CodingStandard\Sniffs\Architecture\DuplicatedClassShortNameSniff: ~
|
||||
# skip temporary due to missing "import" feature in PhpStorm
|
||||
@ -183,20 +163,12 @@ parameters:
|
||||
- 'packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php'
|
||||
- 'packages/PhpSpecToPHPUnit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php'
|
||||
|
||||
Symplify\CodingStandard\Sniffs\DependencyInjection\NoClassInstantiationSniff:
|
||||
# 3rd party api
|
||||
- 'src/PhpParser/Node/Value/ValueResolver.php'
|
||||
|
||||
PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer:
|
||||
- 'packages/BetterPhpDocParser/tests/PhpDocInfo/PhpDocInfo/PhpDocInfoTest.php'
|
||||
# intentional "assertEquals()"
|
||||
- 'tests/PhpParser/Node/NodeFactoryTest.php'
|
||||
- '*TypeResolverTest.php'
|
||||
|
||||
Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer:
|
||||
# buggy with PHP heredoc
|
||||
- 'packages/SOLID/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php'
|
||||
|
||||
Symplify\CodingStandard\Sniffs\Commenting\AnnotationTypeExistsSniff:
|
||||
- '*PhpDocNodeFactory.php'
|
||||
- '*AnnotationReader.php'
|
||||
|
@ -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) {
|
||||
|
@ -14,3 +14,6 @@ services:
|
||||
|
||||
PHPStan\PhpDocParser\Parser\PhpDocParser:
|
||||
alias: 'Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser'
|
||||
|
||||
Doctrine\Common\Annotations\Reader:
|
||||
alias: 'Doctrine\Common\Annotations\AnnotationReader'
|
||||
|
@ -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');
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
11
packages/CodeQuality/src/Exception/TooLongException.php
Normal file
11
packages/CodeQuality/src/Exception/TooLongException.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodeQuality\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
final class TooLongException extends Exception
|
||||
{
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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'];
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -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';
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 '...';
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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] ?? [];
|
||||
}
|
||||
}
|
||||
|
@ -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, '\\');
|
||||
}
|
||||
}
|
||||
|
@ -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 = [];
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -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'];
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ final class DoctrineEntityManipulator
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldUpdate === false) {
|
||||
if (! $shouldUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ PHP
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($conditionStaticType->getValue() !== true) {
|
||||
if (! $conditionStaticType->getValue()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -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)]);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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_;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ PHP
|
||||
}
|
||||
|
||||
$hasEntityGetIdMethodCall = $this->hasEntityGetIdMethodCall($node);
|
||||
if ($hasEntityGetIdMethodCall === false) {
|
||||
if (! $hasEntityGetIdMethodCall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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') {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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+#');
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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, ''));
|
||||
}
|
||||
}
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user