diff --git a/README.md b/README.md index 36fcbff..189e214 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ * Provides correct attributes for `ObjectBehavior`: * public attributes * static properties (with `$this->CONSTANT_NAME`) +* Provides correct class for `getWrappedObject` method + ## Compatibility diff --git a/spec/Proget/Tests/FooSpec.php b/spec/Proget/Tests/FooSpec.php index df50a24..7c3894e 100644 --- a/spec/Proget/Tests/FooSpec.php +++ b/spec/Proget/Tests/FooSpec.php @@ -21,6 +21,10 @@ public function it_should_return_int_from_baz(Bar $bar, Baz $baz): void $bar->baz = $baz; $baz->someInt()->willReturn(99); - $this->getIntFromBaz($bar)->shouldBe(99); + // intentionally to test support for correct return type + $int = $baz->getWrappedObject()->someInt(); + $this->getWrappedObject()->property; + + $this->getIntFromBaz($bar)->shouldBe($int); } } diff --git a/src/Reflection/GetWrappedObjectMethodReflection.php b/src/Reflection/GetWrappedObjectMethodReflection.php new file mode 100644 index 0000000..73d1864 --- /dev/null +++ b/src/Reflection/GetWrappedObjectMethodReflection.php @@ -0,0 +1,73 @@ +methodReflection = $methodReflection; + $this->wrappedClass = $wrappedClass; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->methodReflection->getDeclaringClass(); + } + + public function isStatic(): bool + { + return $this->methodReflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->methodReflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->methodReflection->isPublic(); + } + + public function getName(): string + { + return $this->methodReflection->getName(); + } + + public function getPrototype(): ClassMemberReflection + { + return $this->methodReflection->getPrototype(); + } + + public function getVariants(): array + { + $variant = $this->methodReflection->getVariants()[0]; + + return [ + new FunctionVariant( + $variant->getParameters(), + $variant->isVariadic(), + new ObjectType($this->wrappedClass) + ) + ]; + } +} diff --git a/src/Reflection/ObjectBehaviorMethodsClassReflectionExtension.php b/src/Reflection/ObjectBehaviorMethodsClassReflectionExtension.php index 148591e..3257572 100644 --- a/src/Reflection/ObjectBehaviorMethodsClassReflectionExtension.php +++ b/src/Reflection/ObjectBehaviorMethodsClassReflectionExtension.php @@ -57,10 +57,14 @@ public function getMethod(ClassReflection $classReflection, string $methodName): return $subjectReflection->getMethod($methodName, new OutOfClassScope()); } - return $this->getWrappedClassMethodReflection($classReflection, $methodName); + return new ObjectBehaviorMethodReflection( + $this->broker->getClass( + $this->getSourceClassName($classReflection) + )->getMethod($methodName, new OutOfClassScope()) + ); } - private function getWrappedClassMethodReflection(ClassReflection $classReflection, $methodName): MethodReflection + private function getSourceClassName(ClassReflection $classReflection): string { /** @var PSR0Resource[] $resources */ $resources = $this->locator->findResources((string) $classReflection->getFileName()); @@ -74,8 +78,6 @@ private function getWrappedClassMethodReflection(ClassReflection $classReflectio throw new SpecSourceClassNotFound(sprintf('Spec source class %s not found', $className)); } - $srcClassReflection = $this->broker->getClass($className); - - return new ObjectBehaviorMethodReflection($srcClassReflection->getMethod($methodName, new OutOfClassScope())); + return $className; } } diff --git a/src/Reflection/SpoofedCollaboratorMethodsClassReflectionExtension.php b/src/Reflection/SpoofedCollaboratorMethodsClassReflectionExtension.php index b1a6aed..50b42f7 100644 --- a/src/Reflection/SpoofedCollaboratorMethodsClassReflectionExtension.php +++ b/src/Reflection/SpoofedCollaboratorMethodsClassReflectionExtension.php @@ -35,13 +35,17 @@ public function hasMethod(ClassReflection $classReflection, string $methodName): public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection { + $collaboratorClassName = (string) preg_replace('/Collaborator$/', '', SpoofedCollaboratorRegistry::getAlias($classReflection->getName())); $collaboratorReflection = $this->broker->getClass(Collaborator::class); + + if ($methodName === 'getWrappedObject') { + return new GetWrappedObjectMethodReflection($collaboratorReflection->getMethod($methodName, new OutOfClassScope()), $collaboratorClassName); + } + if ($collaboratorReflection->hasMethod($methodName)) { return $collaboratorReflection->getMethod($methodName, new OutOfClassScope()); } - $collaboratorClassName = (string) preg_replace('/Collaborator$/', '', SpoofedCollaboratorRegistry::getAlias($classReflection->getName())); - return new CollaboratorMethodReflection($this->broker->getClass($collaboratorClassName)->getMethod($methodName, new OutOfClassScope())); } } diff --git a/src/Type/ObjectBehaviorDynamicMethodReturnTypeExtension.php b/src/Type/ObjectBehaviorDynamicMethodReturnTypeExtension.php index 8a31ff2..3035a1f 100644 --- a/src/Type/ObjectBehaviorDynamicMethodReturnTypeExtension.php +++ b/src/Type/ObjectBehaviorDynamicMethodReturnTypeExtension.php @@ -6,9 +6,13 @@ use PhpParser\Node\Expr\MethodCall; use PhpSpec\Locator\PSR0\PSR0Locator; +use PhpSpec\Locator\PSR0\PSR0Resource; use PhpSpec\Locator\Resource; +use PhpSpec\Locator\ResourceLocator; use PhpSpec\ObjectBehavior; +use PhpSpec\Util\Filesystem; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ArrayType; @@ -16,10 +20,21 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\ResourceType; use PHPStan\Type\Type; +use Proget\PHPStan\PhpSpec\Exception\SpecSourceClassNotFound; use Proget\PHPStan\PhpSpec\Reflection\ObjectBehaviorMethodReflection; final class ObjectBehaviorDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { + /** + * @var ResourceLocator + */ + private $locator; + + public function __construct() + { + $this->locator = new PSR0Locator(new Filesystem()); + } + public function getClass(): string { return ObjectBehavior::class; @@ -27,11 +42,15 @@ public function getClass(): string public function isMethodSupported(MethodReflection $methodReflection): bool { - return $methodReflection instanceof ObjectBehaviorMethodReflection; + return $methodReflection instanceof ObjectBehaviorMethodReflection || $methodReflection->getName() === 'getWrappedObject'; } public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { + if ($methodReflection->getName() === 'getWrappedObject') { + return new ObjectType($this->getSourceClassName($scope->getClassReflection())); + } + if (!$methodReflection instanceof ObjectBehaviorMethodReflection) { throw new ShouldNotHappenException(); } @@ -50,4 +69,26 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return new SubjectType($returnType); } + + // todo: looks like duplication from ObjectBehaviorMethodsClassReflectionExtension + private function getSourceClassName(?ClassReflection $classReflection): string + { + if ($classReflection === null) { + throw new ShouldNotHappenException(); + } + + /** @var PSR0Resource[] $resources */ + $resources = $this->locator->findResources((string) $classReflection->getFileName()); + + if (count($resources) === 0) { + throw new SpecSourceClassNotFound(sprintf('Source class from %s not found', $classReflection->getName())); + } + + $className = $resources[0]->getSrcClassname(); + if (!class_exists($className)) { + throw new SpecSourceClassNotFound(sprintf('Spec source class %s not found', $className)); + } + + return $className; + } }