Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ includes:
rules:
- Rector\Utils\PHPStan\Rule\RegisterRelatedPolyfillRectorRule
- Rector\Utils\PHPStan\Rule\NoResolveClassReflectionInForeachRule
- Rector\Utils\PHPStan\Rule\NoGetClassReflectionOnScopeRule
- Rector\Utils\PHPStan\Rule\RegisterRectorRule

parameters:
Expand Down Expand Up @@ -77,6 +78,28 @@ parameters:
offloadCollectorData: false

ignoreErrors:
# Scope is the correct source of the enclosing class reflection in the engine resolvers,
# in rules that reuse the same Scope for other calls, and in ObjectTypeSpecifier where the
# scope reflection is refreshed on class change while ReflectionProvider reads the original file
-
identifier: rector.getClassReflectionOnScope
paths:
- rules/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector.php
- rules/DeadCode/Rector/If_/RemoveAlwaysTrueIfConditionRector.php
- rules/Php55/Rector/FuncCall/GetCalledClassToStaticClassRector.php
- rules/Php81/Rector/Array_/ArrayToFirstClassCallableRector.php
- rules/Php81/Rector/New_/MyCLabsConstructorCallToEnumFromRector.php
- rules/Renaming/Rector/MethodCall/RenameMethodRector.php
- rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php
- rules/TypeDeclaration/Rector/Closure/ClosureReturnTypeFromAssertInstanceOfRector.php
- src/Application/ChangedNodeScopeRefresher.php
- src/NodeTypeResolver/NodeTypeResolver.php
- src/NodeTypeResolver/NodeTypeResolver/NameTypeResolver.php
- src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php
- src/Reflection/ReflectionResolver.php
- src/StaticTypeMapper/Naming/NameScopeFactory.php
- utils/phpstan/src/Rule/NoResolveClassReflectionInForeachRule.php

