415 lines
12 KiB
PHP
415 lines
12 KiB
PHP
<?php declare(strict_types=1);
|
|
/*
|
|
* This file is part of sebastian/code-unit.
|
|
*
|
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
namespace SebastianBergmann\CodeUnit;
|
|
|
|
use function array_keys;
|
|
use function array_merge;
|
|
use function array_unique;
|
|
use function array_values;
|
|
use function class_exists;
|
|
use function explode;
|
|
use function function_exists;
|
|
use function interface_exists;
|
|
use function ksort;
|
|
use function method_exists;
|
|
use function sort;
|
|
use function sprintf;
|
|
use function str_replace;
|
|
use function strpos;
|
|
use function trait_exists;
|
|
use ReflectionClass;
|
|
use ReflectionFunction;
|
|
use ReflectionMethod;
|
|
|
|
final class Mapper
|
|
{
|
|
/**
|
|
* @psalm-return array<string,list<int>>
|
|
*/
|
|
public function codeUnitsToSourceLines(CodeUnitCollection $codeUnits): array
|
|
{
|
|
$result = [];
|
|
|
|
foreach ($codeUnits as $codeUnit) {
|
|
$sourceFileName = $codeUnit->sourceFileName();
|
|
|
|
if (!isset($result[$sourceFileName])) {
|
|
$result[$sourceFileName] = [];
|
|
}
|
|
|
|
$result[$sourceFileName] = array_merge($result[$sourceFileName], $codeUnit->sourceLines());
|
|
}
|
|
|
|
foreach (array_keys($result) as $sourceFileName) {
|
|
$result[$sourceFileName] = array_values(array_unique($result[$sourceFileName]));
|
|
|
|
sort($result[$sourceFileName]);
|
|
}
|
|
|
|
ksort($result);
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @throws InvalidCodeUnitException
|
|
* @throws ReflectionException
|
|
*/
|
|
public function stringToCodeUnits(string $unit): CodeUnitCollection
|
|
{
|
|
if (strpos($unit, '::') !== false) {
|
|
[$firstPart, $secondPart] = explode('::', $unit);
|
|
|
|
if (empty($firstPart) && $this->isUserDefinedFunction($secondPart)) {
|
|
return CodeUnitCollection::fromList(CodeUnit::forFunction($secondPart));
|
|
}
|
|
|
|
if ($this->isUserDefinedClass($firstPart)) {
|
|
if ($secondPart === '<public>') {
|
|
return $this->publicMethodsOfClass($firstPart);
|
|
}
|
|
|
|
if ($secondPart === '<!public>') {
|
|
return $this->protectedAndPrivateMethodsOfClass($firstPart);
|
|
}
|
|
|
|
if ($secondPart === '<protected>') {
|
|
return $this->protectedMethodsOfClass($firstPart);
|
|
}
|
|
|
|
if ($secondPart === '<!protected>') {
|
|
return $this->publicAndPrivateMethodsOfClass($firstPart);
|
|
}
|
|
|
|
if ($secondPart === '<private>') {
|
|
return $this->privateMethodsOfClass($firstPart);
|
|
}
|
|
|
|
if ($secondPart === '<!private>') {
|
|
return $this->publicAndProtectedMethodsOfClass($firstPart);
|
|
}
|
|
|
|
if ($this->isUserDefinedMethod($firstPart, $secondPart)) {
|
|
return CodeUnitCollection::fromList(CodeUnit::forClassMethod($firstPart, $secondPart));
|
|
}
|
|
}
|
|
|
|
if ($this->isUserDefinedInterface($firstPart)) {
|
|
return CodeUnitCollection::fromList(CodeUnit::forInterfaceMethod($firstPart, $secondPart));
|
|
}
|
|
|
|
if ($this->isUserDefinedTrait($firstPart)) {
|
|
return CodeUnitCollection::fromList(CodeUnit::forTraitMethod($firstPart, $secondPart));
|
|
}
|
|
} else {
|
|
if ($this->isUserDefinedClass($unit)) {
|
|
$units = [CodeUnit::forClass($unit)];
|
|
|
|
foreach ($this->reflectorForClass($unit)->getTraits() as $trait) {
|
|
if (!$trait->isUserDefined()) {
|
|
// @codeCoverageIgnoreStart
|
|
continue;
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
$units[] = CodeUnit::forTrait($trait->getName());
|
|
}
|
|
|
|
return CodeUnitCollection::fromArray($units);
|
|
}
|
|
|
|
if ($this->isUserDefinedInterface($unit)) {
|
|
return CodeUnitCollection::fromList(CodeUnit::forInterface($unit));
|
|
}
|
|
|
|
if ($this->isUserDefinedTrait($unit)) {
|
|
return CodeUnitCollection::fromList(CodeUnit::forTrait($unit));
|
|
}
|
|
|
|
if ($this->isUserDefinedFunction($unit)) {
|
|
return CodeUnitCollection::fromList(CodeUnit::forFunction($unit));
|
|
}
|
|
|
|
$unit = str_replace('<extended>', '', $unit);
|
|
|
|
if ($this->isUserDefinedClass($unit)) {
|
|
return $this->classAndParentClassesAndTraits($unit);
|
|
}
|
|
}
|
|
|
|
throw new InvalidCodeUnitException(
|
|
sprintf(
|
|
'"%s" is not a valid code unit',
|
|
$unit
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @psalm-param class-string $className
|
|
*
|
|
* @throws ReflectionException
|
|
*/
|
|
private function publicMethodsOfClass(string $className): CodeUnitCollection
|
|
{
|
|
return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC);
|
|
}
|
|
|
|
/**
|
|
* @psalm-param class-string $className
|
|
*
|
|
* @throws ReflectionException
|
|
*/
|
|
private function publicAndProtectedMethodsOfClass(string $className): CodeUnitCollection
|
|
{
|
|
return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED);
|
|
}
|
|
|
|
/**
|
|
* @psalm-param class-string $className
|
|
*
|
|
* @throws ReflectionException
|
|
*/
|
|
private function publicAndPrivateMethodsOfClass(string $className): CodeUnitCollection
|
|
{
|
|
return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PRIVATE);
|
|
}
|
|
|
|
/**
|
|
* @psalm-param class-string $className
|
|
*
|
|
* @throws ReflectionException
|
|
*/
|
|
private function protectedMethodsOfClass(string $className): CodeUnitCollection
|
|
{
|
|
return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED);
|
|
}
|
|
|
|
/**
|
|
* @psalm-param class-string $className
|
|
*
|
|
* @throws ReflectionException
|
|
*/
|
|
private function protectedAndPrivateMethodsOfClass(string $className): CodeUnitCollection
|
|
{
|
|
return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PRIVATE);
|
|
}
|
|
|
|
/**
|
|
* @psalm-param class-string $className
|
|
*
|
|
* @throws ReflectionException
|
|
*/
|
|
private function privateMethodsOfClass(string $className): CodeUnitCollection
|
|
{
|
|
return $this->methodsOfClass($className, ReflectionMethod::IS_PRIVATE);
|
|
}
|
|
|
|
/**
|
|
* @psalm-param class-string $className
|
|
*
|
|
* @throws ReflectionException
|
|
*/
|
|
private function methodsOfClass(string $className, int $filter): CodeUnitCollection
|
|
{
|
|
$units = [];
|
|
|
|
foreach ($this->reflectorForClass($className)->getMethods($filter) as $method) {
|
|
if (!$method->isUserDefined()) {
|
|
continue;
|
|
}
|
|
|
|
$units[] = CodeUnit::forClassMethod($className, $method->getName());
|
|
}
|
|
|
|
return CodeUnitCollection::fromArray($units);
|
|
}
|
|
|
|
/**
|
|
* @psalm-param class-string $className
|
|
*
|
|
* @throws ReflectionException
|
|
*/
|
|
private function classAndParentClassesAndTraits(string $className): CodeUnitCollection
|
|
{
|
|
$units = [CodeUnit::forClass($className)];
|
|
|
|
$reflector = $this->reflectorForClass($className);
|
|
|
|
foreach ($this->reflectorForClass($className)->getTraits() as $trait) {
|
|
if (!$trait->isUserDefined()) {
|
|
// @codeCoverageIgnoreStart
|
|
continue;
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
$units[] = CodeUnit::forTrait($trait->getName());
|
|
}
|
|
|
|
while ($reflector = $reflector->getParentClass()) {
|
|
if (!$reflector->isUserDefined()) {
|
|
break;
|
|
}
|
|
|
|
$units[] = CodeUnit::forClass($reflector->getName());
|
|
|
|
foreach ($reflector->getTraits() as $trait) {
|
|
if (!$trait->isUserDefined()) {
|
|
// @codeCoverageIgnoreStart
|
|
continue;
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
$units[] = CodeUnit::forTrait($trait->getName());
|
|
}
|
|
}
|
|
|
|
return CodeUnitCollection::fromArray($units);
|
|
}
|
|
|
|
/**
|
|
* @psalm-param class-string $className
|
|
*
|
|
* @throws ReflectionException
|
|
*/
|
|
private function reflectorForClass(string $className): ReflectionClass
|
|
{
|
|
try {
|
|
return new ReflectionClass($className);
|
|
// @codeCoverageIgnoreStart
|
|
} catch (\ReflectionException $e) {
|
|
throw new ReflectionException(
|
|
$e->getMessage(),
|
|
(int) $e->getCode(),
|
|
$e
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
/**
|
|
* @throws ReflectionException
|
|
*/
|
|
private function isUserDefinedFunction(string $functionName): bool
|
|
{
|
|
if (!function_exists($functionName)) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return (new ReflectionFunction($functionName))->isUserDefined();
|
|
// @codeCoverageIgnoreStart
|
|
} catch (\ReflectionException $e) {
|
|
throw new ReflectionException(
|
|
$e->getMessage(),
|
|
(int) $e->getCode(),
|
|
$e
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
/**
|
|
* @throws ReflectionException
|
|
*/
|
|
private function isUserDefinedClass(string $className): bool
|
|
{
|
|
if (!class_exists($className)) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return (new ReflectionClass($className))->isUserDefined();
|
|
// @codeCoverageIgnoreStart
|
|
} catch (\ReflectionException $e) {
|
|
throw new ReflectionException(
|
|
$e->getMessage(),
|
|
(int) $e->getCode(),
|
|
$e
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
/**
|
|
* @throws ReflectionException
|
|
*/
|
|
private function isUserDefinedInterface(string $interfaceName): bool
|
|
{
|
|
if (!interface_exists($interfaceName)) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return (new ReflectionClass($interfaceName))->isUserDefined();
|
|
// @codeCoverageIgnoreStart
|
|
} catch (\ReflectionException $e) {
|
|
throw new ReflectionException(
|
|
$e->getMessage(),
|
|
(int) $e->getCode(),
|
|
$e
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
/**
|
|
* @throws ReflectionException
|
|
*/
|
|
private function isUserDefinedTrait(string $traitName): bool
|
|
{
|
|
if (!trait_exists($traitName)) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return (new ReflectionClass($traitName))->isUserDefined();
|
|
// @codeCoverageIgnoreStart
|
|
} catch (\ReflectionException $e) {
|
|
throw new ReflectionException(
|
|
$e->getMessage(),
|
|
(int) $e->getCode(),
|
|
$e
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
/**
|
|
* @throws ReflectionException
|
|
*/
|
|
private function isUserDefinedMethod(string $className, string $methodName): bool
|
|
{
|
|
if (!class_exists($className)) {
|
|
// @codeCoverageIgnoreStart
|
|
return false;
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
if (!method_exists($className, $methodName)) {
|
|
// @codeCoverageIgnoreStart
|
|
return false;
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
try {
|
|
return (new ReflectionMethod($className, $methodName))->isUserDefined();
|
|
// @codeCoverageIgnoreStart
|
|
} catch (\ReflectionException $e) {
|
|
throw new ReflectionException(
|
|
$e->getMessage(),
|
|
(int) $e->getCode(),
|
|
$e
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
}
|