• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 14434600573

14 Apr 2025 12:01AM UTC coverage: 78.902%. Remained the same
14434600573

Pull #967

github

web-flow
Merge e2f817c09 into 7925a214c
Pull Request #967: PrintProgressDotsTest: fix up param names in data sets

24963 of 31638 relevant lines covered (78.9%)

68.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Reporter.php
1
<?php
2
/**
3
 * Manages reporting of errors and warnings.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer;
11

12
use PHP_CodeSniffer\Exceptions\DeepExitException;
13
use PHP_CodeSniffer\Exceptions\RuntimeException;
14
use PHP_CodeSniffer\Files\File;
15
use PHP_CodeSniffer\Reports\Report;
16
use PHP_CodeSniffer\Util\Common;
17

18
class Reporter
19
{
20

21
    /**
22
     * The config data for the run.
23
     *
24
     * @var \PHP_CodeSniffer\Config
25
     */
26
    public $config = null;
27

28
    /**
29
     * Total number of files that contain errors or warnings.
30
     *
31
     * @var integer
32
     */
33
    public $totalFiles = 0;
34

35
    /**
36
     * Total number of errors found during the run.
37
     *
38
     * @var integer
39
     */
40
    public $totalErrors = 0;
41

42
    /**
43
     * Total number of warnings found during the run.
44
     *
45
     * @var integer
46
     */
47
    public $totalWarnings = 0;
48

49
    /**
50
     * Total number of errors/warnings that can be fixed.
51
     *
52
     * @var integer
53
     */
54
    public $totalFixable = 0;
55

56
    /**
57
     * Total number of errors/warnings that were fixed.
58
     *
59
     * @var integer
60
     */
61
    public $totalFixed = 0;
62

63
    /**
64
     * When the PHPCS run started.
65
     *
66
     * @var float
67
     */
68
    public static $startTime = 0;
69

70
    /**
71
     * A cache of report objects.
72
     *
73
     * @var array
74
     */
75
    private $reports = [];
76

77
    /**
78
     * A cache of opened temporary files.
79
     *
80
     * @var array
81
     */
82
    private $tmpFiles = [];
83

84

85
    /**
86
     * Initialise the reporter.
87
     *
88
     * All reports specified in the config will be created and their
89
     * output file (or a temp file if none is specified) initialised by
90
     * clearing the current contents.
91
     *
92
     * @param \PHP_CodeSniffer\Config $config The config data for the run.
93
     *
94
     * @return void
95
     * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If a custom report class could not be found.
96
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException  If a report class is incorrectly set up.
97
     */
98
    public function __construct(Config $config)
×
99
    {
100
        $this->config = $config;
×
101

102
        foreach ($config->reports as $type => $output) {
×
103
            if ($output === null) {
×
104
                $output = $config->reportFile;
×
105
            }
106

107
            $reportClassName = '';
×
108
            if (strpos($type, '.') !== false) {
×
109
                // This is a path to a custom report class.
110
                $filename = realpath($type);
×
111
                if ($filename === false) {
×
112
                    $error = "ERROR: Custom report \"$type\" not found".PHP_EOL;
×
113
                    throw new DeepExitException($error, 3);
×
114
                }
115

116
                $reportClassName = Autoload::loadFile($filename);
×
117
            } else if (class_exists('PHP_CodeSniffer\Reports\\'.ucfirst($type)) === true) {
×
118
                // PHPCS native report.
119
                $reportClassName = 'PHP_CodeSniffer\Reports\\'.ucfirst($type);
×
120
            } else if (class_exists($type) === true) {
×
121
                // FQN of a custom report.
122
                $reportClassName = $type;
×
123
            } else {
124
                // OK, so not a FQN, try and find the report using the registered namespaces.
125
                $registeredNamespaces = Autoload::getSearchPaths();
×
126
                $trimmedType          = ltrim($type, '\\');
×
127

128
                foreach ($registeredNamespaces as $nsPrefix) {
×
129
                    if ($nsPrefix === '') {
×
130
                        continue;
×
131
                    }
132

133
                    if (class_exists($nsPrefix.'\\'.$trimmedType) === true) {
×
134
                        $reportClassName = $nsPrefix.'\\'.$trimmedType;
×
135
                        break;
×
136
                    }
137
                }
138
            }//end if
139

140
            if ($reportClassName === '') {
×
141
                $error = "ERROR: Class file for report \"$type\" not found".PHP_EOL;
×
142
                throw new DeepExitException($error, 3);
×
143
            }
144

145
            $reportClass = new $reportClassName();
×
146
            if (($reportClass instanceof Report) === false) {
×
147
                throw new RuntimeException('Class "'.$reportClassName.'" must implement the "PHP_CodeSniffer\Report" interface.');
×
148
            }
149

150
            $this->reports[$type] = [
×
151
                'output' => $output,
×
152
                'class'  => $reportClass,
×
153
            ];
×
154

155
            if ($output === null) {
×
156
                // Using a temp file.
157
                // This needs to be set in the constructor so that all
158
                // child procs use the same report file when running in parallel.
159
                $this->tmpFiles[$type] = tempnam(sys_get_temp_dir(), 'phpcs');
×
160
                file_put_contents($this->tmpFiles[$type], '');
×
161
            } else {
162
                file_put_contents($output, '');
×
163
            }
164
        }//end foreach
165

166
    }//end __construct()
