Skip to content
This repository has been archived by the owner on Dec 3, 2023. It is now read-only.

[ECS] Add Find command to simplify sniff/fixer finding #967

Merged
merged 7 commits into from Jul 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/EasyCodingStandard/README.md
Expand Up @@ -202,6 +202,16 @@ vendor/bin/ecs show
vendor/bin/ecs show --config ...
```

**How to find checkers by group or type?**

```bash
vendor/bin/ecs find

vendor/bin/ecs find symplify # for Symplify rules

vendor/bin/ecs find array # for array-related rules
```

**How to clear cache?**

```bash
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

100 changes: 100 additions & 0 deletions packages/EasyCodingStandard/src/Console/Command/FindCommand.php
@@ -0,0 +1,100 @@
<?php declare(strict_types=1);

namespace Symplify\EasyCodingStandard\Console\Command;

use Nette\Utils\Strings;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symplify\EasyCodingStandard\Console\Style\EasyCodingStandardStyle;
use Symplify\EasyCodingStandard\Finder\CheckerClassFinder;
use Symplify\PackageBuilder\Composer\VendorDirProvider;
use Symplify\PackageBuilder\Console\Command\CommandNaming;

final class FindCommand extends Command
{
/**
* @var string
*/
private const ARGUMENT_NAME = 'name';

/**
* @var EasyCodingStandardStyle
*/
private $easyCodingStandardStyle;

/**
* @var CheckerClassFinder
*/
private $checkerClassFinder;

public function __construct(
EasyCodingStandardStyle $easyCodingStandardStyle,
CheckerClassFinder $checkerClassFinder
) {
parent::__construct();

$this->easyCodingStandardStyle = $easyCodingStandardStyle;
$this->checkerClassFinder = $checkerClassFinder;
}

protected function configure(): void
{
$this->setName(CommandNaming::classToName(self::class));
$this->setDescription('Show all available checkers');
$this->addArgument(
self::ARGUMENT_NAME,
InputOption::VALUE_REQUIRED,
'Filter checkers by name, e.g. "array" or "Symplify"'
);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$checkers = $this->checkerClassFinder->findInDirectories([
getcwd() . '/src',
getcwd() . '/packages',
VendorDirProvider::provide(),
]);

$name = $input->getArgument(self::ARGUMENT_NAME);

if ($name) {
$checkers = $this->filterCheckersByName($checkers, $name);
}

if (! count($checkers)) {
$message = 'No checkers found';
if ($name) {
$message .= sprintf(' for "%s" name', $name);
}

$this->easyCodingStandardStyle->note($message);
return 0;
}

sort($checkers);
$this->easyCodingStandardStyle->listing($checkers);

$this->easyCodingStandardStyle->success(sprintf('Found %d checkers', count($checkers)));

return 0;
}

/**
* @param string[] $checkers
* @return string[]
*/
private function filterCheckersByName(array $checkers, string $name): array
{
$filteredCheckers = [];
foreach ($checkers as $checker) {
if (Strings::match($checker, sprintf('#%s#i', preg_quote($name)))) {
$filteredCheckers[] = $checker;
}
}

return $filteredCheckers;
}
}
94 changes: 94 additions & 0 deletions packages/EasyCodingStandard/src/Finder/CheckerClassFinder.php
@@ -0,0 +1,94 @@
<?php declare(strict_types=1);

namespace Symplify\EasyCodingStandard\Finder;

use Nette\Loaders\RobotLoader;
use Nette\Utils\FileSystem;
use PHP_CodeSniffer\Sniffs\Sniff;
use ReflectionClass;

final class CheckerClassFinder
{
/**
* @param string[] $directories
* @return string[]
*/
public function findInDirectories(array $directories): array
{
$robotLoader = $this->createRobotLoaderForDirectories($directories);
$checkerClasses = array_keys($robotLoader->getIndexedClasses());

return $this->filterOutAbstractAndNonPhpClasses($checkerClasses);
}

/**
* @param string[] $directories
*/
private function createRobotLoaderForDirectories(array $directories): RobotLoader
{
$robot = new RobotLoader();
$robot->setTempDirectory($this->createRobotLoaderCacheDirectory());
foreach ($directories as $directory) {
if (! is_dir($directory)) {
continue;
}

$robot->addDirectory($directory);
}

$robot->ignoreDirs += ['tests', 'Tests'];
$robot->acceptFiles = ['*Sniff.php', '*Fixer.php'];
$robot->rebuild();

return $robot;
}

private function createRobotLoaderCacheDirectory(): string
{
$tempDir = sys_get_temp_dir() . '/_checker_finder_robot_loader';
FileSystem::createDir($tempDir);

return $tempDir;
}

/**
* @param string[] $checkerClasses
* @return string[]
*/
private function filterOutAbstractAndNonPhpClasses(array $checkerClasses): array
{
$finalCheckerClasses = [];
foreach ($checkerClasses as $checkerClass) {
if (! class_exists($checkerClass)) {
continue;
}

if ($this->isAbstractClass($checkerClass)) {
continue;
}

if (is_a($checkerClass, Sniff::class, true) && ! $this->doesSniffSupportPhp($checkerClass)) {
continue;
}

$finalCheckerClasses[] = $checkerClass;
}

return $finalCheckerClasses;
}

private function isAbstractClass(string $class): bool
{
return (new ReflectionClass($class))->isAbstract();
}

private function doesSniffSupportPhp(string $sniffClass): bool
{
$vars = get_class_vars($sniffClass);
if (! isset($vars['supportedTokenizers'])) {
return true;
}

return in_array('PHP', $vars['supportedTokenizers'], true);
}
}
@@ -0,0 +1,27 @@
<?php declare(strict_types=1);

namespace Symplify\EasyCodingStandard\Tests\Finder;

use PHPUnit\Framework\TestCase;
use Symplify\EasyCodingStandard\Finder\CheckerClassFinder;
use Symplify\PackageBuilder\Composer\VendorDirProvider;

final class CheckerClassFinderTest extends TestCase
{
/**
* @var CheckerClassFinder
*/
private $checkerClassFinder;

protected function setUp(): void
{
$this->checkerClassFinder = new CheckerClassFinder();
}

public function test(): void
{
$checkerClasses = $this->checkerClassFinder->findInDirectories([VendorDirProvider::provide()]);

$this->assertGreaterThan(250, $checkerClasses);
}
}