Skip to content

Commit

Permalink
Merge pull request #856 from ffflabs/feature/checkstyle_renderer
Browse files Browse the repository at this point in the history
adds checkstyle compatible renderer, suitable for cs2pr or reviewdog
  • Loading branch information
kylekatarnls committed Apr 17, 2021
2 parents 1a0ddb8 + 0101e01 commit 73af583
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 13 deletions.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ At the moment PHPMD comes with the following renderers:
- *ansi*, a command line friendly format.
- *github*, a format that GitHub Actions understands.
- *sarif*, the Static Analysis Results Interchange Format.
- *checkstyle*, language and tool agnostic XML format

Baseline
--------
Expand Down
106 changes: 106 additions & 0 deletions src/main/php/PHPMD/Renderer/CheckStyleRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

namespace PHPMD\Renderer;

use PHPMD\PHPMD;
use PHPMD\Report;
use PHPMD\Renderer\XMLRenderer;

/**
* This class will render a Java-checkstyle compatible xml-report.
* for use with cs2pr and others
*/
class CheckStyleRenderer extends XMLRenderer
{
/**
* Temporary property that holds the name of the last rendered file, it is
* used to detect the next processed file.
*
* @var string
*/
private $fileName;

/**
* Get a violation severity level according to the priority
* of the rule that's being broken
* @see https://checkstyle.sourceforge.io/version/4.4/property_types.html#severity
* - priority 1 maps to error level severity
* - priority 2 maps to warning level severity
* - priority > 2 maps to info level severity
*
* @param integer $priority priority of the broken rule
* @return string either error, warning or info
*/
protected function mapPriorityToSeverity($priority)
{
if ($priority > 2) {
return 'info';
}

return (int)$priority === 2 ? 'warning' : 'error';
}
/**
* This method will be called when the engine has finished the source analysis
* phase.
*
* @param \PHPMD\Report $report
*/
public function renderReport(Report $report)
{
$writer = $this->getWriter();
$writer->write('<checkstyle>');
$writer->write(\PHP_EOL);

foreach ($report->getRuleViolations() as $violation) {
$fileName = $violation->getFileName();

if ($this->fileName !== $fileName) {
// Not first file
if (null !== $this->fileName) {
$writer->write(' </file>' . \PHP_EOL);
}
// Store current file name
$this->fileName = $fileName;

$writer->write(' <file name="' . $fileName . '">' . \PHP_EOL);
}

$rule = $violation->getRule();

$writer->write(' <error');
$writer->write(' line="' . $violation->getBeginLine() . '"');
$writer->write(' endline="' . $violation->getEndLine() . '"');
$writer->write(\sprintf(' severity="%s"', $this->mapPriorityToSeverity($rule->getPriority())));
$writer->write(\sprintf(
' message="%s (%s, %s) "',
\htmlspecialchars($violation->getDescription()),
$rule->getName(),
$rule->getRuleSetName()
));

$this->maybeAdd('package', $violation->getNamespaceName());
$this->maybeAdd('externalInfoUrl', $rule->getExternalInfoUrl());
$this->maybeAdd('function', $violation->getFunctionName());
$this->maybeAdd('class', $violation->getClassName());
$this->maybeAdd('method', $violation->getMethodName());
//$this->_maybeAdd('variable', $violation->getVariableName());

$writer->write(' />' . \PHP_EOL);
}

// Last file and at least one violation
if (null !== $this->fileName) {
$writer->write(' </file>' . \PHP_EOL);
}

foreach ($report->getErrors() as $error) {
$writer->write(' <file name="' . $error->getFile() . '">');
$writer->write($error->getFile());
$writer->write('<error msg="');
$writer->write(\htmlspecialchars($error->getMessage()));
$writer->write(' severity="error" />' . \PHP_EOL);
}

$writer->write('</checkstyle>' . \PHP_EOL);
}
}
2 changes: 1 addition & 1 deletion src/main/php/PHPMD/Renderer/XMLRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public function renderReport(Report $report)
* @param string $value The attribute value.
* @return void
*/
private function maybeAdd($attr, $value)
protected function maybeAdd($attr, $value)
{
if ($value === null || trim($value) === '') {
return;
Expand Down
34 changes: 23 additions & 11 deletions src/main/php/PHPMD/TextUI/CommandLineOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use PHPMD\Renderer\JSONRenderer;
use PHPMD\Renderer\SARIFRenderer;
use PHPMD\Renderer\TextRenderer;
use PHPMD\Renderer\CheckStyleRenderer;
use PHPMD\Renderer\XMLRenderer;
use PHPMD\Rule;

Expand Down Expand Up @@ -241,12 +242,13 @@ public function __construct(array $args, array $availableRuleSets = array())
case '--ignore-violations-on-exit':
$this->ignoreViolationsOnExit = true;
break;
case '--reportfile-checkstyle':
case '--reportfile-html':
case '--reportfile-text':
case '--reportfile-xml':
case '--reportfile-json':
case '--reportfile-sarif':
preg_match('(^\-\-reportfile\-(xml|html|text|json|sarif)$)', $arg, $match);
case '--reportfile-text':
case '--reportfile-xml':
preg_match('(^\-\-reportfile\-(checkstyle|html|json|sarif|text|xml)$)', $arg, $match);
$this->reportFiles[$match[1]] = array_shift($args);
break;
default:
Expand Down Expand Up @@ -452,20 +454,22 @@ public function createRenderer($reportFormat = null)
$reportFormat = $reportFormat ?: $this->reportFormat;

switch ($reportFormat) {
case 'xml':
return $this->createXmlRenderer();
case 'html':
return $this->createHtmlRenderer();
case 'text':
return $this->createTextRenderer();
case 'json':
return $this->createJsonRenderer();
case 'ansi':
return $this->createAnsiRenderer();
case 'checkstyle':
return $this->createCheckStyleRenderer();
case 'github':
return $this->createGitHubRenderer();
case 'html':
return $this->createHtmlRenderer();
case 'json':
return $this->createJsonRenderer();
case 'sarif':
return $this->createSarifRenderer();
case 'text':
return $this->createTextRenderer();
case 'xml':
return $this->createXmlRenderer();
default:
return $this->createCustomRenderer();
}
Expand Down Expand Up @@ -519,6 +523,14 @@ protected function createJsonRenderer()
return new JSONRenderer();
}

/**
* @return \PHPMD\Renderer\JSONRenderer
*/
protected function createCheckStyleRenderer()
{
return new CheckStyleRenderer();
}

/**
* @return \PHPMD\Renderer\SARIFRenderer
*/
Expand Down
105 changes: 105 additions & 0 deletions src/test/php/PHPMD/Renderer/CheckStyleRendererTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php
/**
* This file is part of PHP Mess Detector.
*
* Copyright (c) Manuel Pichler <mapi@phpmd.org>.
* All rights reserved.
*
* Licensed under BSD License
* For full copyright and license information, please see the LICENSE file.
* Redistributions of files must retain the above copyright notice.
*
* @author Manuel Pichler <mapi@phpmd.org>
* @copyright Manuel Pichler. All rights reserved.
* @license https://opensource.org/licenses/bsd-license.php BSD License
* @link http://phpmd.org/
*/

namespace PHPMD\Renderer;

use PHPMD\AbstractTest;
use PHPMD\ProcessingError;
use PHPMD\Stubs\WriterStub;

/**
* Test case for the xml renderer implementation.
*
* @covers \PHPMD\Renderer\XMLRenderer
*/
class CheckStyleRendererTest extends AbstractTest
{
/**
* testRendererCreatesExpectedNumberOfXmlElements
*
* @return void
*/
public function testRendererCreatesExpectedNumberOfXmlElements()
{
// Create a writer instance.
$writer = new WriterStub();

$violations = array(
$this->getRuleViolationMock('/bar.php'),
$this->getRuleViolationMock('/foo.php'),
$this->getRuleViolationMock('/foo.php', 23, 42, null, 'foo <?php bar'),
);

$report = $this->getReportWithNoViolation();
$report->expects($this->once())
->method('getRuleViolations')
->will($this->returnValue(new \ArrayIterator($violations)));
$report->expects($this->once())
->method('getErrors')
->will($this->returnValue(new \ArrayIterator(array())));

$renderer = new XMLRenderer();
$renderer->setWriter($writer);

$renderer->start();
$renderer->renderReport($report);
$renderer->end();

$this->assertXmlEquals(
$writer->getData(),
'renderer/xml_renderer_expected1.xml'
);
}

/**
* testRendererAddsProcessingErrorsToXmlReport
*
* @return void
* @since 1.2.1
*/
public function testRendererAddsProcessingErrorsToXmlReport()
{
// Create a writer instance.
$writer = new WriterStub();

$processingErrors = array(
new ProcessingError('Failed for file "/tmp/foo.php".'),
new ProcessingError('Failed for file "/tmp/bar.php".'),
new ProcessingError('Failed for file "/tmp/baz.php".'),
);

$report = $this->getReportWithNoViolation();
$report->expects($this->once())
->method('getRuleViolations')
->will($this->returnValue(new \ArrayIterator(array())));
$report->expects($this->once())
->method('getErrors')
->will($this->returnValue(new \ArrayIterator($processingErrors)));

$renderer = new XMLRenderer();
$renderer->setWriter($writer);

$renderer->start();
$renderer->renderReport($report);
$renderer->end();

$this->assertXmlEquals(
$writer->getData(),
'renderer/xml_renderer_processing_errors.xml'
);
}
}
2 changes: 1 addition & 1 deletion src/test/php/PHPMD/TextUI/CommandLineOptionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public function testCliUsageContainsAutoDiscoveredRenderers()
$opts = new CommandLineOptions($args);

$this->assertContains(
'Available formats: ansi, baseline, github, html, json, sarif, text, xml.',
'Available formats: ansi, baseline, checkstyle, github, html, json, sarif, text, xml.',
$opts->usage()
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/test/php/PHPMD/TextUI/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ public function testWithMultipleReportFiles()
$text = self::createTempFileUri(),
'--reportfile-json',
$json = self::createTempFileUri(),
'--reportfile-checkstyle',
$checkstyle = self::createTempFileUri(),
'--reportfile-sarif',
$sarif = self::createTempFileUri(),
);
Expand All @@ -170,6 +172,7 @@ public function testWithMultipleReportFiles()
$this->assertFileExists($html);
$this->assertFileExists($text);
$this->assertFileExists($json);
$this->assertFileExists($checkstyle);
$this->assertFileExists($sarif);
}

Expand Down

0 comments on commit 73af583

Please sign in to comment.