diff --git a/src/NodeAwareInterface.php b/src/NodeAwareInterface.php new file mode 100644 index 0000000..4cedff3 --- /dev/null +++ b/src/NodeAwareInterface.php @@ -0,0 +1,29 @@ + + * + * This source file is subject to the license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Go\ParserReflection; + +use PhpParser\Node; + +/** + * Interface for parsed reflections that are aware of their underlying AST node + * + * Implementations narrow the return type of getNode() via covariance to expose + * the concrete node type they wrap (e.g. ClassLike, ClassMethod, Param, etc.) + */ +interface NodeAwareInterface +{ + /** + * Returns the underlying AST node for this reflection + */ + public function getNode(): Node; +} diff --git a/src/ReflectionAttribute.php b/src/ReflectionAttribute.php index 1416144..85299b6 100644 --- a/src/ReflectionAttribute.php +++ b/src/ReflectionAttribute.php @@ -28,7 +28,7 @@ * * @extends \ReflectionAttribute */ -class ReflectionAttribute extends BaseReflectionAttribute +class ReflectionAttribute extends BaseReflectionAttribute implements NodeAwareInterface { /** * Fully-qualified attribute class name. diff --git a/src/ReflectionClass.php b/src/ReflectionClass.php index 00866d6..d54887d 100644 --- a/src/ReflectionClass.php +++ b/src/ReflectionClass.php @@ -30,7 +30,7 @@ * @see \Go\ParserReflection\ReflectionClassTest * @extends \ReflectionClass */ -final class ReflectionClass extends InternalReflectionClass +final class ReflectionClass extends InternalReflectionClass implements NodeAwareInterface { use InternalPropertiesEmulationTrait; use ReflectionClassLikeTrait; diff --git a/src/ReflectionClassConstant.php b/src/ReflectionClassConstant.php index a55926e..6a24445 100644 --- a/src/ReflectionClassConstant.php +++ b/src/ReflectionClassConstant.php @@ -26,7 +26,7 @@ /** * @see \Go\ParserReflection\ReflectionClassConstantTest */ -final class ReflectionClassConstant extends BaseReflectionClassConstant +final class ReflectionClassConstant extends BaseReflectionClassConstant implements NodeAwareInterface { use InternalPropertiesEmulationTrait; use AttributeResolverTrait; diff --git a/src/ReflectionEnum.php b/src/ReflectionEnum.php index 5c44a44..6b7ed3e 100644 --- a/src/ReflectionEnum.php +++ b/src/ReflectionEnum.php @@ -25,7 +25,7 @@ * @see \Go\ParserReflection\ReflectionEnumTest * @extends InternalReflectionEnum<\UnitEnum> */ -final class ReflectionEnum extends InternalReflectionEnum +final class ReflectionEnum extends InternalReflectionEnum implements NodeAwareInterface { use InternalPropertiesEmulationTrait; use ReflectionClassLikeTrait; diff --git a/src/ReflectionEnumBackedCase.php b/src/ReflectionEnumBackedCase.php index bc3e14f..2447b2c 100644 --- a/src/ReflectionEnumBackedCase.php +++ b/src/ReflectionEnumBackedCase.php @@ -20,7 +20,7 @@ /** * AST-based reflection for enum backed cases */ -final class ReflectionEnumBackedCase extends InternalReflectionEnumBackedCase +final class ReflectionEnumBackedCase extends InternalReflectionEnumBackedCase implements NodeAwareInterface { use Traits\InternalPropertiesEmulationTrait; use Traits\AttributeResolverTrait; diff --git a/src/ReflectionEnumUnitCase.php b/src/ReflectionEnumUnitCase.php index 5d47fc6..59f68a9 100644 --- a/src/ReflectionEnumUnitCase.php +++ b/src/ReflectionEnumUnitCase.php @@ -21,7 +21,7 @@ /** * AST-based reflection for enum unit cases */ -final class ReflectionEnumUnitCase extends InternalReflectionEnumUnitCase +final class ReflectionEnumUnitCase extends InternalReflectionEnumUnitCase implements NodeAwareInterface { use InternalPropertiesEmulationTrait; use AttributeResolverTrait; diff --git a/src/ReflectionFileNamespace.php b/src/ReflectionFileNamespace.php index 82a432e..8761d09 100644 --- a/src/ReflectionFileNamespace.php +++ b/src/ReflectionFileNamespace.php @@ -28,7 +28,7 @@ * AST-based reflection for the concrete namespace in the file * @see \Go\ParserReflection\ReflectionFileNamespaceTest */ -class ReflectionFileNamespace +class ReflectionFileNamespace implements NodeAwareInterface { /** * List of classes in the namespace diff --git a/src/ReflectionFunction.php b/src/ReflectionFunction.php index 539a9d4..dbb431d 100644 --- a/src/ReflectionFunction.php +++ b/src/ReflectionFunction.php @@ -22,7 +22,7 @@ * AST-based reflection for function * @see \Go\ParserReflection\ReflectionFunctionTest */ -final class ReflectionFunction extends BaseReflectionFunction +final class ReflectionFunction extends BaseReflectionFunction implements NodeAwareInterface { use InternalPropertiesEmulationTrait; use ReflectionFunctionLikeTrait; diff --git a/src/ReflectionMethod.php b/src/ReflectionMethod.php index fe4659b..90381fb 100644 --- a/src/ReflectionMethod.php +++ b/src/ReflectionMethod.php @@ -26,7 +26,7 @@ * AST-based reflection for the method in a class * @see \Go\ParserReflection\ReflectionMethodTest */ -final class ReflectionMethod extends BaseReflectionMethod +final class ReflectionMethod extends BaseReflectionMethod implements NodeAwareInterface { use InternalPropertiesEmulationTrait; use ReflectionFunctionLikeTrait; diff --git a/src/ReflectionParameter.php b/src/ReflectionParameter.php index 53b5dea..51e8b95 100644 --- a/src/ReflectionParameter.php +++ b/src/ReflectionParameter.php @@ -32,7 +32,7 @@ * AST-based reflection for method/function parameter * @see \Go\ParserReflection\ReflectionParameterTest */ -final class ReflectionParameter extends BaseReflectionParameter +final class ReflectionParameter extends BaseReflectionParameter implements NodeAwareInterface { use InternalPropertiesEmulationTrait; use AttributeResolverTrait; diff --git a/src/ReflectionProperty.php b/src/ReflectionProperty.php index 1db37f1..345fa91 100644 --- a/src/ReflectionProperty.php +++ b/src/ReflectionProperty.php @@ -36,7 +36,7 @@ * AST-based reflection for class property * @see \Go\ParserReflection\ReflectionPropertyTest */ -final class ReflectionProperty extends BaseReflectionProperty +final class ReflectionProperty extends BaseReflectionProperty implements NodeAwareInterface { use InitializationTrait; use InternalPropertiesEmulationTrait; diff --git a/tests/NodeAwareInterfaceTest.php b/tests/NodeAwareInterfaceTest.php new file mode 100644 index 0000000..717a493 --- /dev/null +++ b/tests/NodeAwareInterfaceTest.php @@ -0,0 +1,144 @@ +parsedRefFileNamespace = $reflectionFile->getFileNamespace('Go\ParserReflection\Stub'); + + include_once $fileName; + } + + /** + * Asserts that a reflection implements NodeAwareInterface and returns the expected node type + * + * @param class-string $expectedNodeClass + */ + private function assertNodeAware(object $reflection, string $expectedNodeClass): void + { + $this->assertInstanceOf(NodeAwareInterface::class, $reflection); + $this->assertInstanceOf($expectedNodeClass, $reflection->getNode()); + } + + public function testFileNamespaceIsNodeAware(): void + { + $this->assertNodeAware($this->parsedRefFileNamespace, Namespace_::class); + } + + public function testClassIsNodeAware(): void + { + $refClass = $this->parsedRefFileNamespace->getClass(ClassWithPhp81ReadOnlyProperties::class); + $this->assertNodeAware($refClass, ClassLike::class); + } + + public function testEnumIsNodeAware(): void + { + $refEnum = $this->parsedRefFileNamespace->getEnums()[SimplePhp81EnumWithSuit::class]; + $this->assertNodeAware($refEnum, Enum_::class); + } + + public function testEnumUnitCaseIsNodeAware(): void + { + $refEnum = $this->parsedRefFileNamespace->getEnums()[SimplePhp81EnumWithSuit::class]; + $this->assertNodeAware($refEnum->getCase('Clubs'), EnumCase::class); + } + + public function testEnumBackedCaseIsNodeAware(): void + { + $refEnum = $this->parsedRefFileNamespace->getEnums()[BackedPhp81EnumHTTPMethods::class]; + $this->assertNodeAware($refEnum->getCase('GET'), EnumCase::class); + } + + public function testMethodIsNodeAware(): void + { + $refClass = $this->parsedRefFileNamespace->getClass(ClassWithBackedEnumDefaultValue::class); + $refMethod = $refClass->getMethod('getRefusalDescription'); + $this->assertNodeAware($refMethod, ClassMethod::class); + } + + public function testFunctionIsNodeAware(): void + { + $refFunction = $this->parsedRefFileNamespace->getFunction('functionWithPhp81IntersectionType'); + $this->assertNodeAware($refFunction, Function_::class); + } + + public function testParameterIsNodeAware(): void + { + $refClass = $this->parsedRefFileNamespace->getClass(ClassWithBackedEnumDefaultValue::class); + $refMethod = $refClass->getMethod('getRefusalDescription'); + [$refParameter] = $refMethod->getParameters(); + $this->assertNodeAware($refParameter, Param::class); + } + + public function testPropertyIsNodeAware(): void + { + $refClass = $this->parsedRefFileNamespace->getClass(ClassWithPhp81ReadOnlyProperties::class); + $refProperty = $refClass->getProperty('publicReadonlyInt'); + $this->assertNodeAware($refProperty, PropertyItem::class); + } + + public function testClassConstantIsNodeAware(): void + { + $refClass = $this->parsedRefFileNamespace->getClass(ClassWithPhp81FinalClassConst::class); + $refConstant = $refClass->getReflectionConstant('TEST'); + $this->assertNodeAware($refConstant, ClassConst::class); + } + + public function testEnumCaseConstantIsNodeAware(): void + { + $refEnum = $this->parsedRefFileNamespace->getEnums()[SimplePhp81EnumWithSuit::class]; + $refConstant = $refEnum->getReflectionConstant('Clubs'); + $this->assertNodeAware($refConstant, EnumCase::class); + } + + public function testAttributeIsNodeAware(): void + { + $fileName = stream_resolve_include_path(__DIR__ . '/Stub/FileWithFunction80.php'); + $fileNode = ReflectionEngine::parseFile($fileName); + + $reflectionFile = new ReflectionFile($fileName, $fileNode); + $fileNamespace = $reflectionFile->getFileNamespace('Go\ParserReflection\Stub'); + + $refFunction = $fileNamespace->getFunction('function_with_attribute'); + $attributes = $refFunction->getAttributes(); + + $this->assertNotEmpty($attributes); + $this->assertNodeAware($attributes[0], Attribute::class); + } +}