Skip to content

Commit

Permalink
cache for check:imports
Browse files Browse the repository at this point in the history
  • Loading branch information
imanghafoori1 committed Sep 20, 2024
1 parent 1e80aca commit 3a19197
Show file tree
Hide file tree
Showing 17 changed files with 309 additions and 170 deletions.
25 changes: 10 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ Here is a comprehensive list of placeholders you can use:
|8|`<statement>`|to capture a whole php statement.|
|9|`<name:nam1,nam2>` or `<name>`|for method or function names. `->where` or `::where`|
|10|`<white_space>`|for whitespace blocks|
|11|`<bool>` or `<boolean>`|for true or false (acts case-insensetive)|
|11|`<bool>` or `<boolean>`|for true or false (acts case-insensitive)|
|12|`<number>`|for numeric values|
|13|`<cast>`|for type-casts like: `(array) $a;`|
|14|`<int>` or `"<integer>"`|for integer values|
Expand All @@ -250,12 +250,12 @@ Here is a comprehensive list of placeholders you can use:

>You can also define your own keywords if needed!
>
>You just define a class for your new keyword and append the class path to the end of the `Finder::$keywords[] = MyKeyword::class` property.
>You just define a class for your new keyword and append the classpath to the end of the `Finder::$keywords[] = MyKeyword::class` property.
Just like the default keywords.

**Example:**

:one: Let's say you want to find only the "comments" which contain the word "todo:" in them.
:one: Let's say you want to find only the "comments" that contain the word "todo:" in them.
```php
'todo_comments' => [
'search' => '<comment>',
Expand Down Expand Up @@ -384,32 +384,27 @@ User::query()->where(...)->get();
\App\Models\User::query()->find(...);
```

- The filters here ensure that the captured class reference is a laravel Model and the method name is one of the names mentioned in the list.
- The filters here ensure that the captured class reference is a Laravel Model and the method name is one of the names mentioned in the list.

So it does not tamper with something like this:
```php
User::all(); // The `all` method can not be preceded with `query`
User::all(); // The `all` method can not be preceded by `query`

UserRepo::where(...); /// UserRepo is not a model
```

- This is something which you can never do by regex.
- This is something that you can never do by regex.

<a name="capturing-php-statements" ></a>
:five: **Capturing php "statements":**

Let's say we want to opt into php 7.4 arrow functions:
Let's say we want to opt into PHP v7.4 arrow functions:

```php
'fn' => [
'search' => 'function (<in_between>)<until>{ <statement> }',
'search' => 'function (<in_between>)<until>{ return <statement>; }',
'replace' => 'fn (<1>) => <3>',
'tags' => 'php74,refactor',
'mutator' => function ($matches) {
$matches[2][1] = str_replace(['return ', ';'], '', $matches[2][1]);

return $matches;
}
]

```
Expand Down Expand Up @@ -452,14 +447,14 @@ If we define our pattern like this:

```php
return [
'staty' => [
'pattern_name' => [
'search' => '<var> = <until>;',
]
];
```
For `$c = $a + $b;` they act the same way, but for the second one `"<until>";` will not capture the whole closure and will stop as soon as it reaches `$a++;` and that is a problem.

But if you define your pattern as: `'<var> = <statement>'` it would be smart enough to capture the correct semicolon at the end of closure definition and whole close would be captured.
But if you define your pattern as: `'<var> = <statement>'` it would be smart enough to capture the correct semicolon at the end of the closure definition and the whole close would be captured.

<a name="capturing-global" ></a>
:seven: **Capturing global function calls:**
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
],
"require": {
"php": "^7.2|8.0.*|8.1.*|8.2.*|8.3.*",
"imanghafoori/composer-json": "^1.0.15",
"imanghafoori/composer-json": "^1.0.17",
"composer/class-map-generator": "^1.0.0",
"imanghafoori/php-abstract-filesystem": "^0.1.5",
"imanghafoori/php-search-replace": "^1.1.12",
"imanghafoori/php-token-analyzer": "^0.1.73",
"imanghafoori/php-search-replace": "^1.1.13",
"imanghafoori/php-token-analyzer": "^0.1.83",
"imanghafoori/php-imports-analyzer": "^1.0.6",
"imanghafoori/smart-realtime-facades": "^1.1.8",
"jetbrains/phpstorm-attributes": "1.*",
Expand Down
4 changes: 3 additions & 1 deletion src/Checks/CheckRouteCalls.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ public static function route($path, $errorIt, $errorTxt, $absPath = null, $lineN

public static function checkRouteExists($line, $routeName, $absPath)
{
$matchedRoute = app('router')->getRoutes()->getByName(\trim($routeName, '\'\"'));
$matchedRoute = app('router')->getRoutes()->getByName(
trim($routeName, '\'\"')
);
is_null($matchedRoute) && self::printError($routeName, $absPath, $line);
}

Expand Down
10 changes: 8 additions & 2 deletions src/Commands/CheckAll.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function handle(ErrorPrinter $errorPrinter)
$t1 = microtime(true);
$errorPrinter->printer = $this->output;

// turns off error logging.
// Turns off error logging.
$errorPrinter->logErrors = false;

$this->call('check:psr4', ['--nofix' => $this->option('nofix'), '--force' => $this->option('force')]);
Expand All @@ -40,8 +40,14 @@ public function handle(ErrorPrinter $errorPrinter)
$errorPrinter->logErrors = true;

$this->finishCommand($errorPrinter);
$errorPrinter->printer->writeln('time: '.round(microtime(true) - $t1, 2).' (sec)', 2);
$duration = microtime(true) - $t1;
$errorPrinter->printer->writeln(self::getTimeMsg($duration), 2);

return $errorPrinter->hasErrors() ? 1 : 0;
}

private static function getTimeMsg($time): string
{
return 'time: '.round($time, 2).' (sec)';
}
}
38 changes: 38 additions & 0 deletions src/Commands/EnforceArrowFunctions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Imanghafoori\LaravelMicroscope\Commands;

