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

Add regex search command for easy quick stats #67

Merged
merged 4 commits into from
Jan 7, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,32 @@ You can also add this command to CI, to get instant feedback about unused defini

<br>

## 8. Quick search PHP files with regex

Data beats guess. Do you need a quick idea how many files contain `$this->get('...')` calls? Or another anti-pattern you want to remove?

PhpStorm helps with similar search, but stops counting at 100+. To get exact data about your codebase, use this command:

```bash
vendor/bin/swiss-knife search-regex "#this->get\((.*)\)#"
```


```bash
Going through 1053 *.php files
Searching for regex: #this->get\((.*)\)#

1053/1053 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

* src/Controller/ProjectController.php: 15
* src/Controller/OrderController.php: 5


[OK] Found 20 cases in 2 files

```

<br>

Happy coding!
6 changes: 2 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"illuminate/container": "^11.35",
"nette/robot-loader": "^4.0",
"nette/utils": "^4.0",
"nikic/php-parser": "^5.3",
"nikic/php-parser": "^5.4",
"symfony/console": "^6.4",
"symfony/finder": "^6.4",
"webmozart/assert": "^1.11"
Expand All @@ -20,11 +20,9 @@
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^11.5",
"rector/rector": "^2.0",
"rector/type-perfect": "^2.0",
"shipmonk/composer-dependency-analyser": "^1.8",
"symplify/easy-coding-standard": "^12.4",
"symplify/easy-coding-standard": "^12.5",
"tomasvotruba/type-coverage": "^2.0",
"tomasvotruba/unused-public": "^2.0",
"tomasvotruba/class-leak": "^2.0",
"tracy/tracy": "^2.10"
},
Expand Down
10 changes: 0 additions & 10 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,12 @@ parameters:
- */Fixture/*
- */Source/*

type_perfect:
null_over_false: true
no_mixed_property: true
no_mixed_caller: true
narrow_param: true
narrow_return: true

ignoreErrors:
# unrelated
- '#Parameter \#1 \$className of class Rector\\SwissKnife\\ValueObject\\ClassConstant constructor expects class-string, string given#'

- '#Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class-string<T of object>\|T of object, string given#'

# false positive callable/closure in type-perfect
- '#Parameters should have "array\|\(Closure\)\|null" types as the only types passed to this method#'

# command status enum
-
identifier: return.unusedType
Expand Down
8 changes: 4 additions & 4 deletions src/Behastan/Behastan.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
/**
* @see \Rector\SwissKnife\Tests\Behastan\Behastan\BehastanTest
*/
final class Behastan
final readonly class Behastan
{
public function __construct(
private readonly SymfonyStyle $symfonyStyle,
private readonly DefinitionMasksResolver $definitionMasksResolver,
private readonly UsedInstructionResolver $usedInstructionResolver
private SymfonyStyle $symfonyStyle,
private DefinitionMasksResolver $definitionMasksResolver,
private UsedInstructionResolver $usedInstructionResolver
) {
}

Expand Down
4 changes: 2 additions & 2 deletions src/Behastan/Command/BehastanCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
Assert::allDirectory($testDirectories);

$featureFiles = $this->behatMetafilesFinder->findFeatureFiles($testDirectories);
if (count($featureFiles) === 0) {
if ($featureFiles === []) {
$this->symfonyStyle->error('No *.feature files found. Please provide correct test directory');
return self::FAILURE;
}

$contextFiles = $this->behatMetafilesFinder->findContextFiles($testDirectories);
if (count($contextFiles) === 0) {
if ($contextFiles === []) {
$this->symfonyStyle->error('No *Context.php files found. Please provide correct test directory');
return self::FAILURE;
}
Expand Down
14 changes: 6 additions & 8 deletions src/Behastan/Finder/BehatMetafilesFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@ public function findContextFiles(array $directories): array
Assert::allString($directories);
Assert::allDirectory($directories);

$phpFilesFinder = Finder::create()
$filesFinder = Finder::create()
->files()
->name('*Context.php')
->in($directories)
->getIterator();
->in($directories);

return iterator_to_array($phpFilesFinder);
return iterator_to_array($filesFinder->getIterator());
}

/**
Expand All @@ -38,12 +37,11 @@ public function findFeatureFiles(array $directories): array
Assert::allString($directories);
Assert::allDirectory($directories);

$featureFilesFinder = Finder::create()
$filesFinder = Finder::create()
->files()
->name('*.feature')
->in($directories)
->getIterator();
->in($directories);

return iterator_to_array($featureFilesFinder);
return iterator_to_array($filesFinder->getIterator());
}
}
4 changes: 2 additions & 2 deletions src/Behastan/ValueObject/MaskCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

namespace Rector\SwissKnife\Behastan\ValueObject;

final class MaskCollection
final readonly class MaskCollection
{
/**
* @param AbstractMask[] $masks
*/
public function __construct(
private readonly array $masks
private array $masks
) {
}

Expand Down
87 changes: 87 additions & 0 deletions src/Command/SearchRegexCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace Rector\SwissKnife\Command;

use Nette\Utils\Strings;
use Rector\SwissKnife\Finder\PhpFilesFinder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Webmozart\Assert\Assert;

final class SearchRegexCommand extends Command
{
public function __construct(
private readonly SymfonyStyle $symfonyStyle,
) {
parent::__construct();
}

protected function configure(): void
{
$this->setName('search-regex');

$this->addArgument(
'regex',
InputArgument::REQUIRED,
'Code snippet to look in PHP files in the whole codebase'
);

$this->addOption('project-directory', null, InputOption::VALUE_REQUIRED, 'Project directory', getcwd());

$this->setDescription('Search for regex in PHP files of the whole codebase');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$regex = (string) $input->getArgument('regex');

$projectDirectory = (string) $input->getOption('project-directory');

Assert::directory($projectDirectory);

$phpFileInfos = PhpFilesFinder::find([$projectDirectory]);

$message = sprintf('Going through %d *.php files', count($phpFileInfos));
$this->symfonyStyle->writeln($message);

$this->symfonyStyle->writeln('Searching for regex: ' . $regex);
$this->symfonyStyle->newLine();

$foundCasesCount = 0;
$markedFiles = [];

$progressBar = $this->symfonyStyle->createProgressBar(count($phpFileInfos));

foreach ($phpFileInfos as $phpFileInfo) {
$matches = Strings::matchAll($phpFileInfo->getContents(), $regex);
$currentMatchesCount = count($matches);
if ($currentMatchesCount === 0) {
continue;
}

$foundCasesCount += $currentMatchesCount;
$markedFiles[$phpFileInfo->getRelativePathname()] = $currentMatchesCount;

$progressBar->advance();
}

$progressBar->finish();
$this->symfonyStyle->newLine(2);

ksort($markedFiles);
foreach ($markedFiles as $filePath => $count) {
$this->symfonyStyle->writeln(sprintf(' * %s: %d', $filePath, $count));
}

$this->symfonyStyle->newLine(2);
$this->symfonyStyle->success(sprintf('Found %d cases in %d files', $foundCasesCount, count($markedFiles)));

return self::SUCCESS;
}
}
2 changes: 2 additions & 0 deletions src/DependencyInjection/ContainerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Rector\SwissKnife\Command\NamespaceToPSR4Command;
use Rector\SwissKnife\Command\PrettyJsonCommand;
use Rector\SwissKnife\Command\PrivatizeConstantsCommand;
use Rector\SwissKnife\Command\SearchRegexCommand;
use Rector\SwissKnife\Testing\Command\DetectUnitTestsCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
Expand Down Expand Up @@ -48,6 +49,7 @@ public function create(): Container
$container->make(PrivatizeConstantsCommand::class),

$container->make(BehastanCommand::class),
$container->make(SearchRegexCommand::class),
];

$application->addCommands($commands);
Expand Down
3 changes: 3 additions & 0 deletions src/Finder/PhpFilesFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ private static function createFinderForPathsAndExcludedPaths(array $paths, array
->in($paths)
->name('*.php')
->notPath('vendor')
->notPath('var')
->notPath('data-fixtures')
->notPath('node_modules')
// exclude paths, as notPaths() does no work
->filter(static function (SplFileInfo $splFileInfo) use ($excludedPaths): bool {
foreach ($excludedPaths as $excludedPath) {
Expand Down
6 changes: 4 additions & 2 deletions src/PhpParser/CachedPhpParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use PhpParser\Node\Stmt;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\Parser;
use RuntimeException;
use Throwable;

/**
* Parse file just once
Expand Down Expand Up @@ -36,8 +38,8 @@ public function parseFile(string $filePath): array
$fileContents = FileSystem::read($filePath);
try {
$stmts = $this->phpParser->parse($fileContents);
} catch (\Throwable $throwable) {
throw new \RuntimeException(sprintf(
} catch (Throwable $throwable) {
throw new RuntimeException(sprintf(
'Could not parse file "%s": %s',
$filePath,
$throwable->getMessage()
Expand Down
2 changes: 1 addition & 1 deletion src/PhpParser/Finder/ClassConstFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
/**
* @see \Rector\SwissKnife\Tests\PhpParser\Finder\ClassConstFinder\ClassConstFinderTest
*/
final class ClassConstFinder
final readonly class ClassConstFinder
{
public function __construct(
private CachedPhpParser $cachedPhpParser
Expand Down
2 changes: 1 addition & 1 deletion src/PhpParser/Finder/ClassConstantFetchFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/**
* @see \Rector\SwissKnife\Tests\PhpParser\ClassConstantFetchFinder\ClassConstantFetchFinderTest
*/
final class ClassConstantFetchFinder
final readonly class ClassConstantFetchFinder
{
public function __construct(
private CachedPhpParser $cachedPhpParser,
Expand Down
2 changes: 2 additions & 0 deletions src/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ public function enterNode(Node $node): Node|int|null
$this->classConstantFetches[] = new CurrentClassConstantFetch($currentClassName, $constantName);
return $node;
}

// check if parent class is vendor
if ($this->currentClass->extends instanceof Name) {
$parentClassName = $this->currentClass->extends->toString();
if ($this->isVendorClassName($parentClassName)) {
return null;
}
}

$this->classConstantFetches[] = new ParentClassConstantFetch($currentClassName, $constantName);
return $node;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,7 @@ public function enterNode(Node $node): ?Node
}

$methodName = $node->name->toString();
$mockMethodNames = [
'createMock',
'createPartialMock',
'getMock',
'getMockBuilder',
'mock',
];
$mockMethodNames = ['createMock', 'createPartialMock', 'getMock', 'getMockBuilder', 'mock'];
if (! in_array($methodName, $mockMethodNames, true)) {
return null;
}
Expand Down
Loading
Loading