/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php

https://gitlab.com/jjpa2018/dashboard · PHP · 323 lines · 218 code · 62 blank · 43 comment · 30 complexity · d33f7a7700934fb96fe6fcf80d1c00d2 MD5 · raw file

  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of phpunit/php-code-coverage.
  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\CodeCoverage\StaticAnalysis;
  11. use function implode;
  12. use function rtrim;
  13. use function trim;
  14. use PhpParser\Node;
  15. use PhpParser\Node\ComplexType;
  16. use PhpParser\Node\Identifier;
  17. use PhpParser\Node\IntersectionType;
  18. use PhpParser\Node\Name;
  19. use PhpParser\Node\NullableType;
  20. use PhpParser\Node\Stmt\Class_;
  21. use PhpParser\Node\Stmt\ClassMethod;
  22. use PhpParser\Node\Stmt\Enum_;
  23. use PhpParser\Node\Stmt\Function_;
  24. use PhpParser\Node\Stmt\Interface_;
  25. use PhpParser\Node\Stmt\Trait_;
  26. use PhpParser\Node\UnionType;
  27. use PhpParser\NodeTraverser;
  28. use PhpParser\NodeVisitorAbstract;
  29. use SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor;
  30. /**
  31. * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
  32. */
  33. final class CodeUnitFindingVisitor extends NodeVisitorAbstract
  34. {
  35. /**
  36. * @psalm-var array<string,array{name: string, namespacedName: string, namespace: string, startLine: int, endLine: int, methods: array<string,array{methodName: string, signature: string, visibility: string, startLine: int, endLine: int, ccn: int}>}>
  37. */
  38. private $classes = [];
  39. /**
  40. * @psalm-var array<string,array{name: string, namespacedName: string, namespace: string, startLine: int, endLine: int, methods: array<string,array{methodName: string, signature: string, visibility: string, startLine: int, endLine: int, ccn: int}>}>
  41. */
  42. private $traits = [];
  43. /**
  44. * @psalm-var array<string,array{name: string, namespacedName: string, namespace: string, signature: string, startLine: int, endLine: int, ccn: int}>
  45. */
  46. private $functions = [];
  47. public function enterNode(Node $node): void
  48. {
  49. if ($node instanceof Class_) {
  50. if ($node->isAnonymous()) {
  51. return;
  52. }
  53. $this->processClass($node);
  54. }
  55. if ($node instanceof Trait_) {
  56. $this->processTrait($node);
  57. }
  58. if (!$node instanceof ClassMethod && !$node instanceof Function_) {
  59. return;
  60. }
  61. if ($node instanceof ClassMethod) {
  62. $parentNode = $node->getAttribute('parent');
  63. if ($parentNode instanceof Class_ && $parentNode->isAnonymous()) {
  64. return;
  65. }
  66. $this->processMethod($node);
  67. return;
  68. }
  69. $this->processFunction($node);
  70. }
  71. /**
  72. * @psalm-return array<string,array{name: string, namespacedName: string, namespace: string, startLine: int, endLine: int, methods: array<string,array{methodName: string, signature: string, visibility: string, startLine: int, endLine: int, ccn: int}>}>
  73. */
  74. public function classes(): array
  75. {
  76. return $this->classes;
  77. }
  78. /**
  79. * @psalm-return array<string,array{name: string, namespacedName: string, namespace: string, startLine: int, endLine: int, methods: array<string,array{methodName: string, signature: string, visibility: string, startLine: int, endLine: int, ccn: int}>}>
  80. */
  81. public function traits(): array
  82. {
  83. return $this->traits;
  84. }
  85. /**
  86. * @psalm-return array<string,array{name: string, namespacedName: string, namespace: string, signature: string, startLine: int, endLine: int, ccn: int}>
  87. */
  88. public function functions(): array
  89. {
  90. return $this->functions;
  91. }
  92. /**
  93. * @psalm-param ClassMethod|Function_ $node
  94. */
  95. private function cyclomaticComplexity(Node $node): int
  96. {
  97. assert($node instanceof ClassMethod || $node instanceof Function_);
  98. $nodes = $node->getStmts();
  99. if ($nodes === null) {
  100. return 0;
  101. }
  102. $traverser = new NodeTraverser;
  103. $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor;
  104. $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor);
  105. /* @noinspection UnusedFunctionResultInspection */
  106. $traverser->traverse($nodes);
  107. return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity();
  108. }
  109. /**
  110. * @psalm-param ClassMethod|Function_ $node
  111. */
  112. private function signature(Node $node): string
  113. {
  114. assert($node instanceof ClassMethod || $node instanceof Function_);
  115. $signature = ($node->returnsByRef() ? '&' : '') . $node->name->toString() . '(';
  116. $parameters = [];
  117. foreach ($node->getParams() as $parameter) {
  118. assert(isset($parameter->var->name));
  119. $parameterAsString = '';
  120. if ($parameter->type !== null) {
  121. $parameterAsString = $this->type($parameter->type) . ' ';
  122. }
  123. $parameterAsString .= '$' . $parameter->var->name;
  124. /* @todo Handle default values */
  125. $parameters[] = $parameterAsString;
  126. }
  127. $signature .= implode(', ', $parameters) . ')';
  128. $returnType = $node->getReturnType();
  129. if ($returnType !== null) {
  130. $signature .= ': ' . $this->type($returnType);
  131. }
  132. return $signature;
  133. }
  134. /**
  135. * @psalm-param Identifier|Name|ComplexType $type
  136. */
  137. private function type(Node $type): string
  138. {
  139. assert($type instanceof Identifier || $type instanceof Name || $type instanceof ComplexType);
  140. if ($type instanceof NullableType) {
  141. return '?' . $type->type;
  142. }
  143. if ($type instanceof UnionType || $type instanceof IntersectionType) {
  144. return $this->unionOrIntersectionAsString($type);
  145. }
  146. return $type->toString();
  147. }
  148. private function visibility(ClassMethod $node): string
  149. {
  150. if ($node->isPrivate()) {
  151. return 'private';
  152. }
  153. if ($node->isProtected()) {
  154. return 'protected';
  155. }
  156. return 'public';
  157. }
  158. private function processClass(Class_ $node): void
  159. {
  160. $name = $node->name->toString();
  161. $namespacedName = $node->namespacedName->toString();
  162. $this->classes[$namespacedName] = [
  163. 'name' => $name,
  164. 'namespacedName' => (string) $namespacedName,
  165. 'namespace' => $this->namespace($namespacedName, $name),
  166. 'startLine' => $node->getStartLine(),
  167. 'endLine' => $node->getEndLine(),
  168. 'methods' => [],
  169. ];
  170. }
  171. private function processTrait(Trait_ $node): void
  172. {
  173. $name = $node->name->toString();
  174. $namespacedName = $node->namespacedName->toString();
  175. $this->traits[$namespacedName] = [
  176. 'name' => $name,
  177. 'namespacedName' => (string) $namespacedName,
  178. 'namespace' => $this->namespace($namespacedName, $name),
  179. 'startLine' => $node->getStartLine(),
  180. 'endLine' => $node->getEndLine(),
  181. 'methods' => [],
  182. ];
  183. }
  184. private function processMethod(ClassMethod $node): void
  185. {
  186. $parentNode = $node->getAttribute('parent');
  187. if ($parentNode instanceof Interface_) {
  188. return;
  189. }
  190. assert($parentNode instanceof Class_ || $parentNode instanceof Trait_ || $parentNode instanceof Enum_);
  191. assert(isset($parentNode->name));
  192. assert(isset($parentNode->namespacedName));
  193. assert($parentNode->namespacedName instanceof Name);
  194. $parentName = $parentNode->name->toString();
  195. $parentNamespacedName = $parentNode->namespacedName->toString();
  196. if ($parentNode instanceof Class_) {
  197. $storage = &$this->classes;
  198. } else {
  199. $storage = &$this->traits;
  200. }
  201. if (!isset($storage[$parentNamespacedName])) {
  202. $storage[$parentNamespacedName] = [
  203. 'name' => $parentName,
  204. 'namespacedName' => $parentNamespacedName,
  205. 'namespace' => $this->namespace($parentNamespacedName, $parentName),
  206. 'startLine' => $parentNode->getStartLine(),
  207. 'endLine' => $parentNode->getEndLine(),
  208. 'methods' => [],
  209. ];
  210. }
  211. $storage[$parentNamespacedName]['methods'][$node->name->toString()] = [
  212. 'methodName' => $node->name->toString(),
  213. 'signature' => $this->signature($node),
  214. 'visibility' => $this->visibility($node),
  215. 'startLine' => $node->getStartLine(),
  216. 'endLine' => $node->getEndLine(),
  217. 'ccn' => $this->cyclomaticComplexity($node),
  218. ];
  219. }
  220. private function processFunction(Function_ $node): void
  221. {
  222. assert(isset($node->name));
  223. assert(isset($node->namespacedName));
  224. assert($node->namespacedName instanceof Name);
  225. $name = $node->name->toString();
  226. $namespacedName = $node->namespacedName->toString();
  227. $this->functions[$namespacedName] = [
  228. 'name' => $name,
  229. 'namespacedName' => $namespacedName,
  230. 'namespace' => $this->namespace($namespacedName, $name),
  231. 'signature' => $this->signature($node),
  232. 'startLine' => $node->getStartLine(),
  233. 'endLine' => $node->getEndLine(),
  234. 'ccn' => $this->cyclomaticComplexity($node),
  235. ];
  236. }
  237. private function namespace(string $namespacedName, string $name): string
  238. {
  239. return trim(rtrim($namespacedName, $name), '\\');
  240. }
  241. /**
  242. * @psalm-param UnionType|IntersectionType $type
  243. */
  244. private function unionOrIntersectionAsString(ComplexType $type): string
  245. {
  246. if ($type instanceof UnionType) {
  247. $separator = '|';
  248. } else {
  249. $separator = '&';
  250. }
  251. $types = [];
  252. foreach ($type->types as $_type) {
  253. if ($_type instanceof Name) {
  254. $types[] = $_type->toCodeString();
  255. } else {
  256. $types[] = $_type->toString();
  257. }
  258. }
  259. return implode($separator, $types);
  260. }
  261. }