167

168

169
    /**
170
     * Generates and prints final versions of all reports.
171
     *
172
     * Returns TRUE if any of the reports output content to the screen
173
     * or FALSE if all reports were silently printed to a file.
174
     *
175
     * @return bool
176
     */
177
    public function printReports()
×
178
    {
179
        $toScreen = false;
×
180
        foreach ($this->reports as $type => $report) {
×
181
            if ($report['output'] === null) {
×
182
                $toScreen = true;
×
183
            }
184

185
            $this->printReport($type);
×
186
        }
187

188
        return $toScreen;
×
189

190
    }//end printReports()
191

192

193
    /**
194
     * Generates and prints a single final report.
195
     *
196
     * @param string $report The report type to print.
197
     *
198
     * @return void
199
     */
200
    public function printReport($report)
×
201
    {
202
        $reportClass = $this->reports[$report]['class'];
×
203
        $reportFile  = $this->reports[$report]['output'];
×
204

205
        if ($reportFile !== null) {
×
206
            $filename = $reportFile;
×
207
            $toScreen = false;
×
208
        } else {
209
            if (isset($this->tmpFiles[$report]) === true) {
×
210
                $filename = $this->tmpFiles[$report];
×
211
            } else {
212
                $filename = null;
×
213
            }
214

215
            $toScreen = true;
×
216
        }
217

218
        $reportCache = '';
×
219
        if ($filename !== null) {
×
220
            $reportCache = file_get_contents($filename);
×
221
        }
222

223
        ob_start();
×
224
        $reportClass->generate(
×
225
            $reportCache,
×
226
            $this->totalFiles,
×
227
            $this->totalErrors,
×
228
            $this->totalWarnings,
×
229
            $this->totalFixable,
×
230
            $this->config->showSources,
×
231
            $this->config->reportWidth,
×
232
            $this->config->interactive,
×
233
            $toScreen
×
234
        );
×
235
        $generatedReport = ob_get_contents();
×
236
        ob_end_clean();
×
237

238
        if ($this->config->colors !== true || $reportFile !== null) {
×
239
            $generatedReport = Common::stripColors($generatedReport);
×
240
        }
241

242
        if ($reportFile !== null) {
×
243
            if (PHP_CODESNIFFER_VERBOSITY > 0) {
×
244
                echo $generatedReport;
×
245
            }
246

247
            file_put_contents($reportFile, $generatedReport.PHP_EOL);
×
248
        } else {
249
            echo $generatedReport;
×
250
            if ($filename !== null && file_exists($filename) === true) {
×
251
                unlink($filename);
×
252
                unset($this->tmpFiles[$report]);
×
253
            }
254
        }
255

256
    }//end printReport()
257

258

259
    /**
260
     * Caches the result of a single processed file for all reports.
261
     *
262
     * The report content that is generated is appended to the output file
263
     * assigned to each report. This content may be an intermediate report format
264
     * and not reflect the final report output.
265
     *
266
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file that has been processed.
267
     *
268
     * @return void
269
     */
270
    public function cacheFileReport(File $phpcsFile)
×
271
    {
272
        if (isset($this->config->reports) === false) {
×
273
            // This happens during unit testing, or any time someone just wants
274
            // the error data and not the printed report.
275
            return;
×
276
        }
277

278
        $reportData  = $this->prepareFileReport($phpcsFile);
×
279
        $errorsShown = false;
×
280

281
        foreach ($this->reports as $type => $report) {
×
282
            $reportClass = $report['class'];
×
283

284
            ob_start();
×
285
            $result = $reportClass->generateFileReport($reportData, $phpcsFile, $this->config->showSources, $this->config->reportWidth);
×
286
            if ($result === true) {
×
287
                $errorsShown = true;
×
288
            }
289

290
            $generatedReport = ob_get_contents();
×
291
            ob_end_clean();
×
292

293
            if ($report['output'] === null) {
×
294
                // Using a temp file.
295
                if (isset($this->tmpFiles[$type]) === false) {
×
296
                    // When running in interactive mode, the reporter prints the full
297
                    // report many times, which will unlink the temp file. So we need
298
                    // to create a new one if it doesn't exist.
299
                    $this->tmpFiles[$type] = tempnam(sys_get_temp_dir(), 'phpcs');
×
300
                    file_put_contents($this->tmpFiles[$type], '');
×
301
                }
302

303
                file_put_contents($this->tmpFiles[$type], $generatedReport, (FILE_APPEND | LOCK_EX));
×
304
            } else {
305
                file_put_contents($report['output'], $generatedReport, (FILE_APPEND | LOCK_EX));
×
306
            }//end if
307
        }//end foreach
308

309
        if ($errorsShown === true || PHP_CODESNIFFER_CBF === true) {
×
310
            $this->totalFiles++;
×
311
            $this->totalErrors   += $reportData['errors'];
×
312
            $this->totalWarnings += $reportData['warnings'];
×
313

314
            // When PHPCBF is running, we need to use the fixable error values
315
            // after the report has run and fixed what it can.
316
            if (PHP_CODESNIFFER_CBF === true) {
×
317
                $this->totalFixable += $phpcsFile->getFixableCount();
×
318
                $this->totalFixed   += $phpcsFile->getFixedCount();
×
319
            } else {
320
                $this->totalFixable += $reportData['fixable'];
×
321
            }
322
        }
323

324
    }//end cacheFileReport()
