summaryrefslogtreecommitdiff
path: root/vendor/phpunit/php-code-coverage/src/StaticAnalysis
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/phpunit/php-code-coverage/src/StaticAnalysis')
-rw-r--r--vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php30
-rw-r--r--vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php183
-rw-r--r--vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php323
-rw-r--r--vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php273
-rw-r--r--vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php31
-rw-r--r--vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php106
-rw-r--r--vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php251
7 files changed, 1197 insertions, 0 deletions
diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php
new file mode 100644
index 000000000..00a6d8df1
--- /dev/null
+++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php
@@ -0,0 +1,30 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of phpunit/php-code-coverage.
+ *
+ * (c) Sebastian Bergmann <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
+
+use SebastianBergmann\CodeCoverage\Filter;
+
+final class CacheWarmer
+{
+ public function warmCache(string $cacheDirectory, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode, Filter $filter): void
+ {
+ $analyser = new CachingFileAnalyser(
+ $cacheDirectory,
+ new ParsingFileAnalyser(
+ $useAnnotationsForIgnoringCode,
+ $ignoreDeprecatedCode
+ )
+ );
+
+ foreach ($filter->files() as $file) {
+ $analyser->process($file);
+ }
+ }
+}
diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php
new file mode 100644
index 000000000..cdaad5613
--- /dev/null
+++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php
@@ -0,0 +1,183 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of phpunit/php-code-coverage.
+ *
+ * (c) Sebastian Bergmann <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
+
+use function assert;
+use function crc32;
+use function file_get_contents;
+use function file_put_contents;
+use function is_file;
+use function serialize;
+use GlobIterator;
+use SebastianBergmann\CodeCoverage\Util\Filesystem;
+use SplFileInfo;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
+ */
+final class CachingFileAnalyser implements FileAnalyser
+{
+ /**
+ * @var ?string
+ */
+ private static $cacheVersion;
+
+ /**
+ * @var FileAnalyser
+ */
+ private $analyser;
+
+ /**
+ * @var array
+ */
+ private $cache = [];
+
+ /**
+ * @var string
+ */
+ private $directory;
+
+ public function __construct(string $directory, FileAnalyser $analyser)
+ {
+ Filesystem::createDirectory($directory);
+
+ $this->analyser = $analyser;
+ $this->directory = $directory;
+
+ if (self::$cacheVersion === null) {
+ $this->calculateCacheVersion();
+ }
+ }
+
+ public function classesIn(string $filename): array
+ {
+ if (!isset($this->cache[$filename])) {
+ $this->process($filename);
+ }
+
+ return $this->cache[$filename]['classesIn'];
+ }
+
+ public function traitsIn(string $filename): array
+ {
+ if (!isset($this->cache[$filename])) {
+ $this->process($filename);
+ }
+
+ return $this->cache[$filename]['traitsIn'];
+ }
+
+ public function functionsIn(string $filename): array
+ {
+ if (!isset($this->cache[$filename])) {
+ $this->process($filename);
+ }
+
+ return $this->cache[$filename]['functionsIn'];
+ }
+
+ /**
+ * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int}
+ */
+ public function linesOfCodeFor(string $filename): array
+ {
+ if (!isset($this->cache[$filename])) {
+ $this->process($filename);
+ }
+
+ return $this->cache[$filename]['linesOfCodeFor'];
+ }
+
+ public function executableLinesIn(string $filename): array
+ {
+ if (!isset($this->cache[$filename])) {
+ $this->process($filename);
+ }
+
+ return $this->cache[$filename]['executableLinesIn'];
+ }
+
+ public function ignoredLinesFor(string $filename): array
+ {
+ if (!isset($this->cache[$filename])) {
+ $this->process($filename);
+ }
+
+ return $this->cache[$filename]['ignoredLinesFor'];
+ }
+
+ public function process(string $filename): void
+ {
+ $cache = $this->read($filename);
+
+ if ($cache !== false) {
+ $this->cache[$filename] = $cache;
+
+ return;
+ }
+
+ $this->cache[$filename] = [
+ 'classesIn' => $this->analyser->classesIn($filename),
+ 'traitsIn' => $this->analyser->traitsIn($filename),
+ 'functionsIn' => $this->analyser->functionsIn($filename),
+ 'linesOfCodeFor' => $this->analyser->linesOfCodeFor($filename),
+ 'ignoredLinesFor' => $this->analyser->ignoredLinesFor($filename),
+ 'executableLinesIn' => $this->analyser->executableLinesIn($filename),
+ ];
+
+ $this->write($filename, $this->cache[$filename]);
+ }
+
+ /**
+ * @return mixed
+ */
+ private function read(string $filename)
+ {
+ $cacheFile = $this->cacheFile($filename);
+
+ if (!is_file($cacheFile)) {
+ return false;
+ }
+
+ return unserialize(
+ file_get_contents($cacheFile),
+ ['allowed_classes' => false]
+ );
+ }
+
+ /**
+ * @param mixed $data
+ */
+ private function write(string $filename, $data): void
+ {
+ file_put_contents(
+ $this->cacheFile($filename),
+ serialize($data)
+ );
+ }
+
+ private function cacheFile(string $filename): string
+ {
+ return $this->directory . DIRECTORY_SEPARATOR . hash('sha256', $filename . crc32(file_get_contents($filename)) . self::$cacheVersion);
+ }
+
+ private function calculateCacheVersion(): void
+ {
+ $buffer = '';
+
+ foreach (new GlobIterator(__DIR__ . '/*.php') as $file) {
+ assert($file instanceof SplFileInfo);
+
+ $buffer .= file_get_contents($file->getPathname());
+ }
+
+ self::$cacheVersion = (string) crc32($buffer);
+ }
+}
diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php
new file mode 100644
index 000000000..8d357b202
--- /dev/null
+++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php
@@ -0,0 +1,323 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of phpunit/php-code-coverage.
+ *
+ * (c) Sebastian Bergmann <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
+
+use function implode;
+use function rtrim;
+use function trim;
+use PhpParser\Node;
+use PhpParser\Node\ComplexType;
+use PhpParser\Node\Identifier;
+use PhpParser\Node\IntersectionType;
+use PhpParser\Node\Name;
+use PhpParser\Node\NullableType;
+use PhpParser\Node\Stmt\Class_;
+use PhpParser\Node\Stmt\ClassMethod;
+use PhpParser\Node\Stmt\Enum_;
+use PhpParser\Node\Stmt\Function_;
+use PhpParser\Node\Stmt\Interface_;
+use PhpParser\Node\Stmt\Trait_;
+use PhpParser\Node\UnionType;
+use PhpParser\NodeTraverser;
+use PhpParser\NodeVisitorAbstract;
+use SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
+ */
+final class CodeUnitFindingVisitor extends NodeVisitorAbstract
+{
+ /**
+ * @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}>}>
+ */
+ private $classes = [];
+
+ /**
+ * @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}>}>
+ */
+ private $traits = [];
+
+ /**
+ * @psalm-var array<string,array{name: string, namespacedName: string, namespace: string, signature: string, startLine: int, endLine: int, ccn: int}>
+ */
+ private $functions = [];
+
+ public function enterNode(Node $node): void
+ {
+ if ($node instanceof Class_) {
+ if ($node->isAnonymous()) {
+ return;
+ }
+
+ $this->processClass($node);
+ }
+
+ if ($node instanceof Trait_) {
+ $this->processTrait($node);
+ }
+
+ if (!$node instanceof ClassMethod && !$node instanceof Function_) {
+ return;
+ }
+
+ if ($node instanceof ClassMethod) {
+ $parentNode = $node->getAttribute('parent');
+
+ if ($parentNode instanceof Class_ && $parentNode->isAnonymous()) {
+ return;
+ }
+
+ $this->processMethod($node);
+
+ return;
+ }
+
+ $this->processFunction($node);
+ }
+
+ /**
+ * @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}>}>
+ */
+ public function classes(): array
+ {
+ return $this->classes;
+ }
+
+ /**
+ * @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}>}>
+ */
+ public function traits(): array
+ {
+ return $this->traits;
+ }
+
+ /**
+ * @psalm-return array<string,array{name: string, namespacedName: string, namespace: string, signature: string, startLine: int, endLine: int, ccn: int}>
+ */
+ public function functions(): array
+ {
+ return $this->functions;
+ }
+
+ /**
+ * @psalm-param ClassMethod|Function_ $node
+ */
+ private function cyclomaticComplexity(Node $node): int
+ {
+ assert($node instanceof ClassMethod || $node instanceof Function_);
+
+ $nodes = $node->getStmts();
+
+ if ($nodes === null) {
+ return 0;
+ }
+
+ $traverser = new NodeTraverser;
+
+ $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor;
+
+ $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor);
+
+ /* @noinspection UnusedFunctionResultInspection */
+ $traverser->traverse($nodes);
+
+ return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity();
+ }
+
+ /**
+ * @psalm-param ClassMethod|Function_ $node
+ */
+ private function signature(Node $node): string
+ {
+ assert($node instanceof ClassMethod || $node instanceof Function_);
+
+ $signature = ($node->returnsByRef() ? '&' : '') . $node->name->toString() . '(';
+ $parameters = [];
+
+ foreach ($node->getParams() as $parameter) {
+ assert(isset($parameter->var->name));
+
+ $parameterAsString = '';
+
+ if ($parameter->type !== null) {
+ $parameterAsString = $this->type($parameter->type) . ' ';
+ }
+
+ $parameterAsString .= '$' . $parameter->var->name;
+
+ /* @todo Handle default values */
+
+ $parameters[] = $parameterAsString;
+ }
+
+ $signature .= implode(', ', $parameters) . ')';
+
+ $returnType = $node->getReturnType();
+
+ if ($returnType !== null) {
+ $signature .= ': ' . $this->type($returnType);
+ }
+
+ return $signature;
+ }
+
+ /**
+ * @psalm-param Identifier|Name|ComplexType $type
+ */
+ private function type(Node $type): string
+ {
+ assert($type instanceof Identifier || $type instanceof Name || $type instanceof ComplexType);
+
+ if ($type instanceof NullableType) {
+ return '?' . $type->type;
+ }
+
+ if ($type instanceof UnionType || $type instanceof IntersectionType) {
+ return $this->unionOrIntersectionAsString($type);
+ }
+
+ return $type->toString();
+ }
+
+ private function visibility(ClassMethod $node): string
+ {
+ if ($node->isPrivate()) {
+ return 'private';
+ }
+
+ if ($node->isProtected()) {
+ return 'protected';
+ }
+
+ return 'public';
+ }
+
+ private function processClass(Class_ $node): void
+ {
+ $name = $node->name->toString();
+ $namespacedName = $node->namespacedName->toString();
+
+ $this->classes[$namespacedName] = [
+ 'name' => $name,
+ 'namespacedName' => $namespacedName,
+ 'namespace' => $this->namespace($namespacedName, $name),
+ 'startLine' => $node->getStartLine(),
+ 'endLine' => $node->getEndLine(),
+ 'methods' => [],
+ ];
+ }
+
+ private function processTrait(Trait_ $node): void
+ {
+ $name = $node->name->toString();
+ $namespacedName = $node->namespacedName->toString();
+
+ $this->traits[$namespacedName] = [
+ 'name' => $name,
+ 'namespacedName' => $namespacedName,
+ 'namespace' => $this->namespace($namespacedName, $name),
+ 'startLine' => $node->getStartLine(),
+ 'endLine' => $node->getEndLine(),
+ 'methods' => [],
+ ];
+ }
+
+ private function processMethod(ClassMethod $node): void
+ {
+ $parentNode = $node->getAttribute('parent');
+
+ if ($parentNode instanceof Interface_) {
+ return;
+ }
+
+ assert($parentNode instanceof Class_ || $parentNode instanceof Trait_ || $parentNode instanceof Enum_);
+ assert(isset($parentNode->name));
+ assert(isset($parentNode->namespacedName));
+ assert($parentNode->namespacedName instanceof Name);
+
+ $parentName = $parentNode->name->toString();
+ $parentNamespacedName = $parentNode->namespacedName->toString();
+
+ if ($parentNode instanceof Class_) {
+ $storage = &$this->classes;
+ } else {
+ $storage = &$this->traits;
+ }
+
+ if (!isset($storage[$parentNamespacedName])) {
+ $storage[$parentNamespacedName] = [
+ 'name' => $parentName,
+ 'namespacedName' => $parentNamespacedName,
+ 'namespace' => $this->namespace($parentNamespacedName, $parentName),
+ 'startLine' => $parentNode->getStartLine(),
+ 'endLine' => $parentNode->getEndLine(),
+ 'methods' => [],
+ ];
+ }
+
+ $storage[$parentNamespacedName]['methods'][$node->name->toString()] = [
+ 'methodName' => $node->name->toString(),
+ 'signature' => $this->signature($node),
+ 'visibility' => $this->visibility($node),
+ 'startLine' => $node->getStartLine(),
+ 'endLine' => $node->getEndLine(),
+ 'ccn' => $this->cyclomaticComplexity($node),
+ ];
+ }
+
+ private function processFunction(Function_ $node): void
+ {
+ assert(isset($node->name));
+ assert(isset($node->namespacedName));
+ assert($node->namespacedName instanceof Name);
+
+ $name = $node->name->toString();
+ $namespacedName = $node->namespacedName->toString();
+
+ $this->functions[$namespacedName] = [
+ 'name' => $name,
+ 'namespacedName' => $namespacedName,
+ 'namespace' => $this->namespace($namespacedName, $name),
+ 'signature' => $this->signature($node),
+ 'startLine' => $node->getStartLine(),
+ 'endLine' => $node->getEndLine(),
+ 'ccn' => $this->cyclomaticComplexity($node),
+ ];
+ }
+
+ private function namespace(string $namespacedName, string $name): string
+ {
+ return trim(rtrim($namespacedName, $name), '\\');
+ }
+
+ /**
+ * @psalm-param UnionType|IntersectionType $type
+ */
+ private function unionOrIntersectionAsString(ComplexType $type): string
+ {
+ if ($type instanceof UnionType) {
+ $separator = '|';
+ } else {
+ $separator = '&';
+ }
+
+ $types = [];
+
+ foreach ($type->types as $_type) {
+ if ($_type instanceof Name) {
+ $types[] = $_type->toCodeString();
+ } else {
+ $types[] = $_type->toString();
+ }
+ }
+
+ return implode($separator, $types);
+ }
+}
diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php
new file mode 100644
index 000000000..ae0b08ae7
--- /dev/null
+++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php
@@ -0,0 +1,273 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of phpunit/php-code-coverage.
+ *
+ * (c) Sebastian Bergmann <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
+
+use PhpParser\Node;
+use PhpParser\Node\Expr\Array_;
+use PhpParser\Node\Expr\ArrayDimFetch;
+use PhpParser\Node\Expr\ArrayItem;
+use PhpParser\Node\Expr\Assign;
+use PhpParser\Node\Expr\BinaryOp;
+use PhpParser\Node\Expr\CallLike;
+use PhpParser\Node\Expr\Cast;
+use PhpParser\Node\Expr\Closure;
+use PhpParser\Node\Expr\Match_;
+use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Expr\NullsafePropertyFetch;
+use PhpParser\Node\Expr\PropertyFetch;
+use PhpParser\Node\Expr\StaticPropertyFetch;
+use PhpParser\Node\Expr\Ternary;
+use PhpParser\Node\MatchArm;
+use PhpParser\Node\Scalar\Encapsed;
+use PhpParser\Node\Stmt\Break_;
+use PhpParser\Node\Stmt\Case_;
+use PhpParser\Node\Stmt\Catch_;
+use PhpParser\Node\Stmt\Class_;
+use PhpParser\Node\Stmt\ClassMethod;
+use PhpParser\Node\Stmt\Continue_;
+use PhpParser\Node\Stmt\Do_;
+use PhpParser\Node\Stmt\Echo_;
+use PhpParser\Node\Stmt\Else_;
+use PhpParser\Node\Stmt\ElseIf_;
+use PhpParser\Node\Stmt\Expression;
+use PhpParser\Node\Stmt\Finally_;
+use PhpParser\Node\Stmt\For_;
+use PhpParser\Node\Stmt\Foreach_;
+use PhpParser\Node\Stmt\Goto_;
+use PhpParser\Node\Stmt\If_;
+use PhpParser\Node\Stmt\Property;
+use PhpParser\Node\Stmt\Return_;
+use PhpParser\Node\Stmt\Switch_;
+use PhpParser\Node\Stmt\Throw_;
+use PhpParser\Node\Stmt\TryCatch;
+use PhpParser\Node\Stmt\Unset_;
+use PhpParser\Node\Stmt\While_;
+use PhpParser\NodeVisitorAbstract;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
+ */
+final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
+{
+ /**
+ * @psalm-var array<int, int>
+ */
+ private $executableLines = [];
+
+ /**
+ * @psalm-var array<int, int>
+ */
+ private $propertyLines = [];
+
+ /**
+ * @psalm-var array<int, Return_>
+ */
+ private $returns = [];
+
+ public function enterNode(Node $node): void
+ {
+ $this->savePropertyLines($node);
+
+ if (!$this->isExecutable($node)) {
+ return;
+ }
+
+ foreach ($this->getLines($node) as $line) {
+ if (isset($this->propertyLines[$line])) {
+ return;
+ }
+
+ $this->executableLines[$line] = $line;
+ }
+ }
+
+ /**
+ * @psalm-return array<int, int>
+ */
+ public function executableLines(): array
+ {
+ $this->computeReturns();
+
+ sort($this->executableLines);
+
+ return $this->executableLines;
+ }
+
+ private function savePropertyLines(Node $node): void
+ {
+ if (!$node instanceof Property && !$node instanceof Node\Stmt\ClassConst) {
+ return;
+ }
+
+ foreach (range($node->getStartLine(), $node->getEndLine()) as $index) {
+ $this->propertyLines[$index] = $index;
+ }
+ }
+
+ private function computeReturns(): void
+ {
+ foreach ($this->returns as $return) {
+ foreach (range($return->getStartLine(), $return->getEndLine()) as $loc) {
+ if (isset($this->executableLines[$loc])) {
+ continue 2;
+ }
+ }
+
+ $line = $return->getEndLine();
+
+ if ($return->expr !== null) {
+ $line = $return->expr->getStartLine();
+ }
+
+ $this->executableLines[$line] = $line;
+ }
+ }
+
+ /**
+ * @return int[]
+ */
+ private function getLines(Node $node): array
+ {
+ if ($node instanceof Cast ||
+ $node instanceof PropertyFetch ||
+ $node instanceof NullsafePropertyFetch ||
+ $node instanceof StaticPropertyFetch) {
+ return [$node->getEndLine()];
+ }
+
+ if ($node instanceof ArrayDimFetch) {
+ if (null === $node->dim) {
+ return [];
+ }
+
+ return [$node->dim->getStartLine()];
+ }
+
+ if ($node instanceof Array_) {
+ $startLine = $node->getStartLine();
+
+ if (isset($this->executableLines[$startLine])) {
+ return [];
+ }
+
+ if ([] === $node->items) {
+ return [$node->getEndLine()];
+ }
+
+ if ($node->items[0] instanceof ArrayItem) {
+ return [$node->items[0]->getStartLine()];
+ }
+ }
+
+ if ($node instanceof ClassMethod) {
+ if ($node->name->name !== '__construct') {
+ return [];
+ }
+
+ $existsAPromotedProperty = false;
+
+ foreach ($node->getParams() as $param) {
+ if (0 !== ($param->flags & Class_::VISIBILITY_MODIFIER_MASK)) {
+ $existsAPromotedProperty = true;
+
+ break;
+ }
+ }
+
+ if ($existsAPromotedProperty) {
+ // Only the line with `function` keyword should be listed here
+ // but `nikic/php-parser` doesn't provide a way to fetch it
+ return range($node->getStartLine(), $node->name->getEndLine());
+ }
+
+ return [];
+ }
+
+ if ($node instanceof MethodCall) {
+ return [$node->name->getStartLine()];
+ }
+
+ if ($node instanceof Ternary) {
+ $lines = [$node->cond->getStartLine()];
+
+ if (null !== $node->if) {
+ $lines[] = $node->if->getStartLine();
+ }
+
+ $lines[] = $node->else->getStartLine();
+
+ return $lines;
+ }
+
+ if ($node instanceof Match_) {
+ return [$node->cond->getStartLine()];
+ }
+
+ if ($node instanceof MatchArm) {
+ return [$node->body->getStartLine()];
+ }
+
+ if ($node instanceof Expression && (
+ $node->expr instanceof Cast ||
+ $node->expr instanceof Match_ ||
+ $node->expr instanceof MethodCall
+ )) {
+ return [];
+ }
+
+ if ($node instanceof Return_) {
+ $this->returns[] = $node;
+
+ return [];
+ }
+
+ return [$node->getStartLine()];
+ }
+
+ private function isExecutable(Node $node): bool
+ {
+ return $node instanceof Assign ||
+ $node instanceof ArrayDimFetch ||
+ $node instanceof Array_ ||
+ $node instanceof BinaryOp ||
+ $node instanceof Break_ ||
+ $node instanceof CallLike ||
+ $node instanceof Case_ ||
+ $node instanceof Cast ||
+ $node instanceof Catch_ ||
+ $node instanceof ClassMethod ||
+ $node instanceof Closure ||
+ $node instanceof Continue_ ||
+ $node instanceof Do_ ||
+ $node instanceof Echo_ ||
+ $node instanceof ElseIf_ ||
+ $node instanceof Else_ ||
+ $node instanceof Encapsed ||
+ $node instanceof Expression ||
+ $node instanceof Finally_ ||
+ $node instanceof For_ ||
+ $node instanceof Foreach_ ||
+ $node instanceof Goto_ ||
+ $node instanceof If_ ||
+ $node instanceof Match_ ||
+ $node instanceof MatchArm ||
+ $node instanceof MethodCall ||
+ $node instanceof NullsafePropertyFetch ||
+ $node instanceof PropertyFetch ||
+ $node instanceof Return_ ||
+ $node instanceof StaticPropertyFetch ||
+ $node instanceof Switch_ ||
+ $node instanceof Ternary ||
+ $node instanceof Throw_ ||
+ $node instanceof TryCatch ||
+ $node instanceof Unset_ ||
+ $node instanceof While_;
+ }
+}
diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php
new file mode 100644
index 000000000..3dbcf68f6
--- /dev/null
+++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php
@@ -0,0 +1,31 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of phpunit/php-code-coverage.
+ *
+ * (c) Sebastian Bergmann <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
+ */
+interface FileAnalyser
+{
+ public function classesIn(string $filename): array;
+
+ public function traitsIn(string $filename): array;
+
+ public function functionsIn(string $filename): array;
+
+ /**
+ * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int}
+ */
+ public function linesOfCodeFor(string $filename): array;
+
+ public function executableLinesIn(string $filename): array;
+
+ public function ignoredLinesFor(string $filename): array;
+}
diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php
new file mode 100644
index 000000000..ebb879e84
--- /dev/null
+++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php
@@ -0,0 +1,106 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of phpunit/php-code-coverage.
+ *
+ * (c) Sebastian Bergmann <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
+
+use function array_merge;
+use function range;
+use function strpos;
+use PhpParser\Node;
+use PhpParser\Node\Stmt\Class_;
+use PhpParser\Node\Stmt\ClassMethod;
+use PhpParser\Node\Stmt\Function_;
+use PhpParser\Node\Stmt\Interface_;
+use PhpParser\Node\Stmt\Trait_;
+use PhpParser\NodeVisitorAbstract;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
+ */
+final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract
+{
+ /**
+ * @psalm-var list<int>
+ */
+ private $ignoredLines = [];
+
+ /**
+ * @var bool
+ */
+ private $useAnnotationsForIgnoringCode;
+
+ /**
+ * @var bool
+ */
+ private $ignoreDeprecated;
+
+ public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecated)
+ {
+ $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode;
+ $this->ignoreDeprecated = $ignoreDeprecated;
+ }
+
+ public function enterNode(Node $node): void
+ {
+ if (!$node instanceof Class_ &&
+ !$node instanceof Trait_ &&
+ !$node instanceof Interface_ &&
+ !$node instanceof ClassMethod &&
+ !$node instanceof Function_) {
+ return;
+ }
+
+ if ($node instanceof Class_ && $node->isAnonymous()) {
+ return;
+ }
+
+ // Workaround for https://bugs.xdebug.org/view.php?id=1798
+ if ($node instanceof Class_ ||
+ $node instanceof Trait_ ||
+ $node instanceof Interface_) {
+ $this->ignoredLines[] = $node->getStartLine();
+ }
+
+ if (!$this->useAnnotationsForIgnoringCode) {
+ return;
+ }
+
+ if ($node instanceof Interface_) {
+ return;
+ }
+
+ $docComment = $node->getDocComment();
+
+ if ($docComment === null) {
+ return;
+ }
+
+ if (strpos($docComment->getText(), '@codeCoverageIgnore') !== false) {
+ $this->ignoredLines = array_merge(
+ $this->ignoredLines,
+ range($node->getStartLine(), $node->getEndLine())
+ );
+ }
+
+ if ($this->ignoreDeprecated && strpos($docComment->getText(), '@deprecated') !== false) {
+ $this->ignoredLines = array_merge(
+ $this->ignoredLines,
+ range($node->getStartLine(), $node->getEndLine())
+ );
+ }
+ }
+
+ /**
+ * @psalm-return list<int>
+ */
+ public function ignoredLines(): array
+ {
+ return $this->ignoredLines;
+ }
+}
diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php
new file mode 100644
index 000000000..8edf973e2
--- /dev/null
+++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php
@@ -0,0 +1,251 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of phpunit/php-code-coverage.
+ *
+ * (c) Sebastian Bergmann <[email protected]>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
+
+use function array_unique;
+use function assert;
+use function file_get_contents;
+use function is_array;
+use function max;
+use function sprintf;
+use function substr_count;
+use function token_get_all;
+use function trim;
+use PhpParser\Error;
+use PhpParser\Lexer;
+use PhpParser\NodeTraverser;
+use PhpParser\NodeVisitor\NameResolver;
+use PhpParser\NodeVisitor\ParentConnectingVisitor;
+use PhpParser\ParserFactory;
+use SebastianBergmann\CodeCoverage\ParserException;
+use SebastianBergmann\LinesOfCode\LineCountingVisitor;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
+ */
+final class ParsingFileAnalyser implements FileAnalyser
+{
+ /**
+ * @var array
+ */
+ private $classes = [];
+
+ /**
+ * @var array
+ */
+ private $traits = [];
+
+ /**
+ * @var array
+ */
+ private $functions = [];
+
+ /**
+ * @var array<string,array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int}>
+ */
+ private $linesOfCode = [];
+
+ /**
+ * @var array
+ */
+ private $ignoredLines = [];
+
+ /**
+ * @var array
+ */
+ private $executableLines = [];
+
+ /**
+ * @var bool
+ */
+ private $useAnnotationsForIgnoringCode;
+
+ /**
+ * @var bool
+ */
+ private $ignoreDeprecatedCode;
+
+ public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode)
+ {
+ $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode;
+ $this->ignoreDeprecatedCode = $ignoreDeprecatedCode;
+ }
+
+ public function classesIn(string $filename): array
+ {
+ $this->analyse($filename);
+
+ return $this->classes[$filename];
+ }
+
+ public function traitsIn(string $filename): array
+ {
+ $this->analyse($filename);
+
+ return $this->traits[$filename];
+ }
+
+ public function functionsIn(string $filename): array
+ {
+ $this->analyse($filename);
+
+ return $this->functions[$filename];
+ }
+
+ /**
+ * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int}
+ */
+ public function linesOfCodeFor(string $filename): array
+ {
+ $this->analyse($filename);
+
+ return $this->linesOfCode[$filename];
+ }
+
+ public function executableLinesIn(string $filename): array
+ {
+ $this->analyse($filename);
+
+ return $this->executableLines[$filename];
+ }
+
+ public function ignoredLinesFor(string $filename): array
+ {
+ $this->analyse($filename);
+
+ return $this->ignoredLines[$filename];
+ }
+
+ /**
+ * @throws ParserException
+ */
+ private function analyse(string $filename): void
+ {
+ if (isset($this->classes[$filename])) {
+ return;
+ }
+
+ $source = file_get_contents($filename);
+ $linesOfCode = max(substr_count($source, "\n") + 1, substr_count($source, "\r") + 1);
+
+ if ($linesOfCode === 0 && !empty($source)) {
+ $linesOfCode = 1;
+ }
+
+ $parser = (new ParserFactory)->create(
+ ParserFactory::PREFER_PHP7,
+ new Lexer
+ );
+
+ try {
+ $nodes = $parser->parse($source);
+
+ assert($nodes !== null);
+
+ $traverser = new NodeTraverser;
+ $codeUnitFindingVisitor = new CodeUnitFindingVisitor;
+ $lineCountingVisitor = new LineCountingVisitor($linesOfCode);
+ $ignoredLinesFindingVisitor = new IgnoredLinesFindingVisitor($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode);
+ $executableLinesFindingVisitor = new ExecutableLinesFindingVisitor;
+
+ $traverser->addVisitor(new NameResolver);
+ $traverser->addVisitor(new ParentConnectingVisitor);
+ $traverser->addVisitor($codeUnitFindingVisitor);
+ $traverser->addVisitor($lineCountingVisitor);
+ $traverser->addVisitor($ignoredLinesFindingVisitor);
+ $traverser->addVisitor($executableLinesFindingVisitor);
+
+ /* @noinspection UnusedFunctionResultInspection */
+ $traverser->traverse($nodes);
+ // @codeCoverageIgnoreStart
+ } catch (Error $error) {
+ throw new ParserException(
+ sprintf(
+ 'Cannot parse %s: %s',
+ $filename,
+ $error->getMessage()
+ ),
+ (int) $error->getCode(),
+ $error
+ );
+ }
+ // @codeCoverageIgnoreEnd
+
+ $this->classes[$filename] = $codeUnitFindingVisitor->classes();
+ $this->traits[$filename] = $codeUnitFindingVisitor->traits();
+ $this->functions[$filename] = $codeUnitFindingVisitor->functions();
+ $this->executableLines[$filename] = $executableLinesFindingVisitor->executableLines();
+ $this->ignoredLines[$filename] = [];
+
+ $this->findLinesIgnoredByLineBasedAnnotations($filename, $source, $this->useAnnotationsForIgnoringCode);
+
+ $this->ignoredLines[$filename] = array_unique(
+ array_merge(
+ $this->ignoredLines[$filename],
+ $ignoredLinesFindingVisitor->ignoredLines()
+ )
+ );
+
+ sort($this->ignoredLines[$filename]);
+
+ $result = $lineCountingVisitor->result();
+
+ $this->linesOfCode[$filename] = [
+ 'linesOfCode' => $result->linesOfCode(),
+ 'commentLinesOfCode' => $result->commentLinesOfCode(),
+ 'nonCommentLinesOfCode' => $result->nonCommentLinesOfCode(),
+ ];
+ }
+
+ private function findLinesIgnoredByLineBasedAnnotations(string $filename, string $source, bool $useAnnotationsForIgnoringCode): void
+ {
+ $ignore = false;
+ $stop = false;
+
+ foreach (token_get_all($source) as $token) {
+ if (!is_array($token)) {
+ continue;
+ }
+
+ switch ($token[0]) {
+ case T_COMMENT:
+ case T_DOC_COMMENT:
+ if (!$useAnnotationsForIgnoringCode) {
+ break;
+ }
+
+ $comment = trim($token[1]);
+
+ if ($comment === '// @codeCoverageIgnore' ||
+ $comment === '//@codeCoverageIgnore') {
+ $ignore = true;
+ $stop = true;
+ } elseif ($comment === '// @codeCoverageIgnoreStart' ||
+ $comment === '//@codeCoverageIgnoreStart') {
+ $ignore = true;
+ } elseif ($comment === '// @codeCoverageIgnoreEnd' ||
+ $comment === '//@codeCoverageIgnoreEnd') {
+ $stop = true;
+ }
+
+ break;
+ }
+
+ if ($ignore) {
+ $this->ignoredLines[$filename][] = $token[2];
+
+ if ($stop) {
+ $ignore = false;
+ $stop = false;
+ }
+ }
+ }
+ }
+}