diff --git a/packages/better-php-doc-parser/src/PhpDocNodeFactory/Doctrine/Property_/JoinTablePhpDocNodeFactory.php b/packages/better-php-doc-parser/src/PhpDocNodeFactory/Doctrine/Property_/JoinTablePhpDocNodeFactory.php index bdfa6e187da9..0f28afb68f81 100644 --- a/packages/better-php-doc-parser/src/PhpDocNodeFactory/Doctrine/Property_/JoinTablePhpDocNodeFactory.php +++ b/packages/better-php-doc-parser/src/PhpDocNodeFactory/Doctrine/Property_/JoinTablePhpDocNodeFactory.php @@ -116,7 +116,12 @@ private function createJoinColumnTagValues(string $annotationContent, JoinTable throw new ShouldNotHappenException(); } - $joinColumns = $joinTable->{$type}; + if ($type === self::JOIN_COLUMNS) { + $joinColumns = $joinTable->joinColumns; + } else { + $joinColumns = $joinTable->inverseJoinColumns; + } + foreach ($joinColumns as $key => $joinColumn) { $subAnnotation = $joinColumnContents[$key]; diff --git a/phpstan.neon b/phpstan.neon index e1532b47fecc..966c1805f36d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,6 +10,7 @@ includes: rules: # should be fixed in next part of symplify CS release - Symplify\CodingStandard\Rules\NoClassWithStaticMethodWithoutStaticNameRule + - Symplify\CodingStandard\Rules\SeeAnnotationToTestRule parameters: level: max @@ -19,6 +20,8 @@ parameters: max_cognitive_complexity: 9 # default: 8 parent_classes: - Rector + required_see_types: + - PHPStan\Rules\Rule # to allow installing with various phsptan versions without reporting old errors here reportUnmatchedIgnoredErrors: false @@ -145,7 +148,6 @@ parameters: # bugs - '#Parameter \#1 \$items of class PhpParser\\Node\\Expr\\Array_ constructor expects array, array given#' - - '#Method Rector\\BetterPhpDocParser\\Tests\\PhpDocParser\\AbstractPhpDocInfoTest\:\:parseFileAndGetFirstNodeOfType\(\) should return PhpParser\\Node but returns PhpParser\\Node\|null#' # known value - '#Method Rector\\StrictCodeQuality\\Rector\\Stmt\\VarInlineAnnotationToAssertRector\:\:findVariableByName\(\) should return PhpParser\\Node\\Expr\\Variable\|null but returns PhpParser\\Node\|null#' @@ -227,8 +229,6 @@ parameters: - '#Parameter \#2 \$name of method Rector\\Core\\Rector\\AbstractRector\:\:isVariableName\(\) expects string, string\|null given#' - - '#Parameter \#1 \$node of method Rector\\PostRector\\Collector\\NodesToAddCollector\:\:wrapToExpression\(\) expects PhpParser\\Node\\Expr\|PhpParser\\Node\\Stmt, PhpParser\\Node given#' - # mixed - '#Property Rector\\Polyfill\\ValueObject\\BinaryToVersionCompareCondition\:\:\$expectedValue has no typehint specified#' # node finder @@ -281,10 +281,13 @@ parameters: path: rules/php-spec-to-phpunit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php - - message: "#^Class cognitive complexity for \"EregToPcreTransformer\" is 77, keep it under 50$#" - count: 1 + message: "#^Class cognitive complexity for \"EregToPcreTransformer\" is (.*?), keep it under 50$#" path: rules/php70/src/EregToPcreTransformer.php + - + message: "#Use explicit property fetch names over dynamic#" + path: packages/doctrine-annotation-generated/src/PhpDocNode/ConstantReferenceIdentifierRestorer.php + - "#^Cognitive complexity for \"Rector\\\\Php70\\\\EregToPcreTransformer\\:\\:(.*?)\" is (.*?), keep it under 9$#" - '#Use explicit return value over magic &reference#' @@ -302,3 +305,7 @@ parameters: - '#Class "Rector\\Utils\\DoctrineAnnotationParserSyncer\\Rector\\(.*?)" is missing @see annotation with test case class reference#' - '#Method Rector\\Utils\\DocumentationGenerator\\Node\\NodeClassProvider\:\:getNodeClasses\(\) should return array but returns array#' + + - '#Parameter \#1 \$node of method Rector\\PostRector\\Collector\\NodesToAddCollector\:\:wrapToExpression\(\) expects PhpParser\\Node\\Expr\|PhpParser\\Node\\Stmt, PhpParser\\Node given#' + - '#Access to an undefined property PhpParser\\Node\\Expr\:\:\$class#' + - '#Method Rector\\BetterPhpDocParser\\Tests\\PhpDocParser\\AbstractPhpDocInfoTest\:\:parseFileAndGetFirstNodeOfType\(\) should return PhpParser\\Node but returns PhpParser\\Node\|null#' diff --git a/utils/phpstan-extensions/config/phpstan-extensions.neon b/utils/phpstan-extensions/config/phpstan-extensions.neon index 704aa8e1707d..a1d1decbd2b1 100644 --- a/utils/phpstan-extensions/config/phpstan-extensions.neon +++ b/utils/phpstan-extensions/config/phpstan-extensions.neon @@ -1,21 +1,4 @@ -parameters: - rector: - required_see_types: - - Rector\Core\Contract\Rector\PhpRectorInterface - - PHPStan\Rules\Rule - -parametersSchema: - rector: structure([ - required_see_types: arrayOf(string()) - ]) - services: - - - class: Rector\PHPStanExtensions\Rule\SeeAnnotationToTestRule - tags: [phpstan.rules.rule] - arguments: - requiredSeeTypes: %rector.required_see_types% - - class: Rector\PHPStanExtensions\Rule\PreventParentMethodVisibilityOverrideRule tags: [phpstan.rules.rule] @@ -27,21 +10,28 @@ services: - Rector\PHPStanExtensions\Utils\PHPStanValueResolver # $node->getAttribute($1) => Type|null by $1 - - { class: Rector\PHPStanExtensions\ReturnTypeExtension\GetAttributeReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } + - + class: Rector\PHPStanExtensions\ReturnTypeExtension\GetAttributeReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # ParsedNodesByType->getNodesByType($1) => $1[] - - { class: Rector\PHPStanExtensions\ReturnTypeExtension\ParsedNodesByTypeReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } + - + class: Rector\PHPStanExtensions\ReturnTypeExtension\ParsedNodesByTypeReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # $nameResolver->getName() => in some cases always string - - { class: Rector\PHPStanExtensions\ReturnTypeExtension\NameResolverReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } + - + class: Rector\PHPStanExtensions\ReturnTypeExtension\NameResolverReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # $nameResolverTrait->getName() => in some cases always string - - { class: Rector\PHPStanExtensions\ReturnTypeExtension\NameResolverTraitReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } - - # $betterNodeFinder->findByInstance(..., $1) => $1[] - - { class: Rector\PHPStanExtensions\ReturnTypeExtension\BetterNodeFinderReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } + - + class: Rector\PHPStanExtensions\ReturnTypeExtension\NameResolverTraitReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # $phpDocInfo->getByType($1) => Type|null by $1 - - { class: Rector\PHPStanExtensions\ReturnTypeExtension\PhpDocInfoGetByTypeReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } + - + class: Rector\PHPStanExtensions\ReturnTypeExtension\PhpDocInfoGetByTypeReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # $builder->getNode() => Type - @@ -53,3 +43,13 @@ services: - class: Rector\PHPStanExtensions\ReturnTypeExtension\Builder\ClassBuilderReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + # node finder + - + class: Rector\PHPStanExtensions\ReturnTypeExtension\NodeFinder\FindFirstInstanceOfReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + # $betterNodeFinder->findByInstance(..., $1) => $1[] + - + class: Rector\PHPStanExtensions\ReturnTypeExtension\NodeFinder\FindInstanceOfReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/utils/phpstan-extensions/src/ReturnTypeExtension/NodeFinder/FindFirstInstanceOfReturnTypeExtension.php b/utils/phpstan-extensions/src/ReturnTypeExtension/NodeFinder/FindFirstInstanceOfReturnTypeExtension.php new file mode 100644 index 000000000000..26c1125e792c --- /dev/null +++ b/utils/phpstan-extensions/src/ReturnTypeExtension/NodeFinder/FindFirstInstanceOfReturnTypeExtension.php @@ -0,0 +1,68 @@ +getName(), + ['findFirstInstanceOf', 'findFirstParentInstanceOf', 'findFirstAncestorInstanceOf'], + true + ); + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + $secondArgumentNode = $methodCall->args[1]->value; + + // fallback + if ($this->shouldFallbackToResolvedType($secondArgumentNode)) { + return $returnType; + } + + /** @var ClassConstFetch $secondArgumentNode */ + $class = $secondArgumentNode->class->toString(); + + return new UnionType([new NullType(), new ObjectType($class)]); + } + + private function shouldFallbackToResolvedType(Expr $expr): bool + { + if (! $expr instanceof ClassConstFetch) { + return true; + } + + if (! $expr->class instanceof Name) { + return true; + } + + return (string) $expr->name !== 'class'; + } +} diff --git a/utils/phpstan-extensions/src/ReturnTypeExtension/BetterNodeFinderReturnTypeExtension.php b/utils/phpstan-extensions/src/ReturnTypeExtension/NodeFinder/FindInstanceOfReturnTypeExtension.php similarity index 89% rename from utils/phpstan-extensions/src/ReturnTypeExtension/BetterNodeFinderReturnTypeExtension.php rename to utils/phpstan-extensions/src/ReturnTypeExtension/NodeFinder/FindInstanceOfReturnTypeExtension.php index 06af7fa76c3f..26debcab885d 100644 --- a/utils/phpstan-extensions/src/ReturnTypeExtension/BetterNodeFinderReturnTypeExtension.php +++ b/utils/phpstan-extensions/src/ReturnTypeExtension/NodeFinder/FindInstanceOfReturnTypeExtension.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\PHPStanExtensions\ReturnTypeExtension; +namespace Rector\PHPStanExtensions\ReturnTypeExtension\NodeFinder; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\MethodCall; @@ -17,7 +17,7 @@ use PHPStan\Type\Type; use Rector\Core\PhpParser\Node\BetterNodeFinder; -final class BetterNodeFinderReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class FindInstanceOfReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function getClass(): string { diff --git a/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php b/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php deleted file mode 100644 index be666cdeee21..000000000000 --- a/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php +++ /dev/null @@ -1,146 +0,0 @@ -fileTypeMapper = $fileTypeMapper; - $this->broker = $broker; - $this->requiredSeeTypes = $requiredSeeTypes; - } - - public function getNodeType(): string - { - return Class_::class; - } - - /** - * @param Class_ $node - * @return string[] - */ - public function processNode(Node $node, Scope $scope): array - { - $classReflection = $this->matchClassReflection($node); - if ($classReflection === null) { - return []; - } - - if ($this->shouldSkipClassReflection($classReflection)) { - return []; - } - - $docComment = $node->getDocComment(); - if ($docComment === null) { - return [sprintf(self::ERROR_MESSAGE, $classReflection->getName())]; - } - - $resolvedPhpDoc = $this->resolvePhpDoc($scope, $classReflection, $docComment); - - /** @var PhpDocTagNode[] $seeTags */ - $seeTags = $resolvedPhpDoc->getPhpDocNode()->getTagsByName('@see'); - - if ($this->containsSeeTestCase($seeTags)) { - return []; - } - - return [sprintf(self::ERROR_MESSAGE, $classReflection->getName())]; - } - - private function shouldSkipClassReflection(ClassReflection $classReflection): bool - { - if ($classReflection->isAbstract()) { - return true; - } - - foreach ($this->requiredSeeTypes as $requiredSeeType) { - if ($classReflection->isSubclassOf($requiredSeeType)) { - return false; - } - } - - return true; - } - - private function matchClassReflection(Class_ $node): ?ClassReflection - { - if ($node->name === null) { - return null; - } - - $className = (string) $node->namespacedName; - if (! class_exists($className)) { - return null; - } - - return $this->broker->getClass($className); - } - - private function resolvePhpDoc(Scope $scope, ClassReflection $classReflection, Doc $doc): ResolvedPhpDocBlock - { - return $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $classReflection->getName(), - null, - null, - $doc->getText() - ); - } - - /** - * @param PhpDocTagNode[] $seeTags - */ - private function containsSeeTestCase(array $seeTags): bool - { - foreach ($seeTags as $seeTag) { - if (! $seeTag->value instanceof GenericTagValueNode) { - continue; - } - - if (is_a($seeTag->value->value, TestCase::class, true)) { - return true; - } - } - - return false; - } -} diff --git a/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/CorrectSeeRector.php b/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/CorrectSeeRector.php deleted file mode 100644 index 33f9acdb1404..000000000000 --- a/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/CorrectSeeRector.php +++ /dev/null @@ -1,11 +0,0 @@ -analyse([$filePath], $expectedErrorsWithLines); - } - - public function provideData(): Iterator - { - $errorMessage = sprintf(SeeAnnotationToTestRule::ERROR_MESSAGE, ClassMissingDocBlockRector::class); - yield [__DIR__ . '/Fixture/ClassMissingDocBlockRector.php', [[$errorMessage, 12]]]; - - $errorMessage = sprintf(SeeAnnotationToTestRule::ERROR_MESSAGE, ClassMissingSeeAnnotationRector::class); - yield [__DIR__ . '/Fixture/ClassMissingSeeAnnotationRector.php', [[$errorMessage, 15]]]; - - $errorMessage = sprintf(SeeAnnotationToTestRule::ERROR_MESSAGE, ClassSeeAnnotationSomewhereElseRector::class); - yield [__DIR__ . '/Fixture/ClassSeeAnnotationSomewhereElseRector.php', [[$errorMessage, 15]]]; - - yield [__DIR__ . '/Fixture/CorrectSeeRector.php', []]; - } - - protected function getRule(): Rule - { - return $this->getRuleFromConfig( - SeeAnnotationToTestRule::class, - __DIR__ . '/../../../config/phpstan-extensions.neon' - ); - } -}