33
44use Symfony \Component \Console \Command \Command ;
55use Symfony \Component \Console \Formatter \OutputFormatterStyle ;
6+ use Symfony \Component \Console \Helper \Table ;
67use Symfony \Component \Console \Input \InputArgument ;
78use Symfony \Component \Console \Input \InputDefinition ;
89use Symfony \Component \Console \Input \InputInterface ;
910use Symfony \Component \Console \Input \InputOption ;
1011use Symfony \Component \Console \Output \OutputInterface ;
11- use Symfony \Component \Console \Helper \Table ;
1212
1313class 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