Skip to content

Commit

Permalink
Add a renderer which writes an ansi report string
Browse files Browse the repository at this point in the history
  • Loading branch information
PtrTn committed Feb 16, 2019
1 parent 42bf8ad commit 8553415
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ At the moment PHPMD comes with the following three renderers:
- *xml*, which formats the report as XML.
- *text*, simple textual format.
- *html*, single HTML file with possible problems.
- *ansi*, a command line friendly format.

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

namespace PHPMD\Renderer;

use PHPMD\AbstractRenderer;
use PHPMD\ProcessingError;
use PHPMD\Report;
use PHPMD\RuleViolation;

/**
* This renderer output a command line friendly log with all found violations
* and suspect software artifacts.
*/
class AnsiRenderer extends AbstractRenderer
{

/**
* @param \PHPMD\Report $report
* @return void
*/
public function renderReport(Report $report)
{
$this->writeViolationsReport($report);
$this->writeErrorsReport($report);
$this->writeReportSummary($report);
}

/**
* @param \PHPMD\Report $report
* @return void
*/
private function writeViolationsReport(Report $report)
{
if ($report->isEmpty()) {
return;
}

$padding = $this->getMaxLineNumberLength($report);
$previousFile = null;
foreach ($report->getRuleViolations() as $violation) {
if ($violation->getFileName() !== $previousFile) {
$this->writeViolationFileHeader($violation);
}

$this->writeViolationLine($violation, $padding);
$previousFile = $violation->getFileName();
}
}

/**
* @param \PHPMD\Report $report
* @return int|null
*/
private function getMaxLineNumberLength(Report $report)
{
$maxLength = null;
foreach ($report->getRuleViolations() as $violation) {
if ($maxLength === null) {
$maxLength = strlen($violation->getBeginLine());
continue;
}
if (strlen($violation->getBeginLine()) > $maxLength) {
$maxLength = strlen($violation->getBeginLine());
}
}
return $maxLength;
}

/**
* @param \PHPMD\RuleViolation $violation
* @return void
*/
private function writeViolationFileHeader(RuleViolation $violation)
{
$fileHeader = sprintf(
'FILE: %s',
$violation->getFileName()
);
$this->getWriter()->write(
PHP_EOL . $fileHeader . PHP_EOL .
str_repeat('-', strlen($fileHeader)) . PHP_EOL
);
}

/**
* @param \PHPMD\RuleViolation $violation
* @param int $padding
* @return void
*/
private function writeViolationLine(RuleViolation $violation, $padding)
{
$this->getWriter()->write(sprintf(
" %s | \e[31mVIOLATION\e[0m | %s" . PHP_EOL,
str_pad($violation->getBeginLine(), $padding, ' '),
$violation->getDescription()
));
}

/**
* @param \PHPMD\Report $report
* @return void
*/
private function writeErrorsReport(Report $report)
{
if (!$report->hasErrors()) {
return;
}

/** @var ProcessingError $error */
foreach ($report->getErrors() as $error) {
$errorHeader = sprintf(
"\e[33mERROR\e[0m while parsing %s",
$error->getFile()
);

$this->getWriter()->write(
PHP_EOL . $errorHeader . PHP_EOL .
str_repeat('-', strlen($errorHeader) - 9) . PHP_EOL
);

$this->getWriter()->write(sprintf(
'%s' . PHP_EOL,
$error->getMessage()
));
}
}

/**
* @param \PHPMD\Report $report
* @return void
*/
private function writeReportSummary(Report $report)
{
$this->getWriter()->write(
sprintf(
PHP_EOL . 'Found %s %s and %s %s in %sms' . PHP_EOL,
count($report->getRuleViolations()),
count($report->getRuleViolations()) !== 1 ? 'violations' : 'violation',
iterator_count($report->getErrors()),
iterator_count($report->getErrors()) !== 1 ? 'errors' : 'error',
$report->getElapsedTimeInMillis()
)
);
if (count($report->getRuleViolations()) === 0 && iterator_count($report->getErrors()) === 0) {
$this->getWriter()->write(PHP_EOL . "\e[32mNo mess detected\e[0m" . PHP_EOL);
}
}
}
11 changes: 11 additions & 0 deletions src/main/php/PHPMD/TextUI/CommandLineOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

namespace PHPMD\TextUI;

use PHPMD\Renderer\AnsiRenderer;
use PHPMD\Renderer\HTMLRenderer;
use PHPMD\Renderer\TextRenderer;
use PHPMD\Renderer\XMLRenderer;
Expand Down Expand Up @@ -360,6 +361,8 @@ public function createRenderer($reportFormat = null)
return $this->createHtmlRenderer();
case 'text':
return $this->createTextRenderer();
case 'ansi':
return $this->createAnsiRenderer();
default:
return $this->createCustomRenderer();
}
Expand All @@ -381,6 +384,14 @@ protected function createTextRenderer()
return new TextRenderer();
}

/**
* @return \PHPMD\Renderer\AnsiRenderer
*/
protected function createAnsiRenderer()
{
return new AnsiRenderer();
}

/**
* @return \PHPMD\Renderer\HTMLRenderer
*/
Expand Down
29 changes: 29 additions & 0 deletions src/test/php/PHPMD/AbstractTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,35 @@ protected function getRuleViolationMock(
return $ruleViolation;
}