325

326

327
    /**
328
     * Generate summary information to be used during report generation.
329
     *
330
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file that has been processed.
331
     *
332
     * @return array<string, string|int|array> Prepared report data.
333
     *                                         The format of prepared data is as follows:
334
     *                                         ```
335
     *                                         array(
336
     *                                           'filename' => string The name of the current file.
337
     *                                           'errors'   => int    The number of errors seen in the current file.
338
     *                                           'warnings' => int    The number of warnings seen in the current file.
339
     *                                           'fixable'  => int    The number of fixable issues seen in the current file.
340
     *                                           'messages' => array(
341
     *                                             int <Line number> => array(
342
     *                                               int <Column number> => array(
343
     *                                                 int <Message index> => array(
344
     *                                                   'message'  => string The error/warning message.
345
     *                                                   'source'   => string The full error code for the message.
346
     *                                                   'severity' => int    The severity of the message.
347
     *                                                   'fixable'  => bool   Whether this error/warning is auto-fixable.
348
     *                                                   'type'     => string The type of message. Either 'ERROR' or 'WARNING'.
349
     *                                                 )
350
     *                                               )
351
     *                                             )
352
     *                                           )
353
     *                                         )
354
     *                                         ```
355
     */
356
    public function prepareFileReport(File $phpcsFile)
×
357
    {
358
        $report = [
×
359
            'filename' => Common::stripBasepath($phpcsFile->getFilename(), $this->config->basepath),
×
360
            'errors'   => $phpcsFile->getErrorCount(),
×
361
            'warnings' => $phpcsFile->getWarningCount(),
×
362
            'fixable'  => $phpcsFile->getFixableCount(),
×
363
            'messages' => [],
×
364
        ];
×
365

366
        if ($report['errors'] === 0 && $report['warnings'] === 0) {
×
367
            // Perfect score!
368
            return $report;
×
369
        }
370

371
        if ($this->config->recordErrors === false) {
×
372
            $message  = 'Errors are not being recorded but this report requires error messages. ';
×
373
            $message .= 'This report will not show the correct information.';
×
374
            $report['messages'][1][1] = [
×
375
                [
×
376
                    'message'  => $message,
×
377
                    'source'   => 'Internal.RecordErrors',
×
378
                    'severity' => 5,
×
379
                    'fixable'  => false,
×
380
                    'type'     => 'ERROR',
×
381
                ],
×
382
            ];
×
383
            return $report;
×
384
        }
385

386
        $errors = [];
×
387

388
        // Merge errors and warnings.
389
        foreach ($phpcsFile->getErrors() as $line => $lineErrors) {
×
390
            foreach ($lineErrors as $column => $colErrors) {
×
391
                $newErrors = [];
×
392
                foreach ($colErrors as $data) {
×
393
                    $newErrors[] = [
×
394
                        'message'  => $data['message'],
×
395
                        'source'   => $data['source'],
×
396
                        'severity' => $data['severity'],
×
397
                        'fixable'  => $data['fixable'],
×
398
                        'type'     => 'ERROR',
×
399
                    ];
×
400
                }
401

402
                $errors[$line][$column] = $newErrors;
×
403
            }
404

405
            ksort($errors[$line]);
×
406
        }//end foreach
407

408
        foreach ($phpcsFile->getWarnings() as $line => $lineWarnings) {
×
409
            foreach ($lineWarnings as $column => $colWarnings) {
×
410
                $newWarnings = [];
×
411
                foreach ($colWarnings as $data) {
×
412
                    $newWarnings[] = [
×
413
                        'message'  => $data['message'],
×
414
                        'source'   => $data['source'],
×
415
                        'severity' => $data['severity'],
×
416
                        'fixable'  => $data['fixable'],
×
417
                        'type'     => 'WARNING',
×
418
                    ];
×
419
                }
420

421
                if (isset($errors[$line]) === false) {
×
422
                    $errors[$line] = [];
×
423
                }
424

425
                if (isset($errors[$line][$column]) === true) {
×
426
                    $errors[$line][$column] = array_merge(
×
427
                        $newWarnings,
×
428
                        $errors[$line][$column]
×
429
                    );
×
430
                } else {
431
                    $errors[$line][$column] = $newWarnings;
×
432
                }
433
            }//end foreach
434

435
            ksort($errors[$line]);
×
436
        }//end foreach
437

438
        ksort($errors);
×
439
        $report['messages'] = $errors;
×
440
        return $report;
×
441

442
    }//end prepareFileReport()
443

444

445
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc