Skip to content

Commit

Permalink
Skip parent type with required ctor (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba authored Jan 7, 2025
1 parent 588c6c6 commit 48d4b77
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 6 deletions.
54 changes: 48 additions & 6 deletions src/Rules/Symfony/RequiredOnlyInAbstractRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use Symplify\PHPStanRules\Enum\SymfonyRuleIdentifier;
Expand All @@ -15,31 +17,51 @@
/**
* @see \Symplify\PHPStanRules\Tests\Rules\Symfony\RequiredOnlyInAbstractRule\RequiredOnlyInAbstractRuleTest
*
* @implements Rule<Class_>
* @implements Rule<InClassNode>
*/
final class RequiredOnlyInAbstractRule implements Rule
{
/**
* @var string
*/
public const ERROR_MESSAGE = '#@required is reserved exclusively for abstract classes. For the rest of classes, use clean constructor injection';
public const ERROR_MESSAGE = '#Symfony @required or #[Required] is reserved exclusively for abstract classes. For the rest of classes, use clean constructor injection';

/**
* Magic parent types that require constructor internally,
* so @required on final class is allowed
*
* @var string[]
*/
private const SKIPPED_PARENT_TYPES = [
'Doctrine\ODM\MongoDB\Repository\DocumentRepository',
];

public function getNodeType(): string
{
return Class_::class;
return InClassNode::class;
}

/**
* @param Class_ $node
* @param InClassNode $node
*/
public function processNode(Node $node, Scope $scope): array
{
foreach ($node->getMethods() as $classMethod) {
$originalNode = $node->getOriginalNode();
if (! $originalNode instanceof Class_) {
return [];
}

if ($this->shouldSkipClass($scope)) {
return [];
}

$class = $originalNode;
foreach ($class->getMethods() as $classMethod) {
if (! SymfonyRequiredMethodAnalyzer::detect($classMethod)) {
continue;
}

if ($node->isAbstract()) {
if ($class->isAbstract()) {
continue;
}

Expand All @@ -53,4 +75,24 @@ public function processNode(Node $node, Scope $scope): array

return [];
}

private function shouldSkipClass(Scope $scope): bool
{
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}

if ($classReflection->isAbstract()) {
return true;
}

foreach (self::SKIPPED_PARENT_TYPES as $skippedParentType) {
if ($classReflection->isSubclassOf($skippedParentType)) {
return true;
}
}

return false;
}
}
11 changes: 11 additions & 0 deletions stubs/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Doctrine\ODM\MongoDB\Repository;

if (class_exists('Doctrine\ODM\MongoDB\Repository\DocumentRepository')) {
return;
}

abstract class DocumentRepository
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Symplify\PHPStanRules\Tests\Rules\Symfony\RequiredOnlyInAbstractRule\Fixture;

final class SkipParentDocumentRepository extends \Doctrine\ODM\MongoDB\Repository\DocumentRepository
{
/**
* @required
*/
public function autowire()
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public static function provideData(): Iterator
]]];

yield [__DIR__ . '/Fixture/SkipAbstractClass.php', []];
yield [__DIR__ . '/Fixture/SkipParentDocumentRepository.php', []];
}

protected function getRule(): Rule
Expand Down

0 comments on commit 48d4b77

Please sign in to comment.