Import latest app updates from streamline

An updated set of source files and initialization was provided by streamline to
address issues observed during initial testing. These files have been updated in
order to generate a new set of app images.
This commit is contained in:
2024-04-11 11:16:51 -07:00
parent cb4c6b4c1a
commit 4a89356390
10435 changed files with 107737 additions and 1220538 deletions
@@ -1,250 +0,0 @@
<?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\Node;
use const DIRECTORY_SEPARATOR;
use function array_merge;
use function str_ends_with;
use function str_replace;
use function substr;
use Countable;
use SebastianBergmann\CodeCoverage\Util\Percentage;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @psalm-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
* @psalm-import-type ProcessedFunctionType from \SebastianBergmann\CodeCoverage\Node\File
* @psalm-import-type ProcessedClassType from \SebastianBergmann\CodeCoverage\Node\File
* @psalm-import-type ProcessedTraitType from \SebastianBergmann\CodeCoverage\Node\File
*/
abstract class AbstractNode implements Countable
{
private readonly string $name;
private string $pathAsString;
private array $pathAsArray;
private readonly ?AbstractNode $parent;
private string $id;
public function __construct(string $name, self $parent = null)
{
if (str_ends_with($name, DIRECTORY_SEPARATOR)) {
$name = substr($name, 0, -1);
}
$this->name = $name;
$this->parent = $parent;
$this->processId();
$this->processPath();
}
public function name(): string
{
return $this->name;
}
public function id(): string
{
return $this->id;
}
public function pathAsString(): string
{
return $this->pathAsString;
}
public function pathAsArray(): array
{
return $this->pathAsArray;
}
public function parent(): ?self
{
return $this->parent;
}
public function percentageOfTestedClasses(): Percentage
{
return Percentage::fromFractionAndTotal(
$this->numberOfTestedClasses(),
$this->numberOfClasses(),
);
}
public function percentageOfTestedTraits(): Percentage
{
return Percentage::fromFractionAndTotal(
$this->numberOfTestedTraits(),
$this->numberOfTraits(),
);
}
public function percentageOfTestedClassesAndTraits(): Percentage
{
return Percentage::fromFractionAndTotal(
$this->numberOfTestedClassesAndTraits(),
$this->numberOfClassesAndTraits(),
);
}
public function percentageOfTestedFunctions(): Percentage
{
return Percentage::fromFractionAndTotal(
$this->numberOfTestedFunctions(),
$this->numberOfFunctions(),
);
}
public function percentageOfTestedMethods(): Percentage
{
return Percentage::fromFractionAndTotal(
$this->numberOfTestedMethods(),
$this->numberOfMethods(),
);
}
public function percentageOfTestedFunctionsAndMethods(): Percentage
{
return Percentage::fromFractionAndTotal(
$this->numberOfTestedFunctionsAndMethods(),
$this->numberOfFunctionsAndMethods(),
);
}
public function percentageOfExecutedLines(): Percentage
{
return Percentage::fromFractionAndTotal(
$this->numberOfExecutedLines(),
$this->numberOfExecutableLines(),
);
}
public function percentageOfExecutedBranches(): Percentage
{
return Percentage::fromFractionAndTotal(
$this->numberOfExecutedBranches(),
$this->numberOfExecutableBranches()
);
}
public function percentageOfExecutedPaths(): Percentage
{
return Percentage::fromFractionAndTotal(
$this->numberOfExecutedPaths(),
$this->numberOfExecutablePaths()
);
}
public function numberOfClassesAndTraits(): int
{
return $this->numberOfClasses() + $this->numberOfTraits();
}
public function numberOfTestedClassesAndTraits(): int
{
return $this->numberOfTestedClasses() + $this->numberOfTestedTraits();
}
public function classesAndTraits(): array
{
return array_merge($this->classes(), $this->traits());
}
public function numberOfFunctionsAndMethods(): int
{
return $this->numberOfFunctions() + $this->numberOfMethods();
}
public function numberOfTestedFunctionsAndMethods(): int
{
return $this->numberOfTestedFunctions() + $this->numberOfTestedMethods();
}
/**
* @psalm-return array<string, ProcessedClassType>
*/
abstract public function classes(): array;
/**
* @psalm-return array<string, ProcessedTraitType>
*/
abstract public function traits(): array;
/**
* @psalm-return array<string, ProcessedFunctionType>
*/
abstract public function functions(): array;
/**
* @psalm-return LinesOfCodeType
*/
abstract public function linesOfCode(): array;
abstract public function numberOfExecutableLines(): int;
abstract public function numberOfExecutedLines(): int;
abstract public function numberOfExecutableBranches(): int;
abstract public function numberOfExecutedBranches(): int;
abstract public function numberOfExecutablePaths(): int;
abstract public function numberOfExecutedPaths(): int;
abstract public function numberOfClasses(): int;
abstract public function numberOfTestedClasses(): int;
abstract public function numberOfTraits(): int;
abstract public function numberOfTestedTraits(): int;
abstract public function numberOfMethods(): int;
abstract public function numberOfTestedMethods(): int;
abstract public function numberOfFunctions(): int;
abstract public function numberOfTestedFunctions(): int;
private function processId(): void
{
if ($this->parent === null) {
$this->id = 'index';
return;
}
$parentId = $this->parent->id();
if ($parentId === 'index') {
$this->id = str_replace(':', '_', $this->name);
} else {
$this->id = $parentId . '/' . $this->name;
}
}
private function processPath(): void
{
if ($this->parent === null) {
$this->pathAsArray = [$this];
$this->pathAsString = $this->name;
return;
}
$this->pathAsArray = $this->parent->pathAsArray();
$this->pathAsString = $this->parent->pathAsString() . DIRECTORY_SEPARATOR . $this->name;
$this->pathAsArray[] = $this;
}
}
@@ -1,269 +0,0 @@
<?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\Node;
use const DIRECTORY_SEPARATOR;
use function array_shift;
use function basename;
use function count;
use function dirname;
use function explode;
use function implode;
use function is_file;
use function str_ends_with;
use function str_replace;
use function str_starts_with;
use function substr;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData;
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @psalm-import-type TestType from \SebastianBergmann\CodeCoverage\CodeCoverage
*/
final class Builder
{
private readonly FileAnalyser $analyser;
public function __construct(FileAnalyser $analyser)
{
$this->analyser = $analyser;
}
public function build(CodeCoverage $coverage): Directory
{
$data = clone $coverage->getData(); // clone because path munging is destructive to the original data
$commonPath = $this->reducePaths($data);
$root = new Directory(
$commonPath,
null
);
$this->addItems(
$root,
$this->buildDirectoryStructure($data),
$coverage->getTests()
);
return $root;
}
/**
* @psalm-param array<string, TestType> $tests
*/
private function addItems(Directory $root, array $items, array $tests): void
{
foreach ($items as $key => $value) {
$key = (string) $key;
if (str_ends_with($key, '/f')) {
$key = substr($key, 0, -2);
$filename = $root->pathAsString() . DIRECTORY_SEPARATOR . $key;
if (is_file($filename)) {
$root->addFile(
new File(
$key,
$root,
$value['lineCoverage'],
$value['functionCoverage'],
$tests,
$this->analyser->classesIn($filename),
$this->analyser->traitsIn($filename),
$this->analyser->functionsIn($filename),
$this->analyser->linesOfCodeFor($filename)
)
);
}
} else {
$child = $root->addDirectory($key);
$this->addItems($child, $value, $tests);
}
}
}
/**
* Builds an array representation of the directory structure.
*
* For instance,
*
* <code>
* Array
* (
* [Money.php] => Array
* (
* ...
* )
*
* [MoneyBag.php] => Array
* (
* ...
* )
* )
* </code>
*
* is transformed into
*
* <code>
* Array
* (
* [.] => Array
* (
* [Money.php] => Array
* (
* ...
* )
*
* [MoneyBag.php] => Array
* (
* ...
* )
* )
* )
* </code>
*
* @psalm-return array<string, array<string, array{lineCoverage: array<int, int>, functionCoverage: array<string, array<int, int>>}>>
*/
private function buildDirectoryStructure(ProcessedCodeCoverageData $data): array
{
$result = [];
foreach ($data->coveredFiles() as $originalPath) {
$path = explode(DIRECTORY_SEPARATOR, $originalPath);
$pointer = &$result;
$max = count($path);
for ($i = 0; $i < $max; $i++) {
$type = '';
if ($i === ($max - 1)) {
$type = '/f';
}
$pointer = &$pointer[$path[$i] . $type];
}
$pointer = [
'lineCoverage' => $data->lineCoverage()[$originalPath] ?? [],
'functionCoverage' => $data->functionCoverage()[$originalPath] ?? [],
];
}
return $result;
}
/**
* Reduces the paths by cutting the longest common start path.
*
* For instance,
*
* <code>
* Array
* (
* [/home/sb/Money/Money.php] => Array
* (
* ...
* )
*
* [/home/sb/Money/MoneyBag.php] => Array
* (
* ...
* )
* )
* </code>
*
* is reduced to
*
* <code>
* Array
* (
* [Money.php] => Array
* (
* ...
* )
*
* [MoneyBag.php] => Array
* (
* ...
* )
* )
* </code>
*/
private function reducePaths(ProcessedCodeCoverageData $coverage): string
{
if (empty($coverage->coveredFiles())) {
return '.';
}
$commonPath = '';
$paths = $coverage->coveredFiles();
if (count($paths) === 1) {
$commonPath = dirname($paths[0]) . DIRECTORY_SEPARATOR;
$coverage->renameFile($paths[0], basename($paths[0]));
return $commonPath;
}
$max = count($paths);
for ($i = 0; $i < $max; $i++) {
// strip phar:// prefixes
if (str_starts_with($paths[$i], 'phar://')) {
$paths[$i] = substr($paths[$i], 7);
$paths[$i] = str_replace('/', DIRECTORY_SEPARATOR, $paths[$i]);
}
$paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]);
if (empty($paths[$i][0])) {
$paths[$i][0] = DIRECTORY_SEPARATOR;
}
}
$done = false;
$max = count($paths);
while (!$done) {
for ($i = 0; $i < $max - 1; $i++) {
if (!isset($paths[$i][0]) ||
!isset($paths[$i + 1][0]) ||
$paths[$i][0] !== $paths[$i + 1][0]) {
$done = true;
break;
}
}
if (!$done) {
$commonPath .= $paths[0][0];
if ($paths[0][0] !== DIRECTORY_SEPARATOR) {
$commonPath .= DIRECTORY_SEPARATOR;
}
for ($i = 0; $i < $max; $i++) {
array_shift($paths[$i]);
}
}
}
$original = $coverage->coveredFiles();
$max = count($original);
for ($i = 0; $i < $max; $i++) {
$coverage->renameFile($original[$i], implode(DIRECTORY_SEPARATOR, $paths[$i]));
}
return substr($commonPath, 0, -1);
}
}
@@ -1,43 +0,0 @@
<?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\Node;
use function sprintf;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class CrapIndex
{
private readonly int $cyclomaticComplexity;
private readonly float $codeCoverage;
public function __construct(int $cyclomaticComplexity, float $codeCoverage)
{
$this->cyclomaticComplexity = $cyclomaticComplexity;
$this->codeCoverage = $codeCoverage;
}
public function asString(): string
{
if ($this->codeCoverage === 0.0) {
return (string) ($this->cyclomaticComplexity ** 2 + $this->cyclomaticComplexity);
}
if ($this->codeCoverage >= 95) {
return (string) $this->cyclomaticComplexity;
}
return sprintf(
'%01.2F',
$this->cyclomaticComplexity ** 2 * (1 - $this->codeCoverage / 100) ** 3 + $this->cyclomaticComplexity
);
}
}
@@ -1,370 +0,0 @@
<?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\Node;
use function array_merge;
use function count;
use IteratorAggregate;
use RecursiveIteratorIterator;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @psalm-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
*/
final class Directory extends AbstractNode implements IteratorAggregate
{
/**
* @var list<AbstractNode>
*/
private array $children = [];
/**
* @var list<Directory>
*/
private array $directories = [];
/**
* @var list<File>
*/
private array $files = [];
private ?array $classes = null;
private ?array $traits = null;
private ?array $functions = null;
/**
* @psalm-var null|LinesOfCodeType
*/
private ?array $linesOfCode = null;
private int $numFiles = -1;
private int $numExecutableLines = -1;
private int $numExecutedLines = -1;
private int $numExecutableBranches = -1;
private int $numExecutedBranches = -1;
private int $numExecutablePaths = -1;
private int $numExecutedPaths = -1;
private int $numClasses = -1;
private int $numTestedClasses = -1;
private int $numTraits = -1;
private int $numTestedTraits = -1;
private int $numMethods = -1;
private int $numTestedMethods = -1;
private int $numFunctions = -1;
private int $numTestedFunctions = -1;
public function count(): int
{
if ($this->numFiles === -1) {
$this->numFiles = 0;
foreach ($this->children as $child) {
$this->numFiles += count($child);
}
}
return $this->numFiles;
}
public function getIterator(): RecursiveIteratorIterator
{
return new RecursiveIteratorIterator(
new Iterator($this),
RecursiveIteratorIterator::SELF_FIRST
);
}
public function addDirectory(string $name): self
{
$directory = new self($name, $this);
$this->children[] = $directory;
$this->directories[] = &$this->children[count($this->children) - 1];
return $directory;
}
public function addFile(File $file): void
{
$this->children[] = $file;
$this->files[] = &$this->children[count($this->children) - 1];
$this->numExecutableLines = -1;
$this->numExecutedLines = -1;
}
public function directories(): array
{
return $this->directories;
}
public function files(): array
{
return $this->files;
}
public function children(): array
{
return $this->children;
}
public function classes(): array
{
if ($this->classes === null) {
$this->classes = [];
foreach ($this->children as $child) {
$this->classes = array_merge(
$this->classes,
$child->classes()
);
}
}
return $this->classes;
}
public function traits(): array
{
if ($this->traits === null) {
$this->traits = [];
foreach ($this->children as $child) {
$this->traits = array_merge(
$this->traits,
$child->traits()
);
}
}
return $this->traits;
}
public function functions(): array
{
if ($this->functions === null) {
$this->functions = [];
foreach ($this->children as $child) {
$this->functions = array_merge(
$this->functions,
$child->functions()
);
}
}
return $this->functions;
}
/**
* @psalm-return LinesOfCodeType
*/
public function linesOfCode(): array
{
if ($this->linesOfCode === null) {
$this->linesOfCode = [
'linesOfCode' => 0,
'commentLinesOfCode' => 0,
'nonCommentLinesOfCode' => 0,
];
foreach ($this->children as $child) {
$childLinesOfCode = $child->linesOfCode();
$this->linesOfCode['linesOfCode'] += $childLinesOfCode['linesOfCode'];
$this->linesOfCode['commentLinesOfCode'] += $childLinesOfCode['commentLinesOfCode'];
$this->linesOfCode['nonCommentLinesOfCode'] += $childLinesOfCode['nonCommentLinesOfCode'];
}
}
return $this->linesOfCode;
}
public function numberOfExecutableLines(): int
{
if ($this->numExecutableLines === -1) {
$this->numExecutableLines = 0;
foreach ($this->children as $child) {
$this->numExecutableLines += $child->numberOfExecutableLines();
}
}
return $this->numExecutableLines;
}
public function numberOfExecutedLines(): int
{
if ($this->numExecutedLines === -1) {
$this->numExecutedLines = 0;
foreach ($this->children as $child) {
$this->numExecutedLines += $child->numberOfExecutedLines();
}
}
return $this->numExecutedLines;
}
public function numberOfExecutableBranches(): int
{
if ($this->numExecutableBranches === -1) {
$this->numExecutableBranches = 0;
foreach ($this->children as $child) {
$this->numExecutableBranches += $child->numberOfExecutableBranches();
}
}
return $this->numExecutableBranches;
}
public function numberOfExecutedBranches(): int
{
if ($this->numExecutedBranches === -1) {
$this->numExecutedBranches = 0;
foreach ($this->children as $child) {
$this->numExecutedBranches += $child->numberOfExecutedBranches();
}
}
return $this->numExecutedBranches;
}
public function numberOfExecutablePaths(): int
{
if ($this->numExecutablePaths === -1) {
$this->numExecutablePaths = 0;
foreach ($this->children as $child) {
$this->numExecutablePaths += $child->numberOfExecutablePaths();
}
}
return $this->numExecutablePaths;
}
public function numberOfExecutedPaths(): int
{
if ($this->numExecutedPaths === -1) {
$this->numExecutedPaths = 0;
foreach ($this->children as $child) {
$this->numExecutedPaths += $child->numberOfExecutedPaths();
}
}
return $this->numExecutedPaths;
}
public function numberOfClasses(): int
{
if ($this->numClasses === -1) {
$this->numClasses = 0;
foreach ($this->children as $child) {
$this->numClasses += $child->numberOfClasses();
}
}
return $this->numClasses;
}
public function numberOfTestedClasses(): int
{
if ($this->numTestedClasses === -1) {
$this->numTestedClasses = 0;
foreach ($this->children as $child) {
$this->numTestedClasses += $child->numberOfTestedClasses();
}
}
return $this->numTestedClasses;
}
public function numberOfTraits(): int
{
if ($this->numTraits === -1) {
$this->numTraits = 0;
foreach ($this->children as $child) {
$this->numTraits += $child->numberOfTraits();
}
}
return $this->numTraits;
}
public function numberOfTestedTraits(): int
{
if ($this->numTestedTraits === -1) {
$this->numTestedTraits = 0;
foreach ($this->children as $child) {
$this->numTestedTraits += $child->numberOfTestedTraits();
}
}
return $this->numTestedTraits;
}
public function numberOfMethods(): int
{
if ($this->numMethods === -1) {
$this->numMethods = 0;
foreach ($this->children as $child) {
$this->numMethods += $child->numberOfMethods();
}
}
return $this->numMethods;
}
public function numberOfTestedMethods(): int
{
if ($this->numTestedMethods === -1) {
$this->numTestedMethods = 0;
foreach ($this->children as $child) {
$this->numTestedMethods += $child->numberOfTestedMethods();
}
}
return $this->numTestedMethods;
}
public function numberOfFunctions(): int
{
if ($this->numFunctions === -1) {
$this->numFunctions = 0;
foreach ($this->children as $child) {
$this->numFunctions += $child->numberOfFunctions();
}
}
return $this->numFunctions;
}
public function numberOfTestedFunctions(): int
{
if ($this->numTestedFunctions === -1) {
$this->numTestedFunctions = 0;
foreach ($this->children as $child) {
$this->numTestedFunctions += $child->numberOfTestedFunctions();
}
}
return $this->numTestedFunctions;
}
}
@@ -1,688 +0,0 @@
<?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\Node;
use function array_filter;
use function count;
use function range;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @psalm-import-type CodeUnitFunctionType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @psalm-import-type CodeUnitMethodType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @psalm-import-type CodeUnitClassType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @psalm-import-type CodeUnitTraitType from \SebastianBergmann\CodeCoverage\StaticAnalysis\CodeUnitFindingVisitor
* @psalm-import-type LinesOfCodeType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
* @psalm-import-type LinesType from \SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser
*
* @psalm-type ProcessedFunctionType = array{
* functionName: string,
* namespace: string,
* signature: string,
* startLine: int,
* endLine: int,
* executableLines: int,
* executedLines: int,
* executableBranches: int,
* executedBranches: int,
* executablePaths: int,
* executedPaths: int,
* ccn: int,
* coverage: int|float,
* crap: int|string,
* link: string
* }
* @psalm-type ProcessedMethodType = array{
* methodName: string,
* visibility: string,
* signature: string,
* startLine: int,
* endLine: int,
* executableLines: int,
* executedLines: int,
* executableBranches: int,
* executedBranches: int,
* executablePaths: int,
* executedPaths: int,
* ccn: int,
* coverage: float|int,
* crap: int|string,
* link: string
* }
* @psalm-type ProcessedClassType = array{
* className: string,
* namespace: string,
* methods: array<string, ProcessedMethodType>,
* startLine: int,
* executableLines: int,
* executedLines: int,
* executableBranches: int,
* executedBranches: int,
* executablePaths: int,
* executedPaths: int,
* ccn: int,
* coverage: int|float,
* crap: int|string,
* link: string
* }
* @psalm-type ProcessedTraitType = array{
* traitName: string,
* namespace: string,
* methods: array<string, ProcessedMethodType>,
* startLine: int,
* executableLines: int,
* executedLines: int,
* executableBranches: int,
* executedBranches: int,
* executablePaths: int,
* executedPaths: int,
* ccn: int,
* coverage: float|int,
* crap: int|string,
* link: string
* }
*/
final class File extends AbstractNode
{
/**
* @psalm-var array<int, ?list<non-empty-string>>
*/
private array $lineCoverageData;
private array $functionCoverageData;
private readonly array $testData;
private int $numExecutableLines = 0;
private int $numExecutedLines = 0;
private int $numExecutableBranches = 0;
private int $numExecutedBranches = 0;
private int $numExecutablePaths = 0;
private int $numExecutedPaths = 0;
/**
* @psalm-var array<string, ProcessedClassType>
*/
private array $classes = [];
/**
* @psalm-var array<string, ProcessedTraitType>
*/
private array $traits = [];
/**
* @psalm-var array<string, ProcessedFunctionType>
*/
private array $functions = [];
/**
* @psalm-var LinesOfCodeType
*/
private readonly array $linesOfCode;
private ?int $numClasses = null;
private int $numTestedClasses = 0;
private ?int $numTraits = null;
private int $numTestedTraits = 0;
private ?int $numMethods = null;
private ?int $numTestedMethods = null;
private ?int $numTestedFunctions = null;
/**
* @var array<int, array|array{0: CodeUnitClassType, 1: string}|array{0: CodeUnitFunctionType}|array{0: CodeUnitTraitType, 1: string}>
*/
private array $codeUnitsByLine = [];
/**
* @psalm-param array<int, ?list<non-empty-string>> $lineCoverageData
* @psalm-param LinesOfCodeType $linesOfCode
* @psalm-param array<string, CodeUnitClassType> $classes
* @psalm-param array<string, CodeUnitTraitType> $traits
* @psalm-param array<string, CodeUnitFunctionType> $functions
*/
public function __construct(string $name, AbstractNode $parent, array $lineCoverageData, array $functionCoverageData, array $testData, array $classes, array $traits, array $functions, array $linesOfCode)
{
parent::__construct($name, $parent);
$this->lineCoverageData = $lineCoverageData;
$this->functionCoverageData = $functionCoverageData;
$this->testData = $testData;
$this->linesOfCode = $linesOfCode;
$this->calculateStatistics($classes, $traits, $functions);
}
public function count(): int
{
return 1;
}
/**
* @psalm-return array<int, ?list<non-empty-string>>
*/
public function lineCoverageData(): array
{
return $this->lineCoverageData;
}
public function functionCoverageData(): array
{
return $this->functionCoverageData;
}
public function testData(): array
{
return $this->testData;
}
public function classes(): array
{
return $this->classes;
}
public function traits(): array
{
return $this->traits;
}
public function functions(): array
{
return $this->functions;
}
public function linesOfCode(): array
{
return $this->linesOfCode;
}
public function numberOfExecutableLines(): int
{
return $this->numExecutableLines;
}
public function numberOfExecutedLines(): int
{
return $this->numExecutedLines;
}
public function numberOfExecutableBranches(): int
{
return $this->numExecutableBranches;
}
public function numberOfExecutedBranches(): int
{
return $this->numExecutedBranches;
}
public function numberOfExecutablePaths(): int
{
return $this->numExecutablePaths;
}
public function numberOfExecutedPaths(): int
{
return $this->numExecutedPaths;
}
public function numberOfClasses(): int
{
if ($this->numClasses === null) {
$this->numClasses = 0;
foreach ($this->classes as $class) {
foreach ($class['methods'] as $method) {
if ($method['executableLines'] > 0) {
$this->numClasses++;
continue 2;
}
}
}
}
return $this->numClasses;
}
public function numberOfTestedClasses(): int
{
return $this->numTestedClasses;
}
public function numberOfTraits(): int
{
if ($this->numTraits === null) {
$this->numTraits = 0;
foreach ($this->traits as $trait) {
foreach ($trait['methods'] as $method) {
if ($method['executableLines'] > 0) {
$this->numTraits++;
continue 2;
}
}
}
}
return $this->numTraits;
}
public function numberOfTestedTraits(): int
{
return $this->numTestedTraits;
}
public function numberOfMethods(): int
{
if ($this->numMethods === null) {
$this->numMethods = 0;
foreach ($this->classes as $class) {
foreach ($class['methods'] as $method) {
if ($method['executableLines'] > 0) {
$this->numMethods++;
}
}
}
foreach ($this->traits as $trait) {
foreach ($trait['methods'] as $method) {
if ($method['executableLines'] > 0) {
$this->numMethods++;
}
}
}
}
return $this->numMethods;
}
public function numberOfTestedMethods(): int
{
if ($this->numTestedMethods === null) {
$this->numTestedMethods = 0;
foreach ($this->classes as $class) {
foreach ($class['methods'] as $method) {
if ($method['executableLines'] > 0 &&
$method['coverage'] === 100) {
$this->numTestedMethods++;
}
}
}
foreach ($this->traits as $trait) {
foreach ($trait['methods'] as $method) {
if ($method['executableLines'] > 0 &&
$method['coverage'] === 100) {
$this->numTestedMethods++;
}
}
}
}
return $this->numTestedMethods;
}
public function numberOfFunctions(): int
{
return count($this->functions);
}
public function numberOfTestedFunctions(): int
{
if ($this->numTestedFunctions === null) {
$this->numTestedFunctions = 0;
foreach ($this->functions as $function) {
if ($function['executableLines'] > 0 &&
$function['coverage'] === 100) {
$this->numTestedFunctions++;
}
}
}
return $this->numTestedFunctions;
}
/**
* @psalm-param array<string, CodeUnitClassType> $classes
* @psalm-param array<string, CodeUnitTraitType> $traits
* @psalm-param array<string, CodeUnitFunctionType> $functions
*/
private function calculateStatistics(array $classes, array $traits, array $functions): void
{
foreach (range(1, $this->linesOfCode['linesOfCode']) as $lineNumber) {
$this->codeUnitsByLine[$lineNumber] = [];
}
$this->processClasses($classes);
$this->processTraits($traits);
$this->processFunctions($functions);
foreach (range(1, $this->linesOfCode['linesOfCode']) as $lineNumber) {
if (isset($this->lineCoverageData[$lineNumber])) {
foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) {
$codeUnit['executableLines']++;
}
unset($codeUnit);
$this->numExecutableLines++;
if (count($this->lineCoverageData[$lineNumber]) > 0) {
foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) {
$codeUnit['executedLines']++;
}
unset($codeUnit);
$this->numExecutedLines++;
}
}
}
foreach ($this->traits as &$trait) {
foreach ($trait['methods'] as &$method) {
$methodLineCoverage = $method['executableLines'] ? ($method['executedLines'] / $method['executableLines']) * 100 : 100;
$methodBranchCoverage = $method['executableBranches'] ? ($method['executedBranches'] / $method['executableBranches']) * 100 : 0;
$methodPathCoverage = $method['executablePaths'] ? ($method['executedPaths'] / $method['executablePaths']) * 100 : 0;
$method['coverage'] = $methodBranchCoverage ?: $methodLineCoverage;
$method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage ?: $methodLineCoverage))->asString();
$trait['ccn'] += $method['ccn'];
}
unset($method);
$traitLineCoverage = $trait['executableLines'] ? ($trait['executedLines'] / $trait['executableLines']) * 100 : 100;
$traitBranchCoverage = $trait['executableBranches'] ? ($trait['executedBranches'] / $trait['executableBranches']) * 100 : 0;
$traitPathCoverage = $trait['executablePaths'] ? ($trait['executedPaths'] / $trait['executablePaths']) * 100 : 0;
$trait['coverage'] = $traitBranchCoverage ?: $traitLineCoverage;
$trait['crap'] = (new CrapIndex($trait['ccn'], $traitPathCoverage ?: $traitLineCoverage))->asString();
if ($trait['executableLines'] > 0 && $trait['coverage'] === 100) {
$this->numTestedClasses++;
}
}
unset($trait);
foreach ($this->classes as &$class) {
foreach ($class['methods'] as &$method) {
$methodLineCoverage = $method['executableLines'] ? ($method['executedLines'] / $method['executableLines']) * 100 : 100;
$methodBranchCoverage = $method['executableBranches'] ? ($method['executedBranches'] / $method['executableBranches']) * 100 : 0;
$methodPathCoverage = $method['executablePaths'] ? ($method['executedPaths'] / $method['executablePaths']) * 100 : 0;
$method['coverage'] = $methodBranchCoverage ?: $methodLineCoverage;
$method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage ?: $methodLineCoverage))->asString();
$class['ccn'] += $method['ccn'];
}
unset($method);
$classLineCoverage = $class['executableLines'] ? ($class['executedLines'] / $class['executableLines']) * 100 : 100;
$classBranchCoverage = $class['executableBranches'] ? ($class['executedBranches'] / $class['executableBranches']) * 100 : 0;
$classPathCoverage = $class['executablePaths'] ? ($class['executedPaths'] / $class['executablePaths']) * 100 : 0;
$class['coverage'] = $classBranchCoverage ?: $classLineCoverage;
$class['crap'] = (new CrapIndex($class['ccn'], $classPathCoverage ?: $classLineCoverage))->asString();
if ($class['executableLines'] > 0 && $class['coverage'] === 100) {
$this->numTestedClasses++;
}
}
unset($class);
foreach ($this->functions as &$function) {
$functionLineCoverage = $function['executableLines'] ? ($function['executedLines'] / $function['executableLines']) * 100 : 100;
$functionBranchCoverage = $function['executableBranches'] ? ($function['executedBranches'] / $function['executableBranches']) * 100 : 0;
$functionPathCoverage = $function['executablePaths'] ? ($function['executedPaths'] / $function['executablePaths']) * 100 : 0;
$function['coverage'] = $functionBranchCoverage ?: $functionLineCoverage;
$function['crap'] = (new CrapIndex($function['ccn'], $functionPathCoverage ?: $functionLineCoverage))->asString();
if ($function['coverage'] === 100) {
$this->numTestedFunctions++;
}
}
}
/**
* @psalm-param array<string, CodeUnitClassType> $classes
*/
private function processClasses(array $classes): void
{
$link = $this->id() . '.html#';
foreach ($classes as $className => $class) {
$this->classes[$className] = [
'className' => $className,
'namespace' => $class['namespace'],
'methods' => [],
'startLine' => $class['startLine'],
'executableLines' => 0,
'executedLines' => 0,
'executableBranches' => 0,
'executedBranches' => 0,
'executablePaths' => 0,
'executedPaths' => 0,
'ccn' => 0,
'coverage' => 0,
'crap' => 0,
'link' => $link . $class['startLine'],
];
foreach ($class['methods'] as $methodName => $method) {
$methodData = $this->newMethod($className, $methodName, $method, $link);
$this->classes[$className]['methods'][$methodName] = $methodData;
$this->classes[$className]['executableBranches'] += $methodData['executableBranches'];
$this->classes[$className]['executedBranches'] += $methodData['executedBranches'];
$this->classes[$className]['executablePaths'] += $methodData['executablePaths'];
$this->classes[$className]['executedPaths'] += $methodData['executedPaths'];
$this->numExecutableBranches += $methodData['executableBranches'];
$this->numExecutedBranches += $methodData['executedBranches'];
$this->numExecutablePaths += $methodData['executablePaths'];
$this->numExecutedPaths += $methodData['executedPaths'];
foreach (range($method['startLine'], $method['endLine']) as $lineNumber) {
$this->codeUnitsByLine[$lineNumber] = [
&$this->classes[$className],
&$this->classes[$className]['methods'][$methodName],
];
}
}
}
}
/**
* @psalm-param array<string, CodeUnitTraitType> $traits
*/
private function processTraits(array $traits): void
{
$link = $this->id() . '.html#';
foreach ($traits as $traitName => $trait) {
$this->traits[$traitName] = [
'traitName' => $traitName,
'namespace' => $trait['namespace'],
'methods' => [],
'startLine' => $trait['startLine'],
'executableLines' => 0,
'executedLines' => 0,
'executableBranches' => 0,
'executedBranches' => 0,
'executablePaths' => 0,
'executedPaths' => 0,
'ccn' => 0,
'coverage' => 0,
'crap' => 0,
'link' => $link . $trait['startLine'],
];
foreach ($trait['methods'] as $methodName => $method) {
$methodData = $this->newMethod($traitName, $methodName, $method, $link);
$this->traits[$traitName]['methods'][$methodName] = $methodData;
$this->traits[$traitName]['executableBranches'] += $methodData['executableBranches'];
$this->traits[$traitName]['executedBranches'] += $methodData['executedBranches'];
$this->traits[$traitName]['executablePaths'] += $methodData['executablePaths'];
$this->traits[$traitName]['executedPaths'] += $methodData['executedPaths'];
$this->numExecutableBranches += $methodData['executableBranches'];
$this->numExecutedBranches += $methodData['executedBranches'];
$this->numExecutablePaths += $methodData['executablePaths'];
$this->numExecutedPaths += $methodData['executedPaths'];
foreach (range($method['startLine'], $method['endLine']) as $lineNumber) {
$this->codeUnitsByLine[$lineNumber] = [
&$this->traits[$traitName],
&$this->traits[$traitName]['methods'][$methodName],
];
}
}
}
}
/**
* @psalm-param array<string, CodeUnitFunctionType> $functions
*/
private function processFunctions(array $functions): void
{
$link = $this->id() . '.html#';
foreach ($functions as $functionName => $function) {
$this->functions[$functionName] = [
'functionName' => $functionName,
'namespace' => $function['namespace'],
'signature' => $function['signature'],
'startLine' => $function['startLine'],
'endLine' => $function['endLine'],
'executableLines' => 0,
'executedLines' => 0,
'executableBranches' => 0,
'executedBranches' => 0,
'executablePaths' => 0,
'executedPaths' => 0,
'ccn' => $function['ccn'],
'coverage' => 0,
'crap' => 0,
'link' => $link . $function['startLine'],
];
foreach (range($function['startLine'], $function['endLine']) as $lineNumber) {
$this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]];
}
if (isset($this->functionCoverageData[$functionName]['branches'])) {
$this->functions[$functionName]['executableBranches'] = count(
$this->functionCoverageData[$functionName]['branches']
);
$this->functions[$functionName]['executedBranches'] = count(
array_filter(
$this->functionCoverageData[$functionName]['branches'],
static function (array $branch)
{
return (bool) $branch['hit'];
}
)
);
}
if (isset($this->functionCoverageData[$functionName]['paths'])) {
$this->functions[$functionName]['executablePaths'] = count(
$this->functionCoverageData[$functionName]['paths']
);
$this->functions[$functionName]['executedPaths'] = count(
array_filter(
$this->functionCoverageData[$functionName]['paths'],
static function (array $path)
{
return (bool) $path['hit'];
}
)
);
}
$this->numExecutableBranches += $this->functions[$functionName]['executableBranches'];
$this->numExecutedBranches += $this->functions[$functionName]['executedBranches'];
$this->numExecutablePaths += $this->functions[$functionName]['executablePaths'];
$this->numExecutedPaths += $this->functions[$functionName]['executedPaths'];
}
}
/**
* @psalm-param CodeUnitMethodType $method
*
* @psalm-return ProcessedMethodType
*/
private function newMethod(string $className, string $methodName, array $method, string $link): array
{
$methodData = [
'methodName' => $methodName,
'visibility' => $method['visibility'],
'signature' => $method['signature'],
'startLine' => $method['startLine'],
'endLine' => $method['endLine'],
'executableLines' => 0,
'executedLines' => 0,
'executableBranches' => 0,
'executedBranches' => 0,
'executablePaths' => 0,
'executedPaths' => 0,
'ccn' => $method['ccn'],
'coverage' => 0,
'crap' => 0,
'link' => $link . $method['startLine'],
];
$key = $className . '->' . $methodName;
if (isset($this->functionCoverageData[$key]['branches'])) {
$methodData['executableBranches'] = count(
$this->functionCoverageData[$key]['branches']
);
$methodData['executedBranches'] = count(
array_filter(
$this->functionCoverageData[$key]['branches'],
static function (array $branch)
{
return (bool) $branch['hit'];
}
)
);
}
if (isset($this->functionCoverageData[$key]['paths'])) {
$methodData['executablePaths'] = count(
$this->functionCoverageData[$key]['paths']
);
$methodData['executedPaths'] = count(
array_filter(
$this->functionCoverageData[$key]['paths'],
static function (array $path)
{
return (bool) $path['hit'];
}
)
);
}
return $methodData;
}
}
@@ -1,87 +0,0 @@
<?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\Node;
use function count;
use RecursiveIterator;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class Iterator implements RecursiveIterator
{
private int $position;
/**
* @var list<AbstractNode>
*/
private readonly array $nodes;
public function __construct(Directory $node)
{
$this->nodes = $node->children();
}
/**
* Rewinds the Iterator to the first element.
*/
public function rewind(): void
{
$this->position = 0;
}
/**
* Checks if there is a current element after calls to rewind() or next().
*/
public function valid(): bool
{
return $this->position < count($this->nodes);
}
/**
* Returns the key of the current element.
*/
public function key(): int
{
return $this->position;
}
/**
* Returns the current element.
*/
public function current(): ?AbstractNode
{
return $this->valid() ? $this->nodes[$this->position] : null;
}
/**
* Moves forward to next element.
*/
public function next(): void
{
$this->position++;
}
/**
* Returns the sub iterator for the current element.
*/
public function getChildren(): self
{
return new self($this->nodes[$this->position]);
}
/**
* Checks whether the current element has children.
*/
public function hasChildren(): bool
{
return $this->nodes[$this->position] instanceof Directory;
}
}