refactor: Cleanup git state - commit all staged changes

Major refactoring cleanup:
- Add new controller architecture (class-controller-*.php)
- Add new settings-v2 UI (views/settings-v2/)
- Add new CSS architecture (agentic-sidebar.css, tokens)
- Add esbuild build pipeline (scripts/build.js, package.json)
- Add composer dependencies (vendor/)
- Add frontend src directory (assets/js/src/index.jsx)
- Add documentation files
- Remove old/obsolete files (class-settings.php, old CSS)

This commits all pending changes from previous refactoring efforts.
This commit is contained in:
Dwindi Ramadhana
2026-06-17 05:27:58 +07:00
parent d3f142222c
commit 690991c526
7963 changed files with 941566 additions and 67372 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
<?php
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\Factory\Method;
/**
* @BeforeMethods({"init"})
*/
final class ProjectFactoryBench
{
/**
* @var ProjectFactory
*/
private $factory;
public function init()
{
$this->factory = \phpDocumentor\Reflection\Php\ProjectFactory::createInstance();
}
/**
* @Revs({1, 8, 64, 1024})
*/
public function benchCreateSingleFileProject()
{
$this->factory->create('myProject', [new LocalFile(__DIR__ . '/../assets/phpunit_assert.php')]);
}
}

View File

@@ -0,0 +1,31 @@
<?php
// based on https://ocramius.github.io/blog/automated-code-coverage-check-for-github-pull-requests-with-travis/
$inputFile = __DIR__ . '/../build/logs/clover.xml';
$percentage = min(100, max(0, (int) $argv[1]));
if (!file_exists($inputFile)) {
throw new InvalidArgumentException('Invalid input file provided');
}
if (!$percentage) {
throw new InvalidArgumentException('An integer checked percentage must be given as parameter');
}
$xml = new SimpleXMLElement(file_get_contents($inputFile));
$metrics = $xml->xpath('//metrics');
$totalElements = 0;
$checkedElements = 0;
foreach ($metrics as $metric) {
$totalElements += (int) $metric['elements'];
$checkedElements += (int) $metric['coveredelements'];
}
$coverage = ($checkedElements / $totalElements) * 100;
if ($coverage < $percentage) {
echo 'Code coverage is ' . $coverage . '%, which is below the accepted ' . $percentage . '%' . PHP_EOL;
exit(1);
}
echo 'Code coverage is ' . $coverage . '% - OK!' . PHP_EOL;

View File