/**
* Creates a mocked rul violation instance.
*
* @param string $file
* @param string $message
* @return \PHPMD\ProcessingError
*/
protected function getErrorMock(
$file = '/foo/baz.php',
$message = 'Error in file "/foo/baz.php"') {

$processingError = $this->getMock(
'PHPMD\\ProcessingError',
array(),
array(null),
'',
false
);

$processingError->expects($this->any())
->method('getFile')
->will($this->returnValue($file));
$processingError->expects($this->any())
->method('getMessage')
->will($this->returnValue($message));

return $processingError;
}

/**
* Asserts the actual xml output matches against the expected file.
*
Expand Down
139 changes: 139 additions & 0 deletions src/test/php/PHPMD/Renderer/AnsiRendererTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?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\Stubs\WriterStub;

/**
* Test case for the ansi renderer implementation.
*
* @covers \PHPMD\Renderer\AnsiRendererTest
*/
class AnsiRendererTest extends AbstractTest
{
/**
* testRendererOutputsForReportWithContents
*
* @return void
*/
public function testRendererOutputsForReportWithContents()
{
$writer = new WriterStub();

$violations = array(
$this->getRuleViolationMock('/bar.php', 1),
$this->getRuleViolationMock('/foo.php', 2),
$this->getRuleViolationMock('/foo.php', 3),
);

$errors = array(
$this->getErrorMock(),
);

$report = $this->getReportMock(0);
$report->expects($this->atLeastOnce())
->method('getRuleViolations')
->will($this->returnValue(new \ArrayIterator($violations)));
$report->expects($this->atLeastOnce())
->method('isEmpty')
->will($this->returnValue(false));
$report->expects($this->atLeastOnce())
->method('hasErrors')
->will($this->returnValue(true));
$report->expects($this->atLeastOnce())
->method('getErrors')
->will($this->returnValue(new \ArrayIterator($errors)));
$report->expects($this->once())
->method('getElapsedTimeInMillis')
->will($this->returnValue(200));

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

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

$expectedChunks = [
PHP_EOL . "FILE: /bar.php" . PHP_EOL . "--------------" . PHP_EOL,
" 1 | \e[31mVIOLATION\e[0m | Test description" . PHP_EOL,
PHP_EOL . "FILE: /foo.php" . PHP_EOL . "--------------" . PHP_EOL,
" 2 | \e[31mVIOLATION\e[0m | Test description" . PHP_EOL,
" 3 | \e[31mVIOLATION\e[0m | Test description" . PHP_EOL,
PHP_EOL . "\e[33mERROR\e[0m while parsing /foo/baz.php" . PHP_EOL . "--------------------------------" . PHP_EOL,
"Error in file \"/foo/baz.php\"" . PHP_EOL,
PHP_EOL . "Found 3 violations and 1 error in 200ms" . PHP_EOL,
];

foreach($writer->getChunks() as $i => $chunk) {
$this->assertEquals(
$expectedChunks[$i],
$chunk,
sprintf('Chunk %s did not match expected string', $i)
);
}
}

/**
* testRendererOutputsForReportWithoutContents
*
* @return void
*/
public function testRendererOutputsForReportWithoutContents()
{
$writer = new WriterStub();

$report = $this->getReportMock(0);
$report->expects($this->atLeastOnce())
->method('getRuleViolations')
->will($this->returnValue(new \ArrayIterator([])));
$report->expects($this->atLeastOnce())
->method('isEmpty')
->will($this->returnValue(true));
$report->expects($this->atLeastOnce())
->method('hasErrors')
->will($this->returnValue(false));
$report->expects($this->atLeastOnce())
->method('getErrors')
->will($this->returnValue(new \ArrayIterator([])));
$report->expects($this->once())
->method('getElapsedTimeInMillis')
->will($this->returnValue(200));

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

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

$expectedChunks = [
PHP_EOL . "Found 0 violations and 0 errors in 200ms" . PHP_EOL,
PHP_EOL . "\e[32mNo mess detected\e[0m" . PHP_EOL,
];

foreach($writer->getChunks() as $i => $chunk) {
$this->assertEquals(
$expectedChunks[$i],
$chunk,
sprintf('Chunk %s did not match expected string', $i)
);
}
}
}
3 changes: 2 additions & 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: html, text, xml.', $opts->usage());
$this->assertContains('Available formats: ansi, html, text, xml.', $opts->usage());
}

/**
Expand Down Expand Up @@ -355,6 +355,7 @@ public function dataProviderCreateRenderer()
array('html', 'PHPMD\\Renderer\\HtmlRenderer'),
array('text', 'PHPMD\\Renderer\\TextRenderer'),
array('xml', 'PHPMD\\Renderer\\XmlRenderer'),
array('ansi', 'PHPMD\\Renderer\\AnsiRenderer'),
array('PHPMD_Test_Renderer_PEARRenderer', 'PHPMD_Test_Renderer_PEARRenderer'),
array('PHPMD\\Test\\Renderer\\NamespaceRenderer', 'PHPMD\\Test\\Renderer\\NamespaceRenderer'),
/* Test what happens when class already exists. */
Expand Down

0 comments on commit 8553415

Please sign in to comment.