comparison vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php @ 14:1fec387a4317

Update Drupal core to 8.5.2 via Composer
author Chris Cannam
date Mon, 23 Apr 2018 09:46:53 +0100
parents
children
comparison
equal deleted inserted replaced
13:5fb285c0d0e3 14:1fec387a4317
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
11 namespace SebastianBergmann\CodeCoverage\Report\Html;
12
13 use SebastianBergmann\CodeCoverage\Node\File as FileNode;
14 use SebastianBergmann\CodeCoverage\Util;
15
16 /**
17 * Renders a file node.
18 */
19 class File extends Renderer
20 {
21 /**
22 * @var int
23 */
24 private $htmlspecialcharsFlags;
25
26 /**
27 * Constructor.
28 *
29 * @param string $templatePath
30 * @param string $generator
31 * @param string $date
32 * @param int $lowUpperBound
33 * @param int $highLowerBound
34 */
35 public function __construct($templatePath, $generator, $date, $lowUpperBound, $highLowerBound)
36 {
37 parent::__construct(
38 $templatePath,
39 $generator,
40 $date,
41 $lowUpperBound,
42 $highLowerBound
43 );
44
45 $this->htmlspecialcharsFlags = ENT_COMPAT;
46
47 $this->htmlspecialcharsFlags = $this->htmlspecialcharsFlags | ENT_HTML401 | ENT_SUBSTITUTE;
48 }
49
50 /**
51 * @param FileNode $node
52 * @param string $file
53 */
54 public function render(FileNode $node, $file)
55 {
56 $template = new \Text_Template($this->templatePath . 'file.html', '{{', '}}');
57
58 $template->setVar(
59 [
60 'items' => $this->renderItems($node),
61 'lines' => $this->renderSource($node)
62 ]
63 );
64
65 $this->setCommonTemplateVariables($template, $node);
66
67 $template->renderTo($file);
68 }
69
70 /**
71 * @param FileNode $node
72 *
73 * @return string
74 */
75 protected function renderItems(FileNode $node)
76 {
77 $template = new \Text_Template($this->templatePath . 'file_item.html', '{{', '}}');
78
79 $methodItemTemplate = new \Text_Template(
80 $this->templatePath . 'method_item.html',
81 '{{',
82 '}}'
83 );
84
85 $items = $this->renderItemTemplate(
86 $template,
87 [
88 'name' => 'Total',
89 'numClasses' => $node->getNumClassesAndTraits(),
90 'numTestedClasses' => $node->getNumTestedClassesAndTraits(),
91 'numMethods' => $node->getNumFunctionsAndMethods(),
92 'numTestedMethods' => $node->getNumTestedFunctionsAndMethods(),
93 'linesExecutedPercent' => $node->getLineExecutedPercent(false),
94 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(),
95 'numExecutedLines' => $node->getNumExecutedLines(),
96 'numExecutableLines' => $node->getNumExecutableLines(),
97 'testedMethodsPercent' => $node->getTestedFunctionsAndMethodsPercent(false),
98 'testedMethodsPercentAsString' => $node->getTestedFunctionsAndMethodsPercent(),
99 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false),
100 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(),
101 'crap' => '<abbr title="Change Risk Anti-Patterns (CRAP) Index">CRAP</abbr>'
102 ]
103 );
104
105 $items .= $this->renderFunctionItems(
106 $node->getFunctions(),
107 $methodItemTemplate
108 );
109
110 $items .= $this->renderTraitOrClassItems(
111 $node->getTraits(),
112 $template,
113 $methodItemTemplate
114 );
115
116 $items .= $this->renderTraitOrClassItems(
117 $node->getClasses(),
118 $template,
119 $methodItemTemplate
120 );
121
122 return $items;
123 }
124
125 /**
126 * @param array $items
127 * @param \Text_Template $template
128 * @param \Text_Template $methodItemTemplate
129 *
130 * @return string
131 */
132 protected function renderTraitOrClassItems(array $items, \Text_Template $template, \Text_Template $methodItemTemplate)
133 {
134 $buffer = '';
135
136 if (empty($items)) {
137 return $buffer;
138 }
139
140 foreach ($items as $name => $item) {
141 $numMethods = 0;
142 $numTestedMethods = 0;
143
144 foreach ($item['methods'] as $method) {
145 if ($method['executableLines'] > 0) {
146 $numMethods++;
147
148 if ($method['executedLines'] === $method['executableLines']) {
149 $numTestedMethods++;
150 }
151 }
152 }
153
154 if ($item['executableLines'] > 0) {
155 $numClasses = 1;
156 $numTestedClasses = $numTestedMethods == $numMethods ? 1 : 0;
157 $linesExecutedPercentAsString = Util::percent(
158 $item['executedLines'],
159 $item['executableLines'],
160 true
161 );
162 } else {
163 $numClasses = 'n/a';
164 $numTestedClasses = 'n/a';
165 $linesExecutedPercentAsString = 'n/a';
166 }
167
168 $buffer .= $this->renderItemTemplate(
169 $template,
170 [
171 'name' => $name,
172 'numClasses' => $numClasses,
173 'numTestedClasses' => $numTestedClasses,
174 'numMethods' => $numMethods,
175 'numTestedMethods' => $numTestedMethods,
176 'linesExecutedPercent' => Util::percent(
177 $item['executedLines'],
178 $item['executableLines'],
179 false
180 ),
181 'linesExecutedPercentAsString' => $linesExecutedPercentAsString,
182 'numExecutedLines' => $item['executedLines'],
183 'numExecutableLines' => $item['executableLines'],
184 'testedMethodsPercent' => Util::percent(
185 $numTestedMethods,
186 $numMethods,
187 false
188 ),
189 'testedMethodsPercentAsString' => Util::percent(
190 $numTestedMethods,
191 $numMethods,
192 true
193 ),
194 'testedClassesPercent' => Util::percent(
195 $numTestedMethods == $numMethods ? 1 : 0,
196 1,
197 false
198 ),
199 'testedClassesPercentAsString' => Util::percent(
200 $numTestedMethods == $numMethods ? 1 : 0,
201 1,
202 true
203 ),
204 'crap' => $item['crap']
205 ]
206 );
207
208 foreach ($item['methods'] as $method) {
209 $buffer .= $this->renderFunctionOrMethodItem(
210 $methodItemTemplate,
211 $method,
212 '&nbsp;'
213 );
214 }
215 }
216
217 return $buffer;
218 }
219
220 /**
221 * @param array $functions
222 * @param \Text_Template $template
223 *
224 * @return string
225 */
226 protected function renderFunctionItems(array $functions, \Text_Template $template)
227 {
228 if (empty($functions)) {
229 return '';
230 }
231
232 $buffer = '';
233
234 foreach ($functions as $function) {
235 $buffer .= $this->renderFunctionOrMethodItem(
236 $template,
237 $function
238 );
239 }
240
241 return $buffer;
242 }
243
244 /**
245 * @param \Text_Template $template
246 *
247 * @return string
248 */
249 protected function renderFunctionOrMethodItem(\Text_Template $template, array $item, $indent = '')
250 {
251 $numMethods = 0;
252 $numTestedMethods = 0;
253
254 if ($item['executableLines'] > 0) {
255 $numMethods = 1;
256
257 if ($item['executedLines'] === $item['executableLines']) {
258 $numTestedMethods = 1;
259 }
260 }
261
262 return $this->renderItemTemplate(
263 $template,
264 [
265 'name' => \sprintf(
266 '%s<a href="#%d"><abbr title="%s">%s</abbr></a>',
267 $indent,
268 $item['startLine'],
269 \htmlspecialchars($item['signature']),
270 isset($item['functionName']) ? $item['functionName'] : $item['methodName']
271 ),
272 'numMethods' => $numMethods,
273 'numTestedMethods' => $numTestedMethods,
274 'linesExecutedPercent' => Util::percent(
275 $item['executedLines'],
276 $item['executableLines'],
277 false
278 ),
279 'linesExecutedPercentAsString' => Util::percent(
280 $item['executedLines'],
281 $item['executableLines'],
282 true
283 ),
284 'numExecutedLines' => $item['executedLines'],
285 'numExecutableLines' => $item['executableLines'],
286 'testedMethodsPercent' => Util::percent(
287 $numTestedMethods,
288 1,
289 false
290 ),
291 'testedMethodsPercentAsString' => Util::percent(
292 $numTestedMethods,
293 1,
294 true
295 ),
296 'crap' => $item['crap']
297 ]
298 );
299 }
300
301 /**
302 * @param FileNode $node
303 *
304 * @return string
305 */
306 protected function renderSource(FileNode $node)
307 {
308 $coverageData = $node->getCoverageData();
309 $testData = $node->getTestData();
310 $codeLines = $this->loadFile($node->getPath());
311 $lines = '';
312 $i = 1;
313
314 foreach ($codeLines as $line) {
315 $trClass = '';
316 $popoverContent = '';
317 $popoverTitle = '';
318
319 if (\array_key_exists($i, $coverageData)) {
320 $numTests = ($coverageData[$i] ? \count($coverageData[$i]) : 0);
321
322 if ($coverageData[$i] === null) {
323 $trClass = ' class="warning"';
324 } elseif ($numTests == 0) {
325 $trClass = ' class="danger"';
326 } else {
327 $lineCss = 'covered-by-large-tests';
328 $popoverContent = '<ul>';
329
330 if ($numTests > 1) {
331 $popoverTitle = $numTests . ' tests cover line ' . $i;
332 } else {
333 $popoverTitle = '1 test covers line ' . $i;
334 }
335
336 foreach ($coverageData[$i] as $test) {
337 if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') {
338 $lineCss = 'covered-by-medium-tests';
339 } elseif ($testData[$test]['size'] == 'small') {
340 $lineCss = 'covered-by-small-tests';
341 }
342
343 switch ($testData[$test]['status']) {
344 case 0:
345 switch ($testData[$test]['size']) {
346 case 'small':
347 $testCSS = ' class="covered-by-small-tests"';
348
349 break;
350
351 case 'medium':
352 $testCSS = ' class="covered-by-medium-tests"';
353
354 break;
355
356 default:
357 $testCSS = ' class="covered-by-large-tests"';
358
359 break;
360 }
361
362 break;
363
364 case 1:
365 case 2:
366 $testCSS = ' class="warning"';
367
368 break;
369
370 case 3:
371 $testCSS = ' class="danger"';
372
373 break;
374
375 case 4:
376 $testCSS = ' class="danger"';
377
378 break;
379
380 default:
381 $testCSS = '';
382 }
383
384 $popoverContent .= \sprintf(
385 '<li%s>%s</li>',
386 $testCSS,
387 \htmlspecialchars($test)
388 );
389 }
390
391 $popoverContent .= '</ul>';
392 $trClass = ' class="' . $lineCss . ' popin"';
393 }
394 }
395
396 if (!empty($popoverTitle)) {
397 $popover = \sprintf(
398 ' data-title="%s" data-content="%s" data-placement="bottom" data-html="true"',
399 $popoverTitle,
400 \htmlspecialchars($popoverContent)
401 );
402 } else {
403 $popover = '';
404 }
405
406 $lines .= \sprintf(
407 ' <tr%s%s><td><div align="right"><a name="%d"></a><a href="#%d">%d</a></div></td><td class="codeLine">%s</td></tr>' . "\n",
408 $trClass,
409 $popover,
410 $i,
411 $i,
412 $i,
413 $line
414 );
415
416 $i++;
417 }
418
419 return $lines;
420 }
421
422 /**
423 * @param string $file
424 *
425 * @return array
426 */
427 protected function loadFile($file)
428 {
429 $buffer = \file_get_contents($file);
430 $tokens = \token_get_all($buffer);
431 $result = [''];
432 $i = 0;
433 $stringFlag = false;
434 $fileEndsWithNewLine = \substr($buffer, -1) == "\n";
435
436 unset($buffer);
437
438 foreach ($tokens as $j => $token) {
439 if (\is_string($token)) {
440 if ($token === '"' && $tokens[$j - 1] !== '\\') {
441 $result[$i] .= \sprintf(
442 '<span class="string">%s</span>',
443 \htmlspecialchars($token)
444 );
445
446 $stringFlag = !$stringFlag;
447 } else {
448 $result[$i] .= \sprintf(
449 '<span class="keyword">%s</span>',
450 \htmlspecialchars($token)
451 );
452 }
453
454 continue;
455 }
456
457 list($token, $value) = $token;
458
459 $value = \str_replace(
460 ["\t", ' '],
461 ['&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;'],
462 \htmlspecialchars($value, $this->htmlspecialcharsFlags)
463 );
464
465 if ($value === "\n") {
466 $result[++$i] = '';
467 } else {
468 $lines = \explode("\n", $value);
469
470 foreach ($lines as $jj => $line) {
471 $line = \trim($line);
472
473 if ($line !== '') {
474 if ($stringFlag) {
475 $colour = 'string';
476 } else {
477 switch ($token) {
478 case T_INLINE_HTML:
479 $colour = 'html';
480
481 break;
482
483 case T_COMMENT:
484 case T_DOC_COMMENT:
485 $colour = 'comment';
486
487 break;
488
489 case T_ABSTRACT:
490 case T_ARRAY:
491 case T_AS:
492 case T_BREAK:
493 case T_CALLABLE:
494 case T_CASE:
495 case T_CATCH:
496 case T_CLASS:
497 case T_CLONE:
498 case T_CONTINUE:
499 case T_DEFAULT:
500 case T_ECHO:
501 case T_ELSE:
502 case T_ELSEIF:
503 case T_EMPTY:
504 case T_ENDDECLARE:
505 case T_ENDFOR:
506 case T_ENDFOREACH:
507 case T_ENDIF:
508 case T_ENDSWITCH:
509 case T_ENDWHILE:
510 case T_EXIT:
511 case T_EXTENDS:
512 case T_FINAL:
513 case T_FINALLY:
514 case T_FOREACH:
515 case T_FUNCTION:
516 case T_GLOBAL:
517 case T_IF:
518 case T_IMPLEMENTS:
519 case T_INCLUDE:
520 case T_INCLUDE_ONCE:
521 case T_INSTANCEOF:
522 case T_INSTEADOF:
523 case T_INTERFACE:
524 case T_ISSET:
525 case T_LOGICAL_AND:
526 case T_LOGICAL_OR:
527 case T_LOGICAL_XOR:
528 case T_NAMESPACE:
529 case T_NEW:
530 case T_PRIVATE:
531 case T_PROTECTED:
532 case T_PUBLIC:
533 case T_REQUIRE:
534 case T_REQUIRE_ONCE:
535 case T_RETURN:
536 case T_STATIC:
537 case T_THROW:
538 case T_TRAIT:
539 case T_TRY:
540 case T_UNSET:
541 case T_USE:
542 case T_VAR:
543 case T_WHILE:
544 case T_YIELD:
545 $colour = 'keyword';
546
547 break;
548
549 default:
550 $colour = 'default';
551 }
552 }
553
554 $result[$i] .= \sprintf(
555 '<span class="%s">%s</span>',
556 $colour,
557 $line
558 );
559 }
560
561 if (isset($lines[$jj + 1])) {
562 $result[++$i] = '';
563 }
564 }
565 }
566 }
567
568 if ($fileEndsWithNewLine) {
569 unset($result[\count($result) - 1]);
570 }
571
572 return $result;
573 }
574 }