Mapper.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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_replace;
  24. use function strpos;
  25. use function trait_exists;
  26. use ReflectionClass;
  27. use ReflectionFunction;
  28. use ReflectionMethod;
  29. final class Mapper
  30. {
  31. /**
  32. * @psalm-return array<string,list<int>>
  33. */
  34. public function codeUnitsToSourceLines(CodeUnitCollection $codeUnits): array
  35. {
  36. $result = [];
  37. foreach ($codeUnits as $codeUnit) {
  38. $sourceFileName = $codeUnit->sourceFileName();
  39. if (!isset($result[$sourceFileName])) {
  40. $result[$sourceFileName] = [];
  41. }
  42. $result[$sourceFileName] = array_merge($result[$sourceFileName], $codeUnit->sourceLines());
  43. }
  44. foreach (array_keys($result) as $sourceFileName) {
  45. $result[$sourceFileName] = array_values(array_unique($result[$sourceFileName]));
  46. sort($result[$sourceFileName]);
  47. }
  48. ksort($result);
  49. return $result;
  50. }
  51. /**
  52. * @throws InvalidCodeUnitException
  53. * @throws ReflectionException
  54. */
  55. public function stringToCodeUnits(string $unit): CodeUnitCollection
  56. {
  57. if (strpos($unit, '::') !== false) {
  58. [$firstPart, $secondPart] = explode('::', $unit);
  59. if (empty($firstPart) && $this->isUserDefinedFunction($secondPart)) {
  60. return CodeUnitCollection::fromList(CodeUnit::forFunction($secondPart));
  61. }
  62. if ($this->isUserDefinedClass($firstPart)) {
  63. if ($secondPart === '<public>') {
  64. return $this->publicMethodsOfClass($firstPart);
  65. }
  66. if ($secondPart === '<!public>') {
  67. return $this->protectedAndPrivateMethodsOfClass($firstPart);
  68. }
  69. if ($secondPart === '<protected>') {
  70. return $this->protectedMethodsOfClass($firstPart);
  71. }
  72. if ($secondPart === '<!protected>') {
  73. return $this->publicAndPrivateMethodsOfClass($firstPart);
  74. }
  75. if ($secondPart === '<private>') {
  76. return $this->privateMethodsOfClass($firstPart);
  77. }
  78. if ($secondPart === '<!private>') {
  79. return $this->publicAndProtectedMethodsOfClass($firstPart);
  80. }
  81. if ($this->isUserDefinedMethod($firstPart, $secondPart)) {
  82. return CodeUnitCollection::fromList(CodeUnit::forClassMethod($firstPart, $secondPart));
  83. }
  84. }
  85. if ($this->isUserDefinedInterface($firstPart)) {
  86. return CodeUnitCollection::fromList(CodeUnit::forInterfaceMethod($firstPart, $secondPart));
  87. }
  88. if ($this->isUserDefinedTrait($firstPart)) {
  89. return CodeUnitCollection::fromList(CodeUnit::forTraitMethod($firstPart, $secondPart));
  90. }
  91. } else {
  92. if ($this->isUserDefinedClass($unit)) {
  93. $units = [CodeUnit::forClass($unit)];
  94. foreach ($this->reflectorForClass($unit)->getTraits() as $trait) {
  95. if (!$trait->isUserDefined()) {
  96. // @codeCoverageIgnoreStart
  97. continue;
  98. // @codeCoverageIgnoreEnd
  99. }
  100. $units[] = CodeUnit::forTrait($trait->getName());
  101. }
  102. return CodeUnitCollection::fromArray($units);
  103. }
  104. if ($this->isUserDefinedInterface($unit)) {
  105. return CodeUnitCollection::fromList(CodeUnit::forInterface($unit));
  106. }
  107. if ($this->isUserDefinedTrait($unit)) {
  108. return CodeUnitCollection::fromList(CodeUnit::forTrait($unit));
  109. }
  110. if ($this->isUserDefinedFunction($unit)) {
  111. return CodeUnitCollection::fromList(CodeUnit::forFunction($unit));
  112. }
  113. $unit = str_replace('<extended>', '', $unit);
  114. if ($this->isUserDefinedClass($unit)) {
  115. return $this->classAndParentClassesAndTraits($unit);
  116. }
  117. }
  118. throw new InvalidCodeUnitException(
  119. sprintf(
  120. '"%s" is not a valid code unit',
  121. $unit
  122. )
  123. );
  124. }
  125. /**
  126. * @psalm-param class-string $className
  127. *
  128. * @throws ReflectionException
  129. */
  130. private function publicMethodsOfClass(string $className): CodeUnitCollection
  131. {
  132. return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC);
  133. }
  134. /**
  135. * @psalm-param class-string $className
  136. *
  137. * @throws ReflectionException
  138. */
  139. private function publicAndProtectedMethodsOfClass(string $className): CodeUnitCollection
  140. {
  141. return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED);
  142. }
  143. /**
  144. * @psalm-param class-string $className
  145. *
  146. * @throws ReflectionException
  147. */
  148. private function publicAndPrivateMethodsOfClass(string $className): CodeUnitCollection
  149. {
  150. return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PRIVATE);
  151. }
  152. /**
  153. * @psalm-param class-string $className
  154. *
  155. * @throws ReflectionException
  156. */
  157. private function protectedMethodsOfClass(string $className): CodeUnitCollection
  158. {
  159. return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED);
  160. }
  161. /**
  162. * @psalm-param class-string $className
  163. *
  164. * @throws ReflectionException
  165. */
  166. private function protectedAndPrivateMethodsOfClass(string $className): CodeUnitCollection
  167. {
  168. return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PRIVATE);
  169. }
  170. /**
  171. * @psalm-param class-string $className
  172. *
  173. * @throws ReflectionException
  174. */
  175. private function privateMethodsOfClass(string $className): CodeUnitCollection
  176. {
  177. return $this->methodsOfClass($className, ReflectionMethod::IS_PRIVATE);
  178. }
  179. /**
  180. * @psalm-param class-string $className
  181. *
  182. * @throws ReflectionException
  183. */
  184. private function methodsOfClass(string $className, int $filter): CodeUnitCollection
  185. {
  186. $units = [];
  187. foreach ($this->reflectorForClass($className)->getMethods($filter) as $method) {
  188. if (!$method->isUserDefined()) {
  189. continue;
  190. }
  191. $units[] = CodeUnit::forClassMethod($className, $method->getName());
  192. }
  193. return CodeUnitCollection::fromArray($units);
  194. }
  195. /**
  196. * @psalm-param class-string $className
  197. *
  198. * @throws ReflectionException
  199. */
  200. private function classAndParentClassesAndTraits(string $className): CodeUnitCollection
  201. {
  202. $units = [CodeUnit::forClass($className)];
  203. $reflector = $this->reflectorForClass($className);
  204. foreach ($this->reflectorForClass($className)->getTraits() as $trait) {
  205. if (!$trait->isUserDefined()) {
  206. // @codeCoverageIgnoreStart
  207. continue;
  208. // @codeCoverageIgnoreEnd
  209. }
  210. $units[] = CodeUnit::forTrait($trait->getName());
  211. }
  212. while ($reflector = $reflector->getParentClass()) {
  213. if (!$reflector->isUserDefined()) {
  214. break;
  215. }
  216. $units[] = CodeUnit::forClass($reflector->getName());
  217. foreach ($reflector->getTraits() as $trait) {
  218. if (!$trait->isUserDefined()) {
  219. // @codeCoverageIgnoreStart
  220. continue;
  221. // @codeCoverageIgnoreEnd
  222. }
  223. $units[] = CodeUnit::forTrait($trait->getName());
  224. }
  225. }
  226. return CodeUnitCollection::fromArray($units);
  227. }
  228. /**
  229. * @psalm-param class-string $className
  230. *
  231. * @throws ReflectionException
  232. */
  233. private function reflectorForClass(string $className): ReflectionClass
  234. {
  235. try {
  236. return new ReflectionClass($className);
  237. // @codeCoverageIgnoreStart
  238. } catch (\ReflectionException $e) {
  239. throw new ReflectionException(
  240. $e->getMessage(),
  241. (int) $e->getCode(),
  242. $e
  243. );
  244. }
  245. // @codeCoverageIgnoreEnd
  246. }
  247. /**
  248. * @throws ReflectionException
  249. */
  250. private function isUserDefinedFunction(string $functionName): bool
  251. {
  252. if (!function_exists($functionName)) {
  253. return false;
  254. }
  255. try {
  256. return (new ReflectionFunction($functionName))->isUserDefined();
  257. // @codeCoverageIgnoreStart
  258. } catch (\ReflectionException $e) {
  259. throw new ReflectionException(
  260. $e->getMessage(),
  261. (int) $e->getCode(),
  262. $e
  263. );
  264. }
  265. // @codeCoverageIgnoreEnd
  266. }
  267. /**
  268. * @throws ReflectionException
  269. */
  270. private function isUserDefinedClass(string $className): bool
  271. {
  272. if (!class_exists($className)) {
  273. return false;
  274. }
  275. try {
  276. return (new ReflectionClass($className))->isUserDefined();
  277. // @codeCoverageIgnoreStart
  278. } catch (\ReflectionException $e) {
  279. throw new ReflectionException(
  280. $e->getMessage(),
  281. (int) $e->getCode(),
  282. $e
  283. );
  284. }
  285. // @codeCoverageIgnoreEnd
  286. }
  287. /**
  288. * @throws ReflectionException
  289. */
  290. private function isUserDefinedInterface(string $interfaceName): bool
  291. {
  292. if (!interface_exists($interfaceName)) {
  293. return false;
  294. }
  295. try {
  296. return (new ReflectionClass($interfaceName))->isUserDefined();
  297. // @codeCoverageIgnoreStart
  298. } catch (\ReflectionException $e) {
  299. throw new ReflectionException(
  300. $e->getMessage(),
  301. (int) $e->getCode(),
  302. $e
  303. );
  304. }
  305. // @codeCoverageIgnoreEnd
  306. }
  307. /**
  308. * @throws ReflectionException
  309. */
  310. private function isUserDefinedTrait(string $traitName): bool
  311. {
  312. if (!trait_exists($traitName)) {
  313. return false;
  314. }
  315. try {
  316. return (new ReflectionClass($traitName))->isUserDefined();
  317. // @codeCoverageIgnoreStart
  318. } catch (\ReflectionException $e) {
  319. throw new ReflectionException(
  320. $e->getMessage(),
  321. (int) $e->getCode(),
  322. $e
  323. );
  324. }
  325. // @codeCoverageIgnoreEnd
  326. }
  327. /**
  328. * @throws ReflectionException
  329. */
  330. private function isUserDefinedMethod(string $className, string $methodName): bool
  331. {
  332. if (!class_exists($className)) {
  333. // @codeCoverageIgnoreStart
  334. return false;
  335. // @codeCoverageIgnoreEnd
  336. }
  337. if (!method_exists($className, $methodName)) {
  338. // @codeCoverageIgnoreStart
  339. return false;
  340. // @codeCoverageIgnoreEnd
  341. }
  342. try {
  343. return (new ReflectionMethod($className, $methodName))->isUserDefined();
  344. // @codeCoverageIgnoreStart
  345. } catch (\ReflectionException $e) {
  346. throw new ReflectionException(
  347. $e->getMessage(),
  348. (int) $e->getCode(),
  349. $e
  350. );
  351. }
  352. // @codeCoverageIgnoreEnd
  353. }
  354. }