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
11namespace SebastianBergmann\CodeCoverage\Report\Html;
12
13use SebastianBergmann\CodeCoverage\Node\AbstractNode;
14use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
15
16/**
17 * Renders the dashboard for a directory node.
18 */
19class Dashboard extends Renderer
20{
21    /**
22     * @param DirectoryNode $node
23     * @param string        $file
24     */
25    public function render(DirectoryNode $node, $file)
26    {
27        $classes  = $node->getClassesAndTraits();
28        $template = new \Text_Template(
29            $this->templatePath . 'dashboard.html',
30            '{{',
31            '}}'
32        );
33
34        $this->setCommonTemplateVariables($template, $node);
35
36        $baseLink             = $node->getId() . '/';
37        $complexity           = $this->complexity($classes, $baseLink);
38        $coverageDistribution = $this->coverageDistribution($classes);
39        $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink);
40        $projectRisks         = $this->projectRisks($classes, $baseLink);
41
42        $template->setVar(
43            [
44                'insufficient_coverage_classes' => $insufficientCoverage['class'],
45                'insufficient_coverage_methods' => $insufficientCoverage['method'],
46                'project_risks_classes'         => $projectRisks['class'],
47                'project_risks_methods'         => $projectRisks['method'],
48                'complexity_class'              => $complexity['class'],
49                'complexity_method'             => $complexity['method'],
50                'class_coverage_distribution'   => $coverageDistribution['class'],
51                'method_coverage_distribution'  => $coverageDistribution['method']
52            ]
53        );
54
55        $template->renderTo($file);
56    }
57
58    /**
59     * Returns the data for the Class/Method Complexity charts.
60     *
61     * @param array  $classes
62     * @param string $baseLink
63     *
64     * @return array
65     */
66    protected function complexity(array $classes, $baseLink)
67    {
68        $result = ['class' => [], 'method' => []];
69
70        foreach ($classes as $className => $class) {
71            foreach ($class['methods'] as $methodName => $method) {
72                if ($className != '*') {
73                    $methodName = $className . '::' . $methodName;
74                }
75
76                $result['method'][] = [
77                    $method['coverage'],
78                    $method['ccn'],
79                    sprintf(
80                        '<a href="%s">%s</a>',
81                        str_replace($baseLink, '', $method['link']),
82                        $methodName
83                    )
84                ];
85            }
86
87            $result['class'][] = [
88                $class['coverage'],
89                $class['ccn'],
90                sprintf(
91                    '<a href="%s">%s</a>',
92                    str_replace($baseLink, '', $class['link']),
93                    $className
94                )
95            ];
96        }
97
98        return [
99            'class'  => json_encode($result['class']),
100            'method' => json_encode($result['method'])
101        ];
102    }
103
104    /**
105     * Returns the data for the Class / Method Coverage Distribution chart.
106     *
107     * @param array $classes
108     *
109     * @return array
110     */
111    protected function coverageDistribution(array $classes)
112    {
113        $result = [
114            'class' => [
115                '0%'      => 0,
116                '0-10%'   => 0,
117                '10-20%'  => 0,
118                '20-30%'  => 0,
119                '30-40%'  => 0,
120                '40-50%'  => 0,
121                '50-60%'  => 0,
122                '60-70%'  => 0,
123                '70-80%'  => 0,
124                '80-90%'  => 0,
125                '90-100%' => 0,
126                '100%'    => 0
127            ],
128            'method' => [
129                '0%'      => 0,
130                '0-10%'   => 0,
131                '10-20%'  => 0,
132                '20-30%'  => 0,
133                '30-40%'  => 0,
134                '40-50%'  => 0,
135                '50-60%'  => 0,
136                '60-70%'  => 0,
137                '70-80%'  => 0,
138                '80-90%'  => 0,
139                '90-100%' => 0,
140                '100%'    => 0
141            ]
142        ];
143
144        foreach ($classes as $class) {
145            foreach ($class['methods'] as $methodName => $method) {
146                if ($method['coverage'] == 0) {
147                    $result['method']['0%']++;
148                } elseif ($method['coverage'] == 100) {
149                    $result['method']['100%']++;
150                } else {
151                    $key = floor($method['coverage'] / 10) * 10;
152                    $key = $key . '-' . ($key + 10) . '%';
153                    $result['method'][$key]++;
154                }
155            }
156
157            if ($class['coverage'] == 0) {
158                $result['class']['0%']++;
159            } elseif ($class['coverage'] == 100) {
160                $result['class']['100%']++;
161            } else {
162                $key = floor($class['coverage'] / 10) * 10;
163                $key = $key . '-' . ($key + 10) . '%';
164                $result['class'][$key]++;
165            }
166        }
167
168        return [
169            'class'  => json_encode(array_values($result['class'])),
170            'method' => json_encode(array_values($result['method']))
171        ];
172    }
173
174    /**
175     * Returns the classes / methods with insufficient coverage.
176     *
177     * @param array  $classes
178     * @param string $baseLink
179     *
180     * @return array
181     */
182    protected function insufficientCoverage(array $classes, $baseLink)
183    {
184        $leastTestedClasses = [];
185        $leastTestedMethods = [];
186        $result             = ['class' => '', 'method' => ''];
187
188        foreach ($classes as $className => $class) {
189            foreach ($class['methods'] as $methodName => $method) {
190                if ($method['coverage'] < $this->highLowerBound) {
191                    if ($className != '*') {
192                        $key = $className . '::' . $methodName;
193                    } else {
194                        $key = $methodName;
195                    }
196
197                    $leastTestedMethods[$key] = $method['coverage'];
198                }
199            }
200
201            if ($class['coverage'] < $this->highLowerBound) {
202                $leastTestedClasses[$className] = $class['coverage'];
203            }
204        }
205
206        asort($leastTestedClasses);
207        asort($leastTestedMethods);
208
209        foreach ($leastTestedClasses as $className => $coverage) {
210            $result['class'] .= sprintf(
211                '       <tr><td><a href="%s">%s</a></td><td class="text-right">%d%%</td></tr>' . "\n",
212                str_replace($baseLink, '', $classes[$className]['link']),
213                $className,
214                $coverage
215            );
216        }
217
218        foreach ($leastTestedMethods as $methodName => $coverage) {
219            list($class, $method) = explode('::', $methodName);
220
221            $result['method'] .= sprintf(
222                '       <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d%%</td></tr>' . "\n",
223                str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']),
224                $methodName,
225                $method,
226                $coverage
227            );
228        }
229
230        return $result;
231    }
232
233    /**
234     * Returns the project risks according to the CRAP index.
235     *
236     * @param array  $classes
237     * @param string $baseLink
238     *
239     * @return array
240     */
241    protected function projectRisks(array $classes, $baseLink)
242    {
243        $classRisks  = [];
244        $methodRisks = [];
245        $result      = ['class' => '', 'method' => ''];
246
247        foreach ($classes as $className => $class) {
248            foreach ($class['methods'] as $methodName => $method) {
249                if ($method['coverage'] < $this->highLowerBound &&
250                    $method['ccn'] > 1) {
251                    if ($className != '*') {
252                        $key = $className . '::' . $methodName;
253                    } else {
254                        $key = $methodName;
255                    }
256
257                    $methodRisks[$key] = $method['crap'];
258                }
259            }
260
261            if ($class['coverage'] < $this->highLowerBound &&
262                $class['ccn'] > count($class['methods'])) {
263                $classRisks[$className] = $class['crap'];
264            }
265        }
266
267        arsort($classRisks);
268        arsort($methodRisks);
269
270        foreach ($classRisks as $className => $crap) {
271            $result['class'] .= sprintf(
272                '       <tr><td><a href="%s">%s</a></td><td class="text-right">%d</td></tr>' . "\n",
273                str_replace($baseLink, '', $classes[$className]['link']),
274                $className,
275                $crap
276            );
277        }
278
279        foreach ($methodRisks as $methodName => $crap) {
280            list($class, $method) = explode('::', $methodName);
281
282            $result['method'] .= sprintf(
283                '       <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d</td></tr>' . "\n",
284                str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']),
285                $methodName,
286                $method,
287                $crap
288            );
289        }
290
291        return $result;
292    }
293
294    protected function getActiveBreadcrumb(AbstractNode $node)
295    {
296        return sprintf(
297            '        <li><a href="index.html">%s</a></li>' . "\n" .
298            '        <li class="active">(Dashboard)</li>' . "\n",
299            $node->getName()
300        );
301    }
302}
303