first commit

This commit is contained in:
root
2022-03-06 11:49:27 +00:00
commit 1984e55837
1387 changed files with 121949 additions and 0 deletions

View File

@ -0,0 +1,342 @@
<?php
/*
* This file is part of the php-code-coverage package.
*
* (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 SebastianBergmann\CodeCoverage\Util;
/**
* Base class for nodes in the code coverage information tree.
*/
abstract class AbstractNode implements \Countable
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $path;
/**
* @var array
*/
private $pathArray;
/**
* @var AbstractNode
*/
private $parent;
/**
* @var string
*/
private $id;
/**
* Constructor.
*
* @param string $name
* @param AbstractNode $parent
*/
public function __construct($name, AbstractNode $parent = null)
{
if (substr($name, -1) == '/') {
$name = substr($name, 0, -1);
}
$this->name = $name;
$this->parent = $parent;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getId()
{
if ($this->id === null) {
$parent = $this->getParent();
if ($parent === null) {
$this->id = 'index';
} else {
$parentId = $parent->getId();
if ($parentId == 'index') {
$this->id = str_replace(':', '_', $this->name);
} else {
$this->id = $parentId . '/' . $this->name;
}
}
}
return $this->id;
}
/**
* @return string
*/
public function getPath()
{
if ($this->path === null) {
if ($this->parent === null || $this->parent->getPath() === null || $this->parent->getPath() === false) {
$this->path = $this->name;
} else {
$this->path = $this->parent->getPath() . '/' . $this->name;
}
}
return $this->path;
}
/**
* @return array
*/
public function getPathAsArray()
{
if ($this->pathArray === null) {
if ($this->parent === null) {
$this->pathArray = [];
} else {
$this->pathArray = $this->parent->getPathAsArray();
}
$this->pathArray[] = $this;
}
return $this->pathArray;
}
/**
* @return AbstractNode
*/
public function getParent()
{
return $this->parent;
}
/**
* Returns the percentage of classes that has been tested.
*
* @param bool $asString
*
* @return int
*/
public function getTestedClassesPercent($asString = true)
{
return Util::percent(
$this->getNumTestedClasses(),
$this->getNumClasses(),
$asString
);
}
/**
* Returns the percentage of traits that has been tested.
*
* @param bool $asString
*
* @return int
*/
public function getTestedTraitsPercent($asString = true)
{
return Util::percent(
$this->getNumTestedTraits(),
$this->getNumTraits(),
$asString
);
}
/**
* Returns the percentage of traits that has been tested.
*
* @param bool $asString
*
* @return int
*/
public function getTestedClassesAndTraitsPercent($asString = true)
{
return Util::percent(
$this->getNumTestedClassesAndTraits(),
$this->getNumClassesAndTraits(),
$asString
);
}
/**
* Returns the percentage of methods that has been tested.
*
* @param bool $asString
*
* @return int
*/
public function getTestedMethodsPercent($asString = true)
{
return Util::percent(
$this->getNumTestedMethods(),
$this->getNumMethods(),
$asString
);
}
/**
* Returns the percentage of executed lines.
*
* @param bool $asString
*
* @return int
*/
public function getLineExecutedPercent($asString = true)
{
return Util::percent(
$this->getNumExecutedLines(),
$this->getNumExecutableLines(),
$asString
);
}
/**
* Returns the number of classes and traits.
*
* @return int
*/
public function getNumClassesAndTraits()
{
return $this->getNumClasses() + $this->getNumTraits();
}
/**
* Returns the number of tested classes and traits.
*
* @return int
*/
public function getNumTestedClassesAndTraits()
{
return $this->getNumTestedClasses() + $this->getNumTestedTraits();
}
/**
* Returns the classes and traits of this node.
*
* @return array
*/
public function getClassesAndTraits()
{
return array_merge($this->getClasses(), $this->getTraits());
}
/**
* Returns the classes of this node.
*
* @return array
*/
abstract public function getClasses();
/**
* Returns the traits of this node.
*
* @return array
*/
abstract public function getTraits();
/**
* Returns the functions of this node.
*
* @return array
*/
abstract public function getFunctions();
/**
* Returns the LOC/CLOC/NCLOC of this node.
*
* @return array
*/
abstract public function getLinesOfCode();
/**
* Returns the number of executable lines.
*
* @return int
*/
abstract public function getNumExecutableLines();
/**
* Returns the number of executed lines.
*
* @return int
*/
abstract public function getNumExecutedLines();
/**
* Returns the number of classes.
*
* @return int
*/
abstract public function getNumClasses();
/**
* Returns the number of tested classes.
*
* @return int
*/
abstract public function getNumTestedClasses();
/**
* Returns the number of traits.
*
* @return int
*/
abstract public function getNumTraits();
/**
* Returns the number of tested traits.
*
* @return int
*/
abstract public function getNumTestedTraits();
/**
* Returns the number of methods.
*
* @return int
*/
abstract public function getNumMethods();
/**
* Returns the number of tested methods.
*
* @return int
*/
abstract public function getNumTestedMethods();
/**
* Returns the number of functions.
*
* @return int
*/
abstract public function getNumFunctions();
/**
* Returns the number of tested functions.
*
* @return int
*/
abstract public function getNumTestedFunctions();
}

