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

https://bitbucket.org/alan_cordova/api-sb-map · PHP · 722 lines · 425 code · 106 blank · 191 comment · 52 complexity · 05dd6b0882423cfbad353fbaf491d3d7 MD5 · raw file

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