Skip to content

Commit 87549db

Browse files
Update ScanCommand.php
Adds junit support Adds support for columns
1 parent 48634e3 commit 87549db

File tree

1 file changed

+168
-12
lines changed

1 file changed

+168
-12
lines changed

src/ScanCommand.php

Lines changed: 168 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@
33

44
use Symfony\Component\Console\Command\Command;
55
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
6+
use Symfony\Component\Console\Helper\Table;
67
use Symfony\Component\Console\Input\InputArgument;
78
use Symfony\Component\Console\Input\InputDefinition;
89
use Symfony\Component\Console\Input\InputInterface;
910
use Symfony\Component\Console\Input\InputOption;
1011
use Symfony\Component\Console\Output\OutputInterface;
11-
use Symfony\Component\Console\Helper\Table;
1212

1313
class ScanCommand extends Command
1414
{
1515
protected $args;
1616

1717
const STDOUT = 1;
1818
const JSON = 2;
19+
const JUNIT = 3;
1920

2021
/**
2122
* @var PhpCodeFixer
@@ -49,7 +50,7 @@ class ScanCommand extends Command
4950
/**
5051
* @var string
5152
*/
52-
protected $jsonOutputPath;
53+
protected $outputFile = null;
5354

5455
/**
5556
* @var int
@@ -79,8 +80,10 @@ protected function configure()
7980
implode(', ', PhpCodeFixer::$defaultFileExtensions)),
8081
new InputOption('skip-checks', null, InputOption::VALUE_OPTIONAL,
8182
'Skip all checks containing any of the given values. Pass a comma-separated list for multiple values.'),
82-
new InputOption('output-json', null, InputOption::VALUE_OPTIONAL,
83-
'Path to store json-file with analyze results. If \'-\' passed, json will be printed on stdout.'),
83+
new InputOption('output', null, InputOption::VALUE_OPTIONAL,
84+
'The output type required. Options: stdout, json, junit. Defaults to stdout.'),
85+
new InputOption('output-file', null, InputOption::VALUE_OPTIONAL,
86+
'File path to store results where output is not stdout.'),
8487
new InputArgument('files', InputArgument::IS_ARRAY | InputArgument::REQUIRED,
8588
'Which files you want to analyze (separate multiple names with a space)?'),
8689
])
@@ -96,9 +99,29 @@ protected function configure()
9699
protected function execute(InputInterface $input, OutputInterface $output)
97100
{
98101
try {
99-
$this->jsonOutputPath = $input->getOption('output-json');
100-
if (!empty($this->jsonOutputPath))
101-
$this->outputMode = self::JSON;
102+
if(!empty($input->getOption('output'))){
103+
switch($input->getOption('output')){
104+
case 'json':
105+
$this->outputMode = self::JSON;
106+
break;
107+
case 'junit':
108+
$this->outputMode = self::JUNIT;
109+
break;
110+
case 'stdout':
111+
$this->outputMode = self::STDOUT;
112+
break;
113+
default:
114+
throw new ConfigurationException('Output is not valid. Available outputs: stdout, json, junit');
115+
break;
116+
}
117+
}
118+
119+
if(!empty($input->getOption('output-file'))){
120+
if(!in_array($this->outputMode, [self::JSON, self::JUNIT])){
121+
throw new ConfigurationException('An output file can only be provided for: json, junit');
122+
}
123+
$this->outputFile = trim($input->getOption('output-file'));
124+
}
102125

103126
$this->analyzer = $this->configureAnalyzer(new PhpCodeFixer(), $input, $output);
104127
$this->analyzer->initializeIssuesBank();
@@ -301,7 +324,7 @@ protected function outputToStdout(OutputInterface $output)
301324
if (!empty($report)) {
302325
$table = new Table($output);
303326
$table
304-
->setHeaders([/*'PHP',*/ 'File:Line', 'Type', 'Issue']);
327+
->setHeaders([/*'PHP',*/ 'File (Line:Column)', 'Type', 'Issue']);
305328
$versions = array_keys($report_issues);
306329
sort($versions);
307330

@@ -362,7 +385,7 @@ protected function outputToStdout(OutputInterface $output)
362385
);
363386

364387
$rows[] = [
365-
'<comment>'.$issue->file.'</comment>:'.$issue->line,
388+
'<comment>'.$issue->file.'</comment> ('.$issue->line.':'.$issue->column.')',
366389
$issue->category,
367390
$issue_text,
368391
];
@@ -511,6 +534,7 @@ protected function outputToJson($jsonFile)
511534
'file' => $issue->file,
512535
'path' => $report->getRemovablePath().$issue->file,
513536
'line' => $issue->line,
537+
'column' => $issue->column,
514538
'category' => $issue->category,
515539
'type' => $issue->type,
516540
'checker' => $issue->text,
@@ -541,14 +565,135 @@ protected function outputToJson($jsonFile)
541565
return count($value) > 0;
542566
}), JSON_PRETTY_PRINT);
543567

544-
if ($jsonFile === '-')
568+
if (in_array($jsonFile, ['', null]))
545569
fwrite(STDOUT, $json);
546570
else
547571
file_put_contents($jsonFile, $json);
548572

549573
return $total_issues;
550574
}
551575