@@ -0,0 +1,275 @@
<?php
// phpcs:ignoreFile
/**
* Summary of the File DocBlock.
*
* Description of the File.
*
* @package Luigi\Pizza
* @author Mike van Riel <me@mikevanriel.com>
*/
namespace Luigi\Pizza
{
/**
* The high VAT percentage.
*
* This describes the VAT percentage for all non-food items.
*
* @var integer
*/
const VAT_HIGH = 21;
/**
* The low VAT percentage.
*
* This describes the VAT percentage for all non-food items.
*
* @var integer
*/
define('Luigi\Pizza\VAT_LOW', 6);
/**
* @var integer SIZE_5CM A 5 centimeter pizza size.
* @var integer SIZE_10CM A 10 centimeter pizza size.
* @var integer SIZE_15CM A 15 centimeter pizza size.
* @var integer SIZE_20CM A 20 centimeter pizza size.
* @var integer DEFAULT_SIZE The default Pizza size if you don't provide your own.
*/
const SIZE_5CM = 5, SIZE_10CM = 10, SIZE_15CM = 15, SIZE_20CM = 20, DEFAULT_SIZE = SIZE_20CM;
trait ExampleNestedTrait
{
private function exampleTraitMethod()
{
}
}
/**
* A single item with a value
*
* Represents something with a price.
*/
trait HasPrice
{
use ExampleNestedTrait;
private $temporaryPrice = 1;
public function getPrice()
{
return $this->price;
}
}
/**
* Any class implementing this interface has an associated price.
*
* Using this interface we can easily add the price of all components in a pizza by checking for this interface and
* adding the prices together for all components.
*/
interface Valued
{
const BASE_PRICE = 1;
function getPrice();
}
interface Series
{
}
interface Style extends Valued
{
}
interface Sauce extends Valued
{
}
interface Topping extends Valued, \Serializable
{
}
abstract class PizzaComponentFactory implements \Traversable, Valued
{
public function add()
{
}
/**
* Calculates the price for this specific component.
*
* @param float[] $...additionalPrices Additional costs may be passed
*
* @return float
*/
abstract protected function calculatePrice();
}
final class StyleFactory extends PizzaComponentFactory
{
public function getPrice()
{
}
protected function calculatePrice()
{
}
}
final class SauceFactory extends PizzaComponentFactory
{
public function getPrice()
{
}
protected function calculatePrice()
{
}
}
final class ToppingFactory extends PizzaComponentFactory
{
public function getPrice()
{
}
protected function calculatePrice()
{
}
}
final class ItalianStyle implements Style
{
use HasPrice;
private $price = 2.0;
}
final class AmericanStyle implements Style
{
use HasPrice;
private $price = 1.5;
}
final class TomatoSauce implements Sauce
{
use HasPrice;
private $price = 1.5;
}
final class CheeseTopping implements Topping
{
use HasPrice;
private $price = 1.5;
public function serialize()
{
}
public function unserialize($serialized)
{
}
}
}
namespace Luigi
{
/**
* Class representing a single Pizza.
*
* This is Luigi's famous Pizza.
*
* @package Luigi\Pizza
*/
class Pizza implements Pizza\Valued
{
/**
* The packaging method used to transport the pizza.
*/
const PACKAGING = 'box';
const
/** @var string DELIVERY designates that the delivery method is to deliver the pizza to the customer. */
DELIVERY = 'delivery',
/** @var string PICKUP designates that the delivery method is that the customer picks the pizza up. */
PICKUP = 'pickup';
/** @var static contains the active instance for this Pizza. */
static private $instance;
/**
* @var Pizza\Style $style
* @var Pizza\Sauce|null $sauce
* @var Pizza\Topping[] $toppings
*/
private $style, $sauce, $toppings;
/**
* The size of the pizza in centimeters, defaults to 20cm.
*
* @var int
*/
public $size = \Luigi\Pizza\SIZE_20CM;
var $legacy; // don't use this anymore!
protected
/** @var string $packaging The type of packaging for this Pizza */
$packaging = self::PACKAGING,
/** @var string $deliveryMethod Is the customer picking this pizza up or must it be delivered? */
$deliveryMethod;
private function __construct(Pizza\Style $style)
{
$this->style = $style;
}
/**
* Creates a new instance of a Pizza.
*
* This method can be used to instantiate a new object of this class which can then be retrieved using
* {@see self::getInstance()}.
*
* @param Pizza\Style $style
*
* @see self::getInstance to retrieve the pizza object.
*
* @return void
*/
public static function createInstance(Pizza\Style $style)
{
self::$instance = new static($style);
}
/**
* @return self
*/
static function getInstance()
{
return self::$instance;
}
final public function setSauce(Pizza\Sauce $sauce)
{
$this->sauce = $sauce;
}
final public function addTopping(Pizza\Topping $topping)
{
$this->toppings[] = $topping;
}
public function setSize(&$size = \Luigi\Pizza\SIZE_20CM)
{
}
public function getPrice()
{
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection;
use EliasHaeussler\PHPUnitAttributes\Attribute\RequiresPackage;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\ProjectFactory;
use PHPUnit\Framework\TestCase;
/** @coversNothing */
#[RequiresPackage('nikic/php-parser', '>= 5.2')]
final class AsymmetricAccessorTest extends TestCase
{
public function testAsymmetricAccessor(): void
{
$file = __DIR__ . '/data/PHP84/AsymmetricAccessor.php';
$projectFactory = ProjectFactory::createInstance();
$project = $projectFactory->create('My project', [new LocalFile($file)]);
$class = $project->getFiles()[$file]->getClasses()['\AsymmetricAccessor'];
self::assertEquals(
'public',
$class->getProperties()['\AsymmetricAccessor::$pizza']->getVisibility()->getReadVisibility(),
);
self::assertEquals(
'private',
$class->getProperties()['\AsymmetricAccessor::$pizza']->getVisibility()->getWriteVisibility(),
);
}
public function testAsyncPropertyPromotion(): void
{
$file = __DIR__ . '/data/PHP84/AsymmetricPropertyPromotion.php';
$projectFactory = ProjectFactory::createInstance();
$project = $projectFactory->create('My project', [new LocalFile($file)]);
$class = $project->getFiles()[$file]->getClasses()['\AsymmetricPropertyPromotion'];
self::assertEquals(
'public',
$class->getProperties()['\AsymmetricPropertyPromotion::$pizza']->getVisibility()->getReadVisibility(),
);
self::assertEquals(
'protected',
$class->getProperties()['\AsymmetricPropertyPromotion::$pizza']->getVisibility()->getWriteVisibility(),
);
}
}

View File

@@ -0,0 +1,168 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\Class_;
use phpDocumentor\Reflection\Php\Constant;
use phpDocumentor\Reflection\Php\File as PhpFile;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Object_;
/**
* @coversNothing
*/
final class ClassesTest extends MockeryTestCase
{
const FILE_PIZZA = __DIR__ . '/data/Pizza.php';
const FILE_LUIGI_PIZZA = __DIR__ . '/data/Luigi/Pizza.php';
/** @var ProjectFactory */
private $fixture;
/** @var Project */
private $project;
protected function setUp() : void
{
$this->fixture = ProjectFactory::createInstance();
$this->project = $this->fixture->create(
'MyProject',
[
new LocalFile(self::FILE_PIZZA),
new LocalFile(self::FILE_LUIGI_PIZZA),
]
);
}
public function testItHasAllConstants() : void
{
$file = $this->project->getFiles()[self::FILE_PIZZA];
$className = '\\Pizza';
$constantName = '\\Pizza::PACKAGING';
$class = $this->fetchClassFromFile($className, $file);
$this->assertArrayHasKey($constantName, $class->getConstants());
$constant = $class->getConstants()[$constantName];
$this->assertInstanceOf(Constant::class, $constant);
$this->assertArrayHasKey('\\OVEN_TEMPERATURE', $file->getConstants());
$this->assertArrayHasKey('\\MAX_OVEN_TEMPERATURE', $file->getConstants());
}
public function testTypedPropertiesReturnTheirType() : void
{
$fileName = self::FILE_LUIGI_PIZZA;
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
/** @var Class_ $pizzaClass */
$pizzaClass = $project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza'];
$this->assertArrayHasKey('\\Luigi\\Pizza::$size', $pizzaClass->getProperties());
$this->assertEquals(new Integer(), $pizzaClass->getProperties()['\\Luigi\\Pizza::$size']->getType());
}
public function testUsedTraitsAreIncludedInClass() : void
{
$fileName = self::FILE_LUIGI_PIZZA;
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
/** @var Class_ $pizzaClass */
$pizzaClass = $project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza'];
$this->assertEquals(['\\Luigi\\ExampleNestedTrait' => new Fqsen('\\Luigi\\ExampleNestedTrait')], $pizzaClass->getUsedTraits());
}
public function testWithNamespacedClass() : void
{
$fileName = self::FILE_LUIGI_PIZZA;
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$this->assertArrayHasKey('\\Luigi\\Pizza', $project->getFiles()[$fileName]->getClasses());
$this->assertEquals('\Pizza', $project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza']->getParent());
$this->assertArrayHasKey(
'\\Luigi\\Pizza::$instance',
$project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza']->getProperties()
);
$methods = $project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza']->getMethods();
$this->assertArrayHasKey(
'\\Luigi\\Pizza::__construct()',
$methods
);
$this->assertEquals('style', $methods['\\Luigi\\Pizza::__construct()']->getArguments()[0]->getName());
$this->assertEquals(
new Object_(new Fqsen('\\Luigi\\Pizza\Style')),
$methods['\\Luigi\\Pizza::__construct()']->getArguments()[0]->getType()
);
}
public function testWithUsedParent() : void
{
$fileName = __DIR__ . '/data/Luigi/StyleFactory.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$this->assertArrayHasKey('\\Luigi\\StyleFactory', $project->getFiles()[$fileName]->getClasses());
$this->assertEquals(
'\\Luigi\\Pizza\\PizzaComponentFactory',
$project->getFiles()[$fileName]->getClasses()['\\Luigi\\StyleFactory']->getParent()
);
}
public function testWithInterface() : void
{
$fileName = __DIR__ . '/data/Luigi/Valued.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey('\\Luigi\\Valued', $project->getFiles()[$fileName]->getInterfaces());
}
public function testWithTrait() : void
{
$fileName = __DIR__ . '/data/Luigi/ExampleNestedTrait.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey('\\Luigi\\ExampleNestedTrait', $project->getFiles()[$fileName]->getTraits());
}
private function fetchClassFromFile(string $className, PhpFile $file)
{
$this->assertArrayHasKey($className, $file->getClasses());
return $file->getClasses()[$className];
}
}

View File

@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
namespace integration;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Enum_;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Types\Object_;
use PHPUnit\Framework\TestCase;
use phpDocumentor\Reflection\Types\String_;
/**
* @coversNothing
*/
final class EnumTest extends TestCase
{
const FILE = __DIR__ . '/data/Enums/base.php';
const BACKED_ENUM = __DIR__ . '/data/Enums/backedEnum.php';
const ENUM_WITH_CONSTANT = __DIR__ . '/data/Enums/enumWithConstant.php';
const ENUM_CONSUMER = __DIR__ . '/data/Enums/EnumConsumer.php';
/** @var ProjectFactory */
private $fixture;
/** @var Project */
private $project;
protected function setUp() : void
{
$this->fixture = ProjectFactory::createInstance();
$this->project = $this->fixture->create(
'Enums',
[
new LocalFile(self::FILE),
new LocalFile(self::BACKED_ENUM),
new LocalFile(self::ENUM_WITH_CONSTANT),
new LocalFile(self::ENUM_CONSUMER),
]
);
}
public function testFileHasEnum(): void
{
$file = $this->project->getFiles()[self::FILE];
$enum = $file->getEnums()['\MyNamespace\MyEnum'];
self::assertInstanceOf(Enum_::class, $enum);
self::assertCount(2, $enum->getCases());
self::assertNull($enum->getBackedType());
self::assertArrayHasKey('\MyNamespace\MyEnum::VALUE1', $enum->getCases());
self::assertArrayHasKey('\MyNamespace\MyEnum::VALUE2', $enum->getCases());
}
public function testEnumWithConstant(): void
{
$file = $this->project->getFiles()[self::ENUM_WITH_CONSTANT];
$enum = $file->getEnums()['\MyNamespace\MyEnumWithConstant'];
self::assertInstanceOf(Enum_::class, $enum);
self::assertCount(2, $enum->getConstants());
self::assertArrayHasKey('\MyNamespace\MyEnumWithConstant::MYCONST', $enum->getConstants());
self::assertSame("'MyConstValue'", $enum->getConstants()['\MyNamespace\MyEnumWithConstant::MYCONST']->getValue());
}
public function testBackedEnum(): void
{
$file = $this->project->getFiles()[self::BACKED_ENUM];
$enum = $file->getEnums()['\MyNamespace\MyBackedEnum'];
self::assertInstanceOf(Enum_::class, $enum);
self::assertCount(2, $enum->getCases());
self::assertEquals(new String_(), $enum->getBackedType());
self::assertArrayHasKey('\MyNamespace\MyBackedEnum::VALUE1', $enum->getCases());
self::assertArrayHasKey('\MyNamespace\MyBackedEnum::VALUE2', $enum->getCases());
self::assertSame("'this is value1'", $enum->getCases()['\MyNamespace\MyBackedEnum::VALUE1']->getValue());
self::assertSame("'this is value2'", $enum->getCases()['\MyNamespace\MyBackedEnum::VALUE2']->getValue());
}
public function testEnumSupportInProperty(): void
{
$file = $this->project->getFiles()[self::ENUM_CONSUMER];
$class = $file->getClasses()['\MyNamespace\EnumConsumer'];
self::assertEquals(
'\MyNamespace\MyEnum::VALUE1',
$class->getProperties()['\MyNamespace\EnumConsumer::$myEnum']->getDefault()
);
self::assertEquals(
new Object_(new Fqsen('\MyNamespace\MyEnum')),
$class->getProperties()['\MyNamespace\EnumConsumer::$myEnum']->getType()
);
}
public function testEnumSupportInMethod(): void
{
$file = $this->project->getFiles()[self::ENUM_CONSUMER];
$class = $file->getClasses()['\MyNamespace\EnumConsumer'];
$method = $class->getMethods()['\MyNamespace\EnumConsumer::consume()'];
self::assertEquals(
new Object_(new Fqsen('\MyNamespace\MyEnum')),
$method->getReturnType()
);
self::assertEquals(
new Object_(new Fqsen('\MyNamespace\MyEnum')),
$method->getArguments()[0]->getType()
);
self::assertEquals(
'\MyNamespace\MyEnum::VALUE1',
$method->getArguments()[0]->getDefault()
);
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace integration;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\ProjectFactory;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\TestCase;
/**
* Integration tests to check the correct working of processing a namespace into a project.
*
* @coversNothing
*/
#[CoversNothing]
final class FileDocblockTest extends TestCase
{
/** @var ProjectFactory */
private $fixture;
protected function setUp() : void
{
$this->fixture = ProjectFactory::createInstance();
}
/**
* @dataProvider fileProvider
*/
public function testFileDocblock(string $fileName) : void
{
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertEquals(
'This file is part of phpDocumentor.',
$project->getFiles()[$fileName]->getDocBlock()->getSummary()
);
}
public static function fileProvider() : array
{
return [
[ __DIR__ . '/data/GlobalFiles/empty.php' ],
[ __DIR__ . '/data/GlobalFiles/empty_with_declare.php' ],
[ __DIR__ . '/data/GlobalFiles/empty_shebang.php' ],
[ __DIR__ . '/data/GlobalFiles/psr12.php' ],
[ __DIR__ . '/data/GlobalFiles/docblock_followed_by_html.php' ],
];
}
public function testConditionalFunctionDefine() : void
{
$fileName = __DIR__ . '/data/GlobalFiles/conditional_function.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertCount(
4,
$project->getFiles()[$fileName]->getFunctions()
);
}
public function testGlobalNamespacedFunctionDefine() : void
{
$fileName = __DIR__ . '/data/GlobalFiles/global_namspaced_function.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertCount(
1,
$project->getFiles()[$fileName]->getFunctions()
);
}
public function testFileWithInlineFunction() : void
{
$fileName = __DIR__ . '/data/GlobalFiles/inline_function.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertCount(
1,
$project->getFiles()[$fileName]->getClasses()
);
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace integration;
use EliasHaeussler\PHPUnitAttributes\Attribute\RequiresPackage;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Php\Visibility;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\String_;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\TestCase;
#[RequiresPackage('nikic/php-parser', '>= 5.2')]
#[CoversNothing]
final class InterfacePropertyTest extends TestCase
{
public function testInterfacePropertiesAreParsed(): void
{
$file = __DIR__ . '/data/PHP84/InterfaceProperties.php';
$projectFactory = ProjectFactory::createInstance();
$project = $projectFactory->create('My project', [new LocalFile($file)]);
$interfaces = $project->getFiles()[$file]->getInterfaces();
$hasId = $interfaces['\PHP84\HasId'];
$properties = $hasId->getProperties();
$this->assertCount(1, $properties);
$idProperty = $properties['\PHP84\HasId::$id'];
$this->assertEquals(new Integer(), $idProperty->getType());
$this->assertEquals(new Visibility(Visibility::PUBLIC_), $idProperty->getVisibility());
$this->assertCount(1, $idProperty->getHooks());
$this->assertEquals('get', $idProperty->getHooks()[0]->getName());
$hasName = $interfaces['\PHP84\HasName'];
$properties = $hasName->getProperties();
$this->assertCount(1, $properties);
$nameProperty = $properties['\PHP84\HasName::$name'];
$this->assertEquals(new String_(), $nameProperty->getType());
$this->assertCount(2, $nameProperty->getHooks());
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Metadata;
final class Hook implements Metadata
{
private $hook;
public function __construct(string $hook)
{
$this->hook = $hook;
}
public function key(): string
{
return "project-metadata";
}
public function hook(): string
{
return $this->hook;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Metadata;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Stmt\Expression;
final class HookStrategy implements ProjectFactoryStrategy
{
public function matches(ContextStack $context, object $object): bool
{
if ($object instanceof Expression === false) {
return false;
}
return $object->expr instanceof FuncCall && ((string)$object->expr->name) === 'hook';
}
public function create(ContextStack $context, object $object, StrategyContainer $strategies): void
{
$method = $context->peek();
$method->addMetadata(new Hook($object->expr->args[0]->value->value));
}
}

View File

@@ -0,0 +1,10 @@
<?php
class myHookUsingClass
{
public function test() {
echo "Do something";
hook('foo');
echo "finish";
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Metadata\HookStrategy;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactory;
use PHPUnit\Framework\TestCase;
/**
* @coversNothing
*/
final class MetadataTest extends TestCase
{
const FILE = __DIR__ . '/Metadata/example.php';
public function testCustomMetadata(): void
{
$projectFactory = ProjectFactory::createInstance();
$projectFactory->addStrategy(new HookStrategy());
/** @var Project $project */
$project = $projectFactory->create('My project', [new LocalFile(self::FILE)]);
$class = $project->getFiles()[self::FILE]->getClasses()['\myHookUsingClass'];
self::assertArrayHasKey('project-metadata', $class->getMethods()['\myHookUsingClass::test()']->getMetadata());
}
}

View File

@@ -0,0 +1,230 @@
<?php
declare(strict_types=1);
namespace integration\PHP8;
use DateTimeImmutable;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Argument;
use phpDocumentor\Reflection\Php\Expression;
use phpDocumentor\Reflection\Php\Method;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Php\Property;
use phpDocumentor\Reflection\Php\Visibility;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\String_;
use PHPUnit\Framework\TestCase;
/**
* @coversNothing
*/
class ConstructorPromotionTest extends TestCase
{
private const FILE = __DIR__ . '/../data/PHP8/ConstructorPromotion.php';
private ProjectFactory $fixture;
private Project $project;
protected function setUp() : void
{
$this->fixture = ProjectFactory::createInstance();
$this->project = $this->fixture->create(
'PHP8',
[
new LocalFile(self::FILE),
]
);
}
public function testArgumentsAreReadCorrectly() : void
{
$file = $this->project->getFiles()[self::FILE];
$class = $file->getClasses()['\\PHP8\\ConstructorPromotion'];
$constructor = $this->expectedConstructorMethod();
$constructor->addArgument(new Argument('name', new String_(), "'default name'"));
$constructor->addArgument(
new Argument(
'email',
new Object_(new Fqsen('\\PHP8\\Email')),
new Expression(
'new {{ PHPDOCc27b34d4d91bc4d52190708db8447e09 }}()',
[
'{{ PHPDOCc27b34d4d91bc4d52190708db8447e09 }}' => new Fqsen('\\PHP8\\Email'),
],
)
)
);
$constructor->addArgument(new Argument('birth_date', new Object_(new Fqsen('\\' . DateTimeImmutable::class))));
$constructor->addArgument(
new Argument(
'created_at',
new Object_(new Fqsen('\\' . DateTimeImmutable::class)),
new Expression(
'new {{ PHPDOCf854926c6ee2b49d3385c30295984295 }}(\'now\')',
[
'{{ PHPDOCf854926c6ee2b49d3385c30295984295 }}' => new Fqsen('\\DateTimeImmutable')
]
)
)
);
$constructor->addArgument(
new Argument(
'uses_constants',
new Array_(),
new Expression(
'[{{ PHPDOC19b72d1f430d952a8dfe2384dd4e93dc }}]',
[
'{{ PHPDOC19b72d1f430d952a8dfe2384dd4e93dc }}' => new Fqsen('\self::DEFAULT_VALUE'),
],
),
),
);
self::assertEquals($constructor, $class->getMethods()['\PHP8\ConstructorPromotion::__construct()']);
}
public function testPropertiesAreCreated() : void
{
$file = $this->project->getFiles()[self::FILE];
$class = $file->getClasses()['\\PHP8\\ConstructorPromotion'];
self::assertEquals(
[
'\PHP8\ConstructorPromotion::$name' => $this->expectedNameProperty(),
'\PHP8\ConstructorPromotion::$email' => $this->expectedEmailProperty(),
'\PHP8\ConstructorPromotion::$birth_date' => $this->expectedBirthDateProperty(),
'\PHP8\ConstructorPromotion::$created_at' => $this->expectedCreatedAtProperty(),
'\PHP8\ConstructorPromotion::$uses_constants' => $this->expectedUsesConstantsProperty(),
],
$class->getProperties()
);
}
private function expectedConstructorMethod(): Method
{
return new Method(
new Fqsen('\PHP8\ConstructorPromotion::__construct()'),
new Visibility(Visibility::PUBLIC_),
new DocBlock(
'Constructor with promoted properties',
null,
[
new Param(
'name',
new String_(),
false,
new DocBlock\Description('my docblock name')
)
],
new Context('PHP8', ['DateTimeImmutable' => 'DateTimeImmutable'])
),
false,
false,
false,
new Location(18, 264),
new Location(31, 718)
);
}
private function expectedNameProperty(): Property
{
return new Property(
new Fqsen('\PHP8\ConstructorPromotion::$name'),
new Visibility(Visibility::PUBLIC_),
new DocBlock(
'Summary',
new DocBlock\Description('Description'),
[
new Var_('name', new String_(), new DocBlock\Description('property description'))
],
new Context('PHP8', ['DateTimeImmutable' => 'DateTimeImmutable'])
),
"'default name'",
false,
new Location(26),
new Location(26),
new String_()
);
}
private function expectedEmailProperty(): Property
{
return new Property(
new Fqsen('\PHP8\ConstructorPromotion::$email'),
new Visibility(Visibility::PROTECTED_),
null,
new Expression(
'new {{ PHPDOCc27b34d4d91bc4d52190708db8447e09 }}()',
[
'{{ PHPDOCc27b34d4d91bc4d52190708db8447e09 }}' => new Fqsen('\\PHP8\\Email'),
],
),
false,
new Location(27),
new Location(27),
New Object_(new Fqsen('\\PHP8\\Email')),
);
}
private function expectedBirthDateProperty(): Property
{
return new Property(
new Fqsen('\PHP8\ConstructorPromotion::$birth_date'),
new Visibility(Visibility::PRIVATE_),
null,
null,
false,
new Location(28),
new Location(28),
new Object_(new Fqsen('\\' . DateTimeImmutable::class))
);
}
private function expectedCreatedAtProperty(): Property
{
return new Property(
new Fqsen('\PHP8\ConstructorPromotion::$created_at'),
new Visibility(Visibility::PRIVATE_),
null,
new Expression(
'new {{ PHPDOCf854926c6ee2b49d3385c30295984295 }}(\'now\')',
[
'{{ PHPDOCf854926c6ee2b49d3385c30295984295 }}' => new Fqsen('\\DateTimeImmutable'),
],
),
false,
new Location(29),
new Location(29),
new Object_(new Fqsen('\\' . DateTimeImmutable::class)),
);
}
private function expectedUsesConstantsProperty()
{
return new Property(
new Fqsen('\PHP8\ConstructorPromotion::$uses_constants'),
new Visibility(Visibility::PRIVATE_),
null,
new Expression(
'[{{ PHPDOC19b72d1f430d952a8dfe2384dd4e93dc }}]',
[
'{{ PHPDOC19b72d1f430d952a8dfe2384dd4e93dc }}' => new Fqsen('\self::DEFAULT_VALUE'),
],
),
false,
new Location(30),
new Location(30),
new Array_(),
);
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace integration\PHP8;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Types\Mixed_;
use PHPUnit\Framework\TestCase;
/**
* @coversNothing
*/
final class MixedTypeTest extends TestCase
{
const FILE = __DIR__ . '/../data/PHP8/MixedType.php';
/** @var ProjectFactory */
private $fixture;
public function testSupportMixedType() : void
{
$this->fixture = ProjectFactory::createInstance();
/** @var Project $project */
$project = $this->fixture->create(
'PHP8',
[
new LocalFile(self::FILE),
]
);
$file = $project->getFiles()[self::FILE];
$class = $file->getClasses()['\PHP8\MixedType'];
self::assertEquals(new Mixed_(), $class->getMethods()['\PHP8\MixedType::getProperty()']->getReturnType());
self::assertEquals(new Mixed_(), $class->getMethods()['\PHP8\MixedType::setProperty()']->getArguments()[0]->getType());
self::assertEquals(new Mixed_(), $class->getProperties()['\PHP8\MixedType::$property']->getType());
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace integration\PHP8;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Types\Mixed_;
use phpDocumentor\Reflection\Types\Static_;
use PHPUnit\Framework\TestCase;
/**
* @coversNothing
*/
final class StaticTypeTest extends TestCase
{
const FILE = __DIR__ . '/../data/PHP8/StaticType.php';
/** @var ProjectFactory */
private $fixture;
public function testSupportStaticType() : void
{
$this->fixture = ProjectFactory::createInstance();
/** @var Project $project */
$project = $this->fixture->create(
'PHP8',
[
new LocalFile(self::FILE),
]
);
$file = $project->getFiles()[self::FILE];
$class = $file->getClasses()['\PHP8\StaticType'];
self::assertEquals(new Static_(), $class->getMethods()['\PHP8\StaticType::getProperty()']->getReturnType());
self::assertEquals(new Mixed_(), $class->getMethods()['\PHP8\StaticType::setProperty()']->getArguments()[0]->getType());
self::assertEquals(null, $class->getProperties()['\PHP8\StaticType::$property']->getType());
self::assertTrue($class->getProperties()['\PHP8\StaticType::$property']->isStatic());
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace integration\PHP8;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Types\False_;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Mixed_;
use phpDocumentor\Reflection\Types\Null_;
use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\Static_;
use phpDocumentor\Reflection\Types\String_;
use PHPUnit\Framework\TestCase;
/**
* @coversNothing
*/
final class UnionTypesTest extends TestCase
{
const FILE = __DIR__ . '/../data/PHP8/UnionTypes.php';
/** @var ProjectFactory */
private $fixture;
public function testUnionTypes() : void
{
$this->fixture = ProjectFactory::createInstance();
/** @var Project $project */
$project = $this->fixture->create(
'PHP8',
[
new LocalFile(self::FILE),
]
);
$file = $project->getFiles()[self::FILE];
$class = $file->getClasses()['\PHP8\UnionTypes'];
self::assertEquals(new Compound([new String_(), new Null_(), new Object_(new Fqsen('\Foo\Date'))]), $class->getMethods()['\PHP8\UnionTypes::union()']->getReturnType());
self::assertEquals(new Compound([new Integer(), new False_()]), $class->getMethods()['\PHP8\UnionTypes::union()']->getArguments()[0]->getType());
self::assertEquals(new Compound([new String_(), new Null_(), new False_()]), $class->getProperties()['\PHP8\UnionTypes::$property']->getType());
}
}

View File

@@ -0,0 +1,286 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\Class_;
use phpDocumentor\Reflection\Php\Expression;
use phpDocumentor\Reflection\Php\Function_;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Null_;
use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\String_;
/**
* Integration tests to check the correct working of processing a file into a project.
*
* @coversNothing
*/
class ProjectCreationTest extends MockeryTestCase
{
/** @var ProjectFactory */
private $fixture;
protected function setUp() : void
{
$this->fixture = ProjectFactory::createInstance();
}
public function testCreateProjectWithFunctions() : void
{
$fileName = __DIR__ . '/data/simpleFunction.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$file = $project->getFiles()[$fileName];
$this->assertArrayHasKey('\simpleFunction()', $file->getFunctions());
/** @var Function_ $function */
$function = $file->getFunctions()['\simpleFunction()'];
$this->assertSame('\simpleFunction()', (string) $function->getFqsen());
$this->assertCount(1, $function->getArguments());
}
public function testCreateProjectWithClass() : void
{
$fileName = __DIR__ . '/data/Pizza.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$this->assertArrayHasKey('\\Pizza', $project->getFiles()[$fileName]->getClasses());
$this->assertArrayHasKey(
'\\Pizza::PACKAGING',
$project->getFiles()[$fileName]->getClasses()['\\Pizza']->getConstants()
);
$constant = $project->getFiles()[$fileName]->getClasses()['\\Pizza']->getConstants()['\\Pizza::PACKAGING'];
$this->assertEquals('\'box\'', $constant->getValue());
}
public function testTypedPropertiesReturnTheirType() : void
{
$fileName = __DIR__ . '/data/Luigi/Pizza.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
/** @var Class_ $pizzaClass */
$pizzaClass = $project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza'];
$this->assertArrayHasKey('\\Luigi\\Pizza::$size', $pizzaClass->getProperties());
$this->assertEquals(new Integer(), $pizzaClass->getProperties()['\\Luigi\\Pizza::$size']->getType());
}
public function testFileWithDocBlock() : void
{
$fileName = __DIR__ . '/data/Pizza.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$this->assertInstanceOf(Docblock::class, $project->getFiles()[$fileName]->getDocBlock());
}
public function testWithNamespacedClass() : void
{
$fileName = __DIR__ . '/data/Luigi/Pizza.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$this->assertArrayHasKey('\\Luigi\\Pizza', $project->getFiles()[$fileName]->getClasses());
$this->assertEquals('\Pizza', $project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza']->getParent());
$this->assertArrayHasKey(
'\\Luigi\\Pizza::$instance',
$project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza']->getProperties()
);
$methods = $project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza']->getMethods();
$this->assertArrayHasKey(
'\\Luigi\\Pizza::__construct()',
$methods
);
$this->assertEquals('style', $methods['\\Luigi\\Pizza::__construct()']->getArguments()[0]->getName());
$this->assertEquals(
new Object_(new Fqsen('\\Luigi\\Pizza\Style')),
$methods['\\Luigi\\Pizza::__construct()']->getArguments()[0]->getType()
);
$sauceArgument = $methods['\\Luigi\\Pizza::__construct()']->getArguments()[1];
$this->assertEquals('sauce', $sauceArgument->getName());
$this->assertEquals(
new Php\Expression(
'{{ PHPDOC37a6259cc0c1dae299a7866489dff0bd }}',
[
'{{ PHPDOC37a6259cc0c1dae299a7866489dff0bd }}' => new Null_(),
],
),
$sauceArgument->getDefault(false)
);
}
public function testDocblockOfMethodIsProcessed() : void
{
$fileName = __DIR__ . '/data/Luigi/Pizza.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$methods = $project->getFiles()[$fileName]->getClasses()['\\Luigi\\Pizza']->getMethods();
$createInstanceMethod = $methods['\\Luigi\\Pizza::createInstance()'];
$this->assertInstanceOf(DocBlock::class, $createInstanceMethod->getDocBlock());
$docblock = $createInstanceMethod->getDocBlock();
/** @var Param[] $params */
$params = $docblock->getTagsByName('param');
/** @var Object_ $objectType */
$objectType = $params[0]->getType();
$this->assertEquals(new Fqsen('\Luigi\Pizza\Style'), $objectType->getFqsen());
}
public function testWithUsedParent() : void
{
$fileName = __DIR__ . '/data/Luigi/StyleFactory.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$this->assertArrayHasKey('\\Luigi\\StyleFactory', $project->getFiles()[$fileName]->getClasses());
$this->assertEquals(
'\\Luigi\\Pizza\\PizzaComponentFactory',
$project->getFiles()[$fileName]->getClasses()['\\Luigi\\StyleFactory']->getParent()
);
}
public function testWithInterface() : void
{
$fileName = __DIR__ . '/data/Luigi/Valued.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey('\\Luigi\\Valued', $project->getFiles()[$fileName]->getInterfaces());
}
public function testWithTrait() : void
{
$fileName = __DIR__ . '/data/Luigi/ExampleNestedTrait.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey('\\Luigi\\ExampleNestedTrait', $project->getFiles()[$fileName]->getTraits());
}
public function testWithGlobalConstants() : void
{
$fileName = __DIR__ . '/data/Luigi/constants.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey('\\Luigi\\OVEN_TEMPERATURE', $project->getFiles()[$fileName]->getConstants());
$this->assertArrayHasKey('\\Luigi\\MAX_OVEN_TEMPERATURE', $project->getFiles()[$fileName]->getConstants());
$this->assertArrayHasKey('\\OUTSIDE_OVEN_TEMPERATURE', $project->getFiles()[$fileName]->getConstants());
$this->assertArrayHasKey('\\LuigiFoo\\_OUTSIDE_OVEN_TEMPERATURE', $project->getFiles()[$fileName]->getConstants());
}
public function testInterfaceExtends() : void
{
$fileName = __DIR__ . '/data/Luigi/Packing.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey('\\Luigi\\Packing', $project->getFiles()[$fileName]->getInterfaces());
$interface = current($project->getFiles()[$fileName]->getInterfaces());
$this->assertEquals(['\\Packing' => new Fqsen('\\Packing')], $interface->getParents());
}
public function testMethodReturnType() : void
{
$fileName = __DIR__ . '/data/Packing.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey('\\Packing', $project->getFiles()[$fileName]->getInterfaces());
$interface = current($project->getFiles()[$fileName]->getInterfaces());
$this->assertEquals(new String_(), $interface->getMethods()['\Packing::getName()']->getReturnType());
}
public function testFunctionContantDefaultIsResolved() : void
{
$fileName = __DIR__ . '/data/GlobalFiles/function_constant_default.php';
$project = $this->fixture->create(
'MyProject',
[new LocalFile($fileName)]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$functions = $project->getFiles()[$fileName]->getFunctions();
self::assertEquals(
new Expression(
'{{ PHPDOCa2f2ed4f8ebc2cbb4c21a29dc40ab61d }}',
[
'{{ PHPDOCa2f2ed4f8ebc2cbb4c21a29dc40ab61d }}' => new Fqsen('\Acme\Plugin::class'),
],
),
$functions['\foo()']->getArguments()[0]->getDefault(false)
);
self::assertEquals(
new Expression(
'{{ PHPDOCa8cfde6331bd59eb2ac96f8911c4b666 }}',
[
'{{ PHPDOCa8cfde6331bd59eb2ac96f8911c4b666 }}' => new Object_(),
],
),
$functions['\bar()']->getArguments()[0]->getDefault(false)
);
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\ProjectFactory;
use PHPUnit\Framework\TestCase;
/**
* Integration tests to check the correct working of processing a namespace into a project.
*
* @coversNothing
*/
class ProjectNamespaceTest extends TestCase
{
/**
* @var ProjectFactory
*/
private $fixture;
protected function setUp() : void
{
$this->fixture = $this->fixture = ProjectFactory::createInstance();
}
public function testWithNamespacedClass() : void
{
$fileName = __DIR__ . '/data/Luigi/Pizza.php';
$project = $this->fixture->create(
'My Project',
[ new LocalFile($fileName) ]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$this->assertArrayHasKey('\\Luigi', $project->getNamespaces());
$this->assertEquals(
['\\Luigi\\Pizza' => new Fqsen('\\Luigi\\Pizza')],
$project->getNamespaces()['\\Luigi']->getClasses()
);
}
public function testWithNamespacedConstant() : void
{
$fileName = __DIR__ . '/data/Luigi/constants.php';
$project = $this->fixture->create(
'My Project',
[ new LocalFile($fileName) ]
);
$this->assertArrayHasKey($fileName, $project->getFiles());
$this->assertArrayHasKey('\\Luigi', $project->getNamespaces());
$this->assertEquals(
[
'\\Luigi\\OVEN_TEMPERATURE' => new Fqsen('\\Luigi\\OVEN_TEMPERATURE'),
'\\Luigi\\MAX_OVEN_TEMPERATURE' => new Fqsen('\\Luigi\\MAX_OVEN_TEMPERATURE'),
],
$project->getNamespaces()['\\Luigi']->getConstants()
);
}
}

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace integration;
use EliasHaeussler\PHPUnitAttributes\Attribute\RequiresPackage;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Argument;
use phpDocumentor\Reflection\Php\AsymmetricVisibility;
use phpDocumentor\Reflection\Php\Attribute;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Php\PropertyHook;
use phpDocumentor\Reflection\Php\Visibility;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\String_;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\TestCase;
#[RequiresPackage('nikic/php-parser', '>= 5.2')]
#[CoversNothing]
final class PropertyHookTest extends TestCase
{
public function testPropertyHookWithDocblocks(): void
{
$file = __DIR__ . '/data/PHP84/PropertyHook.php';
$projectFactory = ProjectFactory::createInstance();
$project = $projectFactory->create('My project', [new LocalFile($file)]);
$class = $project->getFiles()[$file]->getClasses()['\PropertyHook'];
$hooks = $class->getProperties()['\PropertyHook::$example']->getHooks();
$this->assertTrue($class->getProperties()['\PropertyHook::$example']->isVirtual());
$this->assertCount(2, $hooks);
$this->assertEquals('get', $hooks[0]->getName());
$this->assertEquals(new Visibility(Visibility::PUBLIC_), $hooks[0]->getVisibility());
$this->assertCount(1, $hooks[0]->getAttributes());
$this->assertCount(0, $hooks[0]->getArguments());
$this->assertSame('Not sure this works, but it gets', $hooks[0]->getDocBlock()->getSummary());
$this->assertEquals('set', $hooks[1]->getName());
$this->assertEquals(new Visibility(Visibility::PUBLIC_), $hooks[1]->getVisibility());
$this->assertCount(1, $hooks[1]->getAttributes());
$this->assertCount(1, $hooks[1]->getArguments());
$this->assertEquals(new Argument(
'value',
new Compound(
[
new String_(),
new Integer()
]
),
), $hooks[1]->getArguments()[0]);
$this->assertSame('Not sure this works, but it gets', $hooks[0]->getDocBlock()->getSummary());
}
public function testPropertyHookAsymmetric(): void
{
$file = __DIR__ . '/data/PHP84/PropertyHookAsymmetric.php';
$projectFactory = ProjectFactory::createInstance();
$project = $projectFactory->create('My project', [new LocalFile($file)]);
$class = $project->getFiles()[$file]->getClasses()['\PropertyHook'];
$hooks = $class->getProperties()['\PropertyHook::$example']->getHooks();
$this->assertEquals(
new AsymmetricVisibility(
new Visibility(Visibility::PUBLIC_),
new Visibility(Visibility::PRIVATE_)
),
$class->getProperties()['\PropertyHook::$example']->getVisibility()
);
$this->assertTrue($class->getProperties()['\PropertyHook::$example']->isVirtual());
$this->assertCount(2, $hooks);
$this->assertEquals('get', $hooks[0]->getName());
$this->assertEquals(new Visibility(Visibility::PUBLIC_), $hooks[0]->getVisibility());
$this->assertCount(0, $hooks[0]->getArguments());
$this->assertEquals('set', $hooks[1]->getName());
$this->assertEquals(new Visibility(Visibility::PRIVATE_), $hooks[1]->getVisibility());
$this->assertCount(1, $hooks[1]->getArguments());
$this->assertEquals(new Argument(
'value',
new Compound(
[
new String_(),
new Integer()
]
),
), $hooks[1]->getArguments()[0]);
}
public function testVirtualProperty(): void
{
$file = __DIR__ . '/data/PHP84/PropertyHookVirtual.php';
$projectFactory = ProjectFactory::createInstance();
$project = $projectFactory->create('My project', [new LocalFile($file)]);
$class = $project->getFiles()[$file]->getClasses()['\PropertyHookVirtual'];
// Test get-only virtual property
$fullNameProperty = $class->getProperties()['\PropertyHookVirtual::$fullName'];
$this->assertTrue($fullNameProperty->isVirtual(), 'Property with getter that doesn\'t reference itself should be virtual');
$this->assertCount(1, $fullNameProperty->getHooks());
$this->assertEquals('get', $fullNameProperty->getHooks()[0]->getName());
// Test set-only virtual property
$compositeNameProperty = $class->getProperties()['\PropertyHookVirtual::$compositeName'];
$this->assertTrue($compositeNameProperty->isVirtual(), 'Property with setter that doesn\'t reference itself should be virtual');
$this->assertCount(1, $compositeNameProperty->getHooks());
$this->assertEquals('set', $compositeNameProperty->getHooks()[0]->getName());
// Test property with both get and set hooks that doesn't reference itself
$completeFullNameProperty = $class->getProperties()['\PropertyHookVirtual::$completeFullName'];
$this->assertTrue($completeFullNameProperty->isVirtual(), 'Property with getter and setter that don\'t reference itself should be virtual');
$this->assertCount(2, $completeFullNameProperty->getHooks());
$nonVirtualPropertyWithoutHooks = $class->getProperties()['\PropertyHookVirtual::$firstName'];
$this->assertFalse($nonVirtualPropertyWithoutHooks->isVirtual(), 'Property without hooks should not be virtual');
$this->assertCount(0, $nonVirtualPropertyWithoutHooks->getHooks());
// Test non-virtual property that references itself
$nonVirtualNameProperty = $class->getProperties()['\PropertyHookVirtual::$nonVirtualName'];
$this->assertFalse($nonVirtualNameProperty->isVirtual(), 'Property with hooks that reference itself should not be virtual');
$this->assertCount(2, $nonVirtualNameProperty->getHooks());
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace integration;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\String_;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\TestCase;
#[CoversNothing]
final class TypedConstantTest extends TestCase
{
public function testTypedClassConstantsHaveType(): void
{
$file = __DIR__ . '/data/PHP83/TypedConstants.php';
$projectFactory = ProjectFactory::createInstance();
$project = $projectFactory->create('My project', [new LocalFile($file)]);
$class = $project->getFiles()[$file]->getClasses()['\PHP83\TypedConstants'];
$constants = $class->getConstants();
$this->assertEquals(new String_(), $constants['\PHP83\TypedConstants::NAME']->getType());
$this->assertEquals(new Integer(), $constants['\PHP83\TypedConstants::COUNT']->getType());
$this->assertEquals(new Compound([new String_(), new Integer()]), $constants['\PHP83\TypedConstants::UNION']->getType());
$this->assertEquals(new Nullable(new String_()), $constants['\PHP83\TypedConstants::NULLABLE']->getType());
$this->assertNull($constants['\PHP83\TypedConstants::UNTYPED']->getType());
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace MyNamespace;
class EnumConsumer
{
public MyEnum $myEnum = MyEnum::VALUE1;
public function consume(MyEnum $enum = MyEnum::VALUE1): MyEnum
{
$this->myEnum = $enum;
}
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace MyNamespace;
enum MyBackedEnum : string
{
case VALUE1 = 'this is value1';
case VALUE2 = 'this is value2';
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace MyNamespace;
enum MyEnum
{
case VALUE1;
case VALUE2;
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace MyNamespace;
enum MyEnumWithConstant
{
public const MYCONST = 'MyConstValue';
public const INT_CONST = 0;
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
if (!function_exists('h')) {
function h() {
}
} elseif(!function_exists('i')) {
if (true) {
function i()
{
}
}
} else {
function j() {
}
}
function a() {
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2015-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
?>
<h1>Test</h1>
<?php
require 'Pizza.php';

View File

@@ -0,0 +1,13 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2015-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
require 'Pizza.php';

View File

@@ -0,0 +1,15 @@
#!/bin/php
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2015-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
require 'Pizza.php';

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2015-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
require 'Pizza.php';

View File

@@ -0,0 +1,14 @@
<h1>Test</h1>
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2015-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
require 'Pizza.php';

View File

@@ -0,0 +1,7 @@
<?php
use Acme\Plugin;
function foo( $output = Plugin::class ) {}
function bar( $output = OBJECT ) {}

View File

@@ -0,0 +1,23 @@
<?php
/**
* setup.php - Document the file.
*
* File long description goes here.
*
* @author Test Sample
*/
namespace {
/**
* autoloader function.
*
* Function long description goes here.
*
* @param string $class Namespaced class name
* @return void
*/
function libAutoload($class){
echo 'Do something';
}
spl_autoload_register('libAutoload');
}

View File

@@ -0,0 +1,11 @@
<?php
class Foo {
public function test() {
function internal() {
return "yep";
}
return internal();
}
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2015-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
declare(strict_types=1);
require 'Pizza.php';

View File

@@ -0,0 +1,21 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace Luigi {
trait ExampleNestedTrait
{
private function exampleTraitMethod()
{
}
}
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2015-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace Luigi;
interface Packing extends \Packing
{
}

View File

@@ -0,0 +1,99 @@
<?php
// phpcs:ignoreFile
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Luigi;
use Luigi\Pizza\Sauce;
use Luigi\Pizza\TomatoSauce;
#[\Food("Pizza")]
#[\Food(country: "Italy", originDate: Pizza::class)]
class Pizza extends \Pizza
{
const
/** @var string DELIVERY designates that the delivery method is to deliver the pizza to the customer. */
DELIVERY = 'delivery',
/** @var string PICKUP designates that the delivery method is that the customer picks the pizza up. */
PICKUP = 'pickup';
use ExampleNestedTrait;
/** @var static contains the active instance for this Pizza. */
static private $instance;
/**
* @var Pizza\Style $style
* @var Pizza\Sauce|null $sauce
* @var Pizza\Topping[] $toppings
*/
private $style, $sauce, $toppings;
/**
* The size of the pizza in centimeters, defaults to 20cm.
*/
public int $size = \Luigi\Pizza\SIZE_20CM;
var $legacy; // don't use this anymore!
protected
/** @var string $packaging The type of packaging for this Pizza */
$packaging = self::PACKAGING,
/** @var string $deliveryMethod Is the customer picking this pizza up or must it be delivered? */
$deliveryMethod;
private function __construct(Pizza\Style $style, Sauce|null $sauce = null)
{
$this->style = $style;
}
/**
* Creates a new instance of a Pizza.
*
* This method can be used to instantiate a new object of this class which can then be retrieved using
* {@see self::getInstance()}.
*
* @param Pizza\Style $style
*
* @see self::getInstance to retrieve the pizza object.
*
* @return void
*/
public static function createInstance(Pizza\Style $style)
{
self::$instance = new static($style);
}
/**
* @return self
*/
static function getInstance()
{
return self::$instance;
}
final public function setSauce(Pizza\Sauce $sauce)
{
$this->sauce = $sauce;
}
final public function addTopping(Pizza\Topping $topping)
{
$this->toppings[] = $topping;
}
public function setSize(&$size = \Luigi\Pizza\SIZE_20CM)
{
}
public function getPrice()
{
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace Luigi;
use Luigi\Pizza\PizzaComponentFactory;
final class StyleFactory extends PizzaComponentFactory
{
public function getPrice()
{
}
protected function calculatePrice()
{
}
}

View File

@@ -0,0 +1,27 @@
<?php
// phpcs:ignoreFile
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace Luigi;
/**
* Any class implementing this interface has an associated price.
*
* Using this interface we can easily add the price of all components in a pizza by checking for this interface and
* adding the prices together for all components.
*/
interface Valued
{
const BASE_PRICE = 1;
function getPrice();
}

View File

@@ -0,0 +1,14 @@
<?php declare(strict_types=1);
namespace Luigi;
const OVEN_TEMPERATURE = 9001;
define('\\Luigi\\MAX_OVEN_TEMPERATURE', 9002);
define('OUTSIDE_OVEN_TEMPERATURE', 9002);
define(__NAMESPACE__ . 'Foo\\_OUTSIDE_OVEN_TEMPERATURE', 9002);
$v = 1;
define($v . '_OUTSIDE_OVEN_TEMPERATURE', 9002);
function in_function_define(){
define('IN_FUNCTION_OVEN_TEMPERATURE', 9003);
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace PHP8;
use DateTimeImmutable;
class ConstructorPromotion
{
private const DEFAULT_VALUE = 'default';
/**
* Constructor with promoted properties
*
* @param string $name my docblock name
*/
public function __construct(
/**
* Summary
*
* Description
*
* @var string $name property description
*/
public string $name = 'default name',
protected Email $email = new Email(),
private DateTimeImmutable $birth_date,
private DateTimeImmutable $created_at = new DateTimeImmutable('now'),
private array $uses_constants = [self::DEFAULT_VALUE],
) {}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace PHP8;
class MixedType
{
public mixed $property;
public function getProperty(): mixed
{
return $this->property;
}
public function setProperty(mixed $value): void
{
$this->property = $value;
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace PHP8;
class StaticType
{
//Static is not allowed here
public static $property;
public function getProperty(): static
{
return $this->property;
}
public function setProperty($value): void
{
$this->property = $value;
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace PHP8;
use Foo\Date;
class UnionTypes
{
private string|null|false $property;
public function union(int|false $test): string|null|Date
{
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace PHP83;
class TypedConstants
{
const string NAME = 'typed';
const int COUNT = 42;
const string|int UNION = 'either';
const ?string NULLABLE = null;
const UNTYPED = 'no type';
}

View File

@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
class AsymmetricAccessor
{
private(set) \Pizza $pizza;
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
class AsymmetricPropertyPromotion
{
public function __construct(
protected(set) Pizza $pizza,
) {}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace PHP84;
interface HasId
{
public int $id { get; }
}
interface HasName
{
public string $name { get; set; }
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
class PropertyHook
{
private bool $modified = false;
/** @var string this is my property */
#[Property(new DateTimeImmutable())]
public string $example = 'default value' {
/** Not sure this works, but it gets */
#[Getter(new DateTimeImmutable())]
get {
if ($this->modified) {
return $this->foo . ' (modified)';
}
return $this->foo;
}
/** Not sure this works, but it sets */
#[Setter(new DateTimeImmutable())]
set(string|int $value) {
$this->foo = strtolower($value);
$this->modified = true;
}
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
class PropertyHook
{
private bool $modified = false;
/** @var string this is my property */
#[Property(new DateTimeImmutable())]
public private(set) string $example = 'default value' {
get {
if ($this->modified) {
return $this->foo . ' (modified)';
}
return $this->foo;
}
set(string|int $value) {
$this->foo = strtolower($value);
$this->modified = true;
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
class PropertyHook
{
private bool $modified = false;
/** @param string $example this is my property */
public function __construct(
/** @var string this is my property */
#[Property(new DateTimeImmutable())]
public string $example = 'default value' {
/** Not sure this works, but it gets */
#[Getter(new DateTimeImmutable())]
get {
if ($this->modified) {
return $this->foo . ' (modified)';
}
return $this->foo;
}
/** Not sure this works, but it sets */
#[Setter(new DateTimeImmutable())]
set(string|int $value) {
$this->foo = strtolower($value);
$this->modified = true;
}
}
)
{
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
class PropertyHookVirtual
{
/**
* A virtual property that composes a full name from first and last name
*/
public string $fullName {
// This is a virtual property with a getter
// It doesn't reference $this->fullName
get {
return $this->firstName . ' ' . $this->lastName;
}
}
/**
* A virtual property that decomposes a full name into first and last name
*/
public string $compositeName {
// This is a virtual property with a setter
// It doesn't reference $this->compositeName
set(string $value) {
[$this->firstName, $this->lastName] = explode(' ', $value, 2);
}
}
/**
* A virtual property with both getter and setter
*/
public string $completeFullName {
// Getter doesn't reference $this->completeFullName
get {
return $this->firstName . ' ' . $this->lastName;
}
// Setter doesn't reference $this->completeFullName
set(string $value) {
[$this->firstName, $this->lastName] = explode(' ', $value, 2);
}
}
/**
* A non-virtual property that references itself in its hook
*/
public string $nonVirtualName {
get {
return $this->nonVirtualName ?? $this->firstName;
}
set(string $value) {
$this->nonVirtualName = $value;
}
}
public function __construct(
private string $firstName = 'John',
private string $lastName = 'Doe'
) {
}
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2015-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
interface Packing
{
public function getName(): string;
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2018 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
/**
* This needs a docblock to separate from
* file docblock
*/
const OVEN_TEMPERATURE = 9001;
define('MAX_OVEN_TEMPERATURE', 9002);
/**
* Pizza base class
*/
class Pizza
{
/**
* The packaging method used to transport the pizza.
*/
const PACKAGING = 'box';
}

View File

@@ -0,0 +1,8 @@
<?php
function simpleFunction(array $options = []): void
{
return;
}
exit();

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\File;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use function md5_file;
#[CoversClass(LocalFile::class)]
class LocalFileTest extends TestCase
{
public function testGetContents(): void
{
$file = new LocalFile(__FILE__);
$this->assertStringEqualsFile(__FILE__, $file->getContents());
}
public function testMd5(): void
{
$file = new LocalFile(__FILE__);
$this->assertEquals(md5_file(__FILE__), $file->md5());
}
public function testNotExistingFileThrowsException(): void
{
$this->expectException(InvalidArgumentException::class);
new LocalFile('aa');
}
public function testPath(): void
{
$file = new LocalFile(__FILE__);
$this->assertEquals(__FILE__, $file->path());
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Middleware;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use stdClass;
#[CoversClass(ChainFactory::class)]
final class ChainFactoryTest extends TestCase
{
public function testItCreatesAChainOfCallablesThatWillInvokeAllMiddlewares(): void
{
$exampleCommand = new class implements Command {
};
$middleware1 = $this->givenAMiddleware('c');
$middleware2 = $this->givenAMiddleware('b');
$chain = ChainFactory::createExecutionChain(
[$middleware1, $middleware2],
static function (): stdClass {
$result = new stdClass();
$result->counter = 'a';
return $result;
},
);
$this->assertInstanceOf(stdClass::class, $chain(new $exampleCommand()));
$this->assertSame('abc', $chain(new $exampleCommand())->counter);
}
public function testItThrowsAnExceptionIfAnythingOtherThanAMiddlewareIsPassed(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
'Middleware must be an instance of phpDocumentor\Reflection\Middleware\Middleware but string was given',
);
$middleware = '1';
ChainFactory::createExecutionChain(
[$middleware],
static fn (): stdClass => new stdClass(),
);
}
private function givenAMiddleware(string $exampleValue): Middleware
{
return new class ($exampleValue) implements Middleware {
public function __construct(private readonly string $exampleAddedValue)
{
}
public function execute(Command $command, callable $next): object
{
$result = $next($command);
$result->counter .= $this->exampleAddedValue;
return $result;
}
};
}
}

View File

@@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\NodeVisitor;
use PhpParser\Node\Const_;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
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 PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeTraverser;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(ElementNameResolver::class)]
class ElementNameResolverTest extends TestCase
{
private ElementNameResolver $fixture;
protected function setUp(): void
{
$this->fixture = new ElementNameResolver();
$this->fixture->beforeTraverse([]);
}
public function testFunctionWithoutNamespace(): void
{
$function = new Function_('myFunction');
$this->fixture->enterNode($function);
$this->assertEquals('\myFunction()', (string) $function->getAttribute('fqsen'));
}
public function testWithClass(): void
{
$class = new Class_('myClass');
$this->fixture->enterNode($class);
$this->assertEquals('\myClass', (string) $class->getAttribute('fqsen'));
}
public function testWithClassMethod(): void
{
$class = new Class_('myClass');
$this->fixture->enterNode($class);
$method = new ClassMethod('method');
$this->fixture->enterNode($method);
$this->assertEquals('\myClass::method()', (string) $method->getAttribute('fqsen'));
}
public function testWithClassProperty(): void
{
$class = new Class_('myClass');
$this->fixture->enterNode($class);
$method = new PropertyProperty('name');
$this->fixture->enterNode($method);
$this->assertEquals('\myClass::$name', (string) $method->getAttribute('fqsen'));
}
/**
* If anonymous classes were processed, we would obtain a
* InvalidArgumentException for an invalid Fqsen.
*/
public function testDoesNotEnterAnonymousClass(): void
{
$class = new Class_(null);
$this->assertEquals(
NodeTraverser::DONT_TRAVERSE_CHILDREN,
$this->fixture->enterNode($class),
);
}
/** @link https://github.com/phpDocumentor/Reflection/issues/103 */
public function testAnonymousClassDoesNotPopParts(): void
{
$anonymousClass = new Class_(null);
$new = new New_($anonymousClass);
$namespace = new Namespace_(new Name('ANamespace'), [new Return_($new)]);
$this->fixture->enterNode($namespace);
$this->fixture->enterNode($new);
$this->fixture->enterNode($anonymousClass);
$this->fixture->leaveNode($anonymousClass);
$this->fixture->leaveNode($new);
$this->fixture->leaveNode($namespace);
$this->assertTrue(true);
}
public function testClassConstant(): void
{
$const = new Const_('MY_CLASS', new String_('value'));
$classConst = new ClassConst([$const]);
$class = new Class_('myClass');
$this->fixture->enterNode($class);
$this->fixture->enterNode($classConst);
$this->fixture->enterNode($const);
$this->assertEquals('\\myClass::MY_CLASS', (string) $const->getAttribute('fqsen'));
}
public function testNamespacedConstant(): void
{
$const = new Const_('MY_CLASS', new String_('value'));
$namespace = new Namespace_(new Name('name'));
$this->fixture->enterNode($namespace);
$this->fixture->enterNode($const);
$this->assertEquals('\\name\\MY_CLASS', (string) $const->getAttribute('fqsen'));
}
public function testNoNameNamespace(): void
{
$const = new Const_('MY_CLASS', new String_('value'));
$namespace = new Namespace_(null);
$this->fixture->enterNode($namespace);
$this->fixture->enterNode($const);
$this->assertEquals('\\MY_CLASS', (string) $const->getAttribute('fqsen'));
}
public function testWithEnumWithCase(): void
{
$enum = new Enum_('myEnum');
$this->fixture->enterNode($enum);
$case = new EnumCase('VALUE1');
$this->fixture->enterNode($case);
$this->assertEquals('\myEnum::VALUE1', (string) $case->getAttribute('fqsen'));
}
}

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Types\Mixed_;
use phpDocumentor\Reflection\Types\String_;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
/**
* Tests the functionality for the Argument class.
*/
#[CoversClass(Argument::class)]
final class ArgumentTest extends TestCase
{
public function testGetTypes(): void
{
$argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, true);
self::assertInstanceOf(Mixed_::class, $argument->getType());
$argument = new Argument(
'myArgument',
new String_(),
new Expression('myDefaultValue'),
true,
true,
);
self::assertEquals(new String_(), $argument->getType());
}
public function testGetName(): void
{
$argument = new Argument('myArgument', null, new Expression('myDefault'), true, true);
self::assertEquals('myArgument', $argument->getName());
}
public function testGetDefault(): void
{
$argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, true);
self::assertEquals(new Expression('myDefaultValue'), $argument->getDefault());
$argument = new Argument('myArgument', null, null, true, true);
self::assertNull($argument->getDefault());
}
public function testGetWhetherArgumentIsPassedByReference(): void
{
$argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, true);
self::assertTrue($argument->isByReference());
$argument = new Argument('myArgument', null, null, false, true);
self::assertFalse($argument->isByReference());
}
public function testGetWhetherArgumentisVariadic(): void
{
$argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, true);
self::assertTrue($argument->isVariadic());
$argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, false);
self::assertFalse($argument->isVariadic());
}
}

View File

@@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
/** @property Class_ $fixture */
#[CoversClass(Class_::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Property')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Method')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
final class Class_Test extends TestCase
{
use MetadataContainerTestHelper;
private Fqsen $parent;
private Fqsen $fqsen;
private DocBlock $docBlock;
/**
* Creates a new (emoty) fixture object.
*/
protected function setUp(): void
{
$this->parent = new Fqsen('\MyParentClass');
$this->fqsen = new Fqsen('\MyClass');
$this->docBlock = new DocBlock('');
$this->fixture = new Class_($this->fqsen, $this->docBlock);
}
private function getFixture(): MetaDataContainerInterface
{
return $this->fixture;
}
public function testGettingName(): void
{
$this->assertSame($this->fqsen->getName(), $this->fixture->getName());
}
public function testGettingFqsen(): void
{
$this->assertSame($this->fqsen, $this->fixture->getFqsen());
}
public function testGettingDocBlock(): void
{
$this->assertSame($this->docBlock, $this->fixture->getDocBlock());
}
public function testGettingParent(): void
{
$class = new Class_($this->fqsen, $this->docBlock);
$this->assertNull($class->getParent());
$class = new Class_($this->fqsen, $this->docBlock, $this->parent);
$this->assertSame($this->parent, $class->getParent());
}
public function testAddAndGettingInterfaces(): void
{
$this->assertEmpty($this->fixture->getInterfaces());
$interface = new Fqsen('\MyInterface');
$this->fixture->addInterface($interface);
$this->assertSame(['\MyInterface' => $interface], $this->fixture->getInterfaces());
}
public function testAddAndGettingConstants(): void
{
$this->assertEmpty($this->fixture->getConstants());
$constant = new Constant(new Fqsen('\MyClass::MY_CONSTANT'));
$this->fixture->addConstant($constant);
$this->assertSame(['\MyClass::MY_CONSTANT' => $constant], $this->fixture->getConstants());
}
public function testAddAndGettingProperties(): void
{
$this->assertEmpty($this->fixture->getProperties());
$property = new Property(new Fqsen('\MyClass::$myProperty'));
$this->fixture->addProperty($property);
$this->assertSame(['\MyClass::$myProperty' => $property], $this->fixture->getProperties());
}
public function testAddAndGettingMethods(): void
{
$this->assertEmpty($this->fixture->getMethods());
$method = new Method(new Fqsen('\MyClass::myMethod()'));
$this->fixture->addMethod($method);
$this->assertSame(['\MyClass::myMethod()' => $method], $this->fixture->getMethods());
}
public function testAddAndGettingUsedTrait(): void
{
$this->assertEmpty($this->fixture->getUsedTraits());
$trait = new Fqsen('\MyTrait');
$this->fixture->addUsedTrait($trait);
$this->assertSame(['\MyTrait' => $trait], $this->fixture->getUsedTraits());
}
public function testGettingWhetherClassIsAbstract(): void
{
$class = new Class_($this->fqsen, $this->docBlock);
$this->assertFalse($class->isAbstract());
$class = new Class_($this->fqsen, $this->docBlock, null, true);
$this->assertTrue($class->isAbstract());
}
public function testGettingWhetherClassIsFinal(): void
{
$class = new Class_($this->fqsen, $this->docBlock);
$this->assertFalse($class->isFinal());
$class = new Class_($this->fqsen, $this->docBlock, null, false, true);
$this->assertTrue($class->isFinal());
}
public function testGettingWhetherClassIsReadOnly(): void
{
$class = new Class_($this->fqsen, $this->docBlock);
$this->assertFalse($class->isReadOnly());
$class = new Class_(
$this->fqsen,
$this->docBlock,
null,
false,
false,
null,
null,
true,
);
$this->assertTrue($class->isReadOnly());
}
public function testLineAndColumnNumberIsReturnedWhenALocationIsProvided(): void
{
$fixture = new Class_(
$this->fqsen,
$this->docBlock,
null,
false,
false,
new Location(100, 20),
new Location(101, 20),
);
$this->assertLineAndColumnNumberIsReturnedWhenALocationIsProvided($fixture);
}
}

View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use phpDocumentor\Reflection\Types\String_;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
/** @property Constant $fixture */
#[CoversClass(Constant::class)]
#[UsesClass('\phpDocumentor\Reflection\DocBlock')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Fqsen')]
final class ConstantTest extends TestCase
{
use MetadataContainerTestHelper;
private Fqsen $fqsen;
private DocBlock $docBlock;
private string $value = 'Value';
/**
* Creates a new (empty) fixture object.
*/
protected function setUp(): void
{
$this->fqsen = new Fqsen('\MySpace\CONSTANT');
$this->docBlock = new DocBlock('');
$this->fixture = new Constant($this->fqsen, $this->docBlock, new Expression($this->value));
}
private function getFixture(): MetaDataContainerInterface
{
return $this->fixture;
}
public function testGetValue(): void
{
self::assertEquals(new Expression($this->value), $this->fixture->getValue());
}
public function testIsFinal(): void
{
self::assertFalse($this->fixture->isFinal());
}
public function testGetFqsen(): void
{
self::assertSame($this->fqsen, $this->fixture->getFqsen());
self::assertSame($this->fqsen->getName(), $this->fixture->getName());
}
public function testGetDocblock(): void
{
self::assertSame($this->docBlock, $this->fixture->getDocBlock());
}
public function testGetVisibility(): void
{
self::assertEquals(new Visibility(Visibility::PUBLIC_), $this->fixture->getVisibility());
}
public function testGetTypeReturnsNullByDefault(): void
{
self::assertNull($this->fixture->getType());
}
public function testGetTypeReturnsTypeWhenProvided(): void
{
$type = new String_();
$fixture = new Constant(
$this->fqsen,
$this->docBlock,
new Expression($this->value),
null,
null,
null,
false,
$type,
);
self::assertSame($type, $fixture->getType());
}
public function testLineAndColumnNumberIsReturnedWhenALocationIsProvided(): void
{
$fixture = new Constant($this->fqsen, $this->docBlock, null, new Location(100, 20), new Location(101, 20));
$this->assertLineAndColumnNumberIsReturnedWhenALocationIsProvided($fixture);
}
}

View File

@@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(EnumCase::class)]
final class EnumCaseTest extends TestCase
{
use MetadataContainerTestHelper;
private EnumCase $fixture;
private Fqsen $fqsen;
private DocBlock $docBlock;
/**
* Creates a new (empty) fixture object.
*/
protected function setUp(): void
{
$this->fqsen = new Fqsen('\Enum::VALUE');
$this->docBlock = new DocBlock('');
// needed for MetaDataContainer testing
$this->fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
);
}
private function getFixture(): MetaDataContainerInterface
{
return $this->fixture;
}
public function testGettingName(): void
{
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
);
$this->assertSame($this->fqsen->getName(), $fixture->getName());
}
public function testGettingFqsen(): void
{
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
);
$this->assertSame($this->fqsen, $fixture->getFqsen());
}
public function testGettingDocBlock(): void
{
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
);
$this->assertSame($this->docBlock, $fixture->getDocBlock());
}
public function testValueCanBeOmitted(): void
{
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
);
$this->assertNull($fixture->getValue());
}
public function testValueCanBeProvidedAsAnExpression(): void
{
$expression = new Expression('Enum case expression');
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
null,
null,
$expression,
);
$this->assertSame($expression, $fixture->getValue(false));
}
public function testValueCanBeReturnedAsString(): void
{
$expression = new Expression('Enum case expression');
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
null,
null,
$expression,
);
$this->assertSame('Enum case expression', $fixture->getValue(true));
}
public function testGetLocationReturnsProvidedValue(): void
{
$location = new Location(15, 10);
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
$location,
);
self::assertSame($location, $fixture->getLocation());
}
public function testGetLocationReturnsUnknownByDefault(): void
{
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
);
self::assertEquals(new Location(-1), $fixture->getLocation());
}
public function testGetEndLocationReturnsProvidedValue(): void
{
$location = new Location(11, 23);
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
null,
$location,
);
self::assertSame($location, $fixture->getEndLocation());
}
public function testGetEndLocationReturnsUnknownByDefault(): void
{
$fixture = new EnumCase(
$this->fqsen,
$this->docBlock,
);
self::assertEquals(new Location(-1), $fixture->getEndLocation());
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
/** @property Enum_ $fixture */
#[CoversClass(Enum_::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Method')]
#[UsesClass('\phpDocumentor\Reflection\Php\EnumCase')]
final class Enum_Test extends TestCase
{
use MetadataContainerTestHelper;
private Fqsen $parent;
private Fqsen $fqsen;
private DocBlock $docBlock;
/**
* Creates a new (emoty) fixture object.
*/
protected function setUp(): void
{
$this->parent = new Fqsen('\MyParentEnum');
$this->fqsen = new Fqsen('\Enum');
$this->docBlock = new DocBlock('');
$this->fixture = new Enum_($this->fqsen, null, $this->docBlock);
}
private function getFixture(): MetaDataContainerInterface
{
return $this->fixture;
}
public function testGettingName(): void
{
$this->assertSame($this->fqsen->getName(), $this->fixture->getName());
}
public function testGetBackedWithOutType(): void
{
$this->assertNull($this->fixture->getBackedType());
}
public function testGettingFqsen(): void
{
$this->assertSame($this->fqsen, $this->fixture->getFqsen());
}
public function testGettingDocBlock(): void
{
$this->assertSame($this->docBlock, $this->fixture->getDocBlock());
}
public function testAddAndGettingInterfaces(): void
{
$this->assertEmpty($this->fixture->getInterfaces());
$interface = new Fqsen('\MyInterface');
$this->fixture->addInterface($interface);
$this->assertSame(['\MyInterface' => $interface], $this->fixture->getInterfaces());
}
public function testAddAndGettingConstants(): void
{
$this->assertEmpty($this->fixture->getConstants());
$constant = new Constant(new Fqsen('\MyClass::MYCONST'));
$this->fixture->addConstant($constant);
$this->assertSame(['\MyClass::MYCONST' => $constant], $this->fixture->getConstants());
}
public function testAddAndGettingMethods(): void
{
$this->assertEmpty($this->fixture->getMethods());
$method = new Method(new Fqsen('\MyClass::myMethod()'));
$this->fixture->addMethod($method);
$this->assertSame(['\MyClass::myMethod()' => $method], $this->fixture->getMethods());
}
public function testAddAndGettingUsedTrait(): void
{
$this->assertEmpty($this->fixture->getUsedTraits());
$trait = new Fqsen('\MyTrait');
$this->fixture->addUsedTrait($trait);
$this->assertSame(['\MyTrait' => $trait], $this->fixture->getUsedTraits());
}
public function testAddAndGettingCases(): void
{
$this->assertEmpty($this->fixture->getCases());
$case = new EnumCase(new Fqsen('\MyEnum::VALUE'), null);
$this->fixture->addCase($case);
$this->assertSame(['\MyEnum::VALUE' => $case], $this->fixture->getCases());
}
public function testLineAndColumnNumberIsReturnedWhenALocationIsProvided(): void
{
$fixture = new Enum_($this->fqsen, null, $this->docBlock, new Location(100, 20), new Location(101, 20));
$this->assertLineAndColumnNumberIsReturnedWhenALocationIsProvided($fixture);
}
}

View File

@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace unit\phpDocumentor\Reflection\Php\Expression;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\PseudoTypes\True_;
use phpDocumentor\Reflection\Types\Null_;
use PhpParser\ParserFactory;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
#[CoversClass(ExpressionPrinter::class)]
final class ExpressionPrinterTest extends TestCase
{
/** @param array<string, Type> $expectedParts */
#[DataProvider('argumentProvider')]
public function testArgumentIsParsed(string $code, string $expectedExpression, array $expectedParts): void
{
$parser = (new ParserFactory())->createForHostVersion();
$node = $parser->parse($code);
$printer = new ExpressionPrinter();
$expression = $printer->prettyPrintExpr($node[0]->params[0]->default);
self::assertSame($expectedExpression, $expression);
self::assertEquals($expectedParts, $printer->getParts());
}
/**
* @return array<string, array{
* 'code': string,
* 'expectedExpression': string,
* 'expectedParts': array<string, Type>
* }>
*/
public static function argumentProvider(): array
{
return [
'myClassDefault' => [
'code' => '<?php function foo(MyClass $arg = new MyClass()) {}',
'expectedExpression' => 'new {{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}()',
'expectedParts' => [
'{{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}' => new Fqsen('\MyClass'),
],
],
// Enum case default.
// After first run, replace ENUM_PLACEHOLDER below with the actual placeholder shown in the failure output.
'enumCaseDefault' => [
'code' => '<?php function foo(MyEnum $arg = MyEnum::CaseA) {}',
'expectedExpression' => '{{ PHPDOC8844445ee68bb81ea3fd9529f906598b }}',
'expectedParts' => [
'{{ PHPDOC8844445ee68bb81ea3fd9529f906598b }}' => new Fqsen('\MyEnum::CaseA'),
],
],
'classConstantDefault' => [
'code' => '<?php function foo(MyClass $arg = MyClass::SOME_CONST) {}',
'expectedExpression' => '{{ PHPDOCe54b7c24dd0f847c3193039223751b3d }}',
'expectedParts' => [
'{{ PHPDOCe54b7c24dd0f847c3193039223751b3d }}' => new Fqsen('\MyClass::SOME_CONST'),
],
],
'selfConstantDefault' => [
'code' => '<?php function foo(MyClass $arg = self::SOME_CONST) {}',
'expectedExpression' => '{{ PHPDOCe54b7c24dd0f847c3193039223751b3d }}',
'expectedParts' => [
'{{ PHPDOCe54b7c24dd0f847c3193039223751b3d }}' => new Fqsen('\self::SOME_CONST'),
],
],
'stringDefault' => [
'code' => '<?php function foo(string $arg = \'hello\') {}',
'expectedExpression' => "'hello'",
'expectedParts' => [],
],
'intDefault' => [
'code' => '<?php function foo(int $arg = 42) {}',
'expectedExpression' => '42',
'expectedParts' => [],
],
'booleanDefault' => [
'code' => '<?php function foo(bool $arg = true) {}',
'expectedExpression' => '{{ PHPDOCb326b5062b2f0e69046810717534cb09 }}',
'expectedParts' => [
'{{ PHPDOCb326b5062b2f0e69046810717534cb09 }}' => new True_(),
],
],
'nullDefault' => [
'code' => '<?php function foo(bool|null $arg = null) {}',
'expectedExpression' => '{{ PHPDOC37a6259cc0c1dae299a7866489dff0bd }}',
'expectedParts' => [
'{{ PHPDOC37a6259cc0c1dae299a7866489dff0bd }}' => new Null_(),
],
],
'emptyArrayDefault' => [
'code' => '<?php function foo(array $arg = []) {}',
'expectedExpression' => '[]',
'expectedParts' => [],
],
'intArrayDefault' => [
'code' => '<?php function foo(array $arg = [1, 2]) {}',
'expectedExpression' => '[1, 2]',
'expectedParts' => [],
],
'stringArrayDefault' => [
'code' => '<?php function foo(array $arg = [\'hello\', \'world\']) {}',
'expectedExpression' => "['hello', 'world']",
'expectedParts' => [],
],
'objectArrayDefault' => [
'code' => '<?php function foo(array $arg = [new MyClass(), new MyClass()]) {}',
'expectedExpression' => '[new {{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}(), new {{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}()]',
'expectedParts' => [
'{{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}' => new Fqsen('\MyClass'),
],
],
];
}
}

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Generator;
use InvalidArgumentException;
use phpDocumentor\Reflection\Fqsen;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use function sprintf;
#[CoversClass(Expression::class)]
final class ExpressionTest extends TestCase
{
private const EXAMPLE_FQSEN = '\\' . self::class;
private const EXAMPLE_FQSEN_PLACEHOLDER = '{{ PHPDOC0450ed2a7bac1efcf0c13b6560767954 }}';
public function testGeneratingPlaceholder(): void
{
$placeholder = Expression::generatePlaceholder(self::EXAMPLE_FQSEN);
self::assertSame(self::EXAMPLE_FQSEN_PLACEHOLDER, $placeholder);
}
public function testGeneratingPlaceholderErrorsUponPassingAnEmptyName(): void
{
$this->expectException(InvalidArgumentException::class);
Expression::generatePlaceholder('');
}
public function testExpressionTemplateCannotBeEmpty(): void
{
$this->expectException(InvalidArgumentException::class);
new Expression('', []);
}
public function testPartsShouldContainFqsensOrTypes(): void
{
$this->expectException(InvalidArgumentException::class);
new Expression('This is an expression', [self::EXAMPLE_FQSEN_PLACEHOLDER => self::EXAMPLE_FQSEN]);
}
public function testGetExpressionTemplateString(): void
{
$expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER);
$parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)];
$expression = new Expression($expressionTemplate, $parts);
$result = $expression->getExpression();
self::assertSame($expressionTemplate, $result);
}
public function testGetExtractedParts(): void
{
$expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER);
$parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)];
$expression = new Expression($expressionTemplate, $parts);
$result = $expression->getParts();
self::assertSame($parts, $result);
}
public function testReplacePlaceholdersWhenCastingToString(): void
{
$expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER);
$parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)];
$expression = new Expression($expressionTemplate, $parts);
$result = (string) $expression;
self::assertSame(sprintf('This is an %s expression', self::EXAMPLE_FQSEN), $result);
}
public function testRenderingExpressionWithoutOverridesIsTheSameAsWhenCastingToString(): void
{
$expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER);
$parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)];
$expression = new Expression($expressionTemplate, $parts);
$result = $expression->render();
self::assertSame((string) $expression, $result);
}
public function testOverridePartsWhenRenderingExpression(): void
{
$replacement = 'ExpressionTest';
$expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER);
$parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)];
$expression = new Expression($expressionTemplate, $parts);
$result = $expression->render([self::EXAMPLE_FQSEN_PLACEHOLDER => $replacement]);
self::assertSame(sprintf('This is an %s expression', $replacement), $result);
}
#[DataProvider('expressionValues')]
public function testExpressionTemplateCreation(string $expression): void
{
$actual = new Expression($expression, []);
self::assertSame($expression, $actual->getExpression());
}
/** @return Generator<string, array{expression: string} */
public static function expressionValues(): Generator
{
$values = ['0', 'null', 'false'];
foreach ($values as $value) {
yield $value => ['expression' => $value];
}
}
}

View File

@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Mockery as m;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use phpDocumentor\Reflection\Fqsen;
use PhpParser\Comment\Doc;
use PhpParser\Node\Const_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\ClassConst;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(ClassConstantIterator::class)]
final class ClassConstantIteratorTest extends MockeryTestCase
{
public function testIterateProps(): void
{
$const1 = new Const_('\Space\MyClass::MY_CONST1', new Variable('1'));
$const1->setAttribute('fqsen', new Fqsen((string) $const1->name));
$const2 = new Const_('\Space\MyClass::MY_CONST2', new Variable('2'));
$const2->setAttribute('fqsen', new Fqsen((string) $const2->name));
$classConstantNode = new ClassConst([$const1, $const2]);
$i = 1;
foreach (new ClassConstantIterator($classConstantNode) as $constant) {
$this->assertEquals('\Space\MyClass::MY_CONST' . $i, $constant->getName());
$this->assertEquals('\Space\MyClass::MY_CONST' . $i, (string) $constant->getFqsen());
$this->assertEquals($i, $constant->getValue()->name);
++$i;
}
}
public function testKey(): void
{
$constantMock = m::mock(ClassConst::class);
$fixture = new ClassConstantIterator($constantMock);
$this->assertEquals(0, $fixture->key());
$fixture->next();
$this->assertEquals(1, $fixture->key());
}
public function testProxyMethods(): void
{
$constantMock = m::mock(ClassConst::class);
$constantMock->shouldReceive('getLine')->once()->andReturn(10);
$fixture = new ClassConstantIterator($constantMock);
$this->assertEquals(10, $fixture->getLine());
}
public function testGetDocCommentPropFirst(): void
{
$const = m::mock(Const_::class);
$classConstants = m::mock(ClassConst::class);
$classConstants->consts = [$const];
$const->shouldReceive('getDocComment')->once()->andReturn(new Doc('test'));
$classConstants->shouldReceive('getDocComment')->never();
$fixture = new ClassConstantIterator($classConstants);
$this->assertEquals('test', $fixture->getDocComment()->getText());
}
public function testGetTypeReturnsNullWhenUntyped(): void
{
$const = new Const_('\Space\MyClass::MY_CONST1', new String_('a'));
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
$classConstantNode = new ClassConst([$const]);
$fixture = new ClassConstantIterator($classConstantNode);
$this->assertNull($fixture->getType());
}
public function testGetTypeReturnsTypeWhenTyped(): void
{
$const = new Const_('\Space\MyClass::MY_CONST1', new String_('a'));
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
$classConstantNode = new ClassConst([$const], 0, [], [], new Identifier('string'));
$fixture = new ClassConstantIterator($classConstantNode);
$this->assertInstanceOf(Identifier::class, $fixture->getType());
$this->assertEquals('string', $fixture->getType()->toString());
}
public function testGetDocComment(): void
{
$const = m::mock(Const_::class);
$classConstants = m::mock(ClassConst::class);
$classConstants->consts = [$const];
$const->shouldReceive('getDocComment')->once()->andReturnNull();
$classConstants->shouldReceive('getDocComment')->once()->andReturn(new Doc('test'));
$fixture = new ClassConstantIterator($classConstants);
$this->assertEquals('test', $fixture->getDocComment()->getText());
}
}

View File

@@ -0,0 +1,214 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\DocBlock as DocBlockDescriptor;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
use phpDocumentor\Reflection\Php\Constant as ConstantDescriptor;
use phpDocumentor\Reflection\Php\Enum_ as EnumElement;
use phpDocumentor\Reflection\Php\Interface_ as InterfaceElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategies;
use phpDocumentor\Reflection\Php\Trait_ as TraitElement;
use phpDocumentor\Reflection\Types\Null_;
use phpDocumentor\Reflection\Types\String_ as StringType;
use PhpParser\Comment\Doc;
use PhpParser\Node\Const_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(ClassConstant::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\ClassConstantIterator')]
#[UsesClass('\phpDocumentor\Reflection\Php\ProjectFactoryStrategies')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
final class ClassConstantTest extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docBlockFactory;
protected function setUp(): void
{
$this->docBlockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new ClassConstant(
$this->docBlockFactory->reveal(),
new PrettyPrinter(),
);
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue($this->fixture->matches(self::createContext(null), $this->buildConstantIteratorStub()));
}
#[DataProvider('visibilityProvider')]
public function testCreateWithVisibility(int $input, string $expectedVisibility, bool $isFinal = false): void
{
$constantStub = $this->buildConstantIteratorStub($input);
$class = $this->performCreate($constantStub);
$constant = current($class->getConstants());
$this->assertConstant($constant, $expectedVisibility);
$this->assertSame($isFinal, $constant->isFinal());
}
/** @return array<string|int[]> */
public static function visibilityProvider(): array
{
return [
[
ClassNode::MODIFIER_PUBLIC,
'public',
],
[
ClassNode::MODIFIER_PROTECTED,
'protected',
],
[
ClassNode::MODIFIER_PRIVATE,
'private',
],
[
ClassNode::MODIFIER_PRIVATE | ClassNode::MODIFIER_FINAL,
'private',
true,
],
];
}
public function testCreateForInterface(): void
{
$interface = new InterfaceElement(new Fqsen('\myInterface'));
$const = new Const_('\Space\MyInterface::MY_CONST1', new String_('a'));
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
$constantStub = new ClassConst([$const], ClassNode::MODIFIER_PUBLIC);
$result = $this->performCreateWith($constantStub, $interface);
self::assertInstanceOf(ConstantDescriptor::class, current($result->getConstants()));
}
public function testCreateForTrait(): void
{
$trait = new TraitElement(new Fqsen('\myTrait'));
$const = new Const_('\Space\MyTrait::MY_CONST1', new String_('a'));
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
$constantStub = new ClassConst([$const], ClassNode::MODIFIER_PUBLIC);
$result = $this->performCreateWith($constantStub, $trait);
self::assertInstanceOf(ConstantDescriptor::class, current($result->getConstants()));
}
public function testCreateForEnum(): void
{
$enum = new EnumElement(new Fqsen('\myEnum'), new Null_());
$const = new Const_('\Space\MyEnum::MY_CONST1', new String_('a'));
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
$constantStub = new ClassConst([$const], ClassNode::MODIFIER_PUBLIC);
$result = $this->performCreateWith($constantStub, $enum);
self::assertInstanceOf(ConstantDescriptor::class, current($result->getConstants()));
}
public function testCreateWithTypedConstant(): void
{
$const = new Const_('\Space\MyClass::MY_CONST1', new String_('a'));
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
$constantStub = new ClassConst([$const], ClassNode::MODIFIER_PUBLIC, [], [], new Identifier('string'));
$class = $this->performCreate($constantStub);
$constant = current($class->getConstants());
$this->assertInstanceOf(ConstantDescriptor::class, $constant);
$this->assertEquals(new StringType(), $constant->getType());
}
public function testCreateWithUntypedConstantHasNullType(): void
{
$constantStub = $this->buildConstantIteratorStub();
$class = $this->performCreate($constantStub);
$constant = current($class->getConstants());
$this->assertNull($constant->getType());
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('text');
$docBlock = new DocBlockDescriptor('text');
$this->docBlockFactory->create('text', null)->willReturn($docBlock);
$const = new Const_('\Space\MyClass::MY_CONST1', new String_('a'), ['comments' => [$doc]]);
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
$constantStub = new ClassConst([$const], ClassNode::MODIFIER_PUBLIC);
$class = $this->performCreate($constantStub);
$constant = current($class->getConstants());
$this->assertConstant($constant, 'public');
$this->assertSame($docBlock, $constant->getDocBlock());
}
private function buildConstantIteratorStub(int $modifier = ClassNode::MODIFIER_PUBLIC): ClassConst
{
$const = new Const_('\Space\MyClass::MY_CONST1', new String_('a'));
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
return new ClassConst([$const], $modifier);
}
private function assertConstant(ConstantDescriptor $constant, string $visibility): void
{
$this->assertInstanceOf(ConstantDescriptor::class, $constant);
$this->assertEquals('\Space\MyClass::MY_CONST1', (string) $constant->getFqsen());
$this->assertEquals('\'a\'', $constant->getValue());
$this->assertEquals($visibility, (string) $constant->getVisibility());
}
private function performCreate(ClassConst $constantStub): ClassElement
{
$class = new ClassElement(new Fqsen('\myClass'));
$this->performCreateWith($constantStub, $class);
return $class;
}
private function performCreateWith(ClassConst $constantStub, Element $parent): Element
{
$factory = new ProjectFactoryStrategies([]);
$this->fixture->create(self::createContext(null)->push($parent), $constantStub, $factory);
return $parent;
}
}

View File

@@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock as DocBlockElement;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
use phpDocumentor\Reflection\Php\File;
use phpDocumentor\Reflection\Php\Method as MethodElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Comment\Doc;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\ClassMethod;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(Class_::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Class_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Property')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Php\Method')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\ClassConstantIterator')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\PropertyIterator')]
final class Class_Test extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docblockFactory;
protected function setUp(): void
{
$this->docblockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new Class_($this->docblockFactory->reveal());
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue(
$this->fixture->matches(
self::createContext(null),
$this->prophesize(ClassNode::class)->reveal(),
),
);
}
public function testSimpleCreate(): void
{
$containerMock = m::mock(StrategyContainer::class);
$classMock = $this->buildClassMock();
$classMock->shouldReceive('getDocComment')->andReturnNull();
$class = $this->performCreate($classMock, $containerMock);
$this->assertInstanceOf(ClassElement::class, $class);
$this->assertEquals('\Space\MyClass', (string) $class->getFqsen());
$this->assertNull($class->getParent());
$this->assertTrue($class->isFinal());
$this->assertTrue($class->isAbstract());
}
public function testClassWithParent(): void
{
$containerMock = m::mock(StrategyContainer::class);
$classMock = $this->buildClassMock();
$classMock->shouldReceive('getDocComment')->andReturnNull();
$classMock->extends = new Name('Space\MyParent');
$class = $this->performCreate($classMock, $containerMock);
$this->assertInstanceOf(ClassElement::class, $class);
$this->assertEquals('\Space\MyClass', (string) $class->getFqsen());
$this->assertEquals('\Space\MyParent', (string) $class->getParent());
}
public function testClassImplementingInterface(): void
{
$containerMock = m::mock(StrategyContainer::class);
$classMock = $this->buildClassMock();
$classMock->shouldReceive('getDocComment')->andReturnNull();
$classMock->extends = new Name('Space\MyParent');
$classMock->implements = [
new Name('MyInterface'),
];
$class = $this->performCreate($classMock, $containerMock);
$this->assertInstanceOf(ClassElement::class, $class);
$this->assertEquals('\Space\MyClass', (string) $class->getFqsen());
$this->assertEquals(
['\MyInterface' => new Fqsen('\MyInterface')],
$class->getInterfaces(),
);
}
public function testIteratesStatements(): void
{
$method1 = new ClassMethod('MyClass::method1');
$method1Descriptor = new MethodElement(new Fqsen('\MyClass::method1'));
$strategyMock = $this->prophesize(ProjectFactoryStrategy::class);
$containerMock = $this->prophesize(StrategyContainer::class);
$classMock = $this->buildClassMock();
$classMock->shouldReceive('getDocComment')->andReturnNull();
$classMock->stmts = [$method1];
$strategyMock->create(Argument::type(ContextStack::class), $method1, $containerMock)
->will(function ($args) use ($method1Descriptor): void {
$args[0]->peek()->addMethod($method1Descriptor);
})
->shouldBeCalled();
$containerMock->findMatching(
Argument::type(ContextStack::class),
$method1,
)->willReturn($strategyMock->reveal());
$class = $this->performCreate($classMock, $containerMock->reveal());
$this->assertInstanceOf(ClassElement::class, $class);
$this->assertEquals('\Space\MyClass', (string) $class->getFqsen());
$this->assertEquals(
['\MyClass::method1' => $method1Descriptor],
$class->getMethods(),
);
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('Text');
$classMock = $this->buildClassMock();
$classMock->shouldReceive('getDocComment')->andReturn($doc);
$docBlock = new DocBlockElement('');
$this->docblockFactory->create('Text', null)->willReturn($docBlock);
$containerMock = m::mock(StrategyContainer::class);
$class = $this->performCreate($classMock, $containerMock);
$this->assertSame($docBlock, $class->getDocBlock());
}
private function buildClassMock(): m\MockInterface|ClassNode
{
$classMock = m::mock(ClassNode::class);
$classMock->shouldReceive('getAttribute')->andReturn(new Fqsen('\Space\MyClass'));
$classMock->implements = [];
$classMock->stmts = [];
$classMock->shouldReceive('isFinal')->andReturn(true);
$classMock->shouldReceive('isAbstract')->andReturn(true);
$classMock->shouldReceive('isReadonly')->andReturn(true);
$classMock->shouldReceive('getLine')->andReturn(1);
$classMock->shouldReceive('getEndLine')->andReturn(2);
return $classMock;
}
private function performCreate(ClassNode $classMock, StrategyContainer $containerMock): ClassElement
{
$file = new File('hash', 'path');
$this->fixture->create(self::createContext(null)->push($file), $classMock, $containerMock);
return current($file->getClasses());
}
}

View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
use phpDocumentor\Reflection\Php\Interface_ as InterfaceElement;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\Property as PropertyElement;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\Visibility;
use PhpParser\Comment\Doc;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\PrettyPrinter\Standard;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(ConstructorPromotion::class)]
final class ConstructorPromotionTest extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $strategy;
private ObjectProphecy $docblockFactory;
protected function setUp(): void
{
$this->strategy = $this->prophesize(ProjectFactoryStrategy::class);
$this->docblockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$printer = $this->prophesize(Standard::class);
$printer->prettyPrintExpr(Argument::any())->willReturn('myType');
$this->fixture = new ConstructorPromotion(
$this->strategy->reveal(),
$this->docblockFactory->reveal(),
$printer->reveal(),
);
}
#[DataProvider('objectProvider')]
public function testMatches(ContextStack $context, object $object, bool $expected): void
{
self::assertEquals($expected, $this->fixture->matches($context, $object));
}
/** @return mixed[][] */
public static function objectProvider(): array
{
$context = new ContextStack(new Project('test'));
return [
'emptyContext' => [
$context,
new stdClass(),
false,
],
'invalid stack type' => [
$context->push(new InterfaceElement(new Fqsen('\MyInterface'))),
new ClassMethod('foo'),
false,
],
'with class but not constructor' => [
$context->push(new ClassElement(new Fqsen('\MyInterface'))),
new ClassMethod('foo'),
false,
],
'with class but and is constructor' => [
$context->push(new ClassElement(new Fqsen('\MyInterface'))),
new ClassMethod('__construct'),
true,
],
];
}
#[DataProvider('visibilityProvider')]
public function testCreateWithProperty(int $flags, string $visibility, bool $readOnly = false): void
{
$methodNode = new ClassMethod('__construct');
$methodNode->params = [
new Param(
new Variable('myArgument'),
new String_('MyDefault'),
new Identifier('string'),
false,
false,
[
'comments' => [
new Doc('text'),
],
],
$flags,
),
];
$docBlock = new DocBlock('Test');
$class = new ClassElement(new Fqsen('\MyClass'));
$context = self::createContext()->push($class);
$this->docblockFactory->create('text', null)->willReturn($docBlock);
$this->strategy->create($context, $methodNode, Argument::type(StrategyContainer::class))
->shouldBeCalled();
$this->fixture->create(
$context,
$methodNode,
$this->prophesize(StrategyContainer::class)->reveal(),
);
$property = current($class->getProperties());
self::assertInstanceOf(PropertyElement::class, $property);
self::assertEquals($visibility, $property->getVisibility());
self::assertSame($docBlock, $property->getDocBlock());
self::assertSame('myType', $property->getDefault());
self::assertEquals('\MyClass::$myArgument', $property->getFqsen());
self::assertSame($readOnly, $property->isReadOnly());
}
/** @return mixed[][] */
public static function visibilityProvider(): array
{
return [
[
ClassNode::MODIFIER_PUBLIC,
Visibility::PUBLIC_,
],
[
ClassNode::MODIFIER_PROTECTED,
Visibility::PROTECTED_,
],
[
ClassNode::MODIFIER_PRIVATE,
Visibility::PRIVATE_,
],
[
ClassNode::MODIFIER_PRIVATE | ClassNode::MODIFIER_READONLY,
Visibility::PRIVATE_,
true,
],
];
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use OutOfBoundsException;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
use phpDocumentor\Reflection\Php\Method;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Types\Context;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
#[CoversClass(ContextStack::class)]
final class ContextStackTest extends PHPUnitTestCase
{
public function testCreate(): void
{
$project = new Project('myProject');
$typeContext = new Context('myNamespace');
$context = new ContextStack($project, $typeContext);
self::assertSame($project, $context->getProject());
self::assertSame($typeContext, $context->getTypeContext());
}
public function testPeekThowsWhenEmpty(): void
{
$this->expectException(OutOfBoundsException::class);
$project = new Project('myProject');
$typeContext = new Context('myNamespace');
$context = new ContextStack($project, $typeContext);
$context->peek();
}
public function testPeekReturnsTopOfStack(): void
{
$class = new ClassElement(new Fqsen('\MyClass'));
$project = new Project('myProject');
$typeContext = new Context('myNamespace');
$context = new ContextStack($project, $typeContext);
$context = $context->push($class);
self::assertSame($class, $context->peek());
self::assertSame($project, $context->getProject());
self::assertSame($typeContext, $context->getTypeContext());
}
public function testCreateWithTypeContext(): void
{
$class = new ClassElement(new Fqsen('\MyClass'));
$project = new Project('myProject');
$typeContext = new Context('myNamespace');
$context = new ContextStack($project);
$context = $context->push($class)->withTypeContext($typeContext);
self::assertSame($class, $context->peek());
self::assertSame($project, $context->getProject());
self::assertSame($typeContext, $context->getTypeContext());
}
public function testSearchEmptyStackResultsInNull(): void
{
$project = new Project('myProject');
$context = new ContextStack($project);
self::assertNull($context->search(ClassElement::class));
}
public function testSearchStackForExistingElementTypeWillReturnTheFirstHit(): void
{
$class = new ClassElement(new Fqsen('\MyClass'));
$project = new Project('myProject');
$context = new ContextStack($project);
$context = $context
->push(new ClassElement(new Fqsen('\OtherClass')))
->push($class)
->push(new Method(new Fqsen('\MyClass::method()')));
self::assertSame($class, $context->search(ClassElement::class));
}
}

View File

@@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\DocBlock as DocBlockDescriptor;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Php\Constant as ConstantDescriptor;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategies;
use phpDocumentor\Reflection\Types\Context;
use PhpParser\Comment\Doc;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Exit_;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(Define::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\ProjectFactoryStrategies')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
final class DefineTest extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docBlockFactory;
protected function setUp(): void
{
$this->docBlockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new Define($this->docBlockFactory->reveal(), new PrettyPrinter());
}
public function testMatches(): void
{
$invalidExpressionType = new Expression(new Exit_());
$invalidFunctionCall = new Expression(new FuncCall(new Name('print')));
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertFalse($this->fixture->matches(self::createContext(null), $invalidExpressionType));
$this->assertFalse($this->fixture->matches(self::createContext(null), $invalidFunctionCall));
$this->assertTrue($this->fixture->matches(self::createContext(null), $this->buildDefineStub()));
}
public function testCreate(): void
{
$constantStub = $this->buildDefineStub();
$file = new FileElement('hash', 'path');
$contextStack = self::createContext(new Context('Space\\MyClass'))->push($file);
$this->fixture->create($contextStack, $constantStub, new ProjectFactoryStrategies([]));
$constant = current($file->getConstants());
$this->assertConstant($constant, '');
}
public function testCreateNamespace(): void
{
$constantStub = $this->buildDefineStub('\\OtherSpace\\MyClass');
$file = new FileElement('hash', 'path');
$contextStack = self::createContext(new Context('Space\\MyClass'))->push($file);
$this->fixture->create($contextStack, $constantStub, new ProjectFactoryStrategies([]));
$constant = current($file->getConstants());
$this->assertConstant($constant, '\\OtherSpace\\MyClass');
}
public function testCreateGlobal(): void
{
$constantStub = $this->buildDefineStub();
$file = new FileElement('hash', 'path');
$contextStack = self::createContext()->push($file);
$this->fixture->create($contextStack, $constantStub, new ProjectFactoryStrategies([]));
$constant = current($file->getConstants());
$this->assertConstant($constant, '');
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('Text');
$docBlock = new DocBlockDescriptor('');
$constantStub = new Expression(
new FuncCall(
new Name('define'),
[
new Arg(new String_('MY_CONST1')),
new Arg(new String_('a')),
],
),
['comments' => [$doc]],
);
$typeContext = new Context('Space\\MyClass');
$this->docBlockFactory->create('Text', $typeContext)->willReturn($docBlock);
$file = new FileElement('hash', 'path');
$contextStack = self::createContext($typeContext)->push($file);
$this->fixture->create($contextStack, $constantStub, new ProjectFactoryStrategies([]));
$constant = current($file->getConstants());
$this->assertConstant($constant, '');
$this->assertSame($docBlock, $constant->getDocBlock());
}
private function buildDefineStub(string $namespace = ''): Expression
{
return new Expression(
new FuncCall(
new Name('define'),
[
new Arg(new String_($namespace ? $namespace . '\\MY_CONST1' : 'MY_CONST1')),
new Arg(new String_('a')),
],
),
);
}
private function assertConstant(ConstantDescriptor $constant, string $namespace): void
{
$this->assertInstanceOf(ConstantDescriptor::class, $constant);
$this->assertEquals($namespace . '\\MY_CONST1', (string) $constant->getFqsen());
$this->assertEquals('\'a\'', $constant->getValue());
$this->assertEquals('public', (string) $constant->getVisibility());
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* PHP Version 5.5
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
/**
* Stub for test purpose only.
*/
final class DummyFactoryStrategy implements ProjectFactoryStrategy
{
/**
* Returns true when the strategy is able to handle the object.
*
* @param mixed $object object to check.
*/
public function matches(ContextStack $context, object $object): bool
{
return true;
}
/**
* Creates an Element out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param mixed $object object to convert to an Element
* @param StrategyContainer $strategies used to convert nested objects.
*
* @return mixed
*/
public function create(ContextStack $context, object $object, StrategyContainer $strategies): void
{
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Enum_ as EnumElement;
use phpDocumentor\Reflection\Php\EnumCase as EnumCaseElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategies;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Stmt\EnumCase as EnumCaseNode;
use PhpParser\PrettyPrinter\Standard;
use PHPUnit\Framework\Attributes\CoversClass;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
#[CoversClass(EnumCase::class)]
#[CoversClass(AbstractFactory::class)]
final class EnumCaseTest extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docblockFactory;
protected function setUp(): void
{
$this->docblockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new EnumCase($this->docblockFactory->reveal(), new Standard());
}
public function testMatches(): void
{
self::assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
self::assertTrue(
$this->fixture->matches(
self::createContext(null),
$this->prophesize(EnumCaseNode::class)->reveal(),
),
);
}
public function testSimpleCreate(): void
{
$containerMock = $this->prophesize(StrategyContainer::class)->reveal();
$enumMock = $this->buildEnumCaseMock();
$enumMock->getDocComment()->willReturn(null);
$result = $this->performCreate($enumMock->reveal());
self::assertInstanceOf(EnumElement::class, $result);
self::assertEquals(
[
'\Space\MyEnum::VALUE' => new EnumCaseElement(
new Fqsen('\Space\MyEnum::VALUE'),
null,
new Location(1),
new Location(2),
),
],
$result->getCases(),
);
}
private function performCreate(EnumCaseNode $enumCase): EnumElement
{
$factory = new ProjectFactoryStrategies([]);
$enum = new EnumElement(new Fqsen('\myEnum'), null);
$this->fixture->create(self::createContext(null)->push($enum), $enumCase, $factory);
return $enum;
}
private function buildEnumCaseMock(): ObjectProphecy
{
$enumMock = $this->prophesize(EnumCaseNode::class);
$enumMock->expr = null;
$enumMock->getAttribute('fqsen')->willReturn(new Fqsen('\Space\MyEnum::VALUE'));
$enumMock->getLine()->willReturn(1);
$enumMock->getEndLine()->willReturn(2);
return $enumMock;
}
}

View File

@@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock as DocBlockElement;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Enum_ as EnumElement;
use phpDocumentor\Reflection\Php\File;
use phpDocumentor\Reflection\Php\Method as MethodElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Types\String_;
use PhpParser\Comment\Doc;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Enum_ as EnumNode;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(Enum_::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Enum_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Method')]
final class Enum_Test extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docblockFactory;
protected function setUp(): void
{
$this->docblockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new Enum_($this->docblockFactory->reveal());
}
public function testMatches(): void
{
self::assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
self::assertTrue(
$this->fixture->matches(
self::createContext(null),
$this->prophesize(EnumNode::class)->reveal(),
),
);
}
public function testSimpleCreate(): void
{
$containerMock = m::mock(StrategyContainer::class);
$enumMock = $this->buildEnumMock();
$enumMock->shouldReceive('getDocComment')->andReturnNull();
$result = $this->performCreate($enumMock, $containerMock);
self::assertInstanceOf(EnumElement::class, $result);
self::assertEquals('\Space\MyEnum', (string) $result->getFqsen());
}
public function testBackedEnumTypeIsSet(): void
{
$containerMock = m::mock(StrategyContainer::class);
$enumMock = $this->buildEnumMock();
$enumMock->shouldReceive('getDocComment')->andReturnNull();
$enumMock->scalarType = new Identifier('string');
$result = $this->performCreate($enumMock, $containerMock);
self::assertInstanceOf(EnumElement::class, $result);
self::assertEquals('\Space\MyEnum', (string) $result->getFqsen());
self::assertEquals(new String_(), $result->getBackedType());
}
public function testClassImplementingInterface(): void
{
$containerMock = m::mock(StrategyContainer::class);
$enumMock = $this->buildEnumMock();
$enumMock->shouldReceive('getDocComment')->andReturnNull();
$enumMock->extends = 'Space\MyParent';
$enumMock->implements = [
new Name('MyInterface'),
];
$result = $this->performCreate($enumMock, $containerMock);
self::assertInstanceOf(EnumElement::class, $result);
self::assertEquals('\Space\MyEnum', (string) $result->getFqsen());
self::assertEquals(
['\MyInterface' => new Fqsen('\MyInterface')],
$result->getInterfaces(),
);
}
public function testIteratesStatements(): void
{
$method1 = new ClassMethod('MyEnum::method1');
$method1Descriptor = new MethodElement(new Fqsen('\MyEnum::method1'));
$strategyMock = $this->prophesize(ProjectFactoryStrategy::class);
$containerMock = $this->prophesize(StrategyContainer::class);
$enumMock = $this->buildEnumMock();
$enumMock->shouldReceive('getDocComment')->andReturnNull();
$enumMock->stmts = [$method1];
$strategyMock->create(Argument::type(ContextStack::class), $method1, $containerMock)
->will(function ($args) use ($method1Descriptor): void {
$args[0]->peek()->addMethod($method1Descriptor);
})
->shouldBeCalled();
$containerMock->findMatching(
Argument::type(ContextStack::class),
$method1,
)->willReturn($strategyMock->reveal());
$result = $this->performCreate($enumMock, $containerMock->reveal());
self::assertInstanceOf(EnumElement::class, $result);
self::assertEquals('\Space\MyEnum', (string) $result->getFqsen());
self::assertEquals(
['\MyEnum::method1' => $method1Descriptor],
$result->getMethods(),
);
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('Text');
$enumMock = $this->buildEnumMock();
$enumMock->shouldReceive('getDocComment')->andReturn($doc);
$docBlock = new DocBlockElement('');
$this->docblockFactory->create('Text', null)->willReturn($docBlock);
$containerMock = m::mock(StrategyContainer::class);
$result = $this->performCreate($enumMock, $containerMock);
self::assertSame($docBlock, $result->getDocBlock());
}
private function buildEnumMock(): m\MockInterface|ClassNode
{
$enumMock = m::mock(EnumNode::class);
$enumMock->scalarType = null;
$enumMock->shouldReceive('getAttribute')->andReturn(new Fqsen('\Space\MyEnum'));
$enumMock->implements = [];
$enumMock->stmts = [];
$enumMock->shouldReceive('getLine')->andReturn(1);
$enumMock->shouldReceive('getEndLine')->andReturn(2);
return $enumMock;
}
private function performCreate(EnumNode $enumMock, StrategyContainer $containerMock): EnumElement
{
$file = new File('hash', 'path');
$this->fixture->create(self::createContext(null)->push($file), $enumMock, $containerMock);
return current($file->getEnums());
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory\File;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategies;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(CreateCommand::class)]
#[UsesClass('phpDocumentor\Reflection\File\LocalFile')]
#[UsesClass('phpDocumentor\Reflection\Php\ProjectFactoryStrategies')]
class CreateCommandTest extends TestCase
{
private CreateCommand $fixture;
private LocalFile $file;
private ProjectFactoryStrategies $strategies;
protected function setUp(): void
{
$this->file = new LocalFile(__FILE__);
$this->strategies = new ProjectFactoryStrategies([]);
$this->fixture = new CreateCommand(
new ContextStack(new Project('test')),
$this->file,
$this->strategies,
);
}
public function testGetFile(): void
{
$this->assertSame($this->file, $this->fixture->getFile());
}
public function testGetStrategies(): void
{
$this->assertSame($this->strategies, $this->fixture->getStrategies());
}
}

View File

@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use InvalidArgumentException;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock as DocBlockDescriptor;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\File as SourceFile;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Middleware\Middleware;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\NodesFactory;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Comment as CommentNode;
use PhpParser\Comment\Doc as DocBlockNode;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
use function file_get_contents;
#[CoversClass(File::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\File')]
#[UsesClass('\phpDocumentor\Reflection\File\LocalFile')]
#[UsesClass('\phpDocumentor\Reflection\Middleware\ChainFactory')]
#[UsesClass('\phpDocumentor\Reflection\Php\Class_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Trait_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Interface_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Function_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\GlobalConstantIterator')]
#[UsesClass('\phpDocumentor\Reflection\Types\NamespaceNodeToContext')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\File\CreateCommand')]
final class FileTest extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $nodesFactoryMock;
private ObjectProphecy $docBlockFactory;
protected function setUp(): void
{
$this->docBlockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->nodesFactoryMock = $this->prophesize(NodesFactory::class);
$this->fixture = new File($this->docBlockFactory->reveal(), $this->nodesFactoryMock->reveal());
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue($this->fixture->matches(self::createContext(null), m::mock(SourceFile::class)));
}
public function testMiddlewareIsExecuted(): void
{
$file = new FileElement('aa', __FILE__);
$this->nodesFactoryMock->create(file_get_contents(__FILE__), Argument::any())->willReturn([]);
$middleware = $this->prophesize(Middleware::class);
$middleware->execute(Argument::any(), Argument::any())->shouldBeCalled()->willReturn($file);
$fixture = new File(
$this->docBlockFactory->reveal(),
$this->nodesFactoryMock->reveal(),
[$middleware->reveal()],
);
$context = self::createContext();
$containerMock = $this->prophesize(StrategyContainer::class);
$fixture->create($context, new SourceFile\LocalFile(__FILE__), $containerMock->reveal());
$result = current($context->getProject()->getFiles());
$this->assertSame($result, $file);
}
public function testMiddlewareIsChecked(): void
{
$this->expectException(InvalidArgumentException::class);
new File($this->docBlockFactory->reveal(), $this->nodesFactoryMock->reveal(), [new stdClass()]);
}
#[DataProvider('nodeProvider')]
public function testFileGetsCommentFromFirstNode(Node $node, DocBlockDescriptor $docblock): void
{
$this->nodesFactoryMock->create(file_get_contents(__FILE__), Argument::any())->willReturn([$node]);
$this->docBlockFactory->create('Text', Argument::any())->willReturn($docblock);
$strategies = $this->prophesize(StrategyContainer::class);
$strategies->findMatching(Argument::type(ContextStack::class), $node)->willReturn(
$this->prophesize(ProjectFactoryStrategy::class)->reveal(),
);
$context = self::createContext();
$this->fixture->create($context, new SourceFile\LocalFile(__FILE__), $strategies->reveal());
$file = current($context->getProject()->getFiles());
$this->assertInstanceOf(FileElement::class, $file);
$this->assertSame($docblock, $file->getDocBlock());
}
/** @return array<string, mixed[]> */
public static function nodeProvider(): array
{
$docBlockNode = new DocBlockNode('Text');
$namespaceNode = new NamespaceNode(new Name('mySpace'));
$namespaceNode->getAttribute('fsqen', new Fqsen('\mySpace'));
$namespaceNode->setAttribute('comments', [$docBlockNode]);
$classNode = new ClassNode('myClass');
$classNode->setAttribute('comments', [$docBlockNode, new DocBlockNode('')]);
$namespaceNode2 = new NamespaceNode(new Name('mySpace'));
$namespaceNode2->getAttribute('fsqen', new Fqsen('\mySpace'));
$namespaceNode2->setAttribute('comments', [new CommentNode('@codingStandardsIgnoreStart'), $docBlockNode]);
return [
'With namespace' => [
$namespaceNode,
new DocBlockDescriptor(''),
],
'With class' => [
$classNode,
new DocBlockDescriptor(''),
],
'With comments' => [
$namespaceNode2,
new DocBlockDescriptor(''),
],
];
}
}

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\DocBlock as DocBlockDescriptor;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\File;
use phpDocumentor\Reflection\Php\Function_ as FunctionDescriptor;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Comment\Doc;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Expression;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(Function_::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Function_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Argument')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\Type')]
final class Function_Test extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docBlockFactory;
protected function setUp(): void
{
$this->docBlockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new Function_($this->docBlockFactory->reveal());
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue($this->fixture->matches(
self::createContext(null)->push(new File('hash', 'path')),
$this->prophesize(\PhpParser\Node\Stmt\Function_::class)->reveal(),
));
}
public function testCreateWithoutParameters(): void
{
$functionMock = $this->prophesize(\PhpParser\Node\Stmt\Function_::class);
$functionMock->byRef = false;
$functionMock->stmts = [];
$functionMock->getAttribute('fqsen')->willReturn(new Fqsen('\SomeSpace::function()'));
$functionMock->params = [];
$functionMock->getDocComment()->willReturn(null);
$functionMock->getLine()->willReturn(1);
$functionMock->getEndLine()->willReturn(2);
$functionMock->getReturnType()->willReturn(null);
$containerMock = $this->prophesize(StrategyContainer::class);
$file = new File('hash', 'path');
$this->fixture->create(self::createContext()->push($file), $functionMock->reveal(), $containerMock->reveal());
$function = current($file->getFunctions());
$this->assertInstanceOf(FunctionDescriptor::class, $function);
$this->assertEquals('\SomeSpace::function()', (string) $function->getFqsen());
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('Text');
$functionMock = $this->prophesize(\PhpParser\Node\Stmt\Function_::class);
$functionMock->byRef = false;
$functionMock->stmts = [];
$functionMock->getAttribute('fqsen')->willReturn(new Fqsen('\SomeSpace::function()'));
$functionMock->params = [];
$functionMock->getDocComment()->willReturn($doc);
$functionMock->getLine()->willReturn(1);
$functionMock->getEndLine()->willReturn(2);
$functionMock->getReturnType()->willReturn(null);
$docBlock = new DocBlockDescriptor('');
$this->docBlockFactory->create('Text', null)->willReturn($docBlock);
$containerMock = $this->prophesize(StrategyContainer::class);
$file = new File('hash', 'path');
$this->fixture->create(self::createContext()->push($file), $functionMock->reveal(), $containerMock->reveal());
$function = current($file->getFunctions());
$this->assertEquals('\SomeSpace::function()', (string) $function->getFqsen());
$this->assertSame($docBlock, $function->getDocBlock());
}
public function testIteratesStatements(): void
{
$doc = new Doc('Text');
$functionMock = $this->prophesize(\PhpParser\Node\Stmt\Function_::class);
$functionMock->byRef = false;
$functionMock->stmts = [];
$functionMock->getAttribute('fqsen')->willReturn(new Fqsen('\SomeSpace::function()'));
$functionMock->params = [];
$functionMock->getDocComment()->willReturn(null);
$functionMock->getLine()->willReturn(1);
$functionMock->getEndLine()->willReturn(2);
$functionMock->getReturnType()->willReturn(null);
$functionMock->stmts = [new Expression(new FuncCall(new Name('hook')))];
$strategyMock = $this->prophesize(ProjectFactoryStrategy::class);
$containerMock = $this->prophesize(StrategyContainer::class);
$containerMock->findMatching(
Argument::type(ContextStack::class),
Argument::type(Expression::class),
)->willReturn($strategyMock->reveal())->shouldBeCalledOnce();
$file = new File('hash', 'path');
$this->fixture->create(
self::createContext(null)->push($file),
$functionMock->reveal(),
$containerMock->reveal(),
);
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Mockery as m;
use phpDocumentor\Reflection\Fqsen;
use PhpParser\Comment\Doc;
use PhpParser\Node\Const_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Const_ as ConstStatement;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(GlobalConstantIterator::class)]
final class GlobalConstantIteratorTest extends m\Adapter\Phpunit\MockeryTestCase
{
public function testIterateProps(): void
{
$const1 = new Const_('\Space\MY_CONST1', new Variable('a'));
$const1->setAttribute('fqsen', new Fqsen((string) $const1->name));
$const2 = new Const_('\Space\MY_CONST2', new Variable('b'));
$const2->setAttribute('fqsen', new Fqsen((string) $const2->name));
$globalConstantNode = new ConstStatement([$const1, $const2]);
$i = 1;
foreach (new GlobalConstantIterator($globalConstantNode) as $constant) {
$this->assertEquals('\Space\MY_CONST' . $i, $constant->getName());
$this->assertEquals('\Space\MY_CONST' . $i, (string) $constant->getFqsen());
++$i;
}
}
public function testKey(): void
{
$constant = m::mock(ConstStatement::class);
$fixture = new GlobalConstantIterator($constant);
$this->assertEquals(0, $fixture->key());
$fixture->next();
$this->assertEquals(1, $fixture->key());
}
public function testProxyMethods(): void
{
$constant = m::mock(ConstStatement::class);
$constant->shouldReceive('getLine')->once()->andReturn(10);
$fixture = new GlobalConstantIterator($constant);
$this->assertEquals(10, $fixture->getLine());
}
public function testGetDocCommentPropFirst(): void
{
$const = m::mock(Const_::class);
$constants = m::mock(ConstStatement::class);
$constants->consts = [$const];
$const->shouldReceive('getDocComment')->once()->andReturn(new Doc('test'));
$constants->shouldReceive('getDocComment')->never();
$fixture = new GlobalConstantIterator($constants);
$this->assertEquals('test', $fixture->getDocComment()->getText());
}
public function testGetDocComment(): void
{
$const = m::mock(Const_::class);
$constants = m::mock(ConstStatement::class);
$constants->consts = [$const];
$const->shouldReceive('getDocComment')->once()->andReturnNull();
$constants->shouldReceive('getDocComment')->once()->andReturn(new Doc('test'));
$fixture = new GlobalConstantIterator($constants);
$this->assertEquals('test', $fixture->getDocComment()->getText());
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock as DocBlockDescriptor;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Constant as ConstantDescriptor;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategies;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Comment\Doc;
use PhpParser\Node\Const_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Const_ as ConstStatement;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(GlobalConstant::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\GlobalConstantIterator')]
#[UsesClass('\phpDocumentor\Reflection\Php\ProjectFactoryStrategies')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
final class GlobalConstantTest extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docBlockFactory;
protected function setUp(): void
{
$this->docBlockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new GlobalConstant($this->docBlockFactory->reveal(), new PrettyPrinter());
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue($this->fixture->matches(self::createContext(null), $this->buildConstantIteratorStub()));
}
public function testCreate(): void
{
$factory = new ProjectFactoryStrategies([]);
$constantStub = $this->buildConstantIteratorStub();
$file = new FileElement('hash', 'path');
$this->fixture->create(self::createContext(null)->push($file), $constantStub, $factory);
$constant = current($file->getConstants());
$this->assertConstant($constant);
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('Text');
$docBlock = new DocBlockDescriptor('');
$const = new Const_('\Space\MyClass\MY_CONST1', new String_('a'), ['comments' => [$doc]]);
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
$constantStub = new ConstStatement([$const]);
$containerMock = m::mock(StrategyContainer::class);
$this->docBlockFactory->create('Text', null)->willReturn($docBlock);
$file = new FileElement('hash', 'path');
$this->fixture->create(self::createContext(null)->push($file), $constantStub, $containerMock);
$constant = current($file->getConstants());
$this->assertConstant($constant);
$this->assertSame($docBlock, $constant->getDocBlock());
}
private function buildConstantIteratorStub(): ConstStatement
{
$const = new Const_('\Space\MyClass\MY_CONST1', new String_('a'));
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
return new ConstStatement([$const]);
}
private function assertConstant(ConstantDescriptor $constant): void
{
$this->assertInstanceOf(ConstantDescriptor::class, $constant);
$this->assertEquals('\Space\MyClass\MY_CONST1', (string) $constant->getFqsen());
$this->assertEquals('\'a\'', $constant->getValue());
$this->assertEquals('public', (string) $constant->getVisibility());
}
}

View File

@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock as DocBlockElement;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\Interface_ as InterfaceElement;
use phpDocumentor\Reflection\Php\Method as MethodElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Comment\Doc;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(Interface_::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Interface_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Method')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\ClassConstantIterator')]
class Interface_Test extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docBlockFactory;
protected function setUp(): void
{
$this->docBlockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new Interface_($this->docBlockFactory->reveal());
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue($this->fixture->matches(self::createContext(null), m::mock(InterfaceNode::class)));
}
public function testSimpleCreate(): void
{
$interfaceMock = $this->buildClassMock();
$interfaceMock->shouldReceive('getDocComment')->andReturnNull();
$containerMock = $this->prophesize(StrategyContainer::class);
$interface = $this->performCreate($interfaceMock, $containerMock->reveal());
$this->assertInstanceOf(InterfaceElement::class, $interface);
$this->assertEquals('\Space\MyInterface', (string) $interface->getFqsen());
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('Text');
$docBlock = new DocBlockElement('');
$this->docBlockFactory->create('Text', null)->willReturn($docBlock);
$containerMock = $this->prophesize(StrategyContainer::class);
$interfaceMock = $this->buildClassMock();
$interfaceMock->shouldReceive('getDocComment')->andReturn($doc);
$interface = $this->performCreate($interfaceMock, $containerMock->reveal());
$this->assertSame($docBlock, $interface->getDocBlock());
}
public function testIteratesStatements(): void
{
$method1 = new ClassMethod('MyClass::method1');
$method1Descriptor = new MethodElement(new Fqsen('\MyClass::method1'));
$strategyMock = $this->prophesize(ProjectFactoryStrategy::class);
$containerMock = $this->prophesize(StrategyContainer::class);
$classMock = $this->buildClassMock();
$classMock->shouldReceive('getDocComment')->andReturnNull();
$classMock->stmts = [$method1];
$strategyMock->create(Argument::type(ContextStack::class), $method1, $containerMock)
->will(function ($args) use ($method1Descriptor): void {
$args[0]->peek()->addMethod($method1Descriptor);
})
->shouldBeCalled();
$containerMock->findMatching(
Argument::type(ContextStack::class),
$method1,
)->willReturn($strategyMock->reveal());
$class = $this->performCreate($classMock, $containerMock->reveal());
$this->assertInstanceOf(InterfaceElement::class, $class);
$this->assertEquals('\Space\MyInterface', (string) $class->getFqsen());
$this->assertEquals(
['\MyClass::method1' => $method1Descriptor],
$class->getMethods(),
);
}
private function buildClassMock(): m\MockInterface|InterfaceNode
{
$interfaceMock = m::mock(InterfaceNode::class);
$interfaceMock->extends = [];
$interfaceMock->stmts = [];
$interfaceMock->shouldReceive('getAttribute')->andReturn(new Fqsen('\Space\MyInterface'));
$interfaceMock->shouldReceive('getLine')->andReturn(1);
$interfaceMock->shouldReceive('getEndLine')->andReturn(2);
return $interfaceMock;
}
private function performCreate(m\MockInterface $interfaceMock, StrategyContainer $containerMock): InterfaceElement
{
$file = new FileElement('hash', 'path');
$this->fixture->create(
self::createContext(null)->push($file),
$interfaceMock,
$containerMock,
);
return current($file->getInterfaces());
}
}

View File

@@ -0,0 +1,168 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Mockery as m;
use Mockery\MockInterface;
use phpDocumentor\Reflection\DocBlock as DocBlockDescriptor;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
use phpDocumentor\Reflection\Php\Method as MethodDescriptor;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Comment\Doc;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(Method::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Method')]
#[UsesClass('\phpDocumentor\Reflection\Php\Argument')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\Type')]
class MethodTest extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docBlockFactory;
protected function setUp(): void
{
$this->docBlockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new Method($this->docBlockFactory->reveal());
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue($this->fixture->matches(self::createContext(null), m::mock(ClassMethod::class)));
}
public function testCreateWithoutParameters(): void
{
$classMethodMock = $this->buildClassMethodMock();
$classMethodMock->params = [];
$classMethodMock->shouldReceive('isPrivate')->once()->andReturn(false);
$classMethodMock->shouldReceive('isProtected')->once()->andReturn(false);
$classMethodMock->shouldReceive('getDocComment')->once()->andReturnNull();
$classMethodMock->shouldReceive('getReturnType')->once()->andReturn(null);
$containerMock = m::mock(StrategyContainer::class);
$containerMock->shouldReceive('findMatching')->never();
$class = new ClassElement(new Fqsen('\\MyClass'));
$this->fixture->create(self::createContext(null)->push($class), $classMethodMock, $containerMock);
$method = current($class->getMethods());
$this->assertInstanceOf(MethodDescriptor::class, $method);
$this->assertEquals('\SomeSpace\Class::function()', (string) $method->getFqsen());
$this->assertEquals('public', (string) $method->getVisibility());
}
public function testCreateProtectedMethod(): void
{
$classMethodMock = $this->buildClassMethodMock();
$classMethodMock->params = [];
$classMethodMock->shouldReceive('isPrivate')->once()->andReturn(false);
$classMethodMock->shouldReceive('isProtected')->once()->andReturn(true);
$classMethodMock->shouldReceive('getDocComment')->once()->andReturnNull();
$classMethodMock->shouldReceive('getReturnType')->once()->andReturn(null);
$containerMock = m::mock(StrategyContainer::class);
$containerMock->shouldReceive('findMatching')->never();
$class = new ClassElement(new Fqsen('\\MyClass'));
$this->fixture->create(self::createContext(null)->push($class), $classMethodMock, $containerMock);
$method = current($class->getMethods());
$this->assertInstanceOf(MethodDescriptor::class, $method);
$this->assertEquals('\SomeSpace\Class::function()', (string) $method->getFqsen());
$this->assertEquals('protected', (string) $method->getVisibility());
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('Text');
$classMethodMock = $this->buildClassMethodMock();
$classMethodMock->params = [];
$classMethodMock->shouldReceive('isPrivate')->once()->andReturn(true);
$classMethodMock->shouldReceive('getDocComment')->andReturn($doc);
$classMethodMock->shouldReceive('getReturnType')->once()->andReturn(null);
$docBlock = new DocBlockDescriptor('');
$this->docBlockFactory->create('Text', null)->willReturn($docBlock);
$containerMock = $this->prophesize(StrategyContainer::class);
$class = new ClassElement(new Fqsen('\\MyClass'));
$this->fixture->create(self::createContext(null)->push($class), $classMethodMock, $containerMock->reveal());
$method = current($class->getMethods());
$this->assertInstanceOf(MethodDescriptor::class, $method);
$this->assertEquals('\SomeSpace\Class::function()', (string) $method->getFqsen());
$this->assertSame($docBlock, $method->getDocBlock());
}
/** @return MockInterface|ClassMethod */
private function buildClassMethodMock(): MockInterface
{
$methodMock = m::mock(ClassMethod::class);
$methodMock->name = new Identifier('function');
$methodMock->byRef = false;
$methodMock->stmts = [];
$methodMock->shouldReceive('getAttribute')->andReturn(new Fqsen('\SomeSpace\Class::function()'));
$methodMock->params = [];
$methodMock->shouldReceive('isStatic')->once()->andReturn(true);
$methodMock->shouldReceive('isFinal')->once()->andReturn(true);
$methodMock->shouldReceive('isAbstract')->once()->andReturn(true);
$methodMock->shouldReceive('getLine')->once()->andReturn(1);
$methodMock->shouldReceive('getStartFilePos')->once()->andReturn(10);
$methodMock->shouldReceive('getEndLine')->once()->andReturn(2);
$methodMock->shouldReceive('getEndFilePos')->once()->andReturn(20);
return $methodMock;
}
public function testIteratesStatements(): void
{
$method1 = $this->buildClassMethodMock();
$method1->shouldReceive('isPrivate')->once()->andReturn(true);
$method1->shouldReceive('getDocComment')->andReturn(null);
$method1->shouldReceive('getReturnType')->once()->andReturn(null);
$method1->stmts = [new Expression(new FuncCall(new Name('hook')))];
$strategyMock = $this->prophesize(ProjectFactoryStrategy::class);
$containerMock = $this->prophesize(StrategyContainer::class);
$containerMock->findMatching(
Argument::type(ContextStack::class),
Argument::type(Expression::class),
)->willReturn($strategyMock->reveal())->shouldBeCalledOnce();
$class = new ClassElement(new Fqsen('\\MyClass'));
$this->fixture->create(self::createContext(null)->push($class), $method1, $containerMock->reveal());
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use InvalidArgumentException;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
use phpDocumentor\Reflection\Php\File;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use PHPUnit\Framework\Attributes\CoversClass;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use stdClass;
use function current;
#[CoversClass(Namespace_::class)]
final class Namespace_Test extends TestCase
{
use ProphecyTrait;
protected function setUp(): void
{
$this->fixture = new Namespace_();
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue($this->fixture->matches(
self::createContext(null),
$this->prophesize(NamespaceNode::class)->reveal(),
));
}
public function testCreateThrowsException(): void
{
$this->expectException(InvalidArgumentException::class);
$this->fixture->create(
self::createContext(null),
new stdClass(),
$this->prophesize(StrategyContainer::class)->reveal(),
);
}
public function testIteratesStatements(): void
{
$class = new ClassNode('\MyClass');
$classElement = new ClassElement(new Fqsen('\MyClass'));
$strategyMock = $this->prophesize(ProjectFactoryStrategy::class);
$containerMock = $this->prophesize(StrategyContainer::class);
$namespace = new NamespaceNode(new Name('MyNamespace'));
$namespace->setAttribute('fqsen', new Fqsen('\MyNamespace'));
$namespace->stmts = [$class];
$strategyMock->create(Argument::type(ContextStack::class), $class, $containerMock)
->will(function ($args) use ($classElement): void {
$args[0]->peek()->addClass($classElement);
})
->shouldBeCalled();
$containerMock->findMatching(
Argument::type(ContextStack::class),
$class,
)->willReturn($strategyMock->reveal());
$file = new File('hash', 'path');
$this->fixture->create(self::createContext(null)->push($file), $namespace, $containerMock->reveal());
$class = current($file->getClasses());
$fqsen = current($file->getNamespaces());
$this->assertInstanceOf(ClassElement::class, $class);
$this->assertEquals('\MyClass', (string) $class->getFqsen());
$this->assertEquals(new Fqsen('\MyNamespace'), $fqsen);
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\Visibility;
use PhpParser\Node\Stmt\Property as PropertyNode;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\PrettyPrinter;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(PropertyBuilder::class)]
class PropertyBuilderTest extends TestCase
{
public function testBuildsPropertyElementWithCorrectAttributes(): void
{
$fqsen = new Fqsen('\MyClass::$property');
$visibility = new Visibility(Visibility::PUBLIC_);
$startLocation = new Location(10);
$endLocation = new Location(20);
$docBlockFactory = $this->createMock(DocBlockFactoryInterface::class);
$valueConverter = $this->createMock(PrettyPrinter\Standard::class);
$strategies = $this->createMock(StrategyContainer::class);
$reducers = [];
$prop1 = new PropertyProperty('prop1');
$propertyNode = new PropertyNode(1, [$prop1]);
$properties = new PropertyIterator($propertyNode);
$builder = PropertyBuilder::create($valueConverter, $docBlockFactory, $strategies, $reducers);
$builder->fqsen($fqsen)
->visibility($properties)
->docblock($properties->getDocComment())
->default($properties->getDefault())
->static(true)
->startLocation($startLocation)
->endLocation($endLocation)
->type($properties->getType())
->readOnly(true)
->hooks($properties->getHooks());
$context = \phpDocumentor\Reflection\Php\Factory\TestCase::createContext();
$property = $builder->build($context);
$this->assertSame($fqsen, $property->getFqsen());
$this->assertEquals($visibility, $property->getVisibility());
$this->assertNull($property->getDocBlock());
$this->assertNull($property->getDefault());
$this->assertTrue($property->isStatic());
$this->assertSame($startLocation, $property->getLocation());
$this->assertSame($endLocation, $property->getEndLocation());
$this->assertNull($property->getType());
$this->assertTrue($property->isReadOnly());
}
}

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Mockery as m;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use PhpParser\Comment\Doc;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Property as PropertyNode;
use PhpParser\Node\Stmt\PropertyProperty;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(PropertyIterator::class)]
class PropertyIteratorTest extends MockeryTestCase
{
public function testIterateProps(): void
{
$prop1 = new PropertyProperty('prop1');
$prop2 = new PropertyProperty('prop2');
$propertyNode = new PropertyNode(1, [$prop1, $prop2]);
$i = 1;
foreach (new PropertyIterator($propertyNode) as $property) {
$this->assertEquals('prop' . $i, $property->getName());
++$i;
}
}
public function testKey(): void
{
$propertyMock = m::mock(PropertyNode::class);
$fixture = new PropertyIterator($propertyMock);
$this->assertEquals(0, $fixture->key());
$fixture->next();
$this->assertEquals(1, $fixture->key());
}
public function testProxyMethods(): void
{
$propertyMock = m::mock(PropertyNode::class);
$propertyMock->shouldReceive('isPublic')->once()->andReturn(true);
$propertyMock->shouldReceive('isProtected')->once()->andReturn(true);
$propertyMock->shouldReceive('isPrivate')->once()->andReturn(true);
$propertyMock->shouldReceive('isStatic')->once()->andReturn(true);
$propertyMock->shouldReceive('isReadOnly')->once()->andReturn(true);
$propertyMock->shouldReceive('getLine')->once()->andReturn(10);
$fixture = new PropertyIterator($propertyMock);
$this->assertTrue($fixture->isStatic());
$this->assertTrue($fixture->isReadOnly());
$this->assertTrue($fixture->isPrivate());
$this->assertTrue($fixture->isProtected());
$this->assertTrue($fixture->isPublic());
$this->assertEquals(10, $fixture->getLine());
}
public function testGetDefault(): void
{
$prop = m::mock(PropertyProperty::class);
$prop->default = new String_('myDefault');
$property = new PropertyNode(1, [$prop]);
$fixture = new PropertyIterator($property);
$this->assertEquals(new String_('myDefault'), $fixture->getDefault());
}
public function testGetDocCommentPropFirst(): void
{
$prop = m::mock(PropertyProperty::class);
$propertyNode = m::mock(PropertyNode::class);
$propertyNode->props = [$prop];
$prop->shouldReceive('getDocComment')->once()->andReturn(new Doc('test'));
$propertyNode->shouldReceive('getDocComment')->never();
$fixture = new PropertyIterator($propertyNode);
$this->assertEquals('test', $fixture->getDocComment()->getText());
}
public function testGetDocComment(): void
{
$prop = m::mock(PropertyProperty::class);
$propertyNode = m::mock(PropertyNode::class);
$propertyNode->props = [$prop];
$prop->shouldReceive('getDocComment')->once()->andReturnNull();
$propertyNode->shouldReceive('getDocComment')->once()->andReturn(new Doc('test'));
$fixture = new PropertyIterator($propertyNode);
$this->assertEquals('test', $fixture->getDocComment()->getText());
}
}

View File

@@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\DocBlock as DocBlockDescriptor;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategies;
use phpDocumentor\Reflection\Php\Property as PropertyDescriptor;
use PhpParser\Comment\Doc;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\Property as PropertyNode;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
use function next;
#[CoversClass(Property::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\PropertyIterator')]
#[UsesClass('\phpDocumentor\Reflection\Php\Property')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Php\ProjectFactoryStrategies')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\Type')]
final class PropertyTest extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docBlockFactory;
protected function setUp(): void
{
$this->docBlockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new Property($this->docBlockFactory->reveal(), new PrettyPrinter());
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue($this->fixture->matches(self::createContext(null), new PropertyNode(1, [])));
}
#[DataProvider('visibilityProvider')]
public function testCreateWithVisibility(int $input, string $expectedVisibility): void
{
$propertyStub = $this->buildPropertyMock($input);
$class = $this->performCreate($propertyStub);
$property = current($class->getProperties());
$this->assertProperty($property, $expectedVisibility);
}
/** @return array<string|int[]> */
public static function visibilityProvider(): array
{
return [
[
ClassNode::MODIFIER_PUBLIC,
'public',
],
[
ClassNode::MODIFIER_PROTECTED,
'protected',
],
[
ClassNode::MODIFIER_PRIVATE,
'private',
],
];
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('text');
$docBlock = new DocBlockDescriptor('text');
$this->docBlockFactory->create('text', null)->willReturn($docBlock);
$property = new PropertyProperty('property', new String_('MyDefault'), ['comments' => [$doc]]);
$property->setAttribute('fqsen', new Fqsen('\myClass::$property'));
$node = new PropertyNode(ClassNode::MODIFIER_PRIVATE | ClassNode::MODIFIER_STATIC, [$property]);
$class = $this->performCreate($node);
$property = current($class->getProperties());
$this->assertProperty($property, 'private');
$this->assertSame($docBlock, $property->getDocBlock());
}
public function testCreateMultipleInOneStatement(): void
{
$property1 = new PropertyProperty('property1', new String_('MyDefault1'));
$property1->setAttribute('fqsen', new Fqsen('\myClass::$property1'));
$property2 = new PropertyProperty('property2', new String_('MyDefault2'));
$property2->setAttribute('fqsen', new Fqsen('\myClass::$property2'));
$node = new PropertyNode(
ClassNode::MODIFIER_PRIVATE | ClassNode::MODIFIER_STATIC,
[$property1, $property2],
);
$class = $this->performCreate($node);
$properties = $class->getProperties();
$property1 = current($properties);
next($properties);
$property2 = current($properties);
$this->assertProperty($property1, 'private', 'property1', '\'MyDefault1\'');
$this->assertProperty($property2, 'private', 'property2', '\'MyDefault2\'');
}
private function buildPropertyMock(int $modifier): PropertyNode
{
$property = new PropertyProperty('property', new String_('MyDefault'));
$property->setAttribute('fqsen', new Fqsen('\myClass::$property'));
return new PropertyNode($modifier | ClassNode::MODIFIER_STATIC, [$property]);
}
private function assertProperty(
PropertyDescriptor $property,
string $visibility,
string $name = 'property',
string|null $default = '\'MyDefault\'',
): void {
$this->assertInstanceOf(PropertyDescriptor::class, $property);
$this->assertEquals('\myClass::$' . $name, (string) $property->getFqsen());
$this->assertTrue($property->isStatic());
$this->assertEquals($default, $property->getDefault());
$this->assertEquals($visibility, (string) $property->getVisibility());
}
private function performCreate(PropertyNode $property): ClassElement
{
$factory = new ProjectFactoryStrategies([]);
$class = new ClassElement(new Fqsen('\myClass'));
$this->fixture->create(self::createContext(null)->push($class), $property, $factory);
return $class;
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use InvalidArgumentException;
use Mockery as m;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Types\Context;
use PHPUnit\Framework\Attributes\CoversClass;
use stdClass;
/**
* Base test case for all strategies, to be sure that they check if the can handle objects before handeling them.
*/
#[CoversClass(AbstractFactory::class)]
abstract class TestCase extends MockeryTestCase
{
protected ProjectFactoryStrategy $fixture;
public static function createContext(Context|null $typeContext = null): ContextStack
{
return new ContextStack(
new Project('test'),
$typeContext,
);
}
public function testCreateThrowsException(): void
{
$this->expectException(InvalidArgumentException::class);
$this->fixture->create(self::createContext(null), new stdClass(), m::mock(StrategyContainer::class));
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use InvalidArgumentException;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Class_ as Class_Element;
use phpDocumentor\Reflection\Php\Enum_ as Enum_Element;
use phpDocumentor\Reflection\Php\Interface_;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategies;
use phpDocumentor\Reflection\Php\Trait_ as Trait_Element;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\TraitUse as TraitUseNode;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
#[CoversClass(TraitUse::class)]
#[CoversClass(AbstractFactory::class)]
final class TraitUseTest extends TestCase
{
/** @return mixed[][] */
public static function consumerProvider(): array
{
return [
[new Class_Element(new Fqsen('\MyClass'))],
[new Trait_Element(new Fqsen('\MyTrait'))],
[new Enum_Element(new Fqsen('\MyEnum'), null)],
];
}
protected function setUp(): void
{
$this->fixture = new TraitUse();
}
public function testMatchesOnlyTraitUseNode(): void
{
self::assertTrue(
$this->fixture->matches(
self::createContext(),
$this->givenTraitUse(),
),
);
}
public function testCreateThrowsExceptionWhenStackDoesNotContainClass(): void
{
$this->expectException(InvalidArgumentException::class);
$context = self::createContext()->push(new Interface_(new Fqsen('\Interface')));
$this->fixture->create($context, $this->givenTraitUse(), new ProjectFactoryStrategies([]));
}
/** @param Class_Element|Trait_Element $traitConsumer */
#[DataProvider('consumerProvider')]
public function testCreateWillAddUsedTraitToContextTop(Element $traitConsumer): void
{
$context = self::createContext()->push($traitConsumer);
$this->fixture->create($context, $this->givenTraitUse(), new ProjectFactoryStrategies([]));
self::assertEquals(['\Foo' => new Fqsen('\Foo')], $traitConsumer->getUsedTraits());
}
private function givenTraitUse(): TraitUseNode
{
return new TraitUseNode([new FullyQualified('Foo')]);
}
}

View File

@@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock as DocBlockElement;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\File;
use phpDocumentor\Reflection\Php\Method as MethodElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\Trait_ as TraitElement;
use PhpParser\Comment\Doc;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Trait_ as TraitNode;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use stdClass;
use function current;
#[CoversClass(Trait_::class)]
#[CoversClass(Trait_::class)]
#[CoversClass(AbstractFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Trait_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Method')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Php\Property')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\PropertyIterator')]
final class Trait_Test extends TestCase
{
use ProphecyTrait;
private ObjectProphecy $docBlockFactory;
protected function setUp(): void
{
$this->docBlockFactory = $this->prophesize(DocBlockFactoryInterface::class);
$this->fixture = new Trait_($this->docBlockFactory->reveal());
}
public function testMatches(): void
{
$this->assertFalse($this->fixture->matches(self::createContext(null), new stdClass()));
$this->assertTrue($this->fixture->matches(self::createContext(null), m::mock(TraitNode::class)));
}
public function testSimpleCreate(): void
{
$containerMock = m::mock(StrategyContainer::class);
$interfaceMock = $this->buildTraitMock();
$interfaceMock->shouldReceive('getDocComment')->andReturnNull();
$trait = $this->performCreate($interfaceMock, $containerMock);
$this->assertInstanceOf(TraitElement::class, $trait);
$this->assertEquals('\Space\MyTrait', (string) $trait->getFqsen());
}
public function testIteratesStatements(): void
{
$method1 = new ClassMethod('\Space\MyTrait::method1');
$method1Descriptor = new MethodElement(new Fqsen('\Space\MyTrait::method1'));
$strategyMock = $this->prophesize(ProjectFactoryStrategy::class);
$containerMock = $this->prophesize(StrategyContainer::class);
$classMock = $this->buildTraitMock();
$classMock->shouldReceive('getDocComment')->andReturnNull();
$classMock->stmts = [$method1];
$strategyMock->create(Argument::type(ContextStack::class), $method1, $containerMock)
->will(function ($args) use ($method1Descriptor): void {
$args[0]->peek()->addMethod($method1Descriptor);
})
->shouldBeCalled();
$containerMock->findMatching(
Argument::type(ContextStack::class),
$method1,
)->willReturn($strategyMock->reveal());
$trait = $this->performCreate($classMock, $containerMock->reveal());
$this->assertEquals('\Space\MyTrait', (string) $trait->getFqsen());
$this->assertEquals(
['\Space\MyTrait::method1' => $method1Descriptor],
$trait->getMethods(),
);
}
public function testCreateWithDocBlock(): void
{
$doc = new Doc('Text');
$traitMock = $this->buildTraitMock();
$traitMock->shouldReceive('getDocComment')->andReturn($doc);
$docBlock = new DocBlockElement('');
$this->docBlockFactory->create('Text', null)->willReturn($docBlock);
$containerMock = m::mock(StrategyContainer::class);
$trait = $this->performCreate($traitMock, $containerMock);
$this->assertSame($docBlock, $trait->getDocBlock());
}
private function buildTraitMock(): m\MockInterface|TraitNode
{
$mock = m::mock(TraitNode::class);
$mock->shouldReceive('getAttribute')->andReturn(new Fqsen('\Space\MyTrait'));
$mock->stmts = [];
$mock->shouldReceive('getLine')->andReturn(1);
$mock->shouldReceive('getEndLine')->andReturn(2);
return $mock;
}
private function performCreate(TraitNode $traitNode, StrategyContainer $containerMock): TraitElement
{
$file = new File('hash', 'path');
$this->fixture->create(self::createContext(null)->push($file), $traitNode, $containerMock);
return current($file->getTraits());
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Float_;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Intersection;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\String_;
use PhpParser\Node\Identifier;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
use PHPUnit\Framework\Attributes\CoversClass;
use PhpUnit\Framework\TestCase as PhpUnitTestCase;
#[CoversClass(Type::class)]
final class TypeTest extends PhpUnitTestCase
{
public function testReturnsNullWhenNoTypeIsPassed(): void
{
$factory = new Type();
$result = $factory->fromPhpParser(null);
$this->assertNull($result);
}
public function testReturnsReflectedType(): void
{
$factory = new Type();
$given = new Name('integer');
$expected = new Integer();
$result = $factory->fromPhpParser($given);
$this->assertEquals($expected, $result);
}
public function testReturnsNullableTypeWhenPassedAPhpParserNullable(): void
{
$factory = new Type();
$given = new NullableType(new Identifier('integer'));
$expected = new Nullable(new Integer());
$result = $factory->fromPhpParser($given);
$this->assertEquals($expected, $result);
}
public function testReturnsUnion(): void
{
$factory = new Type();
$given = new UnionType([new Identifier('integer'), new Identifier('string')]);
$expected = new Compound([new Integer(), new String_()]);
$result = $factory->fromPhpParser($given);
$this->assertEquals($expected, $result);
}
public function testReturnsUnionGivenVariousTypes(): void
{
$factory = new Type();
$given = new UnionType(['integer', new Name('string'), new Identifier('float')]);
$expected = new Compound([new Integer(), new String_(), new Float_()]);
$result = $factory->fromPhpParser($given);
$this->assertEquals($expected, $result);
}
public function testReturnsInterseptionType(): void
{
$factory = new Type();
$given = new IntersectionType(['integer', new Name('string')]);
$expected = new Intersection([new Integer(), new String_()]);
$result = $factory->fromPhpParser($given);
self::assertEquals($expected, $result);
}
}

View File

@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(File::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Trait_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Interface_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Class_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Namespace_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Function_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
final class FileTest extends TestCase
{
use MetadataContainerTestHelper;
public const EXAMPLE_HASH = 'a-hash-string';
public const EXAMPLE_NAME = 'a-path-string';
public const EXAMPLE_PATH = 'example/' . self::EXAMPLE_NAME;
public const EXAMPLE_SOURCE = 'a-source-string';
protected File $fixture;
private DocBlock $docBlock;
/**
* Creates a new (emoty) fixture object.
*/
protected function setUp(): void
{
$this->docBlock = new DocBlock('');
$this->fixture = new File(self::EXAMPLE_HASH, self::EXAMPLE_PATH, self::EXAMPLE_SOURCE, $this->docBlock);
}
private function getFixture(): MetaDataContainerInterface
{
return $this->fixture;
}
public function testAddAndGetClasses(): void
{
$this->assertEmpty($this->fixture->getClasses());
$class = new Class_(new Fqsen('\MySpace\MyClass'));
$this->fixture->addClass($class);
$this->assertEquals(['\MySpace\MyClass' => $class], $this->fixture->getClasses());
}
public function testAddAndGetConstants(): void
{
$this->assertEmpty($this->fixture->getConstants());
$constant = new Constant(new Fqsen('\MySpace::MY_CONSTANT'));
$this->fixture->addConstant($constant);
$this->assertEquals(['\MySpace::MY_CONSTANT' => $constant], $this->fixture->getConstants());
}
public function testAddAndGetFunctions(): void
{
$this->assertEmpty($this->fixture->getFunctions());
$function = new Function_(new Fqsen('\MySpace::MyFunction()'));
$this->fixture->addFunction($function);
$this->assertEquals(['\MySpace::MyFunction()' => $function], $this->fixture->getFunctions());
}
public function testAddAndGetInterfaces(): void
{
$this->assertEmpty($this->fixture->getInterfaces());
$interface = new Interface_(new Fqsen('\MySpace\MyInterface'), []);
$this->fixture->addInterface($interface);
$this->assertEquals(['\MySpace\MyInterface' => $interface], $this->fixture->getInterfaces());
}
public function testAddAndGetTraits(): void
{
$this->assertEmpty($this->fixture->getTraits());
$trait = new Trait_(new Fqsen('\MySpace\MyTrait'));
$this->fixture->addTrait($trait);
$this->assertEquals(['\MySpace\MyTrait' => $trait], $this->fixture->getTraits());
}
public function testAddAndGetEnums(): void
{
$this->assertEmpty($this->fixture->getEnums());
$enum = new Enum_(new Fqsen('\MySpace\MyEnum'), null);
$this->fixture->addEnum($enum);
$this->assertEquals(['\MySpace\MyEnum' => $enum], $this->fixture->getEnums());
}
public function testGetDocBlock(): void
{
$this->assertSame($this->docBlock, $this->fixture->getDocBlock());
}
public function testGetHash(): void
{
$this->assertSame(self::EXAMPLE_HASH, $this->fixture->getHash());
}
public function testGetName(): void
{
$this->assertSame(self::EXAMPLE_NAME, $this->fixture->getName());
}
public function testSetAndGetPath(): void
{
$this->assertSame(self::EXAMPLE_PATH, $this->fixture->getPath());
}
public function testSetAndGetSource(): void
{
$this->assertSame(self::EXAMPLE_SOURCE, $this->fixture->getSource());
}
public function testSetAndGetNamespaceAliases(): void
{
$this->assertEmpty($this->fixture->getNamespaces());
$this->fixture->addNamespace(new Fqsen('\MyNamepace\Foo'));
$this->assertEquals(['\MyNamepace\Foo' => new Fqsen('\MyNamepace\Foo')], $this->fixture->getNamespaces());
}
public function testAddAndGetIncludes(): void
{
$this->assertEmpty($this->fixture->getIncludes());
$include = self::EXAMPLE_PATH;
$this->fixture->addInclude($include);
$this->assertSame([self::EXAMPLE_PATH => self::EXAMPLE_PATH], $this->fixture->getIncludes());
}
}

View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use phpDocumentor\Reflection\Types\Mixed_;
use phpDocumentor\Reflection\Types\String_;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
/** @property Function_ $fixture */
#[CoversClass(Function_::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Argument')]
#[UsesClass('\phpDocumentor\Reflection\DocBlock')]
#[UsesClass('\phpDocumentor\Reflection\Fqsen')]
final class Function_Test extends TestCase
{
use MetadataContainerTestHelper;
private Fqsen $fqsen;
private DocBlock $docBlock;
/**
* Creates a new (empty) fixture object.
*/
protected function setUp(): void
{
$this->fqsen = new Fqsen('\space\MyFunction()');
$this->docBlock = new DocBlock('aa');
$this->fixture = new Function_($this->fqsen, $this->docBlock);
}
private function getFixture(): MetaDataContainerInterface
{
return $this->fixture;
}
public function testGetName(): void
{
$this->assertEquals('MyFunction', $this->fixture->getName());
}
public function testAddAndGetArguments(): void
{
$argument = new Argument('firstArgument');
$this->fixture->addArgument($argument);
$this->assertEquals([$argument], $this->fixture->getArguments());
}
public function testGetFqsen(): void
{
$this->assertSame($this->fqsen, $this->fixture->getFqsen());
}
public function testGetDocblock(): void
{
$this->assertSame($this->docBlock, $this->fixture->getDocBlock());
}
public function testGetDefaultReturnType(): void
{
$function = new Function_($this->fqsen);
$this->assertEquals(new Mixed_(), $function->getReturnType());
}
public function testGetReturnTypeFromConstructor(): void
{
$returnType = new String_();
$function = new Function_($this->fqsen, null, null, null, $returnType);
$this->assertSame($returnType, $function->getReturnType());
}
public function testGetHasReturnByReference(): void
{
$function = new Function_($this->fqsen);
$this->assertSame(false, $function->getHasReturnByReference());
}
public function testGetHasReturnByReferenceFromConstructor(): void
{
$function = new Function_($this->fqsen, null, null, null, null, true);
$this->assertSame(true, $function->getHasReturnByReference());
}
public function testLineAndColumnNumberIsReturnedWhenALocationIsProvided(): void
{
$fixture = new Function_($this->fqsen, $this->docBlock, new Location(100, 20), new Location(101, 20));
$this->assertLineAndColumnNumberIsReturnedWhenALocationIsProvided($fixture);
}
}

View File

@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use InvalidArgumentException;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
/** @property Interface_ $fixture */
#[CoversClass(Interface_::class)]
#[UsesClass('\phpDocumentor\Reflection\DocBlock')]
#[UsesClass('\phpDocumentor\Reflection\Fqsen')]
#[UsesClass('\phpDocumentor\Reflection\Php\Method')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Php\Property')]
final class Interface_Test extends TestCase
{
use MetadataContainerTestHelper;
private Fqsen $fqsen;
private DocBlock $docBlock;
/** @var Fqsen[] */
private array $exampleParents;
/**
* Creates a new (empty) fixture object.
*/
protected function setUp(): void
{
$this->exampleParents = [
new Fqsen('\MySpace\MyParent'),
new Fqsen('\MySpace\MyOtherParent'),
];
$this->fqsen = new Fqsen('\MySpace\MyInterface');
$this->docBlock = new DocBlock('');
$this->fixture = new Interface_($this->fqsen, $this->exampleParents, $this->docBlock);
}
private function getFixture(): MetaDataContainerInterface
{
return $this->fixture;
}
public function testGetName(): void
{
$this->assertSame($this->fqsen->getName(), $this->fixture->getName());
}
public function testGetFqsen(): void
{
$this->assertSame($this->fqsen, $this->fixture->getFqsen());
}
public function testGetDocblock(): void
{
$this->assertSame($this->docBlock, $this->fixture->getDocBlock());
}
public function testSettingAndGettingConstants(): void
{
$this->assertEquals([], $this->fixture->getConstants());
$constant = new Constant(new Fqsen('\MySpace\MyInterface::MY_CONSTANT'));
$this->fixture->addConstant($constant);
$this->assertEquals(['\MySpace\MyInterface::MY_CONSTANT' => $constant], $this->fixture->getConstants());
}
public function testSettingAndGettingMethods(): void
{
$this->assertEquals([], $this->fixture->getMethods());
$method = new Method(new Fqsen('\MySpace\MyInterface::myMethod()'));
$this->fixture->addMethod($method);
$this->assertEquals(['\MySpace\MyInterface::myMethod()' => $method], $this->fixture->getMethods());
}
public function testSettingAndGettingProperties(): void
{
$this->assertEquals([], $this->fixture->getProperties());
$property = new Property(new Fqsen('\MySpace\MyInterface::$myProperty'));
$this->fixture->addProperty($property);
$this->assertEquals(['\MySpace\MyInterface::$myProperty' => $property], $this->fixture->getProperties());
}
public function testReturningTheParentsOfThisInterface(): void
{
$this->assertSame($this->exampleParents, $this->fixture->getParents());
}
public function testLineAndColumnNumberIsReturnedWhenALocationIsProvided(): void
{
$fixture = new Interface_($this->fqsen, [], $this->docBlock, new Location(100, 20), new Location(101, 20));
$this->assertLineAndColumnNumberIsReturnedWhenALocationIsProvided($fixture);
}
public function testArrayWithParentsMustBeFqsenObjects(): void
{
$this->expectException(InvalidArgumentException::class);
new Interface_(new Fqsen('\MyInterface'), ['InvalidInterface']);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Exception;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
trait MetadataContainerTestHelper
{
public function testSetMetaDataForNonExistingKey(): void
{
$stub = new MetadataStub('stub');
$this->getFixture()->addMetadata($stub);
self::assertSame(['stub' => $stub], $this->getFixture()->getMetadata());
}
public function testSetMetaDataWithExistingKeyThrows(): void
{
self::expectException(Exception::class);
$stub = new MetadataStub('stub');
$this->getFixture()->addMetadata($stub);
$this->getFixture()->addMetadata($stub);
}
abstract public function getFixture(): MetaDataContainerInterface;
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Metadata\Metadata;
final class MetadataStub implements Metadata
{
public function __construct(private readonly string $key)
{
}
public function key(): string
{
return $this->key;
}
}

View File

@@ -0,0 +1,167 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use phpDocumentor\Reflection\Types\Mixed_;
use phpDocumentor\Reflection\Types\String_;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
/** @property Method $fixture */
#[CoversClass(Method::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Php\Argument')]
final class MethodTest extends TestCase
{
use MetadataContainerTestHelper;
private Fqsen $fqsen;
private Visibility $visibility;
private DocBlock $docblock;
protected function setUp(): void
{
$this->fqsen = new Fqsen('\My\Space::MyMethod()');
$this->visibility = new Visibility('private');
$this->docblock = new DocBlock('');
$this->fixture = new Method($this->fqsen);
}
private function getFixture(): MetaDataContainerInterface
{
return $this->fixture;
}
public function testGetFqsenAndGetName(): void
{
$method = new Method($this->fqsen);
$this->assertSame($this->fqsen, $method->getFqsen());
$this->assertEquals($this->fqsen->getName(), $method->getName());
}
public function testGetDocBlock(): void
{
$method = new Method($this->fqsen, $this->visibility, $this->docblock);
$this->assertSame($this->docblock, $method->getDocBlock());
}
public function testAddingAndGettingArguments(): void
{
$method = new Method($this->fqsen);
$this->assertEquals([], $method->getArguments());
$argument = new Argument('myArgument');
$method->addArgument($argument);
$this->assertEquals([$argument], $method->getArguments());
}
public function testGettingWhetherMethodIsAbstract(): void
{
$method = new Method($this->fqsen, $this->visibility, $this->docblock, false);
$this->assertFalse($method->isAbstract());
$method = new Method($this->fqsen, $this->visibility, $this->docblock, true);
$this->assertTrue($method->isAbstract());
}
public function testGettingWhetherMethodIsFinal(): void
{
$method = new Method($this->fqsen, $this->visibility, $this->docblock, false, false, false);
$this->assertFalse($method->isFinal());
$method = new Method($this->fqsen, $this->visibility, $this->docblock, false, false, true);
$this->assertTrue($method->isFinal());
}
public function testGettingWhetherMethodIsStatic(): void
{
$method = new Method($this->fqsen, $this->visibility, $this->docblock, false, false, false);
$this->assertFalse($method->isStatic());
$method = new Method($this->fqsen, $this->visibility, $this->docblock, false, true, false);
$this->assertTrue($method->isStatic());
}
public function testGettingVisibility(): void
{
$method = new Method($this->fqsen, $this->visibility, $this->docblock, false, false, false);
$this->assertSame($this->visibility, $method->getVisibility());
}
public function testGetDefaultVisibility(): void
{
$method = new Method($this->fqsen);
$this->assertEquals(new Visibility('public'), $method->getVisibility());
}
public function testGetDefaultReturnType(): void
{
$method = new Method($this->fqsen);
$this->assertEquals(new Mixed_(), $method->getReturnType());
}
public function testGetReturnTypeFromConstructor(): void
{
$returnType = new String_();
$method = new Method(
$this->fqsen,
new Visibility('public'),
null,
false,
false,
false,
null,
null,
$returnType,
);
$this->assertSame($returnType, $method->getReturnType());
}
public function testGetHasReturnByReference(): void
{
$method = new Method($this->fqsen);
$this->assertSame(false, $method->getHasReturnByReference());
}
public function testGetHasReturnByReferenceFromConstructor(): void
{
$method = new Method($this->fqsen, null, null, false, false, false, null, null, null, true);
$this->assertSame(true, $method->getHasReturnByReference());
}
public function testLineAndColumnNumberIsReturnedWhenALocationIsProvided(): void
{
$fixture = new Method(
$this->fqsen,
null,
null,
false,
false,
false,
new Location(100, 20),
new Location(101, 20),
);
$this->assertLineAndColumnNumberIsReturnedWhenALocationIsProvided($fixture);
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
/**
* Tests the functionality for the Namespace_ class.
*/
// @codingStandardsIgnoreStart
#[CoversClass(Namespace_::class)]
class Namespace_Test extends TestCase
// @codingStandardsIgnoreEnd
{
use MetadataContainerTestHelper;
protected Namespace_ $fixture;
private Fqsen $fqsen;
private DocBlock $docBlock;
/**
* Creates a new (emoty) fixture object.
*/
protected function setUp(): void
{
$this->fqsen = new Fqsen('\MySpace');
$this->docBlock = new DocBlock('');
$this->fixture = new Namespace_($this->fqsen, $this->docBlock);
}
private function getFixture(): MetaDataContainerInterface
{
return $this->fixture;
}
public function testAddAndGetClasses(): void
{
$this->assertEmpty($this->fixture->getClasses());
$class = new Fqsen('\MySpace\MyClass');
$this->fixture->addClass($class);
$this->assertEquals(['\MySpace\MyClass' => $class], $this->fixture->getClasses());
}
public function testAddAndGetConstants(): void
{
$this->assertEmpty($this->fixture->getConstants());
$constant = new Fqsen('\MySpace::MY_CONSTANT');
$this->fixture->addConstant($constant);
$this->assertEquals(['\MySpace::MY_CONSTANT' => $constant], $this->fixture->getConstants());
}
public function testAddAndGetFunctions(): void
{
$this->assertEmpty($this->fixture->getFunctions());
$function = new Fqsen('\MySpace\MyFunction()');
$this->fixture->addFunction($function);
$this->assertEquals(['\MySpace\MyFunction()' => $function], $this->fixture->getFunctions());
}
public function testAddAndGetInterfaces(): void
{
$this->assertEmpty($this->fixture->getInterfaces());
$interface = new Fqsen('\MySpace\MyInterface');
$this->fixture->addInterface($interface);
$this->assertEquals(['\MySpace\MyInterface' => $interface], $this->fixture->getInterfaces());
}
public function testAddAndGetTraits(): void
{
$this->assertEmpty($this->fixture->getTraits());
$trait = new Fqsen('\MySpace\MyTrait');
$this->fixture->addTrait($trait);
$this->assertEquals(['\MySpace\MyTrait' => $trait], $this->fixture->getTraits());
}
public function testGetFqsen(): void
{
$this->assertSame($this->fqsen, $this->fixture->getFqsen());
$this->assertEquals($this->fqsen->getName(), $this->fixture->getName());
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Exception;
use phpDocumentor\Reflection\NodeVisitor\ElementNameResolver;
use PhpParser\Error;
use PhpParser\NodeTraverser;
use PhpParser\NodeTraverserInterface;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
#[CoversClass(NodesFactory::class)]
final class NodesFactoryTest extends TestCase
{
use ProphecyTrait;
/**
* Tests that an instance of the NodesFactory can be made using its static factory method.
*
* Unfortunately, we cannot actually inspect whether all recommended items were instantiated, so I create an example
* NodesFactory containing what I expected and this test will verify that no regression took place.
*/
public function testThatAFactoryWithRecommendedComponentsCanBeInstantiated(): void
{
$factory = NodesFactory::createInstance();
$this->assertInstanceOf(NodesFactory::class, $factory);
$this->assertEquals($this->givenTheExpectedDefaultNodesFactory(), $factory);
}
public function testThatCodeGetsConvertedIntoNodes(): void
{
$parser = $this->prophesize(Parser::class);
$parser->parse('this is my code')->willReturn(['parsed code']);
$nodeTraverser = $this->prophesize(NodeTraverserInterface::class);
$nodeTraverser->traverse(['parsed code'])->willReturn(['traversed code']);
$factory = new NodesFactory($parser->reveal(), $nodeTraverser->reveal());
$result = $factory->create('this is my code');
$this->assertSame(['traversed code'], $result);
}
public function testThatParseErrorIncludesFileAndLine(): void
{
$factory = NodesFactory::createInstance();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Syntax error in /some/file.php on line 1:');
$factory->create('<?php class { }', '/some/file.php');
}
public function testThatParseErrorWithoutFilePathOnlyIncludesLine(): void
{
$factory = NodesFactory::createInstance();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Syntax error on line 1:');
$factory->create('<?php class { }');
}
public function testThatParseErrorWrapsOriginalException(): void
{
$parser = $this->prophesize(Parser::class);
$parser->parse('bad code')->willThrow(new Error('Unexpected token', ['startLine' => 42]));
$nodeTraverser = $this->prophesize(NodeTraverserInterface::class);
$factory = new NodesFactory($parser->reveal(), $nodeTraverser->reveal());
try {
$factory->create('bad code', '/path/to/source.php');
$this->fail('Expected Exception was not thrown');
} catch (Exception $e) {
$this->assertSame('Syntax error in /path/to/source.php on line 42: Unexpected token', $e->getMessage());
$this->assertInstanceOf(Error::class, $e->getPrevious());
}
}
private function givenTheExpectedDefaultNodesFactory(): NodesFactory
{
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver());
$traverser->addVisitor(new ElementNameResolver());
return new NodesFactory($parser, $traverser);
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use OutOfBoundsException;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use phpDocumentor\Reflection\Php\Factory\DummyFactoryStrategy;
use phpDocumentor\Reflection\Types\Context;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* Test case for ProjectFactoryStrategies
*/
#[CoversClass(ProjectFactoryStrategies::class)]
class ProjectFactoryStrategiesTest extends TestCase
{
public function testStrategiesAreChecked(): void
{
new ProjectFactoryStrategies([new DummyFactoryStrategy()]);
$this->assertTrue(true);
}
public function testFindMatching(): void
{
$strategy = new DummyFactoryStrategy();
$container = new ProjectFactoryStrategies([$strategy]);
$actual = $container->findMatching(
new ContextStack(new Project('name'), new Context('global')),
new stdClass(),
);
$this->assertSame($strategy, $actual);
}
public function testCreateThrowsExceptionWhenStrategyNotFound(): void
{
$this->expectException(OutOfBoundsException::class);
$container = new ProjectFactoryStrategies([]);
$container->findMatching(
new ContextStack(new Project('name'), new Context('global')),
new stdClass(),
);
}
}

View File

@@ -0,0 +1,269 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use OutOfBoundsException;
use phpDocumentor\Reflection\Exception;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\UsesClass;
use Prophecy\Argument as ProphesizeArgument;
use Prophecy\PhpUnit\ProphecyTrait;
use function array_keys;
use function assert;
use function current;
use function key;
use function md5;
#[CoversClass(ProjectFactory::class)]
#[UsesClass('\phpDocumentor\Reflection\Php\Project')]
#[UsesClass('\phpDocumentor\Reflection\Php\Namespace_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Class_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Interface_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Trait_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Constant')]
#[UsesClass('\phpDocumentor\Reflection\Php\File')]
#[UsesClass('\phpDocumentor\Reflection\Php\Function_')]
#[UsesClass('\phpDocumentor\Reflection\Php\ProjectFactoryStrategies')]
#[UsesClass('\phpDocumentor\Reflection\Php\Visibility')]
#[UsesClass('\phpDocumentor\Reflection\Middleware\ChainFactory')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\Property')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\Method')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\Class_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\Interface_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\ClassConstant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\Define')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\GlobalConstant')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\Trait_')]
#[UsesClass('\phpDocumentor\Reflection\Php\Factory\File')]
#[UsesClass('\phpDocumentor\Reflection\Php\NodesFactory')]
final class ProjectFactoryTest extends MockeryTestCase
{
use ProphecyTrait;
public function testCreatingAnInstanceInstantiatesItWithTheRecommendedStrategies(): void
{
$this->assertInstanceOf(ProjectFactory::class, ProjectFactory::createInstance());
}
public function testCreate(): void
{
$expected = ['some/file.php', 'some/other.php'];
$calls = 0;
$someOtherStrategy = $this->prophesize(ProjectFactoryStrategy::class);
$someOtherStrategy->matches(
ProphesizeArgument::type(ContextStack::class),
ProphesizeArgument::any(),
)->willReturn(false);
$someOtherStrategy->create(
ProphesizeArgument::any(),
ProphesizeArgument::any(),
ProphesizeArgument::any(),
)->shouldNotBeCalled();
$fileStrategyMock = $this->prophesize(ProjectFactoryStrategy::class);
$fileStrategyMock->matches(
ProphesizeArgument::type(ContextStack::class),
ProphesizeArgument::any(),
)->willReturn(true);
$fileStrategyMock->create(
ProphesizeArgument::type(ContextStack::class),
ProphesizeArgument::type(LocalFile::class),
ProphesizeArgument::any(),
)->will(function ($args) use (&$calls, $expected): void {
$context = $args[0];
assert($context instanceof ContextStack);
$file = $args[1];
assert($file instanceof LocalFile);
$context->getProject()->addFile(new File($file->md5(), $expected[$calls++]));
});
$projectFactory = new ProjectFactory([$someOtherStrategy->reveal(), $fileStrategyMock->reveal()]);
$files = [new LocalFile(__FILE__), new LocalFile(__FILE__)];
$project = $projectFactory->create('MyProject', $files);
$this->assertInstanceOf(Project::class, $project);
$projectFilePaths = array_keys($project->getFiles());
$this->assertEquals(['some/file.php', 'some/other.php'], $projectFilePaths);
}
public function testCreateThrowsExceptionWhenStrategyNotFound(): void
{
$this->expectException(OutOfBoundsException::class);
$projectFactory = new ProjectFactory([]);
$projectFactory->create('MyProject', ['aa']);
}
public function testCreateProjectFromFileWithNamespacedClass(): void
{
$file = new File(md5('some/file.php'), 'some/file.php');
$file->addNamespace(new Fqsen('\mySpace'));
$file->addClass(new Class_(new Fqsen('\mySpace\MyClass')));
$namespaces = $this->fetchNamespacesFromSingleFile($file);
$this->assertEquals('\mySpace', key($namespaces));
$mySpace = current($namespaces);
$this->assertInstanceOf(Namespace_::class, $mySpace);
$this->assertEquals('\mySpace\MyClass', key($mySpace->getClasses()));
}
public function testWithNamespacedInterface(): void
{
$file = new File(md5('some/file.php'), 'some/file.php');
$file->addNamespace(new Fqsen('\mySpace'));
$file->addInterface(new Interface_(new Fqsen('\mySpace\MyInterface')));
$namespaces = $this->fetchNamespacesFromSingleFile($file);
$mySpace = current($namespaces);
$this->assertInstanceOf(Namespace_::class, $mySpace);
$this->assertEquals('\mySpace\MyInterface', key($mySpace->getInterfaces()));
}
public function testWithNamespacedFunction(): void
{
$file = new File(md5('some/file.php'), 'some/file.php');
$file->addNamespace(new Fqsen('\mySpace'));
$file->addFunction(new Function_(new Fqsen('\mySpace\function()')));
$namespaces = $this->fetchNamespacesFromSingleFile($file);
$mySpace = current($namespaces);
$this->assertInstanceOf(Namespace_::class, $mySpace);
$this->assertEquals('\mySpace\function()', key($mySpace->getFunctions()));
}
public function testWithNamespacedConstant(): void
{
$file = new File(md5('some/file.php'), 'some/file.php');
$file->addNamespace(new Fqsen('\mySpace'));
$file->addConstant(new Constant(new Fqsen('\mySpace::MY_CONST')));
$namespaces = $this->fetchNamespacesFromSingleFile($file);
$mySpace = current($namespaces);
$this->assertInstanceOf(Namespace_::class, $mySpace);
$this->assertEquals('\mySpace::MY_CONST', key($mySpace->getConstants()));
}
public function testWithNamespacedTrait(): void
{
$file = new File(md5('some/file.php'), 'some/file.php');
$file->addNamespace(new Fqsen('\mySpace'));
$file->addTrait(new Trait_(new Fqsen('\mySpace\MyTrait')));
$namespaces = $this->fetchNamespacesFromSingleFile($file);
$mySpace = current($namespaces);
$this->assertInstanceOf(Namespace_::class, $mySpace);
$this->assertEquals('\mySpace\MyTrait', key($mySpace->getTraits()));
}
public function testNamespaceSpreadOverMultipleFiles(): void
{
$someFile = new File(md5('some/file.php'), 'some/file.php');
$someFile->addNamespace(new Fqsen('\mySpace'));
$someFile->addClass(new Class_(new Fqsen('\mySpace\MyClass')));
$otherFile = new File(md5('some/other.php'), 'some/other.php');
$otherFile->addNamespace(new Fqsen('\mySpace'));
$otherFile->addClass(new Class_(new Fqsen('\mySpace\OtherClass')));
$namespaces = $this->fetchNamespacesFromMultipleFiles([$otherFile, $someFile]);
$this->assertCount(1, $namespaces);
$this->assertCount(2, current($namespaces)->getClasses());
}
public function testSingleFileMultipleNamespaces(): void
{
$someFile = new File(md5('some/file.php'), 'some/file.php');
$someFile->addNamespace(new Fqsen('\mySpace'));
$someFile->addClass(new Class_(new Fqsen('\mySpace\MyClass')));
$someFile->addNamespace(new Fqsen('\mySpace\SubSpace'));
$someFile->addClass(new Class_(new Fqsen('\mySpace\SubSpace\MyClass')));
$namespaces = $this->fetchNamespacesFromSingleFile($someFile);
$this->assertCount(2, $namespaces);
$this->assertArrayHasKey('\mySpace', $namespaces);
$this->assertArrayHasKey('\mySpace\SubSpace', $namespaces);
$this->assertCount(1, $namespaces['\mySpace']->getClasses());
}
/**
* Uses the ProjectFactory to create a Project and returns the namespaces created by the factory.
*
* @return Namespace_[] Namespaces of the project
*
* @throws Exception
*/
private function fetchNamespacesFromSingleFile(File $file): array
{
return $this->fetchNamespacesFromMultipleFiles([$file]);
}
/**
* Uses the ProjectFactory to create a Project and returns the namespaces created by the factory.
*
* @param File[] $files
*
* @return Namespace_[] Namespaces of the project
*
* @throws Exception
*/
private function fetchNamespacesFromMultipleFiles(array $files): array
{
$fileStrategyMock = $this->prophesize(ProjectFactoryStrategy::class);
$fileStrategyMock->matches(
ProphesizeArgument::type(ContextStack::class),
ProphesizeArgument::any(),
)->willReturn(true);
$fileStrategyMock->create(
ProphesizeArgument::type(ContextStack::class),
ProphesizeArgument::type(File::class),
ProphesizeArgument::any(),
)->will(function ($args): void {
$context = $args[0];
assert($context instanceof ContextStack);
$file = $args[1];
assert($file instanceof File);
$context->getProject()->addFile($file);
});
$projectFactory = new ProjectFactory([$fileStrategyMock->reveal()]);
$project = $projectFactory->create('My Project', $files);
return $project->getNamespaces();
}
}

Some files were not shown because too many files have changed in this diff Show More