Mapper.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of sebastian/code-unit.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace SebastianBergmann\CodeUnit;
  11. use function array_keys;
  12. use function array_merge;
  13. use function array_unique;
  14. use function array_values;
  15. use function class_exists;
  16. use function explode;
  17. use function function_exists;
  18. use function interface_exists;
  19. use function ksort;
  20. use function method_exists;
  21. use function sort;
  22. use function sprintf;
  23. use function str_contains;
  24. use function trait_exists;
  25. use ReflectionClass;
  26. use ReflectionFunction;
  27. use ReflectionMethod;
  28. final class Mapper
  29. {
  30. /**
  31. * @psalm-return array<string,list<int>>
  32. */
  33. public function codeUnitsToSourceLines(CodeUnitCollection $codeUnits): array
  34. {
  35. $result = [];
  36. foreach ($codeUnits as $codeUnit) {
  37. $sourceFileName = $codeUnit->sourceFileName();
  38. if (!isset($result[$sourceFileName])) {
  39. $result[$sourceFileName] = [];
  40. }
  41. $result[$sourceFileName] = array_merge($result[$sourceFileName], $codeUnit->sourceLines());
  42. }
  43. foreach (array_keys($result) as $sourceFileName) {
  44. $result[$sourceFileName] = array_values(array_unique($result[$sourceFileName]));
  45. sort($result[$sourceFileName]);
  46. }
  47. ksort($result);
  48. return $result;
  49. }
  50. /**
  51. * @throws InvalidCodeUnitException
  52. * @throws ReflectionException
  53. */
  54. public function stringToCodeUnits(string $unit): CodeUnitCollection
  55. {
  56. if (str_contains($unit, '::')) {
  57. [$firstPart, $secondPart] = explode('::', $unit);
  58. if ($this->isUserDefinedFunction($secondPart)) {
  59. return CodeUnitCollection::fromList(CodeUnit::forFunction($secondPart));
  60. }
  61. if ($this->isUserDefinedMethod($firstPart, $secondPart)) {
  62. return CodeUnitCollection::fromList(CodeUnit::forClassMethod($firstPart, $secondPart));
  63. }
  64. if ($this->isUserDefinedInterface($firstPart)) {
  65. return CodeUnitCollection::fromList(CodeUnit::forInterfaceMethod($firstPart, $secondPart));
  66. }
  67. if ($this->isUserDefinedTrait($firstPart)) {
  68. return CodeUnitCollection::fromList(CodeUnit::forTraitMethod($firstPart, $secondPart));
  69. }
  70. } else {
  71. if ($this->isUserDefinedClass($unit)) {
  72. $units = [CodeUnit::forClass($unit)];
  73. foreach ($this->reflectorForClass($unit)->getTraits() as $trait) {
  74. if (!$trait->isUserDefined()) {
  75. // @codeCoverageIgnoreStart
  76. continue;
  77. // @codeCoverageIgnoreEnd
  78. }
  79. $units[] = CodeUnit::forTrait($trait->getName());
  80. }
  81. return CodeUnitCollection::fromList(...$units);
  82. }
  83. if ($this->isUserDefinedInterface($unit)) {
  84. return CodeUnitCollection::fromList(CodeUnit::forInterface($unit));
  85. }
  86. if ($this->isUserDefinedTrait($unit)) {
  87. return CodeUnitCollection::fromList(CodeUnit::forTrait($unit));
  88. }
  89. if ($this->isUserDefinedFunction($unit)) {
  90. return CodeUnitCollection::fromList(CodeUnit::forFunction($unit));
  91. }
  92. }
  93. throw new InvalidCodeUnitException(
  94. sprintf(
  95. '"%s" is not a valid code unit',
  96. $unit
  97. )
  98. );
  99. }
  100. /**
  101. * @psalm-param class-string $className
  102. *
  103. * @throws ReflectionException
  104. */
  105. private function reflectorForClass(string $className): ReflectionClass
  106. {
  107. try {
  108. return new ReflectionClass($className);
  109. // @codeCoverageIgnoreStart
  110. } catch (\ReflectionException $e) {
  111. throw new ReflectionException(
  112. $e->getMessage(),
  113. $e->getCode(),
  114. $e
  115. );
  116. }
  117. // @codeCoverageIgnoreEnd
  118. }
  119. /**
  120. * @throws ReflectionException
  121. */
  122. private function isUserDefinedFunction(string $functionName): bool
  123. {
  124. if (!function_exists($functionName)) {
  125. return false;
  126. }
  127. try {
  128. return (new ReflectionFunction($functionName))->isUserDefined();
  129. // @codeCoverageIgnoreStart
  130. } catch (\ReflectionException $e) {
  131. throw new ReflectionException(
  132. $e->getMessage(),
  133. $e->getCode(),
  134. $e
  135. );
  136. }
  137. // @codeCoverageIgnoreEnd
  138. }
  139. /**
  140. * @throws ReflectionException
  141. */
  142. private function isUserDefinedClass(string $className): bool
  143. {
  144. if (!class_exists($className)) {
  145. return false;
  146. }
  147. try {
  148. return (new ReflectionClass($className))->isUserDefined();
  149. // @codeCoverageIgnoreStart
  150. } catch (\ReflectionException $e) {
  151. throw new ReflectionException(
  152. $e->getMessage(),
  153. $e->getCode(),
  154. $e
  155. );
  156. }
  157. // @codeCoverageIgnoreEnd
  158. }
  159. /**
  160. * @throws ReflectionException
  161. */
  162. private function isUserDefinedInterface(string $interfaceName): bool
  163. {
  164. if (!interface_exists($interfaceName)) {
  165. return false;
  166. }
  167. try {
  168. return (new ReflectionClass($interfaceName))->isUserDefined();
  169. // @codeCoverageIgnoreStart
  170. } catch (\ReflectionException $e) {
  171. throw new ReflectionException(
  172. $e->getMessage(),
  173. $e->getCode(),
  174. $e
  175. );
  176. }
  177. // @codeCoverageIgnoreEnd
  178. }
  179. /**
  180. * @throws ReflectionException
  181. */
  182. private function isUserDefinedTrait(string $traitName): bool
  183. {
  184. if (!trait_exists($traitName)) {
  185. return false;
  186. }
  187. try {
  188. return (new ReflectionClass($traitName))->isUserDefined();
  189. // @codeCoverageIgnoreStart
  190. } catch (\ReflectionException $e) {
  191. throw new ReflectionException(
  192. $e->getMessage(),
  193. $e->getCode(),
  194. $e
  195. );
  196. }
  197. // @codeCoverageIgnoreEnd
  198. }
  199. /**
  200. * @throws ReflectionException
  201. */
  202. private function isUserDefinedMethod(string $className, string $methodName): bool
  203. {
  204. if (!class_exists($className)) {
  205. // @codeCoverageIgnoreStart
  206. return false;
  207. // @codeCoverageIgnoreEnd
  208. }
  209. if (!method_exists($className, $methodName)) {
  210. // @codeCoverageIgnoreStart
  211. return false;
  212. // @codeCoverageIgnoreEnd
  213. }
  214. try {
  215. return (new ReflectionMethod($className, $methodName))->isUserDefined();
  216. // @codeCoverageIgnoreStart
  217. } catch (\ReflectionException $e) {
  218. throw new ReflectionException(
  219. $e->getMessage(),
  220. $e->getCode(),
  221. $e
  222. );
  223. }
  224. // @codeCoverageIgnoreEnd
  225. }
  226. }