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
29 changes: 29 additions & 0 deletions src/NodeAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);
/**
* Parser Reflection API
*
* @copyright Copyright 2026, Lisachenko Alexander <lisachenko.it@gmail.com>
*
* 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;
}
2 changes: 1 addition & 1 deletion src/ReflectionAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
*
* @extends \ReflectionAttribute<object>
*/
class ReflectionAttribute extends BaseReflectionAttribute
class ReflectionAttribute extends BaseReflectionAttribute implements NodeAwareInterface
{
/**
* Fully-qualified attribute class name.
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
* @see \Go\ParserReflection\ReflectionClassTest
* @extends \ReflectionClass<object>
*/
final class ReflectionClass extends InternalReflectionClass
final class ReflectionClass extends InternalReflectionClass implements NodeAwareInterface
{
use InternalPropertiesEmulationTrait;
use ReflectionClassLikeTrait;
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionClassConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/**
* @see \Go\ParserReflection\ReflectionClassConstantTest
*/
final class ReflectionClassConstant extends BaseReflectionClassConstant
final class ReflectionClassConstant extends BaseReflectionClassConstant implements NodeAwareInterface
{
use InternalPropertiesEmulationTrait;
use AttributeResolverTrait;
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionEnumBackedCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionEnumUnitCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionFileNamespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
144 changes: 144 additions & 0 deletions tests/NodeAwareInterfaceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);

namespace Go\ParserReflection;

use Go\ParserReflection\Stub\BackedPhp81EnumHTTPMethods;
use Go\ParserReflection\Stub\ClassWithBackedEnumDefaultValue;
use Go\ParserReflection\Stub\ClassWithPhp81FinalClassConst;
use Go\ParserReflection\Stub\ClassWithPhp81ReadOnlyProperties;
use Go\ParserReflection\Stub\SimplePhp81EnumWithSuit;
use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\Param;
use PhpParser\Node\PropertyItem;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\EnumCase;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Namespace_;
use PHPUnit\Framework\TestCase;

/**
* Tests that every parsed reflection class exposes its underlying AST node
* through the NodeAwareInterface contract
*
* @see NodeAwareInterface
*/
class NodeAwareInterfaceTest extends TestCase
{
public const STUB_FILE = '/Stub/FileWithClasses81.php';

protected ReflectionFileNamespace $parsedRefFileNamespace;

protected function setUp(): void
{
$fileName = stream_resolve_include_path(__DIR__ . self::STUB_FILE);
$fileNode = ReflectionEngine::parseFile($fileName);

$reflectionFile = new ReflectionFile($fileName, $fileNode);

$this->parsedRefFileNamespace = $reflectionFile->getFileNamespace('Go\ParserReflection\Stub');

include_once $fileName;
}

/**
* Asserts that a reflection implements NodeAwareInterface and returns the expected node type
*
* @param class-string<Node> $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);
}
}