Skip to content

Commit

Permalink
Added --cs-fix option to run phpcbf on changed files
Browse files Browse the repository at this point in the history
  • Loading branch information
matt committed Feb 24, 2024
1 parent bf0c423 commit cc818cf
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 18 deletions.
14 changes: 14 additions & 0 deletions src/CodingStandards/FixerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Kynx\Laminas\FormShape\CodingStandards;

interface FixerInterface
{
public function getName(): string;

public function addFile(string $path): void;

public function fix(): void;
}
39 changes: 39 additions & 0 deletions src/CodingStandards/PhpCodeSnifferFixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Kynx\Laminas\FormShape\CodingStandards;

use Kynx\Laminas\FormShape\CodingStandards\FixerInterface;

use function array_map;
use function basename;
use function escapeshellarg;
use function escapeshellcmd;
use function implode;
use function passthru;

final class PhpCodeSnifferFixer implements FixerInterface
{
private array $paths = [];

public function __construct(private string $phpCbf)
{
}

public function getName(): string
{
return basename($this->phpCbf);
}

public function addFile(string $path): void
{
$this->paths[] = $path;
}

public function fix(): void
{
$paths = array_map(static fn (string $path): string => escapeshellarg($path), $this->paths);
passthru(escapeshellcmd($this->phpCbf) . ' ' . implode(' ', $paths));
}
}
6 changes: 6 additions & 0 deletions src/Command/ProgressListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Kynx\Laminas\FormShape\Command;

use Kynx\Laminas\FormShape\CodingStandards\FixerInterface;
use Kynx\Laminas\FormShape\Form\ProgressListenerInterface;
use ReflectionClass;
use Symfony\Component\Console\Command\Command;
Expand All @@ -23,6 +24,7 @@ final class ProgressListener implements ProgressListenerInterface
*/
public function __construct(
private readonly StyleInterface $io,
private readonly ?FixerInterface $fixer,
private readonly string $cwd,
private readonly array $paths
) {
Expand All @@ -38,6 +40,10 @@ public function success(ReflectionClass $reflection): void
{
$path = substr($reflection->getFileName(), strlen($this->cwd) + 1);
$this->io->text("Processed $path");

if ($this->fixer !== null) {
$this->fixer->addFile($path);
}
}

public function finally(int $processed): void
Expand Down
30 changes: 25 additions & 5 deletions src/Command/PsalmTypeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Kynx\Laminas\FormShape\Command;

use Kynx\Laminas\FormShape\CodingStandards\FixerInterface;
use Kynx\Laminas\FormShape\Form\FormProcessorInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -16,8 +17,10 @@

final class PsalmTypeCommand extends Command
{
public function __construct(private readonly FormProcessorInterface $formProcessor)
{
public function __construct(
private readonly FormProcessorInterface $formProcessor,
private readonly ?FixerInterface $fixer,
) {
parent::__construct();
}

Expand All @@ -35,16 +38,26 @@ protected function configure(): void
'fieldset-types',
null,
InputOption::VALUE_NEGATABLE,
'Add types to fieldsets',
'Add types to fieldsets (enabled by default)',
true
)
->addOption(
'remove-getdata-return',
null,
InputOption::VALUE_NEGATABLE,
'Remove @return from getData(), if present',
'Remove @return from getData(), if present (disabled by default)',
false
);

if ($this->fixer !== null) {
$name = $this->fixer->getName();
$this->addOption(
'cs-fix',
null,
InputOption::VALUE_NONE,
"Run $name on changed files",
);
}
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand All @@ -54,11 +67,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$processFieldsets = (bool) $input->getOption('fieldset-types');
$removeGetDataReturn = (bool) $input->getOption('remove-getdata-return');

$fix = $this->fixer !== null && $input->getOption('cs-fix');

$io = new SymfonyStyle($input, $output);
$listener = new ProgressListener($io, getcwd(), $paths);
$listener = new ProgressListener($io, $fix ? $this->fixer : null, getcwd(), $paths);

$this->formProcessor->process($paths, $listener, $processFieldsets, $removeGetDataReturn);

if ($fix) {
$io->info("Running " . $this->fixer->getName() . "...");
$this->fixer->fix();
}

return $listener->getStatus();
}
}
30 changes: 29 additions & 1 deletion src/Command/PsalmTypeCommandFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,43 @@

namespace Kynx\Laminas\FormShape\Command;

use Composer\InstalledVersions;
use Kynx\Laminas\FormShape\CodingStandards\FixerInterface;
use Kynx\Laminas\FormShape\CodingStandards\PhpCodeSnifferFixer;
use Kynx\Laminas\FormShape\Form\FormProcessor;
use Psr\Container\ContainerInterface;

use function is_executable;
use function sprintf;

final readonly class PsalmTypeCommandFactory
{
public function __invoke(ContainerInterface $container): PsalmTypeCommand
{
return new PsalmTypeCommand(
$container->get(FormProcessor::class)
$container->get(FormProcessor::class),
$this->getFixer()
);
}

private function getFixer(): ?FixerInterface
{
$phpCodeSniffer = null;
foreach (InstalledVersions::getAllRawData() as $installed) {
if (isset($installed['versions']['squizlabs/php_codesniffer'])) {
$phpCodeSniffer = $installed['versions']['squizlabs/php_codesniffer'];
break;
}
}
if ($phpCodeSniffer === null || ! isset($phpCodeSniffer['install_path'])) {
return null;
}

$path = sprintf('%s/bin/phpcbf', $phpCodeSniffer['install_path']);
if (! is_executable($path)) {
return null;
}

return new PhpCodeSnifferFixer($path);
}
}
7 changes: 7 additions & 0 deletions test/CodingStandards/Asset/mock-fixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

$command = array_shift($argv);
echo basename($command) . ' ' . implode(' ', (array) $argv);
28 changes: 28 additions & 0 deletions test/CodingStandards/MockFixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace KynxTest\Laminas\FormShape\CodingStandards;

use Kynx\Laminas\FormShape\CodingStandards\FixerInterface;

final class MockFixer implements FixerInterface
{
public array $paths = [];
public bool $fixed = false;

public function getName(): string
{
return 'mock-fixer';
}

public function addFile(string $path): void
{
$this->paths[] = $path;
}

public function fix(): void
{
$this->fixed = true;
}
}
31 changes: 31 additions & 0 deletions test/CodingStandards/PhpCodeSnifferFixerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace KynxTest\Laminas\FormShape\CodingStandards;

use Kynx\Laminas\FormShape\CodingStandards\PhpCodeSnifferFixer;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

use function ob_get_clean;
use function ob_start;

#[CoversClass(PhpCodeSnifferFixer::class)]
final class PhpCodeSnifferFixerTest extends TestCase
{
public function testFixProcessesPaths(): void
{
$expected = 'mock-fixer.php foo bar';
$fixer = new PhpCodeSnifferFixer(__DIR__ . '/Asset/mock-fixer.php');

$fixer->addFile('foo');
$fixer->addFile('bar');

ob_start();
$fixer->fix();
$actual = ob_get_clean();

self::assertSame($expected, $actual);
}
}
19 changes: 16 additions & 3 deletions test/Command/ProgressListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,33 @@
namespace KynxTest\Laminas\FormShape\Command;

use Kynx\Laminas\FormShape\Command\ProgressListener;
use KynxTest\Laminas\FormShape\CodingStandards\MockFixer;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\StyleInterface;

use function strlen;
use function strrpos;
use function substr;

#[CoversClass(ProgressListener::class)]
final class ProgressListenerTest extends TestCase
{
private StyleInterface&MockObject $style;
private string $cwd;
private ProgressListener $listener;

protected function setUp(): void
{
parent::setUp();

$this->style = self::createMock(StyleInterface::class);
$cwd = substr(__DIR__, 0, (int) strrpos(__DIR__, '/test/Command'));
$this->listener = new ProgressListener($this->style, $cwd, ['test']);
$this->style = self::createMock(StyleInterface::class);
$this->cwd = substr(__DIR__, 0, (int) strrpos(__DIR__, '/test/Command'));

$this->listener = new ProgressListener($this->style, null, $this->cwd, ['test']);
}

public function testErrorOutputsError(): void
Expand All @@ -50,6 +54,15 @@ public function testSuccessOutputsPath(): void
self::assertSame(Command::SUCCESS, $this->listener->getStatus());
}

public function testSuccessAddsFileToFixer(): void
{
$expected = substr(__FILE__, strlen($this->cwd) + 1);
$fixer = new MockFixer();
$listener = new ProgressListener($this->style, $fixer, $this->cwd, ['test']);
$listener->success(new ReflectionClass($this));
self::assertSame([$expected], $fixer->paths);
}

public function testFinallyNoneProcessedOutputsError(): void
{
$expected = "Cannot find any forms at 'test'";
Expand Down
33 changes: 25 additions & 8 deletions test/Command/PsalmTypeCommandFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,9 @@ final class PsalmTypeCommandFactoryTest extends TestCase
public function testInvokeReturnsConfiguredInstance(): void
{
$formProcessor = self::createMock(FormProcessorInterface::class);
$container = self::createStub(ContainerInterface::class);
$container->method('get')
->willReturnMap([
[FormProcessor::class, $formProcessor],
]);

$factory = new PsalmTypeCommandFactory();
$instance = $factory($container);
$container = $this->getContainer($formProcessor);
$factory = new PsalmTypeCommandFactory();
$instance = $factory($container);

$formProcessor->expects(self::once())
->method('process');
Expand All @@ -37,4 +32,26 @@ public function testInvokeReturnsConfiguredInstance(): void
]);
self::assertSame(Command::SUCCESS, $actual);
}

public function testInvokeAddsPhpCodeSnifferFixer(): void
{
$formProcessor = self::createMock(FormProcessorInterface::class);
$container = $this->getContainer($formProcessor);
$factory = new PsalmTypeCommandFactory();
$instance = $factory($container);

$actual = $instance->getDefinition()->hasOption('cs-fix');
self::assertTrue($actual);
}

private function getContainer(FormProcessorInterface $formProcessor): ContainerInterface
{
$container = self::createStub(ContainerInterface::class);
$container->method('get')
->willReturnMap([
[FormProcessor::class, $formProcessor],
]);

return $container;
}
}
Loading

0 comments on commit cc818cf

Please sign in to comment.