View File

@ -0,0 +1,244 @@
<?php
/*
* This file is part of the php-code-coverage package.
*
* (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 SebastianBergmann\CodeCoverage\CodeCoverage;
class Builder
{
/**
* @param CodeCoverage $coverage
*
* @return Directory
*/
public function build(CodeCoverage $coverage)
{
$files = $coverage->getData();
$commonPath = $this->reducePaths($files);
$root = new Directory(
$commonPath,
null
);
$this->addItems(
$root,
$this->buildDirectoryStructure($files),
$coverage->getTests(),
$coverage->getCacheTokens()
);
return $root;
}
/**
* @param Directory $root
* @param array $items
* @param array $tests
* @param bool $cacheTokens
*/
private function addItems(Directory $root, array $items, array $tests, $cacheTokens)
{
foreach ($items as $key => $value) {
if (substr($key, -2) == '/f') {
$key = substr($key, 0, -2);
if (file_exists($root->getPath() . DIRECTORY_SEPARATOR . $key)) {
$root->addFile($key, $value, $tests, $cacheTokens);
}
} else {
$child = $root->addDirectory($key);
$this->addItems($child, $value, $tests, $cacheTokens);
}
}
}
/**
* 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>
*
* @param array $files
*
* @return array
*/
private function buildDirectoryStructure($files)
{
$result = [];
foreach ($files as $path => $file) {
$path = explode('/', $path);
$pointer = &$result;
$max = count($path);
for ($i = 0; $i < $max; $i++) {
if ($i == ($max - 1)) {
$type = '/f';
} else {
$type = '';
}
$pointer = &$pointer[$path[$i] . $type];
}
$pointer = $file;
}
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>
*
* @param array $files
*
* @return string
*/
private function reducePaths(&$files)
{
if (empty($files)) {
return '.';
}
$commonPath = '';
$paths = array_keys($files);
if (count($files) == 1) {
$commonPath = dirname($paths[0]) . '/';
$files[basename($paths[0])] = $files[$paths[0]];
unset($files[$paths[0]]);
return $commonPath;
}
$max = count($paths);
for ($i = 0; $i < $max; $i++) {
// strip phar:// prefixes
if (strpos($paths[$i], 'phar://') === 0) {
$paths[$i] = substr($paths[$i], 7);
$paths[$i] = strtr($paths[$i], '/', DIRECTORY_SEPARATOR);
}
$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 = array_keys($files);
$max = count($original);
for ($i = 0; $i < $max; $i++) {
$files[implode('/', $paths[$i])] = $files[$original[$i]];
unset($files[$original[$i]]);
}
ksort($files);
return substr($commonPath, 0, -1);
}
}

View File

@ -0,0 +1,483 @@
<?php
/*
* This file is part of the php-code-coverage package.
*
* (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 SebastianBergmann\CodeCoverage\InvalidArgumentException;
/**
* Represents a directory in the code coverage information tree.
*/
class Directory extends AbstractNode implements \IteratorAggregate
{
/**
* @var AbstractNode[]
*/
private $children = [];
/**
* @var Directory[]
*/
private $directories = [];
/**
* @var File[]
*/
private $files = [];
/**
* @var array
*/
private $classes;
/**
* @var array
*/
private $traits;
/**
* @var array
*/
private $functions;
/**
* @var array
*/
private $linesOfCode = null;
/**
* @var int
*/
private $numFiles = -1;
/**
* @var int
*/
private $numExecutableLines = -1;
/**
* @var int
*/
private $numExecutedLines = -1;
/**
* @var int
*/
private $numClasses = -1;
/**
* @var int
*/
private $numTestedClasses = -1;
/**
* @var int
*/
private $numTraits = -1;
/**
* @var int
*/
private $numTestedTraits = -1;
/**
* @var int
*/
private $numMethods = -1;
/**
* @var int
*/
private $numTestedMethods = -1;
/**
* @var int
*/
private $numFunctions = -1;
/**
* @var int
*/
private $numTestedFunctions = -1;
/**
* Returns the number of files in/under this node.
*
* @return int
*/
public function count()
{
if ($this->numFiles == -1) {
$this->numFiles = 0;
foreach ($this->children as $child) {
$this->numFiles += count($child);
}
}
return $this->numFiles;
}
/**
* Returns an iterator for this node.
*
* @return \RecursiveIteratorIterator
*/
public function getIterator()
{
return new \RecursiveIteratorIterator(
new Iterator($this),
\RecursiveIteratorIterator::SELF_FIRST
);
}
/**
* Adds a new directory.
*
* @param string $name
*
* @return Directory
*/
public function addDirectory($name)
{
$directory = new self($name, $this);
$this->children[] = $directory;
$this->directories[] = &$this->children[count($this->children) - 1];
return $directory;
}
/**
* Adds a new file.
*
* @param string $name
* @param array $coverageData
* @param array $testData
* @param bool $cacheTokens
*
* @return File
*
* @throws InvalidArgumentException
*/
public function addFile($name, array $coverageData, array $testData, $cacheTokens)
{
$file = new File(
$name,
$this,
$coverageData,
$testData,
$cacheTokens
);
$this->children[] = $file;
$this->files[] = &$this->children[count($this->children) - 1];
$this->numExecutableLines = -1;
$this->numExecutedLines = -1;
return $file;
}
/**
* Returns the directories in this directory.
*
* @return array
*/
public function getDirectories()
{
return $this->directories;
}
/**
* Returns the files in this directory.
*
* @return array
*/
public function getFiles()
{
return $this->files;
}
/**
* Returns the child nodes of this node.
*
* @return array
*/
public function getChildNodes()
{
return $this->children;
}
/**
* Returns the classes of this node.
*
* @return array
*/
public function getClasses()
{
if ($this->classes === null) {
$this->classes = [];
foreach ($this->children as $child) {
$this->classes = array_merge(
$this->classes,
$child->getClasses()
);
}
}
return $this->classes;
}
/**
* Returns the traits of this node.
*
* @return array
*/
public function getTraits()
{
if ($this->traits === null) {
$this->traits = [];
foreach ($this->children as $child) {
$this->traits = array_merge(
$this->traits,
$child->getTraits()
);
}
}
return $this->traits;
}
/**
* Returns the functions of this node.
*
* @return array
*/
public function getFunctions()
{
if ($this->functions === null) {
$this->functions = [];
foreach ($this->children as $child) {
$this->functions = array_merge(
$this->functions,
$child->getFunctions()
);
}
}
return $this->functions;
}
/**
* Returns the LOC/CLOC/NCLOC of this node.
*
* @return array
*/
public function getLinesOfCode()
{
if ($this->linesOfCode === null) {
$this->linesOfCode = ['loc' => 0, 'cloc' => 0, 'ncloc' => 0];
foreach ($this->children as $child) {
$linesOfCode = $child->getLinesOfCode();
$this->linesOfCode['loc'] += $linesOfCode['loc'];
$this->linesOfCode['cloc'] += $linesOfCode['cloc'];
$this->linesOfCode['ncloc'] += $linesOfCode['ncloc'];
}
}
return $this->linesOfCode;
}
/**
* Returns the number of executable lines.
*
* @return int
*/
public function getNumExecutableLines()
{
if ($this->numExecutableLines == -1) {
$this->numExecutableLines = 0;
foreach ($this->children as $child) {
$this->numExecutableLines += $child->getNumExecutableLines();
}
}
return $this->numExecutableLines;
}
/**
* Returns the number of executed lines.
*
* @return int
*/
public function getNumExecutedLines()
{
if ($this->numExecutedLines == -1) {
$this->numExecutedLines = 0;
foreach ($this->children as $child) {
$this->numExecutedLines += $child->getNumExecutedLines();
}
}
return $this->numExecutedLines;
}
/**
* Returns the number of classes.
*
* @return int
*/
public function getNumClasses()
{
if ($this->numClasses == -1) {
$this->numClasses = 0;
foreach ($this->children as $child) {
$this->numClasses += $child->getNumClasses();
}
}
return $this->numClasses;
}
/**
* Returns the number of tested classes.
*
* @return int
*/
public function getNumTestedClasses()
{
if ($this->numTestedClasses == -1) {
$this->numTestedClasses = 0;
foreach ($this->children as $child) {
$this->numTestedClasses += $child->getNumTestedClasses();
}
}
return $this->numTestedClasses;
}
/**
* Returns the number of traits.
*
* @return int
*/
public function getNumTraits()
{
if ($this->numTraits == -1) {
$this->numTraits = 0;
foreach ($this->children as $child) {
$this->numTraits += $child->getNumTraits();
}
}
return $this->numTraits;
}
/**
* Returns the number of tested traits.
*
* @return int
*/
public function getNumTestedTraits()
{
if ($this->numTestedTraits == -1) {
$this->numTestedTraits = 0;
foreach ($this->children as $child) {
$this->numTestedTraits += $child->getNumTestedTraits();
}
}
return $this->numTestedTraits;
}
/**
* Returns the number of methods.
*
* @return int
*/
public function getNumMethods()
{
if ($this->numMethods == -1) {
$this->numMethods = 0;
foreach ($this->children as $child) {
$this->numMethods += $child->getNumMethods();
}
}
return $this->numMethods;
}
/**
* Returns the number of tested methods.
*
* @return int
*/
public function getNumTestedMethods()
{
if ($this->numTestedMethods == -1) {
$this->numTestedMethods = 0;
foreach ($this->children as $child) {
$this->numTestedMethods += $child->getNumTestedMethods();
}
}
return $this->numTestedMethods;
}
/**
* Returns the number of functions.
*
* @return int
*/
public function getNumFunctions()
{
if ($this->numFunctions == -1) {
$this->numFunctions = 0;
foreach ($this->children as $child) {
$this->numFunctions += $child->getNumFunctions();
}
}
return $this->numFunctions;
}
/**
* Returns the number of tested functions.
*
* @return int
*/
public function getNumTestedFunctions()
{
if ($this->numTestedFunctions == -1) {
$this->numTestedFunctions = 0;
foreach ($this->children as $child) {
$this->numTestedFunctions += $child->getNumTestedFunctions();
}
}
return $this->numTestedFunctions;
}
}

View File

@ -0,0 +1,722 @@
<?php
/*
* This file is part of the php-code-coverage package.
*
* (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 SebastianBergmann\CodeCoverage\InvalidArgumentException;
/**
* Represents a file in the code coverage information tree.
*/
class File extends AbstractNode
{
/**
* @var array
*/
private $coverageData;
/**
* @var array
*/
private $testData;
/**
* @var int
*/
private $numExecutableLines = 0;
/**
* @var int
*/
private $numExecutedLines = 0;
/**
* @var array
*/
private $classes = [];
/**
* @var array
*/
private $traits = [];
/**
* @var array
*/
private $functions = [];
/**
* @var array
*/
private $linesOfCode = [];
/**
* @var int
*/
private $numClasses = null;
/**
* @var int
*/
private $numTestedClasses = 0;
/**
* @var int
*/
private $numTraits = null;
/**
* @var int
*/
private $numTestedTraits = 0;
/**
* @var int
*/
private $numMethods = null;
/**
* @var int
*/
private $numTestedMethods = null;
/**
* @var int
*/
private $numTestedFunctions = null;
/**
* @var array
*/
private $startLines = [];
/**
* @var array
*/
private $endLines = [];
/**
* @var bool
*/
private $cacheTokens;
/**
* Constructor.
*
* @param string $name
* @param AbstractNode $parent
* @param array $coverageData
* @param array $testData
* @param bool $cacheTokens
*
* @throws InvalidArgumentException
*/
public function __construct($name, AbstractNode $parent, array $coverageData, array $testData, $cacheTokens)
{
if (!is_bool($cacheTokens)) {
throw InvalidArgumentException::create(
1,
'boolean'
);
}
parent::__construct($name, $parent);
$this->coverageData = $coverageData;
$this->testData = $testData;
$this->cacheTokens = $cacheTokens;
$this->calculateStatistics();
}
/**
* Returns the number of files in/under this node.
*
* @return int
*/
public function count()
{
return 1;
}
/**
* Returns the code coverage data of this node.
*
* @return array
*/
public function getCoverageData()
{
return $this->coverageData;
}
/**
* Returns the test data of this node.
*
* @return array
*/
public function getTestData()
{
return $this->testData;
}
/**
* Returns the classes of this node.
*
* @return array
*/
public function getClasses()
{
return $this->classes;
}
/**
* Returns the traits of this node.
*
* @return array
*/
public function getTraits()
{
return $this->traits;
}
/**
* Returns the functions of this node.
*
* @return array
*/
public function getFunctions()
{
return $this->functions;
}
/**
* Returns the LOC/CLOC/NCLOC of this node.
*
* @return array
*/
public function getLinesOfCode()
{
return $this->linesOfCode;
}
/**
* Returns the number of executable lines.
*
* @return int
*/
public function getNumExecutableLines()
{
return $this->numExecutableLines;
}
/**
* Returns the number of executed lines.
*
* @return int
*/
public function getNumExecutedLines()
{
return $this->numExecutedLines;
}
/**
* Returns the number of classes.
*
* @return int
*/
public function getNumClasses()
{
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;
}
/**
* Returns the number of tested classes.
*
* @return int
*/
public function getNumTestedClasses()
{
return $this->numTestedClasses;
}
/**
* Returns the number of traits.
*
* @return int
*/
public function getNumTraits()
{
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;
}
/**
* Returns the number of tested traits.
*
* @return int
*/
public function getNumTestedTraits()
{
return $this->numTestedTraits;
}
/**
* Returns the number of methods.
*
* @return int
*/
public function getNumMethods()
{
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;
}
/**
* Returns the number of tested methods.
*
* @return int
*/
public function getNumTestedMethods()
{
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;
}
/**
* Returns the number of functions.
*
* @return int
*/
public function getNumFunctions()
{
return count($this->functions);
}
/**
* Returns the number of tested functions.
*
* @return int
*/
public function getNumTestedFunctions()
{
if ($this->numTestedFunctions === null) {
$this->numTestedFunctions = 0;
foreach ($this->functions as $function) {
if ($function['executableLines'] > 0 &&
$function['coverage'] == 100) {
$this->numTestedFunctions++;
}
}
}
return $this->numTestedFunctions;
}
/**
* Calculates coverage statistics for the file.
*/
protected function calculateStatistics()
{
$classStack = $functionStack = [];
if ($this->cacheTokens) {
$tokens = \PHP_Token_Stream_CachingFactory::get($this->getPath());
} else {
$tokens = new \PHP_Token_Stream($this->getPath());
}
$this->processClasses($tokens);
$this->processTraits($tokens);
$this->processFunctions($tokens);
$this->linesOfCode = $tokens->getLinesOfCode();
unset($tokens);
for ($lineNumber = 1; $lineNumber <= $this->linesOfCode['loc']; $lineNumber++) {
if (isset($this->startLines[$lineNumber])) {
// Start line of a class.
if (isset($this->startLines[$lineNumber]['className'])) {
if (isset($currentClass)) {
$classStack[] = &$currentClass;
}
$currentClass = &$this->startLines[$lineNumber];
} // Start line of a trait.
elseif (isset($this->startLines[$lineNumber]['traitName'])) {
$currentTrait = &$this->startLines[$lineNumber];
} // Start line of a method.
elseif (isset($this->startLines[$lineNumber]['methodName'])) {
$currentMethod = &$this->startLines[$lineNumber];
} // Start line of a function.
elseif (isset($this->startLines[$lineNumber]['functionName'])) {
if (isset($currentFunction)) {
$functionStack[] = &$currentFunction;
}
$currentFunction = &$this->startLines[$lineNumber];
}
}
if (isset($this->coverageData[$lineNumber])) {
if (isset($currentClass)) {
$currentClass['executableLines']++;
}
if (isset($currentTrait)) {
$currentTrait['executableLines']++;
}
if (isset($currentMethod)) {
$currentMethod['executableLines']++;
}
if (isset($currentFunction)) {
$currentFunction['executableLines']++;
}
$this->numExecutableLines++;
if (count($this->coverageData[$lineNumber]) > 0) {
if (isset($currentClass)) {
$currentClass['executedLines']++;
}
if (isset($currentTrait)) {
$currentTrait['executedLines']++;
}
if (isset($currentMethod)) {
$currentMethod['executedLines']++;
}
if (isset($currentFunction)) {
$currentFunction['executedLines']++;
}
$this->numExecutedLines++;
}
}
if (isset($this->endLines[$lineNumber])) {
// End line of a class.
if (isset($this->endLines[$lineNumber]['className'])) {
unset($currentClass);
if ($classStack) {
end($classStack);
$key = key($classStack);
$currentClass = &$classStack[$key];
unset($classStack[$key]);
}
} // End line of a trait.
elseif (isset($this->endLines[$lineNumber]['traitName'])) {
unset($currentTrait);
} // End line of a method.
elseif (isset($this->endLines[$lineNumber]['methodName'])) {
unset($currentMethod);
} // End line of a function.
elseif (isset($this->endLines[$lineNumber]['functionName'])) {
unset($currentFunction);
if ($functionStack) {
end($functionStack);
$key = key($functionStack);
$currentFunction = &$functionStack[$key];
unset($functionStack[$key]);
}
}
}
}
foreach ($this->traits as &$trait) {
foreach ($trait['methods'] as &$method) {
if ($method['executableLines'] > 0) {
$method['coverage'] = ($method['executedLines'] /
$method['executableLines']) * 100;
} else {
$method['coverage'] = 100;
}
$method['crap'] = $this->crap(
$method['ccn'],
$method['coverage']
);
$trait['ccn'] += $method['ccn'];
}
if ($trait['executableLines'] > 0) {
$trait['coverage'] = ($trait['executedLines'] /
$trait['executableLines']) * 100;
if ($trait['coverage'] == 100) {
$this->numTestedClasses++;
}
} else {
$trait['coverage'] = 100;
}
$trait['crap'] = $this->crap(
$trait['ccn'],
$trait['coverage']
);
}
foreach ($this->classes as &$class) {
foreach ($class['methods'] as &$method) {
if ($method['executableLines'] > 0) {
$method['coverage'] = ($method['executedLines'] /
$method['executableLines']) * 100;
} else {
$method['coverage'] = 100;
}
$method['crap'] = $this->crap(
$method['ccn'],
$method['coverage']
);
$class['ccn'] += $method['ccn'];
}
if ($class['executableLines'] > 0) {
$class['coverage'] = ($class['executedLines'] /
$class['executableLines']) * 100;
if ($class['coverage'] == 100) {
$this->numTestedClasses++;
}
} else {
$class['coverage'] = 100;
}
$class['crap'] = $this->crap(
$class['ccn'],
$class['coverage']
);
}
}
/**
* @param \PHP_Token_Stream $tokens
*/
protected function processClasses(\PHP_Token_Stream $tokens)
{
$classes = $tokens->getClasses();
unset($tokens);
$link = $this->getId() . '.html#';
foreach ($classes as $className => $class) {
$this->classes[$className] = [
'className' => $className,
'methods' => [],
'startLine' => $class['startLine'],
'executableLines' => 0,
'executedLines' => 0,
'ccn' => 0,
'coverage' => 0,
'crap' => 0,
'package' => $class['package'],
'link' => $link . $class['startLine']
];
$this->startLines[$class['startLine']] = &$this->classes[$className];
$this->endLines[$class['endLine']] = &$this->classes[$className];
foreach ($class['methods'] as $methodName => $method) {
$this->classes[$className]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);
$this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName];
$this->endLines[$method['endLine']] = &$this->classes[$className]['methods'][$methodName];
}
}
}
/**
* @param \PHP_Token_Stream $tokens
*/
protected function processTraits(\PHP_Token_Stream $tokens)
{
$traits = $tokens->getTraits();
unset($tokens);
$link = $this->getId() . '.html#';
foreach ($traits as $traitName => $trait) {
$this->traits[$traitName] = [
'traitName' => $traitName,
'methods' => [],
'startLine' => $trait['startLine'],
'executableLines' => 0,
'executedLines' => 0,
'ccn' => 0,
'coverage' => 0,
'crap' => 0,
'package' => $trait['package'],
'link' => $link . $trait['startLine']
];
$this->startLines[$trait['startLine']] = &$this->traits[$traitName];
$this->endLines[$trait['endLine']] = &$this->traits[$traitName];
foreach ($trait['methods'] as $methodName => $method) {
$this->traits[$traitName]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);
$this->startLines[$method['startLine']] = &$this->traits[$traitName]['methods'][$methodName];
$this->endLines[$method['endLine']] = &$this->traits[$traitName]['methods'][$methodName];
}
}
}
/**
* @param \PHP_Token_Stream $tokens
*/
protected function processFunctions(\PHP_Token_Stream $tokens)
{
$functions = $tokens->getFunctions();
unset($tokens);
$link = $this->getId() . '.html#';
foreach ($functions as $functionName => $function) {
$this->functions[$functionName] = [
'functionName' => $functionName,
'signature' => $function['signature'],
'startLine' => $function['startLine'],
'executableLines' => 0,
'executedLines' => 0,
'ccn' => $function['ccn'],
'coverage' => 0,
'crap' => 0,
'link' => $link . $function['startLine']
];
$this->startLines[$function['startLine']] = &$this->functions[$functionName];
$this->endLines[$function['endLine']] = &$this->functions[$functionName];
}
}
/**
* Calculates the Change Risk Anti-Patterns (CRAP) index for a unit of code
* based on its cyclomatic complexity and percentage of code coverage.
*
* @param int $ccn
* @param float $coverage
*
* @return string
*/
protected function crap($ccn, $coverage)
{
if ($coverage == 0) {
return (string) (pow($ccn, 2) + $ccn);
}
if ($coverage >= 95) {
return (string) $ccn;
}
return sprintf(
'%01.2F',
pow($ccn, 2) * pow(1 - $coverage/100, 3) + $ccn
);
}
/**
* @param string $methodName
* @param array $method
* @param string $link
*
* @return array
*/
private function newMethod($methodName, array $method, $link)
{
return [
'methodName' => $methodName,
'visibility' => $method['visibility'],
'signature' => $method['signature'],
'startLine' => $method['startLine'],
'endLine' => $method['endLine'],
'executableLines' => 0,
'executedLines' => 0,
'ccn' => $method['ccn'],
'coverage' => 0,
'crap' => 0,
'link' => $link . $method['startLine'],
];
}
}