576+
/**
577+
* @param $junitFile
578+
* @return int
579+
*/
580+
protected function outputToJunit($junitFile)
581+
{
582+
583+
$total_issues = 0;
584+
$total_tests = 0;
585+
$data = [];
586+
$filesWithFailures = [];
587+
if (!empty($this->reports)) {
588+
foreach ($this->reports as $report) {
589+
$report_issues = $report->getIssues();
590+
if (!empty($report)) {
591+
$versions = array_keys($report_issues);
592+
sort($versions);
593+
594+
// print issues by version
595+
foreach ($versions as $version) {
596+
// iterate issues
597+
foreach ($report_issues[$version] as $issue) {
598+
$key = $issue->file.'_'.$version;
599+
if(!array_key_exists($key, $data)){
600+
$data[$key] = [
601+
'name' => $issue->path . ' (PHP ' . $version . ')',
602+
'failures' => [],
603+
];
604+
}
605+
$data[$key]['failures'][] = $issue;
606+
$total_issues++;
607+
$total_tests++;
608+
$filesWithFailures[] = $issue->path;
609+
}
610+
}
611+
}
612+
}
613+
}
614+
615+
foreach(array_diff($this->analyzer->scannedFiles, $filesWithFailures) as $path){
616+
$data[] = [
617+
'name' => $path,
618+
'failures' => [],
619+
];
620+
}
621+
622+
// add files that passed as a test
623+
$total_tests += count(array_diff($this->analyzer->scannedFiles, $filesWithFailures));
624+
625+
ob_start();
626+
echo '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
627+
echo '<testsuites name="PhpDeprecationDetector '.PhpCodeFixer::VERSION.'" errors="0" tests="'.$total_tests.'" failures="'.$total_issues.'">'.PHP_EOL;
628+
629+
foreach($data as $test){
630+
$out = new \XMLWriter;
631+
$out->openMemory();
632+
$out->setIndent(true);
633+
634+
$out->startElement('testsuite');
635+
$out->writeAttribute('name', $test['name']);
636+
$out->writeAttribute('errors', 0);
637+
638+
if (count($test['failures']) === 0) {
639+
$out->writeAttribute('tests', 1);
640+
$out->writeAttribute('failures', 0);
641+
642+
$out->startElement('testcase');
643+
$out->writeAttribute('name', $test['name']);
644+
$out->endElement();
645+
} else {
646+
647+
$out->writeAttribute('tests', count($test['failures']));
648+
$out->writeAttribute('failures', count($test['failures']));
649+
650+
if(count($test['failures']) > 0){
651+
// sort by line+column
652+
usort($test['failures'], function($a, $b){
653+
$diff = $a->line - $b->line;
654+
return ($diff !== 0) ? $diff : $a->column - $b->column;
655+
});
656+
657+
foreach($test['failures'] as $failure){
658+
$out->startElement('testcase');
659+
$out->writeAttribute('name', $failure->text.' at '.$failure->path." ($failure->line:$failure->column)");
660+
661+
$out->startElement('failure');
662+
663+
if (!empty($failure->replacement)) {
664+
if ($failure->category === ReportIssue::CHANGED) {
665+
$out->writeAttribute('type', $failure->type);
666+
$out->writeAttribute('message', 'Problem: ' . $failure->text . '; Note: ' . $failure->replacement);
667+
} else {
668+
$out->writeAttribute('type', $failure->type);
669+
$out->writeAttribute('message', 'Problem: ' . $failure->text.($failure->type === 'function' ? '()' : '') . '; Replacement: ' . $failure->replacement.($failure->type === 'function' ? '()' : ''));
670+
}
671+
} else {
672+
$out->writeAttribute('type', $failure->type);
673+
$out->writeAttribute('message', $failure->category);
674+
}
675+
676+
$out->endElement();
677+
678+
$out->endElement();
679+
}
680+
}
681+
}
682+
$out->endElement();
683+
echo $out->flush();
684+
}
685+
686+
echo '</testsuites>'.PHP_EOL;
687+
$junit = ob_get_clean();
688+
689+
if (in_array($junitFile, ['', null]))
690+
fwrite(STDOUT, $junit);
691+
else
692+
file_put_contents($junitFile, $junit);
693+
694+
return $total_issues;
695+
}
696+
552697
/**
553698
* @param OutputInterface $output
554699
*/
@@ -561,7 +706,18 @@ protected function outputAnalyzeResult(OutputInterface $output)
561706
break;
562707

563708
case self::JSON:
564-
$total_issues = $this->outputToJson($this->jsonOutputPath);
709+
$total_issues = $this->outputToJson($this->outputFile);
710+
if ($this->isVerbose()) {
711+
if ($total_issues > 0)
712+
$output->writeln('<bg=red;fg=white>Total problems: ' . $total_issues . '</>');
713+
else
714+
$output->writeln('<bg=green;fg=white>Analyzer has not detected any problems in your code.</>');
715+
$this->printMemoryUsage($output);
716+
}
717+
break;
718+
719+
case self::JUNIT:
720+
$total_issues = $this->outputToJunit($this->outputFile);
565721
if ($this->isVerbose()) {
566722
if ($total_issues > 0)
567723
$output->writeln('<bg=red;fg=white>Total problems: ' . $total_issues . '</>');
@@ -580,6 +736,6 @@ protected function outputAnalyzeResult(OutputInterface $output)
580736
*/
581737
protected function isVerbose()
582738
{
583-
return $this->outputMode !== self::JSON || $this->jsonOutputPath !== '-';
739+
return $this->outputMode == self::STDOUT;
584740
}
585741
}

0 commit comments

Comments
 (0)