/vendor/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/File.php

https://gitlab.com/ealexis.t/trends · PHP · 679 lines · 405 code · 95 blank · 179 comment · 48 complexity · 2b53005da550aed6abb990d9d0184dab MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of the PHP_CodeCoverage package.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * Represents a file in the code coverage information tree.
  12. *
  13. * @since Class available since Release 1.1.0
  14. */
  15. class PHP_CodeCoverage_Report_Node_File extends PHP_CodeCoverage_Report_Node
  16. {
  17. /**
  18. * @var array
  19. */
  20. protected $coverageData;
  21. /**
  22. * @var array
  23. */
  24. protected $testData;
  25. /**
  26. * @var int
  27. */
  28. protected $numExecutableLines = 0;
  29. /**
  30. * @var int
  31. */
  32. protected $numExecutedLines = 0;
  33. /**
  34. * @var array
  35. */
  36. protected $classes = array();
  37. /**
  38. * @var array
  39. */
  40. protected $traits = array();
  41. /**
  42. * @var array
  43. */
  44. protected $functions = array();
  45. /**
  46. * @var array
  47. */
  48. protected $linesOfCode = array();
  49. /**
  50. * @var int
  51. */
  52. protected $numTestedTraits = 0;
  53. /**
  54. * @var int
  55. */
  56. protected $numTestedClasses = 0;
  57. /**
  58. * @var int
  59. */
  60. protected $numMethods = null;
  61. /**
  62. * @var int
  63. */
  64. protected $numTestedMethods = null;
  65. /**
  66. * @var int
  67. */
  68. protected $numTestedFunctions = null;
  69. /**
  70. * @var array
  71. */
  72. protected $startLines = array();
  73. /**
  74. * @var array
  75. */
  76. protected $endLines = array();
  77. /**
  78. * @var bool
  79. */
  80. protected $cacheTokens;
  81. /**
  82. * Constructor.
  83. *
  84. * @param string $name
  85. * @param PHP_CodeCoverage_Report_Node $parent
  86. * @param array $coverageData
  87. * @param array $testData
  88. * @param bool $cacheTokens
  89. * @throws PHP_CodeCoverage_Exception
  90. */
  91. public function __construct($name, PHP_CodeCoverage_Report_Node $parent, array $coverageData, array $testData, $cacheTokens)
  92. {
  93. if (!is_bool($cacheTokens)) {
  94. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  95. 1,
  96. 'boolean'
  97. );
  98. }
  99. parent::__construct($name, $parent);
  100. $this->coverageData = $coverageData;
  101. $this->testData = $testData;
  102. $this->cacheTokens = $cacheTokens;
  103. $this->calculateStatistics();
  104. }
  105. /**
  106. * Returns the number of files in/under this node.
  107. *
  108. * @return int
  109. */
  110. public function count()
  111. {
  112. return 1;
  113. }
  114. /**
  115. * Returns the code coverage data of this node.
  116. *
  117. * @return array
  118. */
  119. public function getCoverageData()
  120. {
  121. return $this->coverageData;
  122. }
  123. /**
  124. * Returns the test data of this node.
  125. *
  126. * @return array
  127. */
  128. public function getTestData()
  129. {
  130. return $this->testData;
  131. }
  132. /**
  133. * Returns the classes of this node.
  134. *
  135. * @return array
  136. */
  137. public function getClasses()
  138. {
  139. return $this->classes;
  140. }
  141. /**
  142. * Returns the traits of this node.
  143. *
  144. * @return array
  145. */
  146. public function getTraits()
  147. {
  148. return $this->traits;
  149. }
  150. /**
  151. * Returns the functions of this node.
  152. *
  153. * @return array
  154. */
  155. public function getFunctions()
  156. {
  157. return $this->functions;
  158. }
  159. /**
  160. * Returns the LOC/CLOC/NCLOC of this node.
  161. *
  162. * @return array
  163. */
  164. public function getLinesOfCode()
  165. {
  166. return $this->linesOfCode;
  167. }
  168. /**
  169. * Returns the number of executable lines.
  170. *
  171. * @return int
  172. */
  173. public function getNumExecutableLines()
  174. {
  175. return $this->numExecutableLines;
  176. }
  177. /**
  178. * Returns the number of executed lines.
  179. *
  180. * @return int
  181. */
  182. public function getNumExecutedLines()
  183. {
  184. return $this->numExecutedLines;
  185. }
  186. /**
  187. * Returns the number of classes.
  188. *
  189. * @return int
  190. */
  191. public function getNumClasses()
  192. {
  193. return count($this->classes);
  194. }
  195. /**
  196. * Returns the number of tested classes.
  197. *
  198. * @return int
  199. */
  200. public function getNumTestedClasses()
  201. {
  202. return $this->numTestedClasses;
  203. }
  204. /**
  205. * Returns the number of traits.
  206. *
  207. * @return int
  208. */
  209. public function getNumTraits()
  210. {
  211. return count($this->traits);
  212. }
  213. /**
  214. * Returns the number of tested traits.
  215. *
  216. * @return int
  217. */
  218. public function getNumTestedTraits()
  219. {
  220. return $this->numTestedTraits;
  221. }
  222. /**
  223. * Returns the number of methods.
  224. *
  225. * @return int
  226. */
  227. public function getNumMethods()
  228. {
  229. if ($this->numMethods === null) {
  230. $this->numMethods = 0;
  231. foreach ($this->classes as $class) {
  232. foreach ($class['methods'] as $method) {
  233. if ($method['executableLines'] > 0) {
  234. $this->numMethods++;
  235. }
  236. }
  237. }
  238. foreach ($this->traits as $trait) {
  239. foreach ($trait['methods'] as $method) {
  240. if ($method['executableLines'] > 0) {
  241. $this->numMethods++;
  242. }
  243. }
  244. }
  245. }
  246. return $this->numMethods;
  247. }
  248. /**
  249. * Returns the number of tested methods.
  250. *
  251. * @return int
  252. */
  253. public function getNumTestedMethods()
  254. {
  255. if ($this->numTestedMethods === null) {
  256. $this->numTestedMethods = 0;
  257. foreach ($this->classes as $class) {
  258. foreach ($class['methods'] as $method) {
  259. if ($method['executableLines'] > 0 &&
  260. $method['coverage'] == 100) {
  261. $this->numTestedMethods++;
  262. }
  263. }
  264. }
  265. foreach ($this->traits as $trait) {
  266. foreach ($trait['methods'] as $method) {
  267. if ($method['executableLines'] > 0 &&
  268. $method['coverage'] == 100) {
  269. $this->numTestedMethods++;
  270. }
  271. }
  272. }
  273. }
  274. return $this->numTestedMethods;
  275. }
  276. /**
  277. * Returns the number of functions.
  278. *
  279. * @return int
  280. */
  281. public function getNumFunctions()
  282. {
  283. return count($this->functions);
  284. }
  285. /**
  286. * Returns the number of tested functions.
  287. *
  288. * @return int
  289. */
  290. public function getNumTestedFunctions()
  291. {
  292. if ($this->numTestedFunctions === null) {
  293. $this->numTestedFunctions = 0;
  294. foreach ($this->functions as $function) {
  295. if ($function['executableLines'] > 0 &&
  296. $function['coverage'] == 100) {
  297. $this->numTestedFunctions++;
  298. }
  299. }
  300. }
  301. return $this->numTestedFunctions;
  302. }
  303. /**
  304. * Calculates coverage statistics for the file.
  305. */
  306. protected function calculateStatistics()
  307. {
  308. $classStack = $functionStack = array();
  309. if ($this->cacheTokens) {
  310. $tokens = PHP_Token_Stream_CachingFactory::get($this->getPath());
  311. } else {
  312. $tokens = new PHP_Token_Stream($this->getPath());
  313. }
  314. $this->processClasses($tokens);
  315. $this->processTraits($tokens);
  316. $this->processFunctions($tokens);
  317. $this->linesOfCode = $tokens->getLinesOfCode();
  318. unset($tokens);
  319. for ($lineNumber = 1; $lineNumber <= $this->linesOfCode['loc']; $lineNumber++) {
  320. if (isset($this->startLines[$lineNumber])) {
  321. // Start line of a class.
  322. if (isset($this->startLines[$lineNumber]['className'])) {
  323. if (isset($currentClass)) {
  324. $classStack[] = &$currentClass;
  325. }
  326. $currentClass = &$this->startLines[$lineNumber];
  327. } // Start line of a trait.
  328. elseif (isset($this->startLines[$lineNumber]['traitName'])) {
  329. $currentTrait = &$this->startLines[$lineNumber];
  330. } // Start line of a method.
  331. elseif (isset($this->startLines[$lineNumber]['methodName'])) {
  332. $currentMethod = &$this->startLines[$lineNumber];
  333. } // Start line of a function.
  334. elseif (isset($this->startLines[$lineNumber]['functionName'])) {
  335. if (isset($currentFunction)) {
  336. $functionStack[] = &$currentFunction;
  337. }
  338. $currentFunction = &$this->startLines[$lineNumber];
  339. }
  340. }
  341. if (isset($this->coverageData[$lineNumber])) {
  342. if (isset($currentClass)) {
  343. $currentClass['executableLines']++;
  344. }
  345. if (isset($currentTrait)) {
  346. $currentTrait['executableLines']++;
  347. }
  348. if (isset($currentMethod)) {
  349. $currentMethod['executableLines']++;
  350. }
  351. if (isset($currentFunction)) {
  352. $currentFunction['executableLines']++;
  353. }
  354. $this->numExecutableLines++;
  355. if (count($this->coverageData[$lineNumber]) > 0) {
  356. if (isset($currentClass)) {
  357. $currentClass['executedLines']++;
  358. }
  359. if (isset($currentTrait)) {
  360. $currentTrait['executedLines']++;
  361. }
  362. if (isset($currentMethod)) {
  363. $currentMethod['executedLines']++;
  364. }
  365. if (isset($currentFunction)) {
  366. $currentFunction['executedLines']++;
  367. }
  368. $this->numExecutedLines++;
  369. }
  370. }
  371. if (isset($this->endLines[$lineNumber])) {
  372. // End line of a class.
  373. if (isset($this->endLines[$lineNumber]['className'])) {
  374. unset($currentClass);
  375. if ($classStack) {
  376. end($classStack);
  377. $key = key($classStack);
  378. $currentClass = &$classStack[$key];
  379. unset($classStack[$key]);
  380. }
  381. } // End line of a trait.
  382. elseif (isset($this->endLines[$lineNumber]['traitName'])) {
  383. unset($currentTrait);
  384. } // End line of a method.
  385. elseif (isset($this->endLines[$lineNumber]['methodName'])) {
  386. unset($currentMethod);
  387. } // End line of a function.
  388. elseif (isset($this->endLines[$lineNumber]['functionName'])) {
  389. unset($currentFunction);
  390. if ($functionStack) {
  391. end($functionStack);
  392. $key = key($functionStack);
  393. $currentFunction = &$functionStack[$key];
  394. unset($functionStack[$key]);
  395. }
  396. }
  397. }
  398. }
  399. foreach ($this->traits as &$trait) {
  400. foreach ($trait['methods'] as &$method) {
  401. if ($method['executableLines'] > 0) {
  402. $method['coverage'] = ($method['executedLines'] /
  403. $method['executableLines']) * 100;
  404. } else {
  405. $method['coverage'] = 100;
  406. }
  407. $method['crap'] = $this->crap(
  408. $method['ccn'],
  409. $method['coverage']
  410. );
  411. $trait['ccn'] += $method['ccn'];
  412. }
  413. if ($trait['executableLines'] > 0) {
  414. $trait['coverage'] = ($trait['executedLines'] /
  415. $trait['executableLines']) * 100;
  416. } else {
  417. $trait['coverage'] = 100;
  418. }
  419. if ($trait['coverage'] == 100) {
  420. $this->numTestedClasses++;
  421. }
  422. $trait['crap'] = $this->crap(
  423. $trait['ccn'],
  424. $trait['coverage']
  425. );
  426. }
  427. foreach ($this->classes as &$class) {
  428. foreach ($class['methods'] as &$method) {
  429. if ($method['executableLines'] > 0) {
  430. $method['coverage'] = ($method['executedLines'] /
  431. $method['executableLines']) * 100;
  432. } else {
  433. $method['coverage'] = 100;
  434. }
  435. $method['crap'] = $this->crap(
  436. $method['ccn'],
  437. $method['coverage']
  438. );
  439. $class['ccn'] += $method['ccn'];
  440. }
  441. if ($class['executableLines'] > 0) {
  442. $class['coverage'] = ($class['executedLines'] /
  443. $class['executableLines']) * 100;
  444. } else {
  445. $class['coverage'] = 100;
  446. }
  447. if ($class['coverage'] == 100) {
  448. $this->numTestedClasses++;
  449. }
  450. $class['crap'] = $this->crap(
  451. $class['ccn'],
  452. $class['coverage']
  453. );
  454. }
  455. }
  456. /**
  457. * @param PHP_Token_Stream $tokens
  458. */
  459. protected function processClasses(PHP_Token_Stream $tokens)
  460. {
  461. $classes = $tokens->getClasses();
  462. unset($tokens);
  463. $link = $this->getId() . '.html#';
  464. foreach ($classes as $className => $class) {
  465. $this->classes[$className] = array(
  466. 'className' => $className,
  467. 'methods' => array(),
  468. 'startLine' => $class['startLine'],
  469. 'executableLines' => 0,
  470. 'executedLines' => 0,
  471. 'ccn' => 0,
  472. 'coverage' => 0,
  473. 'crap' => 0,
  474. 'package' => $class['package'],
  475. 'link' => $link . $class['startLine']
  476. );
  477. $this->startLines[$class['startLine']] = &$this->classes[$className];
  478. $this->endLines[$class['endLine']] = &$this->classes[$className];
  479. foreach ($class['methods'] as $methodName => $method) {
  480. $this->classes[$className]['methods'][$methodName] = array(
  481. 'methodName' => $methodName,
  482. 'signature' => $method['signature'],
  483. 'startLine' => $method['startLine'],
  484. 'endLine' => $method['endLine'],
  485. 'executableLines' => 0,
  486. 'executedLines' => 0,
  487. 'ccn' => $method['ccn'],
  488. 'coverage' => 0,
  489. 'crap' => 0,
  490. 'link' => $link . $method['startLine']
  491. );
  492. $this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName];
  493. $this->endLines[$method['endLine']] = &$this->classes[$className]['methods'][$methodName];
  494. }
  495. }
  496. }
  497. /**
  498. * @param PHP_Token_Stream $tokens
  499. */
  500. protected function processTraits(PHP_Token_Stream $tokens)
  501. {
  502. $traits = $tokens->getTraits();
  503. unset($tokens);
  504. $link = $this->getId() . '.html#';
  505. foreach ($traits as $traitName => $trait) {
  506. $this->traits[$traitName] = array(
  507. 'traitName' => $traitName,
  508. 'methods' => array(),
  509. 'startLine' => $trait['startLine'],
  510. 'executableLines' => 0,
  511. 'executedLines' => 0,
  512. 'ccn' => 0,
  513. 'coverage' => 0,
  514. 'crap' => 0,
  515. 'package' => $trait['package'],
  516. 'link' => $link . $trait['startLine']
  517. );
  518. $this->startLines[$trait['startLine']] = &$this->traits[$traitName];
  519. $this->endLines[$trait['endLine']] = &$this->traits[$traitName];
  520. foreach ($trait['methods'] as $methodName => $method) {
  521. $this->traits[$traitName]['methods'][$methodName] = array(
  522. 'methodName' => $methodName,
  523. 'signature' => $method['signature'],
  524. 'startLine' => $method['startLine'],
  525. 'endLine' => $method['endLine'],
  526. 'executableLines' => 0,
  527. 'executedLines' => 0,
  528. 'ccn' => $method['ccn'],
  529. 'coverage' => 0,
  530. 'crap' => 0,
  531. 'link' => $link . $method['startLine']
  532. );
  533. $this->startLines[$method['startLine']] = &$this->traits[$traitName]['methods'][$methodName];
  534. $this->endLines[$method['endLine']] = &$this->traits[$traitName]['methods'][$methodName];
  535. }
  536. }
  537. }
  538. /**
  539. * @param PHP_Token_Stream $tokens
  540. */
  541. protected function processFunctions(PHP_Token_Stream $tokens)
  542. {
  543. $functions = $tokens->getFunctions();
  544. unset($tokens);
  545. $link = $this->getId() . '.html#';
  546. foreach ($functions as $functionName => $function) {
  547. $this->functions[$functionName] = array(
  548. 'functionName' => $functionName,
  549. 'signature' => $function['signature'],
  550. 'startLine' => $function['startLine'],
  551. 'executableLines' => 0,
  552. 'executedLines' => 0,
  553. 'ccn' => $function['ccn'],
  554. 'coverage' => 0,
  555. 'crap' => 0,
  556. 'link' => $link . $function['startLine']
  557. );
  558. $this->startLines[$function['startLine']] = &$this->functions[$functionName];
  559. $this->endLines[$function['endLine']] = &$this->functions[$functionName];
  560. }
  561. }
  562. /**
  563. * Calculates the Change Risk Anti-Patterns (CRAP) index for a unit of code
  564. * based on its cyclomatic complexity and percentage of code coverage.
  565. *
  566. * @param int $ccn
  567. * @param float $coverage
  568. * @return string
  569. * @since Method available since Release 1.2.0
  570. */
  571. protected function crap($ccn, $coverage)
  572. {
  573. if ($coverage == 0) {
  574. return (string) (pow($ccn, 2) + $ccn);
  575. }
  576. if ($coverage >= 95) {
  577. return (string) $ccn;
  578. }
  579. return sprintf(
  580. '%01.2F',
  581. pow($ccn, 2) * pow(1 - $coverage/100, 3) + $ccn
  582. );
  583. }
  584. }