View File

@ -0,0 +1,103 @@
<?php
/*
* This file is part of the php-code-coverage package.
*
* (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;
/**
* Recursive iterator for node object graphs.
*/
class Iterator implements \RecursiveIterator
{
/**
* @var int
*/
private $position;
/**
* @var AbstractNode[]
*/
private $nodes;
/**
* @param Directory $node
*/
public function __construct(Directory $node)
{
$this->nodes = $node->getChildNodes();
}
/**
* Rewinds the Iterator to the first element.
*/
public function rewind()
{
$this->position = 0;
}
/**
* Checks if there is a current element after calls to rewind() or next().
*
* @return bool
*/
public function valid()
{
return $this->position < count($this->nodes);
}
/**
* Returns the key of the current element.
*
* @return int
*/
public function key()
{
return $this->position;
}
/**
* Returns the current element.
*
* @return \PHPUnit_Framework_Test
*/
public function current()
{
return $this->valid() ? $this->nodes[$this->position] : null;
}
/**
* Moves forward to next element.
*/
public function next()
{
$this->position++;
}
/**
* Returns the sub iterator for the current element.
*
* @return Iterator
*/
public function getChildren()
{
return new self(
$this->nodes[$this->position]
);
}
/**
* Checks whether the current element has children.
*
* @return bool
*/
public function hasChildren()
{
return $this->nodes[$this->position] instanceof Directory;
}
}