use Illuminate\Console\Command;
use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter;
use Imanghafoori\LaravelMicroscope\Traits\LogsErrors;

class EnforceArrowFunctions extends Command
{
use LogsErrors;
use PatternApply;

protected $signature = 'check:arrow_functions {--f|file=} {--d|folder=} {--nofix}';

protected $description = 'Converts anonymous functions into arrow functions.';

protected $customMsg = 'All the function are already converted into the arrow version. \(^_^)/';

public function handle(ErrorPrinter $errorPrinter)
{
event('microscope.start.command');
$this->info('Soaring like an eagle...');

return $this->patternCommand($errorPrinter);
}

private function getPatterns(): array
{
return [
'arrow_functions' => [
'cacheKey' => 'arrow_fn-v1',
'search' => 'function (<in_between>)<until>{return <statement>;}',
'replace' => 'fn (<1>) => <3>',
],
];
}
}
9 changes: 5 additions & 4 deletions src/Features/ActionComments/ActionsComments.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@ private static function checkActions($tokens, $fullNamespace, $absFilePath)
public static function getCallsiteInfo($methods, $route)
{
$callsite = app('router')->getRoutes()->routesInfo[$methods][$route->uri()] ?? [];
$file = $callsite[0]['file'] ?? '';
$absPath = $callsite[0]['file'] ?? '';
$relativePath = trim(str_replace(base_path(), '', $absPath), '\\/');
$relativePath = str_replace('\\', '/', $relativePath);

$line = $callsite[0]['line'] ?? '';
$file = \trim(str_replace(base_path(), '', $file), '\\/');
$file = str_replace('\\', '/', $file);

return [$file, $line];
return [$relativePath, $line];
}

private static function getActionRoutes($allRoutes, $method)
Expand Down
42 changes: 29 additions & 13 deletions src/Features/CheckImports/CheckImportsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Imanghafoori\LaravelMicroscope\Iterators\ClassMapIterator;
use Imanghafoori\LaravelMicroscope\Iterators\FileIterators;
use Imanghafoori\LaravelMicroscope\LaravelPaths\LaravelPaths;
use Imanghafoori\LaravelMicroscope\SearchReplace\CachedFiles;
use Imanghafoori\LaravelMicroscope\SpyClasses\RoutePaths;
use Imanghafoori\LaravelMicroscope\Traits\LogsErrors;
use Imanghafoori\TokenAnalyzer\ImportsAnalyzer;
Expand Down Expand Up @@ -68,6 +69,10 @@ public function handle()
CheckClassReferencesAreValid::$wrongClassRefsHandler = PrintWrongClassRefs::class;
}

if (file_exists($path = CachedFiles::getPathForPattern().'check_imports.php')) {
CheckClassReferencesAreValid::$cache = (require $path) ?: [];
}

if ($this->option('force')) {
FacadeAliasReplacer::$forceReplace = true;
}
Expand All @@ -86,19 +91,15 @@ public function handle()
$folder = ltrim($this->option('folder'), '=');
$folder = rtrim($folder, '/\\');

$routeFiles = FilePath::removeExtraPaths(
RoutePaths::get(),
$folder,
$fileName
);
$routeFiles = FilePath::removeExtraPaths(RoutePaths::get(), $folder, $fileName);

$autoloadedFilesGen = FilePath::removeExtraPaths(
ComposerJson::autoloadedFilesList(base_path()),
$folder,
$fileName
);

$paramProvider = $this->getParamProvider();
$paramProvider = self::getParamProvider();

$checks = $this->checks;
unset($checks[1]);
Expand All @@ -110,7 +111,7 @@ public function handle()

