mirror of
https://github.com/danog/Valinor.git
synced 2024-11-26 20:24:40 +01:00
misc: import namespace token parser inside library
The `TokenParser` is imported from the `doctrine/annotations` package in order to reduce the coupling to this library, which will lead to its removal from the dependencies in an upcoming version.
This commit is contained in:
parent
833193e025
commit
0b8ca98a2c
@ -4,7 +4,10 @@ $finder = PhpCsFixer\Finder::create()->in([
|
||||
'./src',
|
||||
'./tests',
|
||||
'./qa',
|
||||
]);
|
||||
])
|
||||
->notPath('Fixtures/FunctionWithGroupedImportStatements.php')
|
||||
->notPath('Fixtures/FunctionWithSeveralImportStatementsInSameUseStatement.php')
|
||||
->notPath('Fixtures/TwoClassesInDifferentNamespaces.php');
|
||||
|
||||
if (PHP_VERSION_ID < 8_00_00) {
|
||||
$finder = $finder
|
||||
@ -33,6 +36,6 @@ return (new PhpCsFixer\Config())
|
||||
'no_empty_phpdoc' => true,
|
||||
'no_superfluous_phpdoc_tags' => [
|
||||
'allow_mixed' => true,
|
||||
'remove_inheritdoc' => true
|
||||
'remove_inheritdoc' => true,
|
||||
],
|
||||
]);
|
||||
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace CuyZ\Valinor\Type\Parser\Lexer;
|
||||
|
||||
use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
|
||||
use CuyZ\Valinor\Utility\Reflection\ClassAliasParser;
|
||||
use CuyZ\Valinor\Utility\Reflection\PhpParser;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
@ -39,13 +39,13 @@ final class AliasLexer implements TypeLexer
|
||||
|
||||
private function resolve(string $symbol): string
|
||||
{
|
||||
$alias = ClassAliasParser::get()->resolveAlias($symbol, $this->reflection);
|
||||
$alias = $this->resolveAlias($symbol);
|
||||
|
||||
if (strtolower($alias) !== strtolower($symbol)) {
|
||||
return $alias;
|
||||
}
|
||||
|
||||
$namespaced = $this->resolveNamespaced($symbol, $this->reflection);
|
||||
$namespaced = $this->resolveNamespaced($symbol);
|
||||
|
||||
if ($namespaced !== $symbol) {
|
||||
return $namespaced;
|
||||
@ -54,11 +54,36 @@ final class AliasLexer implements TypeLexer
|
||||
return $symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<object>|ReflectionFunction $reflection
|
||||
*/
|
||||
private function resolveNamespaced(string $symbol, Reflector $reflection): string
|
||||
private function resolveAlias(string $symbol): string
|
||||
{
|
||||
$alias = $symbol;
|
||||
|
||||
$namespaceParts = explode('\\', $symbol);
|
||||
$lastPart = array_shift($namespaceParts);
|
||||
|
||||
if ($lastPart) {
|
||||
$alias = strtolower($lastPart);
|
||||
}
|
||||
|
||||
$aliases = PhpParser::parseUseStatements($this->reflection);
|
||||
|
||||
if (! isset($aliases[$alias])) {
|
||||
return $symbol;
|
||||
}
|
||||
|
||||
$full = $aliases[$alias];
|
||||
|
||||
if (! empty($namespaceParts)) {
|
||||
$full .= '\\' . implode('\\', $namespaceParts);
|
||||
}
|
||||
|
||||
return $full;
|
||||
}
|
||||
|
||||
private function resolveNamespaced(string $symbol): string
|
||||
{
|
||||
$reflection = $this->reflection;
|
||||
|
||||
if ($reflection instanceof ReflectionFunction) {
|
||||
$reflection = $reflection->getClosureScopeClass();
|
||||
}
|
||||
|
@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Utility\Reflection;
|
||||
|
||||
use CuyZ\Valinor\Utility\IsSingleton;
|
||||
use CuyZ\Valinor\Utility\Singleton;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use Reflector;
|
||||
|
||||
use function array_shift;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function strtolower;
|
||||
|
||||
/** @internal */
|
||||
final class ClassAliasParser
|
||||
{
|
||||
use IsSingleton;
|
||||
|
||||
/** @var array<string, array<string, string>> */
|
||||
private array $aliases = [];
|
||||
|
||||
/**
|
||||
* If the given symbol was imported as an alias in the given class, the
|
||||
* original value is returned.
|
||||
*
|
||||
* @param ReflectionClass<object>|ReflectionFunction $reflection
|
||||
*/
|
||||
public function resolveAlias(string $symbol, Reflector $reflection): string
|
||||
{
|
||||
$alias = $symbol;
|
||||
|
||||
$namespaceParts = explode('\\', $symbol);
|
||||
$lastPart = array_shift($namespaceParts);
|
||||
|
||||
if ($lastPart) {
|
||||
$alias = strtolower($lastPart);
|
||||
}
|
||||
|
||||
$aliases = $this->aliases($reflection);
|
||||
|
||||
if (! isset($aliases[$alias])) {
|
||||
return $symbol;
|
||||
}
|
||||
|
||||
$full = $aliases[$alias];
|
||||
|
||||
if (! empty($namespaceParts)) {
|
||||
$full .= '\\' . implode('\\', $namespaceParts);
|
||||
}
|
||||
|
||||
return $full;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<object>|ReflectionFunction $reflection
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function aliases(Reflector $reflection): array
|
||||
{
|
||||
/** @infection-ignore-all */
|
||||
return $this->aliases[Reflection::signature($reflection)] ??= Singleton::phpParser()->parseUseStatements($reflection);
|
||||
}
|
||||
}
|
82
src/Utility/Reflection/PhpParser.php
Normal file
82
src/Utility/Reflection/PhpParser.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Utility\Reflection;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
use Reflector;
|
||||
use SplFileObject;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Imported from `doctrine/annotations`:
|
||||
* @link https://github.com/doctrine/annotations/blob/4858ab786a6cb568149209a9112dad3808c8a4de/lib/Doctrine/Common/Annotations/PhpParser.php
|
||||
*/
|
||||
final class PhpParser
|
||||
{
|
||||
/** @var array<string, array<string, string>> */
|
||||
private static array $statements = [];
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<object>|ReflectionFunction|ReflectionMethod $reflection
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function parseUseStatements(Reflector $reflection): array
|
||||
{
|
||||
// @infection-ignore-all
|
||||
return self::$statements[Reflection::signature($reflection)] ??= self::fetchUseStatements($reflection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<object>|ReflectionFunction|ReflectionMethod $reflection
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private static function fetchUseStatements(Reflector $reflection): array
|
||||
{
|
||||
$filename = $reflection->getFileName();
|
||||
$startLine = $reflection->getStartLine();
|
||||
|
||||
if ($reflection instanceof ReflectionMethod) {
|
||||
$namespaceName = $reflection->getDeclaringClass()->getNamespaceName();
|
||||
} elseif ($reflection instanceof ReflectionFunction && $reflection->getClosureScopeClass()) {
|
||||
$namespaceName = $reflection->getClosureScopeClass()->getNamespaceName();
|
||||
} else {
|
||||
$namespaceName = $reflection->getNamespaceName();
|
||||
}
|
||||
|
||||
// @infection-ignore-all these values will never be `true`
|
||||
if ($filename === false || $startLine === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! is_file($filename)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$content = self::getFileContent($filename, $startLine);
|
||||
|
||||
return (new TokenParser($content))->parseUseStatements($namespaceName);
|
||||
}
|
||||
|
||||
private static function getFileContent(string $filename, int $lineNumber): string
|
||||
{
|
||||
// @infection-ignore-all no need to test with `-1`
|
||||
$lineCnt = 0;
|
||||
$content = '';
|
||||
$file = new SplFileObject($filename);
|
||||
|
||||
while (! $file->eof()) {
|
||||
if ($lineCnt++ === $lineNumber) {
|
||||
break;
|
||||
}
|
||||
|
||||
$content .= $file->fgets();
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
157
src/Utility/Reflection/TokenParser.php
Normal file
157
src/Utility/Reflection/TokenParser.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Utility\Reflection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Imported from `doctrine/annotations`:
|
||||
* @link https://github.com/doctrine/annotations/blob/de990c9a69782a8b15fa8c9248de0ef4d82ed701/lib/Doctrine/Common/Annotations/TokenParser.php
|
||||
*/
|
||||
final class TokenParser
|
||||
{
|
||||
/** @var array<int, array{0: int, 1: string}|string> */
|
||||
private array $tokens;
|
||||
|
||||
private int $numTokens;
|
||||
|
||||
private int $pointer = 0;
|
||||
|
||||
public function __construct(string $content)
|
||||
{
|
||||
$this->tokens = token_get_all($content);
|
||||
|
||||
/** @see https://github.com/doctrine/annotations/blob/4858ab786a6cb568149209a9112dad3808c8a4de/lib/Doctrine/Common/Annotations/TokenParser.php#L55-L61 */
|
||||
// @infection-ignore-all
|
||||
token_get_all("<?php\n/**\n *\n */"); // @phpstan-ignore-line
|
||||
|
||||
$this->numTokens = count($this->tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function parseUseStatements(string $namespaceName): array
|
||||
{
|
||||
$currentNamespace = '';
|
||||
$statements = [];
|
||||
|
||||
while ($token = $this->next()) {
|
||||
if ($currentNamespace === $namespaceName && $token[0] === T_USE) {
|
||||
$statements = array_merge($statements, $this->parseUseStatement());
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token[0] !== T_NAMESPACE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentNamespace = $this->parseNamespace();
|
||||
|
||||
// Get fresh array for new namespace. This is to prevent the parser
|
||||
// to collect the use statements for a previous namespace with the
|
||||
// same name (this is the case if a namespace is defined twice).
|
||||
$statements = [];
|
||||
}
|
||||
|
||||
return $statements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function parseUseStatement(): array
|
||||
{
|
||||
$groupRoot = '';
|
||||
$class = '';
|
||||
$alias = '';
|
||||
$statements = [];
|
||||
$explicitAlias = false;
|
||||
|
||||
while ($token = $this->next()) {
|
||||
if (! $explicitAlias && $token[0] === T_STRING) {
|
||||
$class .= $token[1]; // @PHP8.0 remove concatenation
|
||||
$alias = $token[1];
|
||||
} elseif ($explicitAlias && $token[0] === T_STRING) {
|
||||
$alias = $token[1];
|
||||
} elseif (PHP_VERSION_ID >= 80000 // @PHP8.0 remove condition
|
||||
&& ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
|
||||
) {
|
||||
$class .= $token[1]; // @PHP8.0 remove concatenation
|
||||
$classSplit = explode('\\', $token[1]);
|
||||
$alias = $classSplit[count($classSplit) - 1];
|
||||
} elseif ($token[0] === T_NS_SEPARATOR) {
|
||||
$class .= '\\';
|
||||
$alias = '';
|
||||
} elseif ($token[0] === T_AS) {
|
||||
$explicitAlias = true;
|
||||
$alias = '';
|
||||
} elseif ($token === ',') {
|
||||
$statements[strtolower($alias)] = $groupRoot . $class;
|
||||
$class = '';
|
||||
$alias = '';
|
||||
$explicitAlias = false;
|
||||
} elseif ($token === ';') {
|
||||
if ($alias !== '') {
|
||||
$statements[strtolower($alias)] = $groupRoot . $class;
|
||||
}
|
||||
break;
|
||||
} elseif ($token === '{') {
|
||||
$groupRoot = $class;
|
||||
$class = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $statements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next non whitespace and non comment token.
|
||||
*
|
||||
* @return array{0: int, 1: string}|string|null
|
||||
*/
|
||||
private function next()
|
||||
{
|
||||
for ($i = $this->pointer; $i < $this->numTokens; $i++) {
|
||||
$this->pointer++;
|
||||
|
||||
if ($this->tokens[$i][0] === T_WHITESPACE
|
||||
|| $this->tokens[$i][0] === T_COMMENT
|
||||
|| $this->tokens[$i][0] === T_DOC_COMMENT
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $this->tokens[$i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function parseNamespace(): string
|
||||
{
|
||||
$name = '';
|
||||
|
||||
// @PHP8.0 remove `infection-ignore-all`
|
||||
// @infection-ignore-all
|
||||
while (($token = $this->next())
|
||||
// @PHP8.0 remove conditions
|
||||
&& (
|
||||
(
|
||||
PHP_VERSION_ID < 80000
|
||||
&& ($token[0] === T_NS_SEPARATOR || $token[0] === T_STRING)
|
||||
)
|
||||
|| (
|
||||
PHP_VERSION_ID >= 80000
|
||||
&& ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
|
||||
)
|
||||
)
|
||||
) {
|
||||
$name .= $token[1]; // @PHP8.0 `return $token[1];` and `throw Error()` at the end of the method
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Utility;
|
||||
|
||||
use CuyZ\Valinor\Utility\Reflection\PhpParser;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Doctrine\Common\Annotations\PhpParser;
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
|
||||
/** @internal */
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures;
|
||||
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Bar as BarAlias;
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Foo;
|
||||
use DateTimeImmutable;
|
||||
use stdClass as stdClassAlias;
|
||||
|
||||
final class ClassInSingleNamespace
|
||||
{
|
||||
// @PHP8.0 promoted properties
|
||||
public Foo $classInNamespaceWithoutAlias;
|
||||
public BarAlias $classInNamespaceWithAlias;
|
||||
public DateTimeImmutable $classInRootNamespaceWithoutAlias;
|
||||
public stdClassAlias $classInRootNamespaceWithAlias;
|
||||
|
||||
public function __construct(
|
||||
Foo $classInNamespaceWithoutAlias,
|
||||
BarAlias $classInNamespaceWithAlias,
|
||||
DateTimeImmutable $classInRootNamespaceWithoutAlias,
|
||||
stdClassAlias $classInRootNamespaceWithAlias
|
||||
) {
|
||||
$this->classInNamespaceWithoutAlias = $classInNamespaceWithoutAlias;
|
||||
$this->classInNamespaceWithAlias = $classInNamespaceWithAlias;
|
||||
$this->classInRootNamespaceWithoutAlias = $classInRootNamespaceWithoutAlias;
|
||||
$this->classInRootNamespaceWithAlias = $classInRootNamespaceWithAlias;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Foo as FooAlias;
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Bar as BarAlias;
|
||||
|
||||
function function_in_root_namespace(
|
||||
FooAlias $foo,
|
||||
BarAlias $bar
|
||||
): void {
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures;
|
||||
|
||||
// Only one use statement with two grouped import statements, no trailing comma.
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\ {
|
||||
Foo as FooAlias,
|
||||
Bar as BarAlias // no trailing comma
|
||||
};
|
||||
|
||||
// Only one use statement with two grouped import statements, trailing comma.
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\{
|
||||
Foo as AnotherFooAlias,
|
||||
Bar as AnotherBarAlias, // trailing comma
|
||||
};
|
||||
|
||||
function function_with_grouped_import_statements(
|
||||
FooAlias $foo,
|
||||
BarAlias $bar,
|
||||
AnotherFooAlias $anotherFoo,
|
||||
AnotherBarAlias $anotherBar
|
||||
): void {
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures;
|
||||
|
||||
// Only one use statement with two import statements.
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Foo as FooAlias,
|
||||
CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Bar as BarAlias;
|
||||
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Foo as AnotherFooAlias,
|
||||
CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Bar as AnotherBarAlias;
|
||||
|
||||
function function_with_several_import_statements_in_same_use_statement(
|
||||
FooAlias $foo,
|
||||
BarAlias $bar,
|
||||
AnotherFooAlias $anotherFoo,
|
||||
AnotherBarAlias $anotherBar
|
||||
): void {
|
||||
}
|
9
tests/Unit/Utility/Reflection/Fixtures/SubDir/Bar.php
Normal file
9
tests/Unit/Utility/Reflection/Fixtures/SubDir/Bar.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir;
|
||||
|
||||
final class Bar
|
||||
{
|
||||
}
|
9
tests/Unit/Utility/Reflection/Fixtures/SubDir/Foo.php
Normal file
9
tests/Unit/Utility/Reflection/Fixtures/SubDir/Foo.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir;
|
||||
|
||||
final class Foo
|
||||
{
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// A commented namespace should not be parsed
|
||||
//
|
||||
// namespace CuyZ\Valinor\Tests\Fixtures\WithAliasA {
|
||||
// use DateTime as SomeDateTimeAlias;
|
||||
// }
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Fixtures\WithAliasA {
|
||||
|
||||
use CuyZ\Valinor\Tests\Fixtures\WithAliasB\ClassB;
|
||||
use CuyZ\Valinor\Tests\Fixtures\WithAliasB\ClassB as classBAlias;
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Bar as BarAlias;
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Foo;
|
||||
use DateTimeImmutable;
|
||||
use stdClass as stdClassAlias;
|
||||
|
||||
function functionA(
|
||||
Foo $classInOtherFileWithoutAlias,
|
||||
BarAlias $classInOtherFileWithAlias,
|
||||
ClassB $classInSameFileWithoutAlias,
|
||||
classBAlias $classInSameFileWithAlias,
|
||||
DateTimeImmutable $classInRootNamespaceWithoutAlias,
|
||||
stdClassAlias $classInRootNamespaceWithAlias
|
||||
): void {
|
||||
}
|
||||
|
||||
class ClassA
|
||||
{
|
||||
// @PHP8.0 promoted properties
|
||||
public Foo $classInOtherFileWithoutAlias;
|
||||
public BarAlias $classInOtherFileWithAlias;
|
||||
public classB $classInSameFileWithoutAlias;
|
||||
public classBAlias $classInSameFileWithAlias;
|
||||
public DateTimeImmutable $classInRootNamespaceWithoutAlias;
|
||||
public stdClassAlias $classInRootNamespaceWithAlias;
|
||||
|
||||
public function __construct(
|
||||
Foo $classInOtherFileWithoutAlias,
|
||||
BarAlias $classInOtherFileWithAlias,
|
||||
ClassB $classInSameFileWithoutAlias,
|
||||
classBAlias $classInSameFileWithAlias,
|
||||
DateTimeImmutable $classInRootNamespaceWithoutAlias,
|
||||
stdClassAlias $classInRootNamespaceWithAlias
|
||||
) {
|
||||
$this->classInOtherFileWithoutAlias = $classInOtherFileWithoutAlias;
|
||||
$this->classInOtherFileWithAlias = $classInOtherFileWithAlias;
|
||||
$this->classInSameFileWithoutAlias = $classInSameFileWithoutAlias;
|
||||
$this->classInSameFileWithAlias = $classInSameFileWithAlias;
|
||||
$this->classInRootNamespaceWithoutAlias = $classInRootNamespaceWithoutAlias;
|
||||
$this->classInRootNamespaceWithAlias = $classInRootNamespaceWithAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First case of a duplicated namespace: the alias `AnotherDateTimeAlias` is not
|
||||
// accessible in the second case below and should not be fetched by the parser.
|
||||
namespace CuyZ\Valinor\Tests\Fixtures\WithAliasB {
|
||||
use DateTime as AnotherDateTimeAlias;
|
||||
}
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Fixtures\WithAliasB {
|
||||
|
||||
use CuyZ\Valinor\Tests\Fixtures\WithAliasA\ClassA;
|
||||
use CuyZ\Valinor\Tests\Fixtures\WithAliasA\ClassA as classAAlias;
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Bar as BarAlias;
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Foo;
|
||||
use DateTimeImmutable;
|
||||
use stdClass as stdClassAlias;
|
||||
|
||||
function functionB(
|
||||
Foo $classInOtherFileWithoutAlias,
|
||||
BarAlias $classInOtherFileWithAlias,
|
||||
ClassA $classInSameFileWithoutAlias,
|
||||
classAAlias $classInSameFileWithAlias,
|
||||
DateTimeImmutable $classInRootNamespaceWithoutAlias,
|
||||
stdClassAlias $classInRootNamespaceWithAlias
|
||||
): void {
|
||||
}
|
||||
|
||||
class ClassB
|
||||
{
|
||||
// @PHP8.0 promoted properties
|
||||
public Foo $classInOtherFileWithoutAlias;
|
||||
public BarAlias $classInOtherFileWithAlias;
|
||||
public classA $classInSameFileWithoutAlias;
|
||||
public classAAlias $classInSameFileWithAlias;
|
||||
public DateTimeImmutable $classInRootNamespaceWithoutAlias;
|
||||
public stdClassAlias $classInRootNamespaceWithAlias;
|
||||
|
||||
public function __construct(
|
||||
Foo $classInOtherFileWithoutAlias,
|
||||
BarAlias $classInOtherFileWithAlias,
|
||||
ClassA $classInSameFileWithoutAlias,
|
||||
classAAlias $classInSameFileWithAlias,
|
||||
DateTimeImmutable $classInRootNamespaceWithoutAlias,
|
||||
stdClassAlias $classInRootNamespaceWithAlias
|
||||
) {
|
||||
$this->classInOtherFileWithoutAlias = $classInOtherFileWithoutAlias;
|
||||
$this->classInOtherFileWithAlias = $classInOtherFileWithAlias;
|
||||
$this->classInSameFileWithoutAlias = $classInSameFileWithoutAlias;
|
||||
$this->classInSameFileWithAlias = $classInSameFileWithAlias;
|
||||
$this->classInRootNamespaceWithoutAlias = $classInRootNamespaceWithoutAlias;
|
||||
$this->classInRootNamespaceWithAlias = $classInRootNamespaceWithAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Third case of a duplicated namespace: the alias `YetAnotherDateTimeAlias` is
|
||||
// not accessible in the second case above and should not be fetched by the
|
||||
// parser.
|
||||
namespace CuyZ\Valinor\Tests\Fixtures\WithAliasB {
|
||||
use DateTimeImmutable as YetAnotherDateTimeAlias;
|
||||
}
|
167
tests/Unit/Utility/Reflection/PhpParserTest.php
Normal file
167
tests/Unit/Utility/Reflection/PhpParserTest.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Utility\Reflection;
|
||||
|
||||
use CuyZ\Valinor\Tests\Fixtures\WithAliasA\ClassA;
|
||||
use CuyZ\Valinor\Tests\Fixtures\WithAliasB\ClassB;
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\ClassInSingleNamespace;
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Bar;
|
||||
use CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\SubDir\Foo;
|
||||
use CuyZ\Valinor\Utility\Reflection\PhpParser;
|
||||
use Generator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
||||
require_once __DIR__ . '/Fixtures/TwoClassesInDifferentNamespaces.php';
|
||||
require_once __DIR__ . '/Fixtures/FunctionInRootNamespace.php';
|
||||
require_once __DIR__ . '/Fixtures/FunctionWithSeveralImportStatementsInSameUseStatement.php';
|
||||
require_once __DIR__ . '/Fixtures/FunctionWithGroupedImportStatements.php';
|
||||
|
||||
final class PhpParserTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider useStatementsDataProvider
|
||||
* @template T of object
|
||||
*
|
||||
* @param ReflectionClass<T>|ReflectionFunction|ReflectionMethod $reflection
|
||||
* @param array<string, string> $expectedMap
|
||||
*/
|
||||
public function test_parse_use_statements($reflection, array $expectedMap): void
|
||||
{
|
||||
$actualMap = PhpParser::parseUseStatements($reflection);
|
||||
|
||||
self::assertSame($expectedMap, $actualMap);
|
||||
}
|
||||
|
||||
public function useStatementsDataProvider(): Generator
|
||||
{
|
||||
yield 'no use statements' => [
|
||||
new ReflectionClass(\stdClass::class),
|
||||
[]
|
||||
];
|
||||
|
||||
yield 'one namespace' => [
|
||||
new ReflectionClass(ClassInSingleNamespace::class),
|
||||
[
|
||||
'baralias' => Bar::class,
|
||||
'foo' => Foo::class,
|
||||
'datetimeimmutable' => \DateTimeImmutable::class,
|
||||
'stdclassalias' => \stdClass::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'multiple namespaces, class A' => [
|
||||
new ReflectionClass(ClassA::class),
|
||||
[
|
||||
'classb' => ClassB::class,
|
||||
'classbalias' => ClassB::class,
|
||||
'baralias' => Bar::class,
|
||||
'foo' => Foo::class,
|
||||
'datetimeimmutable' => \DateTimeImmutable::class,
|
||||
'stdclassalias' => \stdClass::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'multiple namespaces, class B' => [
|
||||
new ReflectionClass(ClassB::class),
|
||||
[
|
||||
'classa' => ClassA::class,
|
||||
'classaalias' => ClassA::class,
|
||||
'baralias' => Bar::class,
|
||||
'foo' => Foo::class,
|
||||
'datetimeimmutable' => \DateTimeImmutable::class,
|
||||
'stdclassalias' => \stdClass::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'multiple namespaces, function A' => [
|
||||
new ReflectionFunction('\CuyZ\Valinor\Tests\Fixtures\WithAliasA\functionA'),
|
||||
[
|
||||
'classb' => ClassB::class,
|
||||
'classbalias' => ClassB::class,
|
||||
'baralias' => Bar::class,
|
||||
'foo' => Foo::class,
|
||||
'datetimeimmutable' => \DateTimeImmutable::class,
|
||||
'stdclassalias' => \stdClass::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'multiple namespaces, function B' => [
|
||||
new ReflectionFunction('\CuyZ\Valinor\Tests\Fixtures\WithAliasB\functionB'),
|
||||
[
|
||||
'classa' => ClassA::class,
|
||||
'classaalias' => ClassA::class,
|
||||
'baralias' => Bar::class,
|
||||
'foo' => Foo::class,
|
||||
'datetimeimmutable' => \DateTimeImmutable::class,
|
||||
'stdclassalias' => \stdClass::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'one namespace, method' => [
|
||||
new ReflectionMethod(ClassInSingleNamespace::class, '__construct'),
|
||||
[
|
||||
'baralias' => Bar::class,
|
||||
'foo' => Foo::class,
|
||||
'datetimeimmutable' => \DateTimeImmutable::class,
|
||||
'stdclassalias' => \stdClass::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'multiple namespaces, class A method' => [
|
||||
new ReflectionMethod(ClassA::class, '__construct'),
|
||||
[
|
||||
'classb' => ClassB::class,
|
||||
'classbalias' => ClassB::class,
|
||||
'baralias' => Bar::class,
|
||||
'foo' => Foo::class,
|
||||
'datetimeimmutable' => \DateTimeImmutable::class,
|
||||
'stdclassalias' => \stdClass::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'multiple namespaces, class B method' => [
|
||||
new ReflectionMethod(ClassB::class, '__construct'),
|
||||
[
|
||||
'classa' => ClassA::class,
|
||||
'classaalias' => ClassA::class,
|
||||
'baralias' => Bar::class,
|
||||
'foo' => Foo::class,
|
||||
'datetimeimmutable' => \DateTimeImmutable::class,
|
||||
'stdclassalias' => \stdClass::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'function in root namespace' => [
|
||||
new ReflectionFunction('function_in_root_namespace'),
|
||||
[
|
||||
'fooalias' => Foo::class,
|
||||
'baralias' => Bar::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'one namespace, one use statement, two import statements' => [
|
||||
new ReflectionFunction('CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\function_with_several_import_statements_in_same_use_statement'),
|
||||
[
|
||||
'fooalias' => Foo::class,
|
||||
'baralias' => Bar::class,
|
||||
'anotherfooalias' => Foo::class,
|
||||
'anotherbaralias' => Bar::class,
|
||||
]
|
||||
];
|
||||
|
||||
yield 'one namespace, one use statement, two grouped import statements' => [
|
||||
new ReflectionFunction('\CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\function_with_grouped_import_statements'),
|
||||
[
|
||||
'fooalias' => Foo::class,
|
||||
'baralias' => Bar::class,
|
||||
'anotherfooalias' => Foo::class,
|
||||
'anotherbaralias' => Bar::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user