diff --git a/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/inside_closure.php.inc b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/inside_closure.php.inc new file mode 100644 index 00000000000..73a5ea8a670 --- /dev/null +++ b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/inside_closure.php.inc @@ -0,0 +1,26 @@ +setAccessible(true); + $reflectionProperty->getValue(); +}; + +?> +----- +getValue(); +}; + +?> diff --git a/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/inside_if_condition.php.inc b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/inside_if_condition.php.inc new file mode 100644 index 00000000000..a8cbb9a93bf --- /dev/null +++ b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/inside_if_condition.php.inc @@ -0,0 +1,40 @@ +setAccessible(true); + $reflectionProperty->getValue($this); + } + } +} + +?> +----- +getValue($this); + } + } +} + +?> diff --git a/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/skip_method_call_caller.php.inc b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/skip_method_call_caller.php.inc new file mode 100644 index 00000000000..a205323b90e --- /dev/null +++ b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/skip_method_call_caller.php.inc @@ -0,0 +1,18 @@ +getReflectionProperty()->setAccessible(true); + } + + private function getReflectionProperty(): ReflectionProperty + { + return new ReflectionProperty('Foo', 'bar'); + } +} diff --git a/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/skip_non_reflection_object.php.inc b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/skip_non_reflection_object.php.inc new file mode 100644 index 00000000000..1e233117a2e --- /dev/null +++ b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/skip_non_reflection_object.php.inc @@ -0,0 +1,12 @@ +setAccessible(true); + } +} diff --git a/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/skip_untyped_param.php.inc b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/skip_untyped_param.php.inc new file mode 100644 index 00000000000..412e9d652e7 --- /dev/null +++ b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/skip_untyped_param.php.inc @@ -0,0 +1,11 @@ +setAccessible(true); + } +} diff --git a/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/standalone_function.php.inc b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/standalone_function.php.inc new file mode 100644 index 00000000000..29a8fb96df1 --- /dev/null +++ b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/standalone_function.php.inc @@ -0,0 +1,28 @@ +setAccessible(true); + $reflectionMethod->invoke(null); +} + +?> +----- +invoke(null); +} + +?> diff --git a/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/typed_param.php.inc b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/typed_param.php.inc new file mode 100644 index 00000000000..1c263ce544a --- /dev/null +++ b/rules-tests/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector/Fixture/typed_param.php.inc @@ -0,0 +1,32 @@ +setAccessible(true); + $reflectionProperty->getValue(); + } +} + +?> +----- +getValue(); + } +} + +?> diff --git a/rules-tests/Php85/Rector/Property/AddOverrideAttributeToOverriddenPropertiesRector/Fixture/fixture.php.inc b/rules-tests/Php85/Rector/Property/AddOverrideAttributeToOverriddenPropertiesRector/Fixture/fixture.php.inc.skip similarity index 100% rename from rules-tests/Php85/Rector/Property/AddOverrideAttributeToOverriddenPropertiesRector/Fixture/fixture.php.inc rename to rules-tests/Php85/Rector/Property/AddOverrideAttributeToOverriddenPropertiesRector/Fixture/fixture.php.inc.skip diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/query_parts_empty_arrays.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/query_parts_empty_arrays.php.inc.skip similarity index 100% rename from rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/query_parts_empty_arrays.php.inc rename to rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/query_parts_empty_arrays.php.inc.skip diff --git a/rules/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector.php b/rules/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector.php index a8d5e05abe4..1f62f6abe94 100644 --- a/rules/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector.php +++ b/rules/Php81/Rector/MethodCall/RemoveReflectionSetAccessibleCallsRector.php @@ -8,7 +8,8 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Stmt\Expression; use PhpParser\NodeVisitor; -use PHPStan\Type\ObjectType; +use Rector\Analyzer\SimpleScope\SimpleScope; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersion; use Rector\VersionBonding\Contract\MinPhpVersionInterface; @@ -18,6 +19,8 @@ /** * As of PHP 8.1.0, calling `Reflection*::setAccessible()` has no effect. * + * Caller type comes from the PHPStan-free SimpleScope attached by SimpleScopeNodeVisitor. + * * @see https://www.php.net/manual/en/reflectionmethod.setaccessible.php * @see https://www.php.net/manual/en/reflectionproperty.setaccessible.php * @see \Rector\Tests\Php81\Rector\MethodCall\RemoveReflectionSetAccessibleCallsRector\RemoveReflectionSetAccessibleCallsRectorTest @@ -37,23 +40,25 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?int { - if ($node->expr instanceof MethodCall === false) { + if (! $node->expr instanceof MethodCall) { return null; } $methodCall = $node->expr; + if (! $this->isName($methodCall->name, 'setAccessible')) { + return null; + } - if ($this->isName($methodCall->name, 'setAccessible') === false) { + $simpleScope = $node->getAttribute(AttributeKey::SIMPLE_SCOPE); + if (! $simpleScope instanceof SimpleScope) { return null; } - if ($this->isObjectType($methodCall->var, new ObjectType('ReflectionProperty')) - || $this->isObjectType($methodCall->var, new ObjectType('ReflectionMethod')) - ) { - return NodeVisitor::REMOVE_NODE; + if (! $simpleScope->isObjectType($methodCall->var, 'ReflectionProperty', 'ReflectionMethod')) { + return null; } - return null; + return NodeVisitor::REMOVE_NODE; } public function getRuleDefinition(): RuleDefinition diff --git a/src/Analyzer/SimpleScope/SimpleScope.php b/src/Analyzer/SimpleScope/SimpleScope.php new file mode 100644 index 00000000000..10e20cbf63f --- /dev/null +++ b/src/Analyzer/SimpleScope/SimpleScope.php @@ -0,0 +1,90 @@ + + */ + private array $variableTypes = []; + + public function setVariableType(string $name, SimpleTypeInterface $simpleType): void + { + $this->variableTypes[$name] = $simpleType; + } + + public function isObjectType(Expr $expr, string ...$classNames): bool + { + $simpleType = $this->getType($expr); + if (! $simpleType instanceof ObjectType) { + return false; + } + + return $simpleType->isInstanceOf(...$classNames); + } + + public function getType(Expr $expr): SimpleTypeInterface + { + if ($expr instanceof String_) { + return new StringType(); + } + + if ($expr instanceof Int_) { + return new IntegerType(); + } + + if ($expr instanceof Array_) { + return new ArrayType(); + } + + if ($expr instanceof ConstFetch) { + return $this->resolveConstFetchType($expr); + } + + if ($expr instanceof New_ && $expr->class instanceof Name) { + return new ObjectType($expr->class->toString()); + } + + if ($expr instanceof Variable && is_string($expr->name)) { + return $this->variableTypes[$expr->name] ?? new MixedType(); + } + + return new MixedType(); + } + + private function resolveConstFetchType(ConstFetch $constFetch): SimpleTypeInterface + { + $constantName = strtolower($constFetch->name->toString()); + + if ($constantName === 'null') { + return new NullType(); + } + + if ($constantName === 'true' || $constantName === 'false') { + return new BooleanType(); + } + + return new MixedType(); + } +} diff --git a/src/Analyzer/SimpleScope/SimpleScopeResolver.php b/src/Analyzer/SimpleScope/SimpleScopeResolver.php new file mode 100644 index 00000000000..50ab8fe8c36 --- /dev/null +++ b/src/Analyzer/SimpleScope/SimpleScopeResolver.php @@ -0,0 +1,89 @@ +nodeFinder = new NodeFinder(); + } + + /** + * @param Node[] $stmts + */ + public function resolve(array $stmts): SimpleScope + { + $simpleScope = new SimpleScope(); + + foreach ($this->nodeFinder->findInstanceOf($stmts, Param::class) as $param) { + $this->seedParam($simpleScope, $param); + } + + foreach ($this->nodeFinder->findInstanceOf($stmts, Assign::class) as $assign) { + if (! $assign->var instanceof Variable || ! is_string($assign->var->name)) { + continue; + } + + $simpleScope->setVariableType($assign->var->name, $simpleScope->getType($assign->expr)); + } + + return $simpleScope; + } + + private function seedParam(SimpleScope $simpleScope, Param $param): void + { + if (! $param->var instanceof Variable || ! is_string($param->var->name)) { + return; + } + + $paramType = $this->resolveParamType($param); + if (! $paramType instanceof SimpleTypeInterface) { + return; + } + + $simpleScope->setVariableType($param->var->name, $paramType); + } + + private function resolveParamType(Param $param): ?SimpleTypeInterface + { + if ($param->type instanceof Name) { + return new ObjectType($param->type->toString()); + } + + if (! $param->type instanceof Identifier) { + return null; + } + + return match ($param->type->toLowerString()) { + 'string' => new StringType(), + 'int' => new IntegerType(), + 'bool' => new BooleanType(), + 'array' => new ArrayType(), + default => null, + }; + } +} diff --git a/src/Analyzer/SimpleType/ArrayType.php b/src/Analyzer/SimpleType/ArrayType.php new file mode 100644 index 00000000000..e5c8a9d5452 --- /dev/null +++ b/src/Analyzer/SimpleType/ArrayType.php @@ -0,0 +1,11 @@ +className, $classNames, true); + } +} diff --git a/src/Analyzer/SimpleType/StringType.php b/src/Analyzer/SimpleType/StringType.php new file mode 100644 index 00000000000..db00fc44382 --- /dev/null +++ b/src/Analyzer/SimpleType/StringType.php @@ -0,0 +1,11 @@ +getStmts(); + if ($stmts === null) { + return null; + } + + $simpleScope = $this->simpleScopeResolver->resolve([$node]); + $this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, static function (Node $subNode) use ( + $simpleScope + ): null { + $subNode->setAttribute(AttributeKey::SIMPLE_SCOPE, $simpleScope); + return null; + }); + + return null; + } +} diff --git a/tests/Analyzer/SimpleScope/SimpleScopeResolverTest.php b/tests/Analyzer/SimpleScope/SimpleScopeResolverTest.php new file mode 100644 index 00000000000..2dd19963ff2 --- /dev/null +++ b/tests/Analyzer/SimpleScope/SimpleScopeResolverTest.php @@ -0,0 +1,95 @@ +simpleScopeResolver = new SimpleScopeResolver(); + } + + public function testResolvesNewAssignToObjectType(): void + { + $simpleScope = $this->resolveCode(<<<'PHP' +getType(new Variable('dateTime')); + $this->assertInstanceOf(ObjectType::class, $simpleType); + $this->assertTrue($simpleType->isInstanceOf('DateTime')); + } + + public function testIsObjectType(): void + { + $simpleScope = $this->resolveCode(<<<'PHP' +assertTrue($simpleScope->isObjectType(new Variable('dateTime'), 'DateTime')); + $this->assertFalse($simpleScope->isObjectType(new Variable('dateTime'), 'stdClass')); + $this->assertFalse($simpleScope->isObjectType(new Variable('missing'), 'DateTime')); + } + + public function testResolvesTypedParam(): void + { + $simpleScope = $this->resolveCode(<<<'PHP' +assertInstanceOf(StringType::class, $simpleScope->getType(new Variable('name'))); + } + + public function testUnknownVariableIsMixed(): void + { + $simpleScope = $this->resolveCode(<<<'PHP' +assertInstanceOf(MixedType::class, $simpleScope->getType(new Variable('missing'))); + } + + public function testResolvesLiteralType(): void + { + $simpleScope = $this->resolveCode('assertInstanceOf(StringType::class, $simpleScope->getType(new String_('hello'))); + } + + private function resolveCode(string $code): SimpleScope + { + $parser = new ParserFactory() + ->createForNewestSupportedVersion(); + $stmts = $parser->parse($code); + + return $this->simpleScopeResolver->resolve($stmts ?? []); + } +}