Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds checkstyle compatible renderer, suitable for cs2pr or reviewdog #856

Merged
merged 11 commits into from
Apr 17, 2021
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ At the moment PHPMD comes with the following renderers:
- *json*, formats JSON report.
- *ansi*, a command line friendly format.
- *github*, a format that GitHub Actions understands.
- *checkstyle*, language and tool agnostic XML format

PHPMD for enterprise
--------------------
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');
kylekatarnls marked this conversation as resolved.
Show resolved Hide resolved
$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());
ffflabs marked this conversation as resolved.
Show resolved Hide resolved
//$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
14 changes: 13 additions & 1 deletion src/main/php/PHPMD/TextUI/CommandLineOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use PHPMD\Renderer\HTMLRenderer;
use PHPMD\Renderer\JSONRenderer;
use PHPMD\Renderer\TextRenderer;
use PHPMD\Renderer\CheckStyleRenderer;
use PHPMD\Renderer\XMLRenderer;
use PHPMD\Rule;

Expand Down Expand Up @@ -209,8 +210,9 @@ public function __construct(array $args, array $availableRuleSets = array())
case '--reportfile-html':
case '--reportfile-text':
case '--reportfile-xml':
case '--reportfile-checkstyle':
case '--reportfile-json':
preg_match('(^\-\-reportfile\-(xml|html|text|json)$)', $arg, $match);
preg_match('(^\-\-reportfile\-(xml|checkstyle|html|text|json)$)', $arg, $match);
$this->reportFiles[$match[1]] = array_shift($args);
break;
default:
Expand Down Expand Up @@ -397,6 +399,8 @@ public function createRenderer($reportFormat = null)
return $this->createAnsiRenderer();
case 'github':
return $this->createGitHubRenderer();
case 'checkstyle':
return $this->createCheckStyleRenderer();
default:
return $this->createCustomRenderer();
}
Expand Down Expand Up @@ -450,6 +454,14 @@ protected function createJsonRenderer()
return new JSONRenderer();
}

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

/**
* @return \PHPMD\AbstractRenderer
* @throws \InvalidArgumentException
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 @@ -236,7 +236,7 @@ public function testCliUsageContainsAutoDiscoveredRenderers()
$args = array(__FILE__, __FILE__, 'text', 'codesize');
$opts = new CommandLineOptions($args);

$this->assertContains('Available formats: ansi, github, html, json, text, xml.', $opts->usage());
$this->assertContains('Available formats: ansi, checkstyle, github, html, json, 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 @@ -122,6 +122,8 @@ public function testWithMultipleReportFiles()
$text = self::createTempFileUri(),
'--reportfile-json',
$json = self::createTempFileUri(),
'--reportfile-checkstyle',
$checkstyle = self::createTempFileUri(),
);

Command::main($args);
Expand All @@ -130,6 +132,7 @@ public function testWithMultipleReportFiles()
$this->assertFileExists($html);
$this->assertFileExists($text);
$this->assertFileExists($json);
$this->assertFileExists($checkstyle);
}

public function testOutput()
Expand Down