Counter.php 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of sebastian/lines-of-code.
  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\LinesOfCode;
  11. use function assert;
  12. use function file_get_contents;
  13. use function substr_count;
  14. use PhpParser\Error;
  15. use PhpParser\Node;
  16. use PhpParser\NodeTraverser;
  17. use PhpParser\ParserFactory;
  18. final class Counter
  19. {
  20. /**
  21. * @throws RuntimeException
  22. */
  23. public function countInSourceFile(string $sourceFile): LinesOfCode
  24. {
  25. return $this->countInSourceString(file_get_contents($sourceFile));
  26. }
  27. /**
  28. * @throws RuntimeException
  29. */
  30. public function countInSourceString(string $source): LinesOfCode
  31. {
  32. $linesOfCode = substr_count($source, "\n");
  33. if ($linesOfCode === 0 && !empty($source)) {
  34. $linesOfCode = 1;
  35. }
  36. assert($linesOfCode >= 0);
  37. try {
  38. $nodes = (new ParserFactory)->createForHostVersion()->parse($source);
  39. assert($nodes !== null);
  40. return $this->countInAbstractSyntaxTree($linesOfCode, $nodes);
  41. // @codeCoverageIgnoreStart
  42. } catch (Error $error) {
  43. throw new RuntimeException(
  44. $error->getMessage(),
  45. $error->getCode(),
  46. $error,
  47. );
  48. }
  49. // @codeCoverageIgnoreEnd
  50. }
  51. /**
  52. * @psalm-param non-negative-int $linesOfCode
  53. *
  54. * @param Node[] $nodes
  55. *
  56. * @throws RuntimeException
  57. */
  58. public function countInAbstractSyntaxTree(int $linesOfCode, array $nodes): LinesOfCode
  59. {
  60. $traverser = new NodeTraverser;
  61. $visitor = new LineCountingVisitor($linesOfCode);
  62. $traverser->addVisitor($visitor);
  63. try {
  64. /* @noinspection UnusedFunctionResultInspection */
  65. $traverser->traverse($nodes);
  66. // @codeCoverageIgnoreStart
  67. } catch (Error $error) {
  68. throw new RuntimeException(
  69. $error->getMessage(),
  70. $error->getCode(),
  71. $error,
  72. );
  73. }
  74. // @codeCoverageIgnoreEnd
  75. return $visitor->result();
  76. }
  77. }