upgrade to laravel 8.x
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
|
||||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function filemtime;
|
||||
use function hash;
|
||||
use function is_file;
|
||||
use function serialize;
|
||||
use function unserialize;
|
||||
use SebastianBergmann\CodeCoverage\Directory;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
|
||||
*/
|
||||
abstract class Cache
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $directory;
|
||||
|
||||
public function __construct(string $directory)
|
||||
{
|
||||
Directory::create($directory);
|
||||
|
||||
$this->directory = $directory;
|
||||
}
|
||||
|
||||
protected function has(string $filename, string $key): bool
|
||||
{
|
||||
$cacheFile = $this->cacheFile($filename, $key);
|
||||
|
||||
if (!is_file($cacheFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filemtime($cacheFile) < filemtime($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param list<class-string> $allowedClasses
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function read(string $filename, string $key, array $allowedClasses = [])
|
||||
{
|
||||
$options = ['allowed_classes' => false];
|
||||
|
||||
if (!empty($allowedClasses)) {
|
||||
$options = ['allowed_classes' => $allowedClasses];
|
||||
}
|
||||
|
||||
return unserialize(
|
||||
file_get_contents(
|
||||
$this->cacheFile($filename, $key)
|
||||
),
|
||||
$options
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
*/
|
||||
protected function write(string $filename, string $key, $data): void
|
||||
{
|
||||
file_put_contents(
|
||||
$this->cacheFile($filename, $key),
|
||||
serialize($data)
|
||||
);
|
||||
}
|
||||
|
||||
private function cacheFile(string $filename, string $key): string
|
||||
{
|
||||
return $this->directory . DIRECTORY_SEPARATOR . hash('sha256', $filename . $key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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
|
||||
{
|
||||
$coveredFileAnalyser = new CachingCoveredFileAnalyser(
|
||||
$cacheDirectory,
|
||||
new ParsingCoveredFileAnalyser(
|
||||
$useAnnotationsForIgnoringCode,
|
||||
$ignoreDeprecatedCode
|
||||
)
|
||||
);
|
||||
|
||||
$uncoveredFileAnalyser = new CachingUncoveredFileAnalyser(
|
||||
$cacheDirectory,
|
||||
new ParsingUncoveredFileAnalyser
|
||||
);
|
||||
|
||||
foreach ($filter->files() as $file) {
|
||||
$coveredFileAnalyser->process($file);
|
||||
|
||||
/* @noinspection UnusedFunctionResultInspection */
|
||||
$uncoveredFileAnalyser->executableLinesIn($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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\LinesOfCode\LinesOfCode;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
|
||||
*/
|
||||
final class CachingCoveredFileAnalyser extends Cache implements CoveredFileAnalyser
|
||||
{
|
||||
/**
|
||||
* @var CoveredFileAnalyser
|
||||
*/
|
||||
private $coveredFileAnalyser;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cache = [];
|
||||
|
||||
public function __construct(string $directory, CoveredFileAnalyser $coveredFileAnalyser)
|
||||
{
|
||||
parent::__construct($directory);
|
||||
|
||||
$this->coveredFileAnalyser = $coveredFileAnalyser;
|
||||
}
|
||||
|
||||
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'];
|
||||
}
|
||||
|
||||
public function linesOfCodeFor(string $filename): LinesOfCode
|
||||
{
|
||||
if (!isset($this->cache[$filename])) {
|
||||
$this->process($filename);
|
||||
}
|
||||
|
||||
return $this->cache[$filename]['linesOfCodeFor'];
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
if ($this->has($filename, __CLASS__)) {
|
||||
$this->cache[$filename] = $this->read($filename, __CLASS__, [LinesOfCode::class]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cache[$filename] = [
|
||||
'classesIn' => $this->coveredFileAnalyser->classesIn($filename),
|
||||
'traitsIn' => $this->coveredFileAnalyser->traitsIn($filename),
|
||||
'functionsIn' => $this->coveredFileAnalyser->functionsIn($filename),
|
||||
'linesOfCodeFor' => $this->coveredFileAnalyser->linesOfCodeFor($filename),
|
||||
'ignoredLinesFor' => $this->coveredFileAnalyser->ignoredLinesFor($filename),
|
||||
];
|
||||
|
||||
$this->write($filename, __CLASS__, $this->cache[$filename]);
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
final class CachingUncoveredFileAnalyser extends Cache implements UncoveredFileAnalyser
|
||||
{
|
||||
/**
|
||||
* @var UncoveredFileAnalyser
|
||||
*/
|
||||
private $uncoveredFileAnalyser;
|
||||
|
||||
public function __construct(string $directory, UncoveredFileAnalyser $uncoveredFileAnalyser)
|
||||
{
|
||||
parent::__construct($directory);
|
||||
|
||||
$this->uncoveredFileAnalyser = $uncoveredFileAnalyser;
|
||||
}
|
||||
|
||||
public function executableLinesIn(string $filename): array
|
||||
{
|
||||
if ($this->has($filename, __METHOD__)) {
|
||||
return $this->read($filename, __METHOD__);
|
||||
}
|
||||
|
||||
$data = $this->uncoveredFileAnalyser->executableLinesIn($filename);
|
||||
|
||||
$this->write($filename, __METHOD__, $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
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\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
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $classes = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $traits = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $functions = [];
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
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 null;
|
||||
}
|
||||
|
||||
if ($node instanceof ClassMethod) {
|
||||
$parentNode = $node->getAttribute('parent');
|
||||
|
||||
if ($parentNode instanceof Class_ && $parentNode->isAnonymous()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->processMethod($node);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->processFunction($node);
|
||||
}
|
||||
|
||||
public function classes(): array
|
||||
{
|
||||
return $this->classes;
|
||||
}
|
||||
|
||||
public function traits(): array
|
||||
{
|
||||
return $this->traits;
|
||||
}
|
||||
|
||||
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|NullableType|UnionType $type
|
||||
*/
|
||||
private function type(Node $type): string
|
||||
{
|
||||
assert($type instanceof Identifier || $type instanceof Name || $type instanceof NullableType || $type instanceof UnionType);
|
||||
|
||||
if ($type instanceof NullableType) {
|
||||
return '?' . $type->type;
|
||||
}
|
||||
|
||||
if ($type instanceof UnionType) {
|
||||
$types = [];
|
||||
|
||||
foreach ($type->types as $_type) {
|
||||
$types[] = $_type->toString();
|
||||
}
|
||||
|
||||
return implode('|', $types);
|
||||
}
|
||||
|
||||
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_);
|
||||
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), '\\');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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\LinesOfCode\LinesOfCode;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
|
||||
*/
|
||||
interface CoveredFileAnalyser
|
||||
{
|
||||
public function classesIn(string $filename): array;
|
||||
|
||||
public function traitsIn(string $filename): array;
|
||||
|
||||
public function functionsIn(string $filename): array;
|
||||
|
||||
public function linesOfCodeFor(string $filename): LinesOfCode;
|
||||
|
||||
public function ignoredLinesFor(string $filename): array;
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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 sort;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Break_;
|
||||
use PhpParser\Node\Stmt\Case_;
|
||||
use PhpParser\Node\Stmt\Catch_;
|
||||
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\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 list<int>
|
||||
*/
|
||||
private $executableLines = [];
|
||||
|
||||
public function enterNode(Node $node): void
|
||||
{
|
||||
if (!$this->isExecutable($node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->executableLines[] = $node->getStartLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return list<int>
|
||||
*/
|
||||
public function executableLines(): array
|
||||
{
|
||||
$executableLines = array_unique($this->executableLines);
|
||||
|
||||
sort($executableLines);
|
||||
|
||||
return $executableLines;
|
||||
}
|
||||
|
||||
private function isExecutable(Node $node): bool
|
||||
{
|
||||
return $node instanceof Break_ ||
|
||||
$node instanceof Case_ ||
|
||||
$node instanceof Catch_ ||
|
||||
$node instanceof Continue_ ||
|
||||
$node instanceof Do_ ||
|
||||
$node instanceof Echo_ ||
|
||||
$node instanceof ElseIf_ ||
|
||||
$node instanceof Else_ ||
|
||||
$node instanceof Expression ||
|
||||
$node instanceof Finally_ ||
|
||||
$node instanceof Foreach_ ||
|
||||
$node instanceof For_ ||
|
||||
$node instanceof Goto_ ||
|
||||
$node instanceof If_ ||
|
||||
$node instanceof Return_ ||
|
||||
$node instanceof Switch_ ||
|
||||
$node instanceof Throw_ ||
|
||||
$node instanceof TryCatch ||
|
||||
$node instanceof Unset_ ||
|
||||
$node instanceof While_;
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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\NodeTraverser;
|
||||
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): ?int
|
||||
{
|
||||
if (!$node instanceof Class_ &&
|
||||
!$node instanceof Trait_ &&
|
||||
!$node instanceof Interface_ &&
|
||||
!$node instanceof ClassMethod &&
|
||||
!$node instanceof Function_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($node instanceof Class_ && $node->isAnonymous()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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 null;
|
||||
}
|
||||
|
||||
if ($node instanceof Interface_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$docComment = $node->getDocComment();
|
||||
|
||||
if ($docComment === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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())
|
||||
);
|
||||
}
|
||||
|
||||
if ($node instanceof ClassMethod || $node instanceof Function_) {
|
||||
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return list<int>
|
||||
*/
|
||||
public function ignoredLines(): array
|
||||
{
|
||||
return $this->ignoredLines;
|
||||
}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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 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;
|
||||
use SebastianBergmann\LinesOfCode\LinesOfCode;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
|
||||
*/
|
||||
final class ParsingCoveredFileAnalyser implements CoveredFileAnalyser
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $classes = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $traits = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $functions = [];
|
||||
|
||||
/**
|
||||
* @var LinesOfCode[]
|
||||
*/
|
||||
private $linesOfCode = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $ignoredLines = [];
|
||||
|
||||
/**
|
||||
* @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];
|
||||
}
|
||||
|
||||
public function linesOfCodeFor(string $filename): LinesOfCode
|
||||
{
|
||||
$this->analyse($filename);
|
||||
|
||||
return $this->linesOfCode[$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 = substr_count($source, "\n");
|
||||
|
||||
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);
|
||||
|
||||
$traverser->addVisitor(new NameResolver);
|
||||
$traverser->addVisitor(new ParentConnectingVisitor);
|
||||
$traverser->addVisitor($codeUnitFindingVisitor);
|
||||
$traverser->addVisitor($lineCountingVisitor);
|
||||
$traverser->addVisitor($ignoredLinesFindingVisitor);
|
||||
|
||||
/* @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->linesOfCode[$filename] = $lineCountingVisitor->result();
|
||||
$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]);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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\Error;
|
||||
use PhpParser\Lexer;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
|
||||
*/
|
||||
final class ParsingUncoveredFileAnalyser implements UncoveredFileAnalyser
|
||||
{
|
||||
public function executableLinesIn(string $filename): array
|
||||
{
|
||||
$parser = (new ParserFactory)->create(
|
||||
ParserFactory::PREFER_PHP7,
|
||||
new Lexer
|
||||
);
|
||||
|
||||
try {
|
||||
$nodes = $parser->parse(file_get_contents($filename));
|
||||
|
||||
assert($nodes !== null);
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
$visitor = new ExecutableLinesFindingVisitor;
|
||||
|
||||
$traverser->addVisitor($visitor);
|
||||
|
||||
/* @noinspection UnusedFunctionResultInspection */
|
||||
$traverser->traverse($nodes);
|
||||
|
||||
return $visitor->executableLines();
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Error $error) {
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of phpunit/php-code-coverage.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* 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 UncoveredFileAnalyser
|
||||
{
|
||||
public function executableLinesIn(string $filename): array;
|
||||
}
|
||||
Reference in New Issue
Block a user