# called magically by parent
-
message: '#^Unused Rector\\PhpParser\\Printer\\BetterStandardPrinter\:\:(pInterpolatedStringPart|pStmt_FileNode)$#'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
Expand All @@ -27,9 +26,9 @@
use Rector\PHPStan\ScopeFetcher;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\Rector\AbstractRector;
use Rector\Reflection\ReflectionResolver;
use Rector\RuleDoc\CodeSample\CodeSample;
use Rector\RuleDoc\RuleDefinition;
use Rector\StaticTypeMapper\Resolver\ClassNameFromObjectTypeResolver;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\StaticTypeMapper\ValueObject\Type\SelfStaticType;
use Rector\Symfony\CodeQuality\Enum\ResponseClass;
Expand All @@ -48,7 +47,6 @@ final class ReturnTypeFromReturnNewRector extends AbstractRector implements MinP
public function __construct(
private readonly TypeFactory $typeFactory,
private readonly ReflectionProvider $reflectionProvider,
private readonly ReflectionResolver $reflectionResolver,
private readonly StrictReturnNewAnalyzer $strictReturnNewAnalyzer,
private readonly ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard,
private readonly ClassAnalyzer $classAnalyzer,
Expand Down Expand Up @@ -156,11 +154,14 @@ private function createObjectTypeFromNew(New_ $new): ObjectType|ObjectWithoutCla

$className = $this->getName($new->class);
if ($className === ObjectReference::STATIC || $className === ObjectReference::SELF) {
$classReflection = $this->reflectionResolver->resolveClassReflection($new);
if (! $classReflection instanceof ClassReflection) {
$classType = $this->nodeTypeResolver->getType($new->class);
$resolvedClassName = ClassNameFromObjectTypeResolver::resolve($classType);
if ($resolvedClassName === null || ! $this->reflectionProvider->hasClass($resolvedClassName)) {
throw new ShouldNotHappenException();
}

$classReflection = $this->reflectionProvider->getClass($resolvedClassName);

if ($className === ObjectReference::SELF) {
return new SelfStaticType($classReflection);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Interface_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\MixedType;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;

/**
* @see \Rector\Tests\NodeTypeResolver\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\ClassTypeResolverTest
Expand All @@ -25,7 +22,8 @@
final readonly class ClassAndInterfaceTypeResolver implements NodeTypeResolverInterface
{
public function __construct(
private NodeNameResolver $nodeNameResolver
private NodeNameResolver $nodeNameResolver,
private ReflectionProvider $reflectionProvider,
) {
}

Expand All @@ -42,18 +40,11 @@ public function getNodeClasses(): array
*/
public function resolve(Node $node): Type
{
$scope = $node->getAttribute(AttributeKey::SCOPE);

if (! $scope instanceof Scope) {
// new node probably
return new MixedType();
}

$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return new ObjectType((string) $this->nodeNameResolver->getName($node));
$className = $this->nodeNameResolver->getName($node);
if ($className === null || ! $this->reflectionProvider->hasClass($className)) {
return new ObjectType((string) $className);
}

return new ObjectType($classReflection->getName(), null, $classReflection);
return new ObjectType($className, null, $this->reflectionProvider->getClass($className));
}
}
27 changes: 15 additions & 12 deletions src/PhpParser/Node/Value/ValueResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ public function getValue(Arg|Expr|InterpolatedStringPart $expr, bool $resolvedCl
$class = $this->nodeNameResolver->getName($expr->class);

if (in_array($class, [ObjectReference::SELF, ObjectReference::STATIC], true)) {
$classReflection = $this->reflectionResolver->resolveClassReflection($expr);
if ($classReflection instanceof ClassReflection) {
return $classReflection->getName();
$classType = $this->nodeTypeResolver->getType($expr->class);
$className = ClassNameFromObjectTypeResolver::resolve($classType);
if ($className !== null) {
return $className;
}
}

Expand Down Expand Up @@ -333,18 +334,20 @@ private function resolveClassFromSelfStaticParent(ClassConstFetch $classConstFet
return '';
}

$classReflection = $this->reflectionResolver->resolveClassReflection($classConstFetch);
if (! $classReflection instanceof ClassReflection) {
throw new ShouldNotHappenException(
'Complete class parent node for to class const fetch, so "self" or "static" references is resolvable to a class name'
);
}

if ($class !== ObjectReference::PARENT) {
return $classReflection->getName();
$classType = $this->nodeTypeResolver->getType($classConstFetch->class);
$className = ClassNameFromObjectTypeResolver::resolve($classType);
if ($className === null) {
throw new ShouldNotHappenException(
'Complete class parent node for to class const fetch, so "self" or "static" references is resolvable to a class name'
);
}

return $className;
}

if (! $classReflection->isClass()) {
$classReflection = $this->reflectionResolver->resolveClassReflection($classConstFetch);
if (! $classReflection instanceof ClassReflection || ! $classReflection->isClass()) {
throw new ShouldNotHappenException(
'Complete class parent node for to class const fetch, so "parent" references is resolvable to lookup parent class'
);
Expand Down
57 changes: 57 additions & 0 deletions utils/phpstan/src/Rule/NoGetClassReflectionOnScopeRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Rector\Utils\PHPStan\Rule;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ObjectType;

/**
* @implements Rule<MethodCall>
* @see \Rector\Utils\PHPStan\Tests\Rule\NoGetClassReflectionOnScopeRule\NoGetClassReflectionOnScopeRuleTest
*/
final class NoGetClassReflectionOnScopeRule implements Rule
{
private const string ERROR_MESSAGE = 'Do not call getClassReflection() on a Scope. Resolve the class name and fetch the reflection via ReflectionProvider->getClass() instead.';

private const string METHOD_NAME = 'getClassReflection';

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

/**
* @param MethodCall $node
* @return list<IdentifierRuleError>
*/
public function processNode(Node $node, Scope $scope): array
{
if (! $node->name instanceof Identifier) {
return [];
}

if ($node->name->toString() !== self::METHOD_NAME) {
return [];
}

$callerType = $scope->getType($node->var);
if (! new ObjectType(Scope::class)->isSuperTypeOf($callerType)->yes()) {
return [];
}

return [
RuleErrorBuilder::message(self::ERROR_MESSAGE)
->identifier('rector.getClassReflectionOnScope')
->line($node->getStartLine())
->build(),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Rector\Utils\PHPStan\Tests\Rule\NoGetClassReflectionOnScopeRule;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use Rector\Utils\PHPStan\Rule\NoGetClassReflectionOnScopeRule;

/**
* @extends RuleTestCase<NoGetClassReflectionOnScopeRule>
*/
final class NoGetClassReflectionOnScopeRuleTest extends RuleTestCase
{
public function testGetClassReflectionOnScope(): void
{
$expectedErrorMessage = 'Do not call getClassReflection() on a Scope. Resolve the class name and fetch the reflection via ReflectionProvider->getClass() instead.';

$this->analyse([__DIR__ . '/Source/GetClassReflectionOnScope.php'], [[$expectedErrorMessage, 13]]);
}

public function testSkipOtherMethodCall(): void
{
$this->analyse([__DIR__ . '/Source/SkipOtherMethodCall.php'], []);
}

protected function getRule(): Rule
{
return new NoGetClassReflectionOnScopeRule();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Rector\Utils\PHPStan\Tests\Rule\NoGetClassReflectionOnScopeRule\Source;

use PHPStan\Analyser\Scope;

final class GetClassReflectionOnScope
{
public function run(Scope $scope): void
{
$classReflection = $scope->getClassReflection();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Rector\Utils\PHPStan\Tests\Rule\NoGetClassReflectionOnScopeRule\Source;

use PHPStan\Analyser\Scope;

final class SkipOtherMethodCall
{
public function run(Scope $scope): void
{
$isInClass = $scope->isInClass();
}
}
Loading