$foldersStats = FileIterators::checkFolders(
$checks,
$this->getLaravelFolders(),
self::getLaravelFolders(),
$paramProvider,
$fileName,
$folder
Expand All @@ -131,7 +132,7 @@ public function handle()
$messages[0] = CheckImportReporter::totalImportsMsg();
$messages[1] = Reporters\Psr4Report::printAutoload($psr4Stats, $classMapStats);
$messages[2] = CheckImportReporter::header();
$messages[3] = $this->getFilesStats();
$messages[3] = self::getFilesStats();
$messages[4] = Reporters\BladeReport::getBladeStats($bladeStats);
$messages[5] = Reporters\LaravelFoldersReport::foldersStats($foldersStats);
$messages[6] = CheckImportReporter::getRouteStats($routeFiles);
Expand All @@ -148,15 +149,20 @@ public function handle()
$errorPrinter->printTime();

if (Thanks::shouldShow()) {
$this->printThanks($this);
self::printThanks($this);
}

if ($cache = CheckClassReferencesAreValid::$cache) {
self::writeCacheContent($cache);
self::printThanks($this);
}

$this->line('');

return ErrorCounter::getTotalErrors() > 0 ? 1 : 0;
}

private function printThanks($command)
private static function printThanks($command)
{
$command->line(PHP_EOL);
foreach (Thanks::messages() as $msg) {
Expand All @@ -167,7 +173,7 @@ private function printThanks($command)
/**
* @return \Closure
*/
private function getParamProvider()
private static function getParamProvider()
{
return function (PhpFileDescriptor $file) {
$imports = ParseUseStatement::parseUseStatements($file->getTokens());
Expand All @@ -176,7 +182,7 @@ private function getParamProvider()
};
}

private function getFilesStats()
private static function getFilesStats()
{
$filesCount = ChecksOnPsr4Classes::$checkedFilesCount;

Expand All @@ -186,11 +192,21 @@ private function getFilesStats()
/**
* @return array<string, \Generator>
*/
private function getLaravelFolders()
private static function getLaravelFolders()
{
return [
'config' => LaravelPaths::configDirs(),
'migrations' => LaravelPaths::migrationDirs(),
];
}

private static function writeCacheContent(array $cache): void
{
$folder = CachedFiles::getPathForPattern();
! is_dir($folder) && mkdir($folder);
$content = CachedFiles::getCacheFileContents($cache);
$path = $folder.'check_imports.php';
file_exists($path) && chmod($path, 0777);
file_put_contents($path, $content);
}
}
39 changes: 32 additions & 7 deletions src/Features/CheckImports/Checks/CheckClassReferencesAreValid.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Imanghafoori\LaravelMicroscope\Features\CheckImports\Checks;

use Closure;
use Imanghafoori\LaravelMicroscope\Check;
use Imanghafoori\LaravelMicroscope\Features\CheckImports\Handlers;
use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor;
Expand All @@ -13,6 +14,8 @@ class CheckClassReferencesAreValid implements Check

public static $checkExtra = true;

public static $cache = [];

public static $extraCorrectImportsHandler = Handlers\ExtraCorrectImports::class;

public static $extraWrongImportsHandler = Handlers\ExtraWrongImports::class;
Expand All @@ -21,17 +24,34 @@ class CheckClassReferencesAreValid implements Check

public static function check(PhpFileDescriptor $file, $imports = [])
{
$tokens = $file->getTokens();
loopStart:
$md5 = $file->getMd5();
$absFilePath = $file->getAbsolutePath();

loopStart:
$tokens = $file->getTokens();
$refFinder = function () use ($file, $tokens, $imports) {
$absFilePath = $file->getAbsolutePath();

return ImportsAnalyzer::findClassRefs($tokens, $absFilePath, $imports);
};

if (count($tokens) > 100) {
$refFinder = function () use ($md5, $refFinder) {
return self::getForever($md5, $refFinder);
};
}

[
$classReferences,
$hostNamespace,
$extraWrongImports,
$extraCorrectImports,
$wrongClassRefs,
$wrongDocblockRefs,
] = ImportsAnalyzer::getWrongRefs($tokens, $absFilePath, $imports);
$extraImports,
$docblockRefs,
$attributeReferences,
] = $refFinder();

[$wrongClassRefs] = ImportsAnalyzer::filterWrongClassRefs($classReferences, $absFilePath);
[$wrongDocblockRefs] = ImportsAnalyzer::filterWrongClassRefs($docblockRefs, $absFilePath);
[$extraWrongImports, $extraCorrectImports] = ImportsAnalyzer::filterWrongClassRefs($extraImports, $absFilePath);

if (self::$checkWrong && self::$wrongClassRefsHandler) {
[$tokens, $isFixed] = self::$wrongClassRefsHandler::handle(
Expand Down Expand Up @@ -63,4 +83,9 @@ private static function handleExtraImports($absFilePath, $extraWrongImports, $ex
self::$extraCorrectImportsHandler::handle($extraCorrectImports, $absFilePath);
}
}

public static function getForever($md5, Closure $refFinder)
{
return self::$cache[$md5] ?? (self::$cache[$md5] = $refFinder());
}
}
Loading

0 comments on commit 3a19197

Please sign in to comment.