From 1f63ed3e55553cff1e2ef4c579a29ae8308dd90d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 4 Oct 2021 23:11:10 +0200 Subject: [PATCH] Reworking trait scope run (#952) * try reworking trait scope run * load configs from extension installer * add trait type * load configs from extension installer in PHPStan internally in Rector too * [ci-review] Rector Rectify Co-authored-by: GitHub Action --- .../CommentRemover/CommentRemoverTest.php | 2 +- packages/Comments/CommentRemover.php | 2 +- .../Reflection/FamilyRelationsAnalyzer.php | 1 + .../PHPStanServicesFactory.php | 41 ++++++++++++++ .../NodeScopeAndMetadataDecorator.php | 1 + .../PropertyFetchTypeResolver.php | 13 ----- .../NodeTypeResolver/VariableTypeResolver.php | 15 ----- .../Collector/TraitNodeScopeCollector.php | 30 ---------- .../Scope/PHPStanNodeScopeResolver.php | 56 +++++++++++-------- .../Behavior/MultipleFilesChangedTrait.php | 6 +- phpstan-for-rector.neon | 9 --- phpstan.neon | 8 ++- .../Fixture/skip_mixed_scope.php.inc | 8 --- .../Fixture/json_body_trait.php.inc | 43 ++++++++++++++ .../Fixture/skip_trait.php.inc | 23 -------- .../PrivatizeFinalClassMethodRector.php | 4 ++ src/Stubs/DummyTraitClass.php | 12 ++++ 17 files changed, 150 insertions(+), 124 deletions(-) delete mode 100644 packages/NodeTypeResolver/PHPStan/Collector/TraitNodeScopeCollector.php create mode 100644 rules-tests/Php74/Rector/Property/TypedPropertyRector/Fixture/json_body_trait.php.inc delete mode 100644 rules-tests/Php74/Rector/Property/TypedPropertyRector/Fixture/skip_trait.php.inc create mode 100644 src/Stubs/DummyTraitClass.php diff --git a/packages-tests/Comments/CommentRemover/CommentRemoverTest.php b/packages-tests/Comments/CommentRemover/CommentRemoverTest.php index 6a6208b2827..40b79c8661f 100644 --- a/packages-tests/Comments/CommentRemover/CommentRemoverTest.php +++ b/packages-tests/Comments/CommentRemover/CommentRemoverTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\Comments\Tests\CommentRemover; +namespace Rector\Tests\Comments\CommentRemover; use Iterator; use Rector\Comments\CommentRemover; diff --git a/packages/Comments/CommentRemover.php b/packages/Comments/CommentRemover.php index 235502c5913..04f0e9deba2 100644 --- a/packages/Comments/CommentRemover.php +++ b/packages/Comments/CommentRemover.php @@ -11,7 +11,7 @@ use Rector\Comments\NodeTraverser\CommentRemovingNodeTraverser; use Rector\NodeTypeResolver\Node\AttributeKey; /** - * @see \Rector\Comments\Tests\CommentRemover\CommentRemoverTest + * @see \Rector\Tests\Comments\CommentRemover\CommentRemoverTest */ final class CommentRemover { diff --git a/packages/FamilyTree/Reflection/FamilyRelationsAnalyzer.php b/packages/FamilyTree/Reflection/FamilyRelationsAnalyzer.php index 9e370746bcf..7b2716d2df1 100644 --- a/packages/FamilyTree/Reflection/FamilyRelationsAnalyzer.php +++ b/packages/FamilyTree/Reflection/FamilyRelationsAnalyzer.php @@ -161,6 +161,7 @@ final class FamilyRelationsAnalyzer } } + /** @var string[] $ancestorNames */ return $ancestorNames; } diff --git a/packages/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php b/packages/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php index 49cc0b61ac6..2f3e7b2efbd 100644 --- a/packages/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php +++ b/packages/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php @@ -11,11 +11,14 @@ use PHPStan\Dependency\DependencyResolver; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\ContainerFactory; use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; +use PHPStan\ExtensionInstaller\GeneratedConfig; use PHPStan\File\FileHelper; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Configuration\Option; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider; +use ReflectionClass; use Symplify\PackageBuilder\Parameter\ParameterProvider; /** @@ -36,6 +39,9 @@ final class PHPStanServicesFactory $additionalConfigFiles[] = __DIR__ . '/../../../config/phpstan/static-reflection.neon'; $additionalConfigFiles[] = __DIR__ . '/../../../config/phpstan/better-infer.neon'; + $extensionConfigFiles = $this->resolveExtensionConfigs(); + $additionalConfigFiles = array_merge($additionalConfigFiles, $extensionConfigFiles); + $existingAdditionalConfigFiles = array_filter($additionalConfigFiles, 'file_exists'); $this->container = $containerFactory->create(sys_get_temp_dir(), $existingAdditionalConfigFiles, []); @@ -112,4 +118,39 @@ final class PHPStanServicesFactory { return $this->container->getByType(DynamicSourceLocatorProvider::class); } + + /** + * @return string[] + */ + private function resolveExtensionConfigs(): array + { + // same logic as in PHPStan for extension installed - https://github.com/phpstan/phpstan-src/blob/5956ec4f6cd09c8d7db9466ed4e7f25706f37a43/src/Command/CommandHelper.php#L195-L222 + if (! class_exists('PHPStan\ExtensionInstaller\GeneratedConfig')) { + return []; + } + + $reflectionClass = new ReflectionClass(GeneratedConfig::class); + $generatedConfigClassFileName = $reflectionClass->getFileName(); + if ($generatedConfigClassFileName === false) { + throw new ShouldNotHappenException(); + } + + $generatedConfigDirectory = dirname($generatedConfigClassFileName); + + $extensionConfigFiles = []; + + foreach (GeneratedConfig::EXTENSIONS as $extension) { + $fileNames = $extension['extra']['includes'] ?? []; + foreach ($fileNames as $fileName) { + $configFilePath = $generatedConfigDirectory . '/' . $extension['relative_install_path'] . '/' . $fileName; + if (! file_exists($configFilePath)) { + continue; + } + + $extensionConfigFiles[] = $configFilePath; + } + } + + return $extensionConfigFiles; + } } diff --git a/packages/NodeTypeResolver/NodeScopeAndMetadataDecorator.php b/packages/NodeTypeResolver/NodeScopeAndMetadataDecorator.php index 56396492c60..560227cfd22 100644 --- a/packages/NodeTypeResolver/NodeScopeAndMetadataDecorator.php +++ b/packages/NodeTypeResolver/NodeScopeAndMetadataDecorator.php @@ -57,6 +57,7 @@ final class NodeScopeAndMetadataDecorator $nodes = $nodeTraverser->traverse($nodes); $smartFileInfo = $file->getSmartFileInfo(); + $nodes = $this->phpStanNodeScopeResolver->processNodes($nodes, $smartFileInfo); $nodeTraverserForPreservingName = new NodeTraverser(); diff --git a/packages/NodeTypeResolver/NodeTypeResolver/PropertyFetchTypeResolver.php b/packages/NodeTypeResolver/NodeTypeResolver/PropertyFetchTypeResolver.php index e47e657a4fe..a17635116a4 100644 --- a/packages/NodeTypeResolver/NodeTypeResolver/PropertyFetchTypeResolver.php +++ b/packages/NodeTypeResolver/NodeTypeResolver/PropertyFetchTypeResolver.php @@ -7,7 +7,6 @@ namespace Rector\NodeTypeResolver\NodeTypeResolver; use PhpParser\Node; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Stmt\ClassLike; -use PhpParser\Node\Stmt\Trait_; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; @@ -17,7 +16,6 @@ use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; -use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector; use Symfony\Contracts\Service\Attribute\Required; /** @@ -29,7 +27,6 @@ final class PropertyFetchTypeResolver implements NodeTypeResolverInterface public function __construct( private NodeNameResolver $nodeNameResolver, - private TraitNodeScopeCollector $traitNodeScopeCollector, private ReflectionProvider $reflectionProvider ) { } @@ -61,16 +58,6 @@ final class PropertyFetchTypeResolver implements NodeTypeResolverInterface /** @var Scope|null $scope */ $scope = $node->getAttribute(AttributeKey::SCOPE); - - if (! $scope instanceof Scope) { - $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); - if ($classNode instanceof Trait_) { - /** @var string $traitName */ - $traitName = $classNode->getAttribute(AttributeKey::CLASS_NAME); - $scope = $this->traitNodeScopeCollector->getScopeForTrait($traitName); - } - } - if (! $scope instanceof Scope) { $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); // fallback to class, since property fetches are not scoped by PHPStan diff --git a/packages/NodeTypeResolver/NodeTypeResolver/VariableTypeResolver.php b/packages/NodeTypeResolver/NodeTypeResolver/VariableTypeResolver.php index 80cb11d017f..42fb551cffc 100644 --- a/packages/NodeTypeResolver/NodeTypeResolver/VariableTypeResolver.php +++ b/packages/NodeTypeResolver/NodeTypeResolver/VariableTypeResolver.php @@ -7,7 +7,6 @@ namespace Rector\NodeTypeResolver\NodeTypeResolver; use PhpParser\Node; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Param; -use PhpParser\Node\Stmt\Trait_; use PHPStan\Analyser\Scope; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -15,7 +14,6 @@ use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface; use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector; /** * @see \Rector\Tests\NodeTypeResolver\PerNodeTypeResolver\VariableTypeResolver\VariableTypeResolverTest @@ -29,7 +27,6 @@ final class VariableTypeResolver implements NodeTypeResolverInterface public function __construct( private NodeNameResolver $nodeNameResolver, - private TraitNodeScopeCollector $traitNodeScopeCollector, private PhpDocInfoFactory $phpDocInfoFactory ) { } @@ -85,18 +82,6 @@ final class VariableTypeResolver implements NodeTypeResolverInterface return $nodeScope; } - // is node in trait - $classLike = $variable->getAttribute(AttributeKey::CLASS_NODE); - if ($classLike instanceof Trait_) { - /** @var string $traitName */ - $traitName = $variable->getAttribute(AttributeKey::CLASS_NAME); - $traitNodeScope = $this->traitNodeScopeCollector->getScopeForTrait($traitName); - - if ($traitNodeScope !== null) { - return $traitNodeScope; - } - } - return $this->resolveFromParentNodes($variable); } diff --git a/packages/NodeTypeResolver/PHPStan/Collector/TraitNodeScopeCollector.php b/packages/NodeTypeResolver/PHPStan/Collector/TraitNodeScopeCollector.php deleted file mode 100644 index 38133f82aa6..00000000000 --- a/packages/NodeTypeResolver/PHPStan/Collector/TraitNodeScopeCollector.php +++ /dev/null @@ -1,30 +0,0 @@ - - */ - private array $scopeByTraitNodeHash = []; - - public function addForTrait(string $traitName, Scope $scope): void - { - // probably set from another class - if (isset($this->scopeByTraitNodeHash[$traitName])) { - return; - } - - $this->scopeByTraitNodeHash[$traitName] = $scope; - } - - public function getScopeForTrait(string $traitName): ?Scope - { - return $this->scopeByTraitNodeHash[$traitName] ?? null; - } -} diff --git a/packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php b/packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php index 34492e5fc68..eb4d5b62363 100644 --- a/packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php +++ b/packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php @@ -16,12 +16,12 @@ use PHPStan\AnalysedCodeException; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; +use PHPStan\Analyser\ScopeContext; use PHPStan\BetterReflection\Reflection\Exception\NotAnInterfaceReflection; use PHPStan\BetterReflection\Reflector\ClassReflector; use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; use PHPStan\Node\UnreachableStatementNode; -use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\Caching\Detector\ChangedFilesDetector; @@ -30,8 +30,8 @@ use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\PhpParser\Node\BetterNodeFinder; use Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator; use Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator; +use Rector\Core\Stubs\DummyTraitClass; use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector; use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor; use Symplify\PackageBuilder\Reflection\PrivatesAccessor; use Symplify\SmartFileSystem\SmartFileInfo; @@ -62,7 +62,6 @@ final class PHPStanNodeScopeResolver private ReflectionProvider $reflectionProvider, private RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor, private ScopeFactory $scopeFactory, - private TraitNodeScopeCollector $traitNodeScopeCollector, private PrivatesAccessor $privatesAccessor, private RenamedClassesSourceLocator $renamedClassesSourceLocator, private ParentAttributeSourceLocator $parentAttributeSourceLocator, @@ -81,26 +80,26 @@ final class PHPStanNodeScopeResolver $scope = $this->scopeFactory->createFromFile($smartFileInfo); // skip chain method calls, performance issue: https://github.com/phpstan/phpstan/issues/254 - $nodeCallback = function (Node $node, Scope $scope): void { - // traversing trait inside class that is using it scope (from referenced) - the trait traversed by Rector is different (directly from parsed file) - if ($scope->isInTrait()) { - // has just entereted trait, to avoid adding it for ever ynode - $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); - if ($parentNode instanceof Trait_) { - /** @var ClassReflection $classReflection */ - $classReflection = $scope->getTraitReflection(); - $traitName = $classReflection->getName(); + $nodeCallback = function (Node $node, MutatingScope $scope) use (&$nodeCallback): void { + if ($node instanceof Trait_) { + $traitName = $this->resolveClassName($node); - $this->traitNodeScopeCollector->addForTrait($traitName, $scope); - } + $traitReflectionClass = $this->reflectionProvider->getClass($traitName); + $scopeContext = $this->createDummyClassScopeContext($scope); + $traitScope = clone $scope; + $this->privatesAccessor->setPrivateProperty($traitScope, 'context', $scopeContext); + + $traitScope = $traitScope->enterTrait($traitReflectionClass); + + $this->nodeScopeResolver->processStmtNodes($node, $node->stmts, $traitScope, $nodeCallback); return; } // the class reflection is resolved AFTER entering to class node // so we need to get it from the first after this one if ($node instanceof Class_ || $node instanceof Interface_) { - /** @var Scope $scope */ + /** @var MutatingScope $scope */ $scope = $this->resolveClassOrInterfaceScope($node, $scope); } @@ -217,24 +216,25 @@ final class PHPStanNodeScopeResolver $nodeTraverser->traverse($nodes); } - private function resolveClassOrInterfaceScope(Class_ | Interface_ $classLike, Scope $scope): Scope - { + private function resolveClassOrInterfaceScope( + Class_ | Interface_ $classLike, + MutatingScope $mutatingScope + ): Scope { $className = $this->resolveClassName($classLike); // is anonymous class? - not possible to enter it since PHPStan 0.12.33, see https://github.com/phpstan/phpstan-src/commit/e87fb0ec26f9c8552bbeef26a868b1e5d8185e91 if ($classLike instanceof Class_ && Strings::match($className, self::ANONYMOUS_CLASS_START_REGEX)) { - $classReflection = $this->reflectionProvider->getAnonymousClassReflection($classLike, $scope); + $classReflection = $this->reflectionProvider->getAnonymousClassReflection($classLike, $mutatingScope); } elseif (! $this->reflectionProvider->hasClass($className)) { - return $scope; + return $mutatingScope; } else { $classReflection = $this->reflectionProvider->getClass($className); } - /** @var MutatingScope $scope */ - return $scope->enterClass($classReflection); + return $mutatingScope->enterClass($classReflection); } - private function resolveClassName(Class_ | Interface_ $classLike): string + private function resolveClassName(Class_ | Interface_ | Trait_ $classLike): string { if (property_exists($classLike, 'namespacedName')) { return (string) $classLike->namespacedName; @@ -293,4 +293,16 @@ final class PHPStanNodeScopeResolver ]); $this->privatesAccessor->setPrivateProperty($classReflector, 'sourceLocator', $aggregateSourceLocator); } + + private function createDummyClassScopeContext(MutatingScope $mutatingScope): ScopeContext + { + // this has to be faked, because trait PHPStan does not traverse trait without a class + /** @var ScopeContext $scopeContext */ + $scopeContext = $this->privatesAccessor->getPrivateProperty($mutatingScope, 'context'); + $dummyClassReflection = $this->reflectionProvider->getClass(DummyTraitClass::class); + + // faking a class reflection + return ScopeContext::create($scopeContext->getFile()) + ->enterClass($dummyClassReflection); + } } diff --git a/packages/Testing/PHPUnit/Behavior/MultipleFilesChangedTrait.php b/packages/Testing/PHPUnit/Behavior/MultipleFilesChangedTrait.php index e23095803b2..841cd480886 100644 --- a/packages/Testing/PHPUnit/Behavior/MultipleFilesChangedTrait.php +++ b/packages/Testing/PHPUnit/Behavior/MultipleFilesChangedTrait.php @@ -29,7 +29,11 @@ trait MultipleFilesChangedTrait $fixturePath = $this->getFixtureTempDirectory() . '/' . $fixtureFileInfo->getFilename(); $this->createFixtureDir($fixturePath); $fixtureContent = $originalContent; - if (trim($expectedContent)) { + + /** @var string $expectedContent */ + $trimmedExpectedContent = trim($expectedContent); + + if ($trimmedExpectedContent !== '') { $fixtureContent .= $separator . $expectedContent; } diff --git a/phpstan-for-rector.neon b/phpstan-for-rector.neon index acb022d9e2f..ebe9b002eea 100644 --- a/phpstan-for-rector.neon +++ b/phpstan-for-rector.neon @@ -8,12 +8,3 @@ parameters: # see https://github.com/rectorphp/rector/issues/3490#issue-634342324 featureToggles: disableRuntimeReflectionProvider: true - -includes: - - vendor/symplify/phpstan-rules/packages/symfony/config/services.neon - - vendor/rector/phpstan-rules/config/config.neon - - vendor/symplify/phpstan-extensions/config/config.neon - - - vendor/symplify/phpstan-rules/config/services/services.neon - - vendor/symplify/phpstan-rules/packages/object-calisthenics/config/object-calisthenics-rules.neon - - vendor/symplify/phpstan-rules/packages/cognitive-complexity/config/cognitive-complexity-services.neon diff --git a/phpstan.neon b/phpstan.neon index 717ef457839..6520782552d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -184,7 +184,10 @@ parameters: # class_exists is forbidden to enforce static reflection, but in a compiler pass we want runtime autoloading - message: '#Function "class_exists\(\)" cannot be used/left in the code#' - path: src/DependencyInjection/CompilerPass/VerifyRectorServiceExistsCompilerPass.php + paths: + - src/DependencyInjection/CompilerPass/VerifyRectorServiceExistsCompilerPass.php + # for config class reflection + - packages/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php # known values from other methods - @@ -505,3 +508,6 @@ parameters: - '#(.*?) but returns array#' - '#Parameter "\w+" cannot have default value#' + + # scope & mutating scope mish-mash + - '#Parameter \#4 \$nodeCallback of method PHPStan\\Analyser\\NodeScopeResolver\:\:processStmtNodes\(\) expects callable\(PhpParser\\Node, PHPStan\\Analyser\\Scope\)\: void, Closure\(PhpParser\\Node, PHPStan\\Analyser\\MutatingScope\)\: void given#' diff --git a/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/skip_mixed_scope.php.inc b/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/skip_mixed_scope.php.inc index 085e010f4cc..5f3134ac012 100644 --- a/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/skip_mixed_scope.php.inc +++ b/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/skip_mixed_scope.php.inc @@ -2,14 +2,6 @@ namespace Rector\Tests\Arguments\Rector\ClassMethod\ArgumentAdderRector\Fixture; -class Value -{ - public function doSomething() - { - - } -} - class SomeProperty { public $value; diff --git a/rules-tests/Php74/Rector/Property/TypedPropertyRector/Fixture/json_body_trait.php.inc b/rules-tests/Php74/Rector/Property/TypedPropertyRector/Fixture/json_body_trait.php.inc new file mode 100644 index 00000000000..189d473c724 --- /dev/null +++ b/rules-tests/Php74/Rector/Property/TypedPropertyRector/Fixture/json_body_trait.php.inc @@ -0,0 +1,43 @@ +body = $body; + return $this; + } + + public function getJsonBody(): ?array + { + return $this->body; + } +} + +?> +----- +body = $body; + return $this; + } + + public function getJsonBody(): ?array + { + return $this->body; + } +} + +?> diff --git a/rules-tests/Php74/Rector/Property/TypedPropertyRector/Fixture/skip_trait.php.inc b/rules-tests/Php74/Rector/Property/TypedPropertyRector/Fixture/skip_trait.php.inc deleted file mode 100644 index 93ec1cfacb8..00000000000 --- a/rules-tests/Php74/Rector/Property/TypedPropertyRector/Fixture/skip_trait.php.inc +++ /dev/null @@ -1,23 +0,0 @@ -body = $body; - return $this; - } - - public function getJsonBody(): ?array { - return $this->body; - } -} - -class UsingTraig -{ - use JsonBodyTrait; -} - diff --git a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php index f6a532383c9..804f0c666d1 100644 --- a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php +++ b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php @@ -70,6 +70,10 @@ CODE_SAMPLE return null; } + if ($scope->isInTrait()) { + return null; + } + $classReflection = $scope->getClassReflection(); if (! $classReflection instanceof ClassReflection) { return null; diff --git a/src/Stubs/DummyTraitClass.php b/src/Stubs/DummyTraitClass.php new file mode 100644 index 00000000000..aa683c96d4a --- /dev/null +++ b/src/Stubs/DummyTraitClass.php @@ -0,0 +1,12 @@ +