Skip to content

Commit

Permalink
Merge pull request #343 from johnbacon/checkstyle-support
Browse files Browse the repository at this point in the history
Add checkstyle reporting option
  • Loading branch information
driftingly authored Sep 1, 2023
2 parents b1d5c86 + 4422bfa commit 1db6b58
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ vendor/
.php-cs-fixer.cache
.phpunit.result.cache
composer.lock
/.idea
2 changes: 1 addition & 1 deletion bin/tlint
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env php
<?php

const TLINT_VERSION = 'v9.0.0';
const TLINT_VERSION = 'v9.1.0';

foreach (
[
Expand Down
5 changes: 5 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ Want the output from a file as JSON? (Primarily used for integration with editor
tlint lint test.php --json
```

Want the output from a file as a [checkstyle XML report](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.22.0/doc/schemas/fix/checkstyle.xsd)? (Primarily used with CI tools like [reviewdog](https://github.com/reviewdog/reviewdog) and [cs2pr](https://github.com/staabm/annotate-pull-request-from-checkstyle))
```
tlint lint test.php --checkstyle
```

Want to only run a single linter?

```
Expand Down
49 changes: 49 additions & 0 deletions src/Commands/LintCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Tighten\TLint\Commands;

use DOMDocument;
use DOMElement;
use PhpParser\Error;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
Expand All @@ -17,6 +19,7 @@ class LintCommand extends BaseCommand
{
private const NO_LINTS_FOUND_OR_SUCCESS = 0;
private const LINTS_FOUND_OR_ERROR = 1;
private DOMElement $checkstyle;

protected function configure()
{
Expand All @@ -36,6 +39,9 @@ protected function configure()
new InputOption(
'json'
),
new InputOption(
'checkstyle'
),
new InputOption(
'only',
null,
Expand All @@ -55,6 +61,13 @@ protected function execute(InputInterface $input, OutputInterface $output)
return self::NO_LINTS_FOUND_OR_SUCCESS;
}

if ($input->getOption('checkstyle')) {
$dom = new DOMDocument('1.0', 'UTF-8');
$this->checkstyle = $dom->createElement('checkstyle');
$this->checkstyle->setAttribute('version', '3.7.2');
$dom->appendChild($this->checkstyle);
}

if (is_file($fileOrDirectory)) {
$finalResponseCode = $this->lintFile($input, $output, $fileOrDirectory);
} elseif (is_dir($fileOrDirectory)) {
Expand All @@ -79,6 +92,13 @@ protected function execute(InputInterface $input, OutputInterface $output)
return self::NO_LINTS_FOUND_OR_SUCCESS;
}

if ($input->getOption('checkstyle')) {
$dom->formatOutput = true;
$output->write($dom->saveXML());

return self::NO_LINTS_FOUND_OR_SUCCESS;
}

if ($finalResponseCode === self::NO_LINTS_FOUND_OR_SUCCESS) {
$output->writeLn('LGTM!');
}
Expand Down Expand Up @@ -134,6 +154,10 @@ private function lintFile(InputInterface $input, OutputInterface $output, $file)
return $this->outputLintsAsJson($output, $file, $lints);
}

if ($input->getOption('checkstyle')) {
return $this->outputLintsAsCheckstyle($output, $file, $lints);
}

return $this->outputLints($output, $file, $lints);
}

Expand All @@ -156,6 +180,31 @@ private function outputLintsAsJson(OutputInterface $output, $file, $lints)
return self::NO_LINTS_FOUND_OR_SUCCESS;
}

private function outputLintsAsCheckstyle(OutputInterface $output, $file, $lints)
{
if (! empty($lints)) {
foreach ($lints as $lint) {
$fileNode = $this->checkstyle->ownerDocument->createElement('file');
$fileNode->setAttribute('name', $file);
$this->checkstyle->appendChild($fileNode);

$title = explode(PHP_EOL, (string) $lint)[0];

$errorNode = $this->checkstyle->ownerDocument->createElement('error');
$errorNode->setAttribute('line', $lint->getNode()->getStartLine());
$errorNode->setAttribute('severity', 'error');
$errorNode->setAttribute('message', $title);
$errorNode->setAttribute('source', basename(str_replace('\\', '/', get_class($lint->getLinter()))));

$fileNode->appendChild($errorNode);
}

return self::LINTS_FOUND_OR_ERROR;
}

return self::NO_LINTS_FOUND_OR_SUCCESS;
}

private function outputLints(OutputInterface $output, $file, $lints)
{
if (! empty($lints)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,10 @@ class Thing
$this->assertSame($file, $formatted);
}

/** @test */
public function catches_missing_line_between_visibility_changes_in_anon_class()
{
$file = <<<'file'
/** @test */
public function catches_missing_line_between_visibility_changes_in_anon_class()
{
$file = <<<'file'
<?php
namespace App;
Expand All @@ -312,7 +312,7 @@ public function getThing(): NodeVisitorAbstract
}
file;

$expected = <<<'file'
$expected = <<<'file'
<?php
namespace App;
Expand All @@ -333,8 +333,8 @@ public function getThing(): NodeVisitorAbstract
}
file;

$formatted = (new TFormat)->format(new OneLineBetweenClassVisibilityChanges($file));
$formatted = (new TFormat)->format(new OneLineBetweenClassVisibilityChanges($file));

$this->assertSame($expected, $formatted);
}
$this->assertSame($expected, $formatted);
}
}
88 changes: 88 additions & 0 deletions tests/Linting/CanOutputLintsAsCheckstyleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Tests\Linting;

use DomDocument;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Tighten\TLint\Commands\LintCommand;
use Tighten\TLint\Linters\OneLineBetweenClassVisibilityChanges;

class CanOutputLintsAsCheckstyleTest extends TestCase
{
/** @test */
public function can_use_checkstyle_flag_with_lints()
{
$application = new Application;
$command = new LintCommand;
$application->add($command);
$commandTester = new CommandTester($command);

$file = <<<'file'
<?php
class Test
{
public $test1;
private $test2;
}

file;

$filePath = tempnam(sys_get_temp_dir(), 'test');

file_put_contents($filePath, $file);

$commandTester->execute([
'command' => $command->getName(),
'file or directory' => $filePath,
'--checkstyle' => true,
]);

$output = $commandTester->getDisplay();
$xml = new DomDocument;
$xml->loadXML($output);
$error = $xml->getElementsByTagName('error')->item(0)->attributes;

$this->assertEquals('6', $error->getNamedItem('line')->nodeValue);
$this->assertEquals('error', $error->getNamedItem('severity')->nodeValue);
$this->assertEquals('! ' . OneLineBetweenClassVisibilityChanges::DESCRIPTION, $error->getNamedItem('message')->nodeValue);
$this->assertEquals('OneLineBetweenClassVisibilityChanges', $error->getNamedItem('source')->nodeValue);

$this->assertEquals(0, $commandTester->getStatusCode());
}

/** @test */
public function can_use_checkstyle_flag_without_lints()
{
$application = new Application;
$command = new LintCommand;
$application->add($command);
$commandTester = new CommandTester($command);

$file = <<<'file'
<?php
echo 'a';

file;

$filePath = tempnam(sys_get_temp_dir(), 'test');

file_put_contents($filePath, $file);

$commandTester->execute([
'command' => $command->getName(),
'file or directory' => $filePath,
'--checkstyle' => true,
]);

$output = $commandTester->getDisplay();

$xml = new DOMDocument;
$xml->loadXML($output);
$this->assertEquals(0, $xml->getElementsByTagName('file')->length);
$this->assertEquals(0, $commandTester->getStatusCode());
}
}
18 changes: 9 additions & 9 deletions tests/Linting/Linters/UseAuthHelperOverFacadeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,23 @@ public function catches_auth_facade_usage_in_code()
$this->assertEquals(4, $lints[0]->getNode()->getLine());
}

/** @test */
public function does_not_trigger_on_non_auth_call()
{
$file = <<<file
/** @test */
public function does_not_trigger_on_non_auth_call()
{
$file = <<<file
<?php
use Some\Other\AuthClass as Auth;
echo Auth::user()->name;
file;

$lints = (new TLint)->lint(
new UseAuthHelperOverFacade($file, '.php')
);
$lints = (new TLint)->lint(
new UseAuthHelperOverFacade($file, '.php')
);

$this->assertEmpty($lints);
}
$this->assertEmpty($lints);
}

/** @test */
public function does_not_trigger_on_non_facade_call()
Expand Down

0 comments on commit 1db6b58

Please sign in to comment.