From c1b4ace090df0db6a1d295ae1c9b6aaf77555f3b Mon Sep 17 00:00:00 2001 From: Iman Ghafoori Date: Thu, 23 May 2024 01:42:00 +0330 Subject: [PATCH] refactor --- composer.json | 2 +- src/Analyzers/Fixer.php | 58 +++---- src/Checks/CheckEarlyReturn.php | 6 +- src/Checks/CheckIsQuery.php | 6 +- src/Checks/CheckRouteCalls.php | 6 +- src/Checks/CheckRubySyntax.php | 7 +- src/Checks/CheckStringy.php | 97 ------------ src/Checks/PSR12/CurlyBraces.php | 12 +- src/Commands/CheckCompact.php | 2 + src/Commands/CheckEarlyReturns.php | 2 + src/Commands/CheckEndIf.php | 2 + src/Commands/ClassifyStrings.php | 33 ---- src/Commands/EnforceQuery.php | 2 + src/Commands/PatternApply.php | 6 +- src/Commands/PrettyPrintRoutes.php | 4 +- .../ConsolePrinterInstaller.php | 3 - src/ErrorReporters/ErrorPrinter.php | 15 +- .../ActionComments/ActionsComments.php | 42 +++-- src/Features/ActionComments/CommentMaker.php | 16 +- .../CheckClassyStrings/CheckStringy.php | 104 +++++++++++++ .../CheckClassyStrings/CheckStringyMsg.php | 34 ++++ .../CheckClassyStrings/ClassifyStrings.php | 46 ++++++ src/Features/CheckDD/CheckDD.php | 8 +- src/Features/CheckDD/CheckDDCommand.php | 13 +- .../CheckDeadControllers.php | 15 +- .../RoutelessControllerActions.php} | 43 ++--- .../CheckFacadeDocblocks/FacadeDocblocks.php | 10 +- .../CheckGenericDocBlocksCommand.php | 15 +- .../GenericDocblocks.php | 47 +++--- .../CheckImports/CheckImportsCommand.php | 9 +- .../Checks/CheckClassAtMethod.php | 6 +- .../Checks/CheckClassReferencesAreValid.php | 6 +- .../CheckImports/Reporters/AutoloadFiles.php | 5 +- .../Reporters/CheckImportReporter.php | 4 +- .../CheckImports/Reporters/Psr4Report.php | 36 +++-- .../CheckImports/Reporters/Reporting.php | 13 +- src/Features/CheckView/BladeFile.php | 10 -- src/Features/CheckView/Check/CheckView.php | 26 +++- .../Check/CheckViewFilesExistence.php | 13 +- src/Features/CheckView/CheckViewsCommand.php | 14 +- src/Features/CheckView/ViewsInstaller.php | 22 --- .../ExtractBladePartial.php | 6 +- .../FacadeAlias/CheckAliasesCommand.php | 5 +- .../FacadeAlias/FacadeAliasesCheck.php | 6 +- src/Features/ListModels/SubclassFinder.php | 2 +- src/Features/Psr4/CheckPsr4ArtisanCommand.php | 71 ++++----- src/Features/Psr4/CheckPsr4Printer.php | 147 +++++++++++------- src/Features/Psr4/ClassRefCorrector.php | 106 +++++++++---- src/Features/Psr4/Confirm.php | 24 +++ .../Psr4/FilePathsForReferenceFix.php | 3 +- src/Features/Psr4/NamespaceFixer.php | 16 +- src/Features/Psr4/Psr4Errors.php | 92 +++++------ src/Features/Psr4/TypeStatistics.php | 52 +++++++ src/ForPsr4LoadedClasses.php | 14 +- src/Foundations/Path.php | 66 ++++++++ src/Foundations/PhpFileDescriptor.php | 137 ++++++++++++++++ src/Iterators/BaseIterator.php | 16 +- src/Iterators/BladeFiles/CheckBladePaths.php | 20 ++- src/Iterators/Check.php | 4 +- src/Iterators/ChecksOnPsr4Classes.php | 20 +-- src/LaravelMicroscopeServiceProvider.php | 92 +++++------ src/SearchReplace/PatternRefactorings.php | 6 +- src/ServiceProvider/CommandsRegistry.php | 46 ++++++ src/SpyClasses/RoutePaths.php | 3 +- .../CheckClassReferencesAreValidTest.php | 12 +- .../{wongImport.stub => wrongImport.stub} | 0 tests/NamespaceCorrectorTest.php | 9 +- 67 files changed, 1143 insertions(+), 652 deletions(-) delete mode 100644 src/Checks/CheckStringy.php delete mode 100644 src/Commands/ClassifyStrings.php create mode 100644 src/Features/CheckClassyStrings/CheckStringy.php create mode 100644 src/Features/CheckClassyStrings/CheckStringyMsg.php create mode 100644 src/Features/CheckClassyStrings/ClassifyStrings.php rename src/{Commands => Features/CheckDeadControllers}/CheckDeadControllers.php (58%) rename src/{Checks/RoutelessActions.php => Features/CheckDeadControllers/RoutelessControllerActions.php} (64%) delete mode 100644 src/Features/CheckView/BladeFile.php delete mode 100644 src/Features/CheckView/ViewsInstaller.php create mode 100644 src/Features/Psr4/Confirm.php create mode 100644 src/Features/Psr4/TypeStatistics.php create mode 100644 src/Foundations/Path.php create mode 100644 src/Foundations/PhpFileDescriptor.php create mode 100644 src/ServiceProvider/CommandsRegistry.php rename tests/CheckImports/{wongImport.stub => wrongImport.stub} (100%) diff --git a/composer.json b/composer.json index d3c74a1e..5a53b3eb 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.2|8.0.*|8.1.*|8.2.*|8.3.*", - "imanghafoori/composer-json": "^1.0.14", + "imanghafoori/composer-json": "^1.0.15", "composer/class-map-generator": "^1.0.0", "imanghafoori/php-abstract-filesystem": "^0.1.5", "imanghafoori/php-search-replace": "^1.1.12", diff --git a/src/Analyzers/Fixer.php b/src/Analyzers/Fixer.php index 2bcb1227..0eb77774 100644 --- a/src/Analyzers/Fixer.php +++ b/src/Analyzers/Fixer.php @@ -4,9 +4,8 @@ use ImanGhafoori\ComposerJson\NamespaceCalculator; use Imanghafoori\Filesystem\FileManipulator; -use Imanghafoori\Filesystem\Filesystem; use Imanghafoori\LaravelMicroscope\ClassListProvider; -use Imanghafoori\SearchReplace\Searcher; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\ParseUseStatement; class Fixer @@ -62,10 +61,11 @@ public static function fixReference($absPath, $inlinedClassRef, $lineNum) } $fullClassPath = $correct[0]; - $contextClassNamespace = ComposerJson::make()->getNamespacedClassFromPath($absPath); + $file = PhpFileDescriptor::make($absPath); + $contextClassNamespace = $file->getNamespace(); if (NamespaceCalculator::haveSameNamespace($contextClassNamespace, $fullClassPath)) { - return [self::doReplacement($absPath, $inlinedClassRef, class_basename($fullClassPath), $lineNum), $correct]; + return [self::doReplacement($file, $inlinedClassRef, class_basename($fullClassPath), $lineNum), $correct]; } $uses = ParseUseStatement::parseUseStatements(token_get_all(file_get_contents($absPath)))[1]; @@ -80,11 +80,11 @@ public static function fixReference($absPath, $inlinedClassRef, $lineNum) $fullClassPath = $classBaseName; } - return [self::doReplacement($absPath, $inlinedClassRef, $fullClassPath, $lineNum), $correct]; + return [self::doReplacement($file, $inlinedClassRef, $fullClassPath, $lineNum), $correct]; } // Replace in the class reference - self::doReplacement($absPath, $inlinedClassRef, $classBaseName, $lineNum); + self::doReplacement($file, $inlinedClassRef, $classBaseName, $lineNum); // Insert a new import at the top $lineNum = array_values($uses)[0][1]; // first use statement @@ -105,49 +105,35 @@ public static function fixImport($absPath, $import, $lineNum, $isAliased) { $correct = self::guessCorrect(class_basename($import)); - if (\count($correct) !== 1) { + if (count($correct) !== 1) { return [false, $correct]; } - $tokens = token_get_all(file_get_contents($absPath)); - $hostNamespacedClass = ComposerJson::make()->getNamespacedClassFromPath($absPath); + $file = PhpFileDescriptor::make($absPath); + $hostNamespacedClass = $file->getNamespace(); // We just remove the wrong import if it is not needed. if (! $isAliased && NamespaceCalculator::haveSameNamespace($hostNamespacedClass, $correct[0])) { - return [self::replaceSave("use $import;", '', $tokens, $absPath), [' Deleted!']]; - } - - return [self::replaceSave("use $import;", 'use '.$correct[0].';'.PHP_EOL, $tokens, $absPath), $correct]; - } + $lines = $file->searchReplacePatterns("use $import;", ''); - private static function replaceSave($old, $new, array $tokens, $absPath) - { - [$newVersion, $lines] = Searcher::searchReplace([ - 'fix' => [ - 'search' => $old, - 'replace' => $new, - ], - ], $tokens); + return [$lines, [' Deleted!']]; + } - Filesystem::$fileSystem::file_put_contents($absPath, $newVersion); + $lines = $file->searchReplacePatterns("use $import;", 'use '.$correct[0].';'.PHP_EOL); - return $lines; + return [$lines, $correct]; } - private static function doReplacement($absPath, $wrongRef, $correctRef, $lineNum) + private static function doReplacement(PhpFileDescriptor $file, $wrongRef, $correctRef, $lineNum) { - if (version_compare(PHP_VERSION, '8.0.0') === 1) { - return FileManipulator::replaceFirst($absPath, $wrongRef, $correctRef, $lineNum); + if (self::phpVersionIsMoreOrEqualTo('8.0.0')) { + return $file->replaceAtLine($wrongRef, $correctRef, $lineNum); } - $tokens = token_get_all(file_get_contents($absPath)); - [$newVersion, $lines] = Searcher::searchReplace([ - 'fix' => [ - 'search' => $wrongRef, - 'replace' => $correctRef, - ], - ], $tokens); - Filesystem::$fileSystem::file_put_contents($absPath, $newVersion); + return (bool) $file->searchReplacePatterns($wrongRef, $correctRef); + } - return (bool) $lines; + private static function phpVersionIsMoreOrEqualTo($version): bool + { + return version_compare(PHP_VERSION, $version) !== -1; } } diff --git a/src/Checks/CheckEarlyReturn.php b/src/Checks/CheckEarlyReturn.php index 3dbb8abe..d2108bfb 100644 --- a/src/Checks/CheckEarlyReturn.php +++ b/src/Checks/CheckEarlyReturn.php @@ -6,12 +6,16 @@ use Imanghafoori\LaravelMicroscope\Check; use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter; use Imanghafoori\LaravelMicroscope\FileReaders\FilePath; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\Refactor; class CheckEarlyReturn implements Check { - public static function check(array $tokens, $absFilePath, $params) + public static function check(PhpFileDescriptor $file, $params) { + $tokens = $file->getTokens(); + $absFilePath = $file->getAbsolutePath(); + $nofix = $params['nofix']; $nofixCallback = $params['nofixCallback']; $fixCallback = $params['fixCallback']; diff --git a/src/Checks/CheckIsQuery.php b/src/Checks/CheckIsQuery.php index 04b8d94a..460dccee 100644 --- a/src/Checks/CheckIsQuery.php +++ b/src/Checks/CheckIsQuery.php @@ -6,12 +6,16 @@ use Illuminate\Support\Facades\DB; use Imanghafoori\LaravelMicroscope\Check; use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\ParseUseStatement; class CheckIsQuery implements Check { - public static function check($tokens, $absPath) + public static function check(PhpFileDescriptor $file) { + $tokens = $file->getTokens(); + $absPath = $file->getAbsolutePath(); + [$classes] = ParseUseStatement::findClassReferences($tokens); foreach ($classes as $class) { diff --git a/src/Checks/CheckRouteCalls.php b/src/Checks/CheckRouteCalls.php index 8fa82378..67c35956 100644 --- a/src/Checks/CheckRouteCalls.php +++ b/src/Checks/CheckRouteCalls.php @@ -4,6 +4,7 @@ use Imanghafoori\LaravelMicroscope\Check; use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\FunctionCall; class CheckRouteCalls implements Check @@ -12,11 +13,14 @@ class CheckRouteCalls implements Check public static $skippedRouteCallsNum = 0; - public static function check($tokens, $absFilePath) + public static function check(PhpFileDescriptor $file) { // we skip the very first tokens: 'getTokens(); + $absFilePath = $file->getAbsolutePath(); + $total = \count($tokens) - 3; while ($i < $total) { $index = FunctionCall::isGlobalCall('route', $tokens, $i); diff --git a/src/Checks/CheckRubySyntax.php b/src/Checks/CheckRubySyntax.php index 9188cc0c..3ded7546 100644 --- a/src/Checks/CheckRubySyntax.php +++ b/src/Checks/CheckRubySyntax.php @@ -6,17 +6,20 @@ use Imanghafoori\LaravelMicroscope\Check; use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter; use Imanghafoori\LaravelMicroscope\FileReaders\FilePath; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\Refactor; use Imanghafoori\TokenAnalyzer\SyntaxNormalizer; class CheckRubySyntax implements Check { - public static function check($tokens, $absFilePath) + public static function check(PhpFileDescriptor $file) { if (empty($tokens) || $tokens[0][0] !== T_OPEN_TAG) { return false; } + $absFilePath = $file->getAbsolutePath(); + try { $tokens = SyntaxNormalizer::normalizeSyntax($tokens, true); } catch (Exception $e) { @@ -41,7 +44,7 @@ private static function getConfirm($filePath) return ErrorPrinter::singleton()->printer->confirm('Replacing endif in: '.$filePath); } - private static function requestIssue(string $path) + private static function requestIssue($path) { dump('(O_o) Well, It seems we had some problem parsing the contents of: (o_O)'); dump('Submit an issue on github: https://github.com/imanghafoori1/microscope'); diff --git a/src/Checks/CheckStringy.php b/src/Checks/CheckStringy.php deleted file mode 100644 index 866b877a..00000000 --- a/src/Checks/CheckStringy.php +++ /dev/null @@ -1,97 +0,0 @@ -checkStringy($tokens, $absFilePath); - } - - public function checkStringy($tokens, $absFilePath) - { - $errorPrinter = resolve(ErrorPrinter::class); - foreach (ComposerJson::readPsr4() as $psr4) { - $namespaces = array_keys($psr4); - foreach ($tokens as $token) { - if ($token[0] !== T_CONSTANT_ENCAPSED_STRING) { - continue; - } - - $classPath = \trim($token[1], '\'\"'); - - if (! $this->isPossiblyClassyString($namespaces, $classPath)) { - continue; - } - - if (! \class_exists(\str_replace('\\\\', '\\', $classPath))) { - if (self::refersToDir($classPath)) { - continue; - } - $errorPrinter->wrongUsedClassError($absFilePath, $token[1], $token[2]); - continue; - } - - $errorPrinter->printLink($absFilePath, $token[2]); - $command = app('current.command'); - - if (! self::ask($errorPrinter->printer, $token, $absFilePath)) { - continue; - } - - $classPath = $this->getClassyPath($classPath); - $command->info('✔ Replaced with: '.$classPath.''); - - $contextClass = ComposerJson::make()->getNamespacedClassFromPath($absFilePath); - - if (NamespaceCalculator::haveSameNamespace($contextClass, $classPath)) { - $classPath = trim(class_basename($classPath), '\\'); - } - - FileManipulator::replaceFirst($absFilePath, $token[1], $classPath); - $width = (new Terminal)->getWidth() - 4; - $command->info(' '.str_repeat('_', $width).''); - } - } - } - - protected function getClassyPath($string) - { - ($string[0] !== '\\') && ($string = '\\'.$string); - $string .= '::class'; - - return str_replace('\\\\', '\\', $string); - } - - private function isPossiblyClassyString($namespaces, $classPath) - { - $chars = ['@', ' ', ',', ':', '/', '.', '-']; - - return Str::contains($classPath, $namespaces) && - ! in_array($classPath, $namespaces) && - ! Str::contains($classPath, $chars) && - ! Str::endsWith($classPath, '\\'); - } - - private static function ask($printer, $token, $absFilePath) - { - $printer->text($token[2].' |'.file($absFilePath)[$token[2] - 1]); - $text = 'Replace: '.$token[1].' with ::class version of it?'; - - return $printer->confirm($text, true); - } - - private static function refersToDir(string $classPath) - { - return is_dir(base_path(ComposerJson::make()->getRelativePathFromNamespace($classPath))); - } -} diff --git a/src/Checks/PSR12/CurlyBraces.php b/src/Checks/PSR12/CurlyBraces.php index 31f46934..41fccf5f 100644 --- a/src/Checks/PSR12/CurlyBraces.php +++ b/src/Checks/PSR12/CurlyBraces.php @@ -3,14 +3,18 @@ namespace Imanghafoori\LaravelMicroscope\Checks\PSR12; use Imanghafoori\LaravelMicroscope\Check; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\Refactor; class CurlyBraces implements Check { public static $command; - public static function check($tokens, $absFilePath) + public static function check(PhpFileDescriptor $file) { + $tokens = $file->getTokens(); + $absFilePath = $file->getAbsolutePath(); + self::addPublicKeyword($tokens, $absFilePath); } @@ -37,20 +41,20 @@ private static function addPublicKeyword($tokens, $absolutePath) } } - private static function openCurly($token, $level, $tokens, $i, $classFilePath) + private static function openCurly($token, $level, $tokens, $i, $absFilePath) { if ($token == '{' && ! in_array($tokens[$i - 1][0], [T_DOUBLE_COLON, T_OBJECT_OPERATOR])) { $sp = str_repeat(' ', $level); if ($tokens[$i + 1][0] === T_WHITESPACE) { if ($tokens[$i + 1][1] !== PHP_EOL.$sp && $tokens[$i + 1][1] !== "\n".$sp) { $tokens[$i + 1][1] = PHP_EOL.$sp; - Refactor::saveTokens($classFilePath, $tokens); + Refactor::saveTokens($absFilePath, $tokens); } else { // } } else { array_splice($tokens, $i + 1, 0, [[T_WHITESPACE, PHP_EOL.$sp]]); - Refactor::saveTokens($classFilePath, $tokens); + Refactor::saveTokens($absFilePath, $tokens); } } } diff --git a/src/Commands/CheckCompact.php b/src/Commands/CheckCompact.php index 1e9e5b06..98353d94 100644 --- a/src/Commands/CheckCompact.php +++ b/src/Commands/CheckCompact.php @@ -11,6 +11,7 @@ use Imanghafoori\TokenAnalyzer\FunctionCall; use Imanghafoori\TokenAnalyzer\Ifs; use Imanghafoori\TokenAnalyzer\TokenManager; +use JetBrains\PhpStorm\ExpectedValues; class CheckCompact extends Command { @@ -18,6 +19,7 @@ class CheckCompact extends Command protected $description = 'Checks that compact() function calls are correct'; + #[ExpectedValues(values: [0, 1])] public function handle() { event('microscope.start.command'); diff --git a/src/Commands/CheckEarlyReturns.php b/src/Commands/CheckEarlyReturns.php index 1ea8caad..d216f7ba 100644 --- a/src/Commands/CheckEarlyReturns.php +++ b/src/Commands/CheckEarlyReturns.php @@ -9,6 +9,7 @@ use Imanghafoori\LaravelMicroscope\FileReaders\FilePath; use Imanghafoori\LaravelMicroscope\ForPsr4LoadedClasses; use Imanghafoori\LaravelMicroscope\Iterators\ClassMapIterator; +use JetBrains\PhpStorm\ExpectedValues; class CheckEarlyReturns extends Command { @@ -16,6 +17,7 @@ class CheckEarlyReturns extends Command protected $description = 'Applies the early return on the classes'; + #[ExpectedValues(values: [0, 1])] public function handle() { ErrorPrinter::singleton($this->output); diff --git a/src/Commands/CheckEndIf.php b/src/Commands/CheckEndIf.php index e06536d7..cc7214e1 100644 --- a/src/Commands/CheckEndIf.php +++ b/src/Commands/CheckEndIf.php @@ -8,6 +8,7 @@ use Imanghafoori\LaravelMicroscope\Features\CheckImports\Reporters\Psr4Report; use Imanghafoori\LaravelMicroscope\ForPsr4LoadedClasses; use Imanghafoori\LaravelMicroscope\Iterators\ClassMapIterator; +use JetBrains\PhpStorm\ExpectedValues; class CheckEndIf extends Command { @@ -15,6 +16,7 @@ class CheckEndIf extends Command protected $description = 'replaces ruby like syntax of php (endif) with curly brackets.'; + #[ExpectedValues(values: [0, 1])] public function handle(ErrorPrinter $errorPrinter) { if (! $this->startWarning()) { diff --git a/src/Commands/ClassifyStrings.php b/src/Commands/ClassifyStrings.php deleted file mode 100644 index cd67ef2b..00000000 --- a/src/Commands/ClassifyStrings.php +++ /dev/null @@ -1,33 +0,0 @@ -info('Checking stringy classes...'); - app()->singleton('current.command', function () { - return $this; - }); - $errorPrinter->printer = $this->output; - - $fileName = ltrim($this->option('file'), '='); - $folder = ltrim($this->option('folder'), '='); - - ForPsr4LoadedClasses::checkNow([CheckStringy::class], [], $fileName, $folder); - - $this->getOutput()->writeln(' ✔ - Finished looking for stringy classes.'); - - return $errorPrinter->hasErrors() ? 1 : 0; - } -} diff --git a/src/Commands/EnforceQuery.php b/src/Commands/EnforceQuery.php index 05334886..f567fa57 100644 --- a/src/Commands/EnforceQuery.php +++ b/src/Commands/EnforceQuery.php @@ -8,6 +8,7 @@ use Imanghafoori\LaravelMicroscope\SearchReplace\IsSubClassOf; use Imanghafoori\LaravelMicroscope\Traits\LogsErrors; use Imanghafoori\SearchReplace\Filters; +use JetBrains\PhpStorm\ExpectedValues; class EnforceQuery extends Command { @@ -20,6 +21,7 @@ class EnforceQuery extends Command protected $customMsg = 'No case was found to add ::query()-> to it. \(^_^)/'; + #[ExpectedValues(values: [0, 1])] public function handle(ErrorPrinter $errorPrinter) { event('microscope.start.command'); diff --git a/src/Commands/PatternApply.php b/src/Commands/PatternApply.php index f1dacc3d..5285ad11 100644 --- a/src/Commands/PatternApply.php +++ b/src/Commands/PatternApply.php @@ -6,6 +6,7 @@ use Imanghafoori\LaravelMicroscope\Features\CheckImports\Reporters; use Imanghafoori\LaravelMicroscope\ForPsr4LoadedClasses; use Imanghafoori\LaravelMicroscope\Iterators\BladeFiles; +use Imanghafoori\LaravelMicroscope\Iterators\BladeFiles\CheckBladePaths; use Imanghafoori\LaravelMicroscope\Iterators\ClassMapIterator; use Imanghafoori\LaravelMicroscope\SearchReplace\PatternRefactorings; use Imanghafoori\SearchReplace\PatternParser; @@ -47,11 +48,12 @@ private function appliesPatterns(array $patterns, string $fileName, string $fold $psr4Stats = ForPsr4LoadedClasses::check($check, $paramProvider, $fileName, $folder); $classMapStats = ClassMapIterator::iterate(base_path(), $check, [$parsedPatterns], $fileName, $folder); - //$bladeStats = BladeFiles::check($check, [$parsedPatterns], $fileName, $folder); + CheckBladePaths::$readOnly = false; + $bladeStats = BladeFiles::check($check, [$parsedPatterns], $fileName, $folder); $this->getOutput()->writeln(implode(PHP_EOL, [ Reporters\Psr4Report::printAutoload($psr4Stats, $classMapStats), - //Reporters\BladeReport::getBladeStats($bladeStats), + Reporters\BladeReport::getBladeStats($bladeStats), ])); } } diff --git a/src/Commands/PrettyPrintRoutes.php b/src/Commands/PrettyPrintRoutes.php index e0f552a6..d5e694c9 100644 --- a/src/Commands/PrettyPrintRoutes.php +++ b/src/Commands/PrettyPrintRoutes.php @@ -45,7 +45,7 @@ private function writeIt($route, $filename) $action = $this->getAction($route->getActionName()); - if (\count($methods) == 1) { + if (count($methods) == 1) { $definition = PHP_EOL.$this->getMovableRoute($route, $methods, $action, $middlewares); file_put_contents($filename, $definition, FILE_APPEND); @@ -112,7 +112,7 @@ private function getAction($action) private function getMiddlewares($route) { $middlewares = $route->gatherMiddleware(); - $middlewares && $middlewares = "'".\implode("', '", $route->gatherMiddleware())."'"; + $middlewares && $middlewares = "'".implode("', '", $route->gatherMiddleware())."'"; return $middlewares ? '->middleware(['.$middlewares.'])' : ''; } diff --git a/src/ErrorReporters/ConsolePrinterInstaller.php b/src/ErrorReporters/ConsolePrinterInstaller.php index 58c53737..0c6b0cfc 100644 --- a/src/ErrorReporters/ConsolePrinterInstaller.php +++ b/src/ErrorReporters/ConsolePrinterInstaller.php @@ -5,7 +5,6 @@ use Illuminate\Support\Facades\Event; use Illuminate\Support\Str; use Imanghafoori\LaravelMicroscope\Features\CheckEnvCalls\EnvFound; -use Imanghafoori\LaravelMicroscope\Features\CheckView\ViewsInstaller; use Imanghafoori\LaravelMicroscope\Features\RouteOverride\Installer as RouteOverrideInstaller; class ConsolePrinterInstaller @@ -47,8 +46,6 @@ protected static function getKey($commandType) public static function boot() { - ViewsInstaller::boot(); - RouteOverrideInstaller::install(); EnvFound::listen(); diff --git a/src/ErrorReporters/ErrorPrinter.php b/src/ErrorReporters/ErrorPrinter.php index c4607f0d..cf2b9c2c 100644 --- a/src/ErrorReporters/ErrorPrinter.php +++ b/src/ErrorReporters/ErrorPrinter.php @@ -105,9 +105,9 @@ public function simplePendError($yellowText, $absPath, $lineNumber, $key, $heade $this->addPendingError($absPath, $lineNumber, $key, $header, $errorData); } - public function color($msg) + public function color($msg, $color = 'blue') { - return "$msg"; + return "$msg"; } public function print($msg, $path = ' ') @@ -120,13 +120,13 @@ public function printHeader($msg) $number = ++$this->total; ($number < 10) && $number = " $number"; - $number = ''.$number.' '; - $path = " $number"; + $number = $this->color($number, 'cyan'); + $path = " $number "; $width = (new Terminal)->getWidth() - 6; PendingError::$maxLength = max(PendingError::$maxLength, strlen($msg), $width); PendingError::$maxLength = min(PendingError::$maxLength, $width); - $this->print(''.$msg.'', $path); + $this->print($this->color($msg, 'red'), $path); } public function end() @@ -251,4 +251,9 @@ private function getTimeMessage() return "time: {$duration} (sec)"; } + + public static function lineSeparator(): string + { + return ' '.str_repeat('_', (new Terminal)->getWidth() - 3).''; + } } diff --git a/src/Features/ActionComments/ActionsComments.php b/src/Features/ActionComments/ActionsComments.php index b460ede4..5b2ae2f2 100644 --- a/src/Features/ActionComments/ActionsComments.php +++ b/src/Features/ActionComments/ActionsComments.php @@ -3,7 +3,8 @@ namespace Imanghafoori\LaravelMicroscope\Features\ActionComments; use Imanghafoori\LaravelMicroscope\Check; -use Imanghafoori\LaravelMicroscope\Checks\RoutelessActions; +use Imanghafoori\LaravelMicroscope\Features\CheckDeadControllers\RoutelessControllerActions; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\ClassMethods; use Imanghafoori\TokenAnalyzer\Refactor; @@ -13,39 +14,42 @@ class ActionsComments implements Check public static $controllers = []; - public static function check($tokens, $absFilePath, $params, $classFilePath, $psr4Path, $psr4Namespace) + public static function check(PhpFileDescriptor $file) { - $fullNamespace = RoutelessActions::getFullNamespace($classFilePath, $psr4Path, $psr4Namespace); + $tokens = $file->getTokens(); + $absFilePath = $file->getAbsolutePath(); + + $fullNamespace = $file->getNamespace(); if (isset(static::$controllers[trim($fullNamespace, '\\')])) { - self::checkActions($tokens, $fullNamespace, $classFilePath); + self::checkActions($tokens, $fullNamespace, $absFilePath); } } - private static function checkActions($tokens, $fullNamespace, $path) + private static function checkActions($tokens, $fullNamespace, $absFilePath) { $methods = ClassMethods::read($tokens)['methods']; - $methods = RoutelessActions::getControllerActions($methods); - $routelessActions = []; + $methods = RoutelessControllerActions::getControllerActions($methods); $shouldSave = false; $allRoutes = app('router')->getRoutes()->getRoutes(); foreach ($methods as $method) { - $classAtMethod = RoutelessActions::classAtMethod($fullNamespace, $method['name'][1]); - $actions = self::getActions($allRoutes, $classAtMethod); + $classAtMethod = RoutelessControllerActions::classAtMethod($fullNamespace, $method['name'][1]); + $routes = self::getActionRoutes($allRoutes, $classAtMethod); - if (! $actions) { + if (! $routes) { continue; } /** * @var $route \Illuminate\Routing\Route */ - $msg = CommentMaker::getComment($actions); + $msg = CommentMaker::getComment($routes); $commentIndex = $method['startBodyIndex'][0] + 1; if (T_DOC_COMMENT !== $tokens[$commentIndex + 1][0]) { + // in case there is no doc-block $shouldSave = true; $tokens[$commentIndex][1] = "\n ".$msg.$tokens[$commentIndex][1]; } elseif ($msg !== $tokens[$commentIndex + 1][1]) { @@ -53,17 +57,12 @@ private static function checkActions($tokens, $fullNamespace, $path) $shouldSave = true; $tokens[$commentIndex + 1][1] = $msg; } - - $line = $method['name'][2]; - $routelessActions[] = [$line, $classAtMethod]; } $question = 'Add route definition into the: '.$fullNamespace.''; if ($shouldSave && self::$command->confirm($question, true)) { - Refactor::saveTokens($path->getRealpath(), $tokens); + Refactor::saveTokens($absFilePath, $tokens); } - - return $routelessActions; } public static function getCallsiteInfo($methods, $route) @@ -77,14 +76,13 @@ public static function getCallsiteInfo($methods, $route) return [$file, $line]; } - private static function getActions($allRoutes, $classAtMethod) + private static function getActionRoutes($allRoutes, $method) { - $actions = []; + $routes = []; foreach ($allRoutes as $route) { - $action = $route->getAction('uses'); - $classAtMethod === $action && $actions[] = $route; + $method === $route->getAction('uses') && ($routes[] = $route); } - return $actions; + return $routes; } } diff --git a/src/Features/ActionComments/CommentMaker.php b/src/Features/ActionComments/CommentMaker.php index edfa6a1c..87080b32 100644 --- a/src/Features/ActionComments/CommentMaker.php +++ b/src/Features/ActionComments/CommentMaker.php @@ -4,14 +4,15 @@ class CommentMaker { - public static function getComment(array $actions) + private const separator = "\n *"; + + public static function getComment(array $routes) { $msg = '/**'; - $separator = "\n *"; - foreach ($actions as $i => $action) { - $i === count($actions) - 1 && $separator = ''; - $msg .= "\n ".rtrim(self::getMsg($action)).$separator; + foreach ($routes as $i => $route) { + $sep = self::getSeparator(isset($routes[$i + 1])); + $msg .= "\n ".rtrim(self::getMsg($route)).$sep; } $msg .= "\n */"; @@ -78,4 +79,9 @@ private static function getUrl($route) return $url; } + + private static function getSeparator($isFinal) + { + return $isFinal ? '' : self::separator; + } } diff --git a/src/Features/CheckClassyStrings/CheckStringy.php b/src/Features/CheckClassyStrings/CheckStringy.php new file mode 100644 index 00000000..19cc4fbf --- /dev/null +++ b/src/Features/CheckClassyStrings/CheckStringy.php @@ -0,0 +1,104 @@ +getTokens() as $token) { + if ($token[0] !== T_CONSTANT_ENCAPSED_STRING) { + continue; + } + + $classPath = trim($token[1], '\'\"'); + + if (! self::isPossiblyClassyString($namespaces, $classPath)) { + continue; + } + + $lineNum = $token[2]; + $absFilePath = $file->getAbsolutePath(); + if (! class_exists(str_replace('\\\\', '\\', $classPath))) { + if (self::refersToDir($classPath)) { + continue; + } + $errorPrinter->wrongUsedClassError($absFilePath, $token[1], $lineNum); + continue; + } + + $errorPrinter->printLink($absFilePath, $lineNum); + $command = app('current.command'); + + if (! self::ask($errorPrinter->printer, $lineNum, $token[1], $file)) { + continue; + } + $replacement = self::getClassPath($classPath, $file); + self::performReplacementProcess($token[1], $replacement, $command, $file); + } + } + } + + private static function isPossiblyClassyString($namespaces, $classPath) + { + $chars = ['@', ' ', ',', ':', '/', '.', '-']; + + return Str::contains($classPath, $namespaces) && + ! in_array($classPath, $namespaces) && + ! Str::contains($classPath, $chars) && + ! Str::endsWith($classPath, '\\'); + } + + private static function ask($printer, $lineNumber, $classPath, PhpFileDescriptor $file) + { + $printer->text(CheckStringyMsg::getLineContents($lineNumber, $file)); + + return $printer->confirm(CheckStringyMsg::question($classPath), true); + } + + private static function refersToDir($classPath) + { + return is_dir(base_path(ComposerJson::make()->getRelativePathFromNamespace($classPath))); + } + + private static function performReplacementProcess($classyString, $classPath, $command, PhpFileDescriptor $file) + { + $command->info(CheckStringyMsg::successfulReplacementMsg($classPath)); + + // todo: should replace tokens not the file contents. + FileManipulator::replaceFirst($file->getAbsolutePath(), $classyString, $classPath); + + $command->info(CheckStringyMsg::lineSeparator()); + } + + public static function getClassPath(string $classPath, PhpFileDescriptor $file) + { + // Put back-slash at the beginning. + ($classPath[0] !== '\\') && ($classPath = '\\'.$classPath); + + $classPath .= '::class'; + + // Remove possible double back-slash: + $classPath = str_replace('\\\\', '\\', $classPath); + + // Remove unnecessary qualifier if possible. + $contextClass = $file->getNamespace(); + + if (NamespaceCalculator::haveSameNamespace($contextClass, $classPath)) { + $classPath = trim(class_basename($classPath), '\\'); + } + + return $classPath; + } +} diff --git a/src/Features/CheckClassyStrings/CheckStringyMsg.php b/src/Features/CheckClassyStrings/CheckStringyMsg.php new file mode 100644 index 00000000..53dd944d --- /dev/null +++ b/src/Features/CheckClassyStrings/CheckStringyMsg.php @@ -0,0 +1,34 @@ +✔ Replaced with: '.$classPath.''; + } + + public static function lineSeparator(): string + { + return ' '.str_repeat('_', (new Terminal)->getWidth() - 4).''; + } + + public static function question($class) + { + return 'Replace: '.$class.' with ::class version of it?'; + } + + public static function getLineContents($lineNumber, PhpFileDescriptor $file) + { + return $lineNumber.' |'.$file->getLine($lineNumber); + } + + public static function finished() + { + return ' ✔ - Finished looking for stringy classes.'; + } +} diff --git a/src/Features/CheckClassyStrings/ClassifyStrings.php b/src/Features/CheckClassyStrings/ClassifyStrings.php new file mode 100644 index 00000000..7ffa3d5d --- /dev/null +++ b/src/Features/CheckClassyStrings/ClassifyStrings.php @@ -0,0 +1,46 @@ +info('Checking stringy classes...'); + app()->singleton('current.command', function () { + return $this; + }); + $errorPrinter->printer = $this->output; + + $fileName = ltrim($this->option('file'), '='); + $folder = ltrim($this->option('folder'), '='); + + [$psr4Stats, $classMapStats] = self::classifyString($fileName, $folder); + + $this->getOutput()->writeln(implode(PHP_EOL, [ + Psr4Report::printAutoload($psr4Stats, $classMapStats), + ])); + + $this->getOutput()->writeln(CheckStringyMsg::finished()); + + return $errorPrinter->hasErrors() ? 1 : 0; + } + + public static function classifyString(string $fileName, string $folder): array + { + $psr4Stats = ForPsr4LoadedClasses::check([CheckStringy::class], [], $fileName, $folder); + $classMapStats = ClassMapIterator::iterate(base_path(), [CheckStringy::class], [], $fileName, $folder); + + return [$psr4Stats, $classMapStats]; + } +} diff --git a/src/Features/CheckDD/CheckDD.php b/src/Features/CheckDD/CheckDD.php index 5d3c8ae4..437eac7f 100644 --- a/src/Features/CheckDD/CheckDD.php +++ b/src/Features/CheckDD/CheckDD.php @@ -3,12 +3,16 @@ namespace Imanghafoori\LaravelMicroscope\Features\CheckDD; use Imanghafoori\LaravelMicroscope\Check; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\FunctionCall; class CheckDD implements Check { - public static function check($tokens, $absPath, $params) + public static function check(PhpFileDescriptor $file, $params) { + $tokens = $file->getTokens(); + $absPath = $file->getAbsolutePath(); + $callback = $params[0]; foreach ($tokens as $i => $token) { if ( @@ -23,4 +27,4 @@ public static function check($tokens, $absPath, $params) } } } -} \ No newline at end of file +} diff --git a/src/Features/CheckDD/CheckDDCommand.php b/src/Features/CheckDD/CheckDDCommand.php index 0dda5c0c..6dcc098d 100644 --- a/src/Features/CheckDD/CheckDDCommand.php +++ b/src/Features/CheckDD/CheckDDCommand.php @@ -7,6 +7,7 @@ use Imanghafoori\LaravelMicroscope\Features\CheckImports\Reporters\LaravelFoldersReport; use Imanghafoori\LaravelMicroscope\Features\CheckImports\Reporters\Psr4Report; use Imanghafoori\LaravelMicroscope\ForPsr4LoadedClasses; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\LaravelMicroscope\Iterators\ClassMapIterator; use Imanghafoori\LaravelMicroscope\Iterators\FileIterators; use Imanghafoori\LaravelMicroscope\LaravelPaths\LaravelPaths; @@ -27,22 +28,22 @@ public function handle() $fileName = ltrim($this->option('file'), '='); $folder = ltrim($this->option('folder'), '='); - $callback = function ($tokens, $absPath, $token) { + $paramProvider = function (PhpFileDescriptor $file, $token) { ErrorPrinter::singleton()->simplePendError( - $token[1], $absPath, $token[2], 'ddFound', 'Debug function found: ' + $token[1], $file->getTokens(), $token[2], 'ddFound', 'Debug function found: ' ); }; - $psr4Stats = ForPsr4LoadedClasses::check([CheckDD::class], [$callback], $fileName, $folder); - $classMapStats = ClassMapIterator::iterate(base_path(), [CheckDD::class], [$callback], $fileName, $folder); + $psr4Stats = ForPsr4LoadedClasses::check([CheckDD::class], [$paramProvider], $fileName, $folder); + $classMapStats = ClassMapIterator::iterate(base_path(), [CheckDD::class], [$paramProvider], $fileName, $folder); $foldersStats = FileIterators::checkFolders( - [CheckDD::class], $this->getLaravelFolders(), [$callback], $fileName, $folder + [CheckDD::class], $this->getLaravelFolders(), [$paramProvider], $fileName, $folder ); $this->getOutput()->writeln(implode(PHP_EOL, [ Psr4Report::printAutoload($psr4Stats, $classMapStats), - LaravelFoldersReport::foldersStats($foldersStats) + LaravelFoldersReport::foldersStats($foldersStats), ])); $this->getOutput()->writeln(' - Finished looking for debug functions. ('.self::$checkedCallsNum.' files checked)'); diff --git a/src/Commands/CheckDeadControllers.php b/src/Features/CheckDeadControllers/CheckDeadControllers.php similarity index 58% rename from src/Commands/CheckDeadControllers.php rename to src/Features/CheckDeadControllers/CheckDeadControllers.php index 07328a1a..0ad9d2c2 100644 --- a/src/Commands/CheckDeadControllers.php +++ b/src/Features/CheckDeadControllers/CheckDeadControllers.php @@ -1,10 +1,10 @@ printer = $this->output; - ForPsr4LoadedClasses::checkNow([RoutelessActions::class]); + $fileName = ltrim($this->option('file'), '='); + $folder = ltrim($this->option('folder'), '='); + + $psr4Stats = ForPsr4LoadedClasses::check([RoutelessControllerActions::class], [], $fileName, $folder); + + $this->getOutput()->writeln(implode(PHP_EOL, [ + Psr4Report::printAutoload($psr4Stats, []), + ])); $this->finishCommand($errorPrinter); $errorPrinter->printTime(); diff --git a/src/Checks/RoutelessActions.php b/src/Features/CheckDeadControllers/RoutelessControllerActions.php similarity index 64% rename from src/Checks/RoutelessActions.php rename to src/Features/CheckDeadControllers/RoutelessControllerActions.php index 636ef9f5..c8cbcefe 100644 --- a/src/Checks/RoutelessActions.php +++ b/src/Features/CheckDeadControllers/RoutelessControllerActions.php @@ -1,21 +1,20 @@ getNamespace(); if (! self::isLaravelController($fullNamespace)) { return; @@ -26,9 +25,9 @@ public static function check($tokens, $absFilePath, $params, $classFilePath, $ps return; } - $actions = self::findOrphanActions($tokens, $fullNamespace); + $actions = self::findOrphanActions($file->getTokens(), $fullNamespace); - self::printErrors($actions, $absFilePath); + self::printErrors($actions, $file->getAbsolutePath()); } public static function getControllerActions($methods) @@ -57,16 +56,6 @@ public static function getControllerActions($methods) return $orphanMethods; } - public static function getNamespacedClassName($classFilePath, $psr4Path, $psr4Namespace) - { - $absFilePath = $classFilePath->getRealPath(); - $className = $classFilePath->getFilename(); - $relativePath = \str_replace(base_path(), '', $absFilePath); - $namespace = NamespaceCalculator::calculateCorrectNamespace($relativePath, $psr4Path, $psr4Namespace); - - return $namespace.'\\'.$className; - } - public static function isLaravelController($fullNamespace) { try { @@ -77,13 +66,6 @@ public static function isLaravelController($fullNamespace) } } - public static function getFullNamespace($classFilePath, $psr4Path, $psr4Namespace) - { - $fullNamespace = self::getNamespacedClassName($classFilePath, $psr4Path, $psr4Namespace); - - return Str::replaceFirst('.php', '', $fullNamespace); - } - protected static function findOrphanActions($tokens, $fullNamespace) { $class = ClassMethods::read($tokens); @@ -107,9 +89,9 @@ protected static function findOrphanActions($tokens, $fullNamespace) public static function classAtMethod($fullNamespace, $methodName) { - ($methodName == '__invoke') ? ($methodName = '') : ($methodName = '@'.$methodName); + $methodName = $methodName === '__invoke' ? '' : '@'.$methodName; - return \trim($fullNamespace, '\\').$methodName; + return trim($fullNamespace, '\\').$methodName; } protected static function getByAction($classAtMethod) @@ -117,7 +99,7 @@ protected static function getByAction($classAtMethod) return app('router')->getRoutes()->getByAction($classAtMethod); } - private static function printErrors(array $actions, $absFilePath): void + private static function printErrors(array $actions, $absFilePath) { $errorPrinter = ErrorPrinter::singleton(); @@ -125,9 +107,4 @@ private static function printErrors(array $actions, $absFilePath): void $errorPrinter->simplePendError($action[1], $absFilePath, $action[0], 'routelessCtrl', 'No route is defined for controller action:'); } } - - private static function invoke($method, string $classAtMethod): bool - { - return $method === '__invoke' && ! self::getByAction($classAtMethod) && ! self::getByAction($classAtMethod.'@__invoke'); - } } diff --git a/src/Features/CheckFacadeDocblocks/FacadeDocblocks.php b/src/Features/CheckFacadeDocblocks/FacadeDocblocks.php index 1b8ccc82..bc9d462c 100644 --- a/src/Features/CheckFacadeDocblocks/FacadeDocblocks.php +++ b/src/Features/CheckFacadeDocblocks/FacadeDocblocks.php @@ -6,7 +6,7 @@ use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Facade; use Imanghafoori\Filesystem\Filesystem; -use Imanghafoori\LaravelMicroscope\Analyzers\ComposerJson; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\RealtimeFacades\SmartRealTimeFacadesProvider; use Imanghafoori\SearchReplace\Searcher; use ReflectionClass; @@ -16,9 +16,11 @@ class FacadeDocblocks { public static $command; - public static function check($tokens, $absFilePath) + public static function check(PhpFileDescriptor $file) { - $facade = ComposerJson::make()->getNamespacedClassFromPath($absFilePath); + $absFilePath = $file->getAbsolutePath(); + + $facade = $file->getNamespace(); if (! self::isFacade($facade)) { return null; @@ -41,7 +43,7 @@ public static function check($tokens, $absFilePath) } } - self::addDocBlocks($accessor, $facade, $tokens, $absFilePath); + self::addDocBlocks($accessor, $facade, $file->getTokens(), $absFilePath); } private static function addDocBlocks(string $accessor, $facade, $tokens, $absFilePath) diff --git a/src/Features/CheckGenericDocBlocks/CheckGenericDocBlocksCommand.php b/src/Features/CheckGenericDocBlocks/CheckGenericDocBlocksCommand.php index 52cad077..b231d5ef 100644 --- a/src/Features/CheckGenericDocBlocks/CheckGenericDocBlocksCommand.php +++ b/src/Features/CheckGenericDocBlocks/CheckGenericDocBlocksCommand.php @@ -3,6 +3,7 @@ namespace Imanghafoori\LaravelMicroscope\Features\CheckGenericDocBlocks; use Illuminate\Console\Command; +use Imanghafoori\LaravelMicroscope\Features\CheckImports\Reporters\Psr4Report; use Imanghafoori\LaravelMicroscope\ForPsr4LoadedClasses; class CheckGenericDocBlocksCommand extends Command @@ -15,10 +16,18 @@ public function handle() { $this->info('Removing generic doc-blocks...'); - GenericDocblocks::$confirmer = $this->getConformer(); + GenericDocblocks::$conformer = $this->getConformer(); - $results = ForPsr4LoadedClasses::check([GenericDocblocks::class], [], ltrim($this->option('file'), '='), ltrim($this->option('folder'), '=')); - iterator_to_array($results); + $psr4Stats = ForPsr4LoadedClasses::check( + [GenericDocblocks::class], + [], + ltrim($this->option('file'), '='), + ltrim($this->option('folder'), '=') + ); + + $this->getOutput()->writeln(implode(PHP_EOL, [ + Psr4Report::printAutoload($psr4Stats, []), + ])); $this->info(GenericDocblocks::$foundCount.' generic doc-blocks were found.'); $this->info(GenericDocblocks::$removedCount.' of them were removed.'); diff --git a/src/Features/CheckGenericDocBlocks/GenericDocblocks.php b/src/Features/CheckGenericDocBlocks/GenericDocblocks.php index 97a0e6d1..bc022b52 100644 --- a/src/Features/CheckGenericDocBlocks/GenericDocblocks.php +++ b/src/Features/CheckGenericDocBlocks/GenericDocblocks.php @@ -3,7 +3,8 @@ namespace Imanghafoori\LaravelMicroscope\Features\CheckGenericDocBlocks; use Imanghafoori\LaravelMicroscope\Check; -use Imanghafoori\LaravelMicroscope\Checks\RoutelessActions; +use Imanghafoori\LaravelMicroscope\Features\CheckDeadControllers\RoutelessControllerActions; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\Refactor; use Imanghafoori\TokenAnalyzer\Str; @@ -20,7 +21,7 @@ class GenericDocblocks implements Check '* Handle the incoming request.', ]; - public static $confirmer; + public static $conformer; public static $foundCount = 0; @@ -28,28 +29,20 @@ class GenericDocblocks implements Check public static $controllers = []; - public static function check($tokens, $absFilePath, $params, $classFilePath, $psr4Path, $psr4Namespace) + public static function check(PhpFileDescriptor $file) { - $fullNamespace = RoutelessActions::getFullNamespace($classFilePath, $psr4Path, $psr4Namespace); + $tokens = $file->getTokens(); - if (! RoutelessActions::isLaravelController($fullNamespace)) { + $fullNamespace = $file->getNamespace(); + + if (! RoutelessControllerActions::isLaravelController($fullNamespace)) { return null; } - $hasReplacement = false; - foreach ($tokens as $i => $token) { - if ($token[0] !== T_DOC_COMMENT) { - continue; - } + [$hasReplacement, $tokens] = self::removeDocBlocks($tokens); - if (self::shouldBeRemoved($token[1])) { - self::$foundCount++; - $hasReplacement = true; - $tokens = self::removeDocblock($tokens, $i); - } - } - - if ($hasReplacement && (self::$confirmer)($absFilePath)) { + $absFilePath = $file->getAbsolutePath(); + if ($hasReplacement && (self::$conformer)($absFilePath)) { Refactor::saveTokens($absFilePath, $tokens); } } @@ -73,4 +66,22 @@ private static function shouldBeRemoved($docblock) { return Str::contains($docblock, self::statements); } + + private static function removeDocBlocks(array $tokens): array + { + $hasReplacement = false; + foreach ($tokens as $i => $token) { + if ($token[0] !== T_DOC_COMMENT) { + continue; + } + + if (self::shouldBeRemoved($token[1])) { + self::$foundCount++; + $hasReplacement = true; + $tokens = self::removeDocblock($tokens, $i); + } + } + + return [$hasReplacement, $tokens]; + } } diff --git a/src/Features/CheckImports/CheckImportsCommand.php b/src/Features/CheckImports/CheckImportsCommand.php index c681dbeb..d3bb992f 100644 --- a/src/Features/CheckImports/CheckImportsCommand.php +++ b/src/Features/CheckImports/CheckImportsCommand.php @@ -17,6 +17,7 @@ use Imanghafoori\LaravelMicroscope\Features\Thanks; use Imanghafoori\LaravelMicroscope\FileReaders\FilePath; use Imanghafoori\LaravelMicroscope\ForPsr4LoadedClasses; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\LaravelMicroscope\Iterators\BladeFiles; use Imanghafoori\LaravelMicroscope\Iterators\ChecksOnPsr4Classes; use Imanghafoori\LaravelMicroscope\Iterators\ClassMapIterator; @@ -133,8 +134,8 @@ public function handle() $messages[3] = $this->getFilesStats(); $messages[4] = Reporters\BladeReport::getBladeStats($bladeStats); $messages[5] = Reporters\LaravelFoldersReport::foldersStats($foldersStats); - $messages[6] = CheckImportReporter::getRouteStats(base_path(), $routeFiles); - $messages[7] = AutoloadFiles::getLines(base_path(), $autoloadedFilesGen); + $messages[6] = CheckImportReporter::getRouteStats($routeFiles); + $messages[7] = AutoloadFiles::getLines($autoloadedFilesGen); $messages[8] = Reporters\SummeryReport::summery($errorPrinter->errorsCounts); if (! ImportsAnalyzer::$checkedRefCount) { @@ -168,8 +169,8 @@ private function printThanks($command) */ private function getParamProvider() { - return function ($tokens) { - $imports = ParseUseStatement::parseUseStatements($tokens); + return function (PhpFileDescriptor $file) { + $imports = ParseUseStatement::parseUseStatements($file->getTokens()); return $imports[0] ?: [$imports[1]]; }; diff --git a/src/Features/CheckImports/Checks/CheckClassAtMethod.php b/src/Features/CheckImports/Checks/CheckClassAtMethod.php index 08f2c2e3..de41faf1 100644 --- a/src/Features/CheckImports/Checks/CheckClassAtMethod.php +++ b/src/Features/CheckImports/Checks/CheckClassAtMethod.php @@ -4,13 +4,17 @@ use Imanghafoori\LaravelMicroscope\Check; use Imanghafoori\LaravelMicroscope\Features\CheckImports\Handlers\ClassAtMethodHandler; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; class CheckClassAtMethod implements Check { public static $handler = ClassAtMethodHandler::class; - public static function check($tokens, $absFilePath) + public static function check(PhpFileDescriptor $file) { + $tokens = $file->getTokens(); + $absFilePath = $file->getAbsolutePath(); + $replaced = self::$handler::handle( $absFilePath, self::getAtSignTokens($tokens, $absFilePath) diff --git a/src/Features/CheckImports/Checks/CheckClassReferencesAreValid.php b/src/Features/CheckImports/Checks/CheckClassReferencesAreValid.php index 135c67ab..2e9bd4de 100644 --- a/src/Features/CheckImports/Checks/CheckClassReferencesAreValid.php +++ b/src/Features/CheckImports/Checks/CheckClassReferencesAreValid.php @@ -4,6 +4,7 @@ use Imanghafoori\LaravelMicroscope\Check; use Imanghafoori\LaravelMicroscope\Features\CheckImports\Handlers; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\ImportsAnalyzer; class CheckClassReferencesAreValid implements Check @@ -18,8 +19,11 @@ class CheckClassReferencesAreValid implements Check public static $wrongClassRefsHandler = Handlers\FixWrongClassRefs::class; - public static function check($tokens, $absFilePath, $imports = []) + public static function check(PhpFileDescriptor $file, $imports = []) { + $tokens = $file->getTokens(); + $absFilePath = $file->getAbsolutePath(); + loopStart: [ $hostNamespace, diff --git a/src/Features/CheckImports/Reporters/AutoloadFiles.php b/src/Features/CheckImports/Reporters/AutoloadFiles.php index 2fd2fb5e..ca0e5ccb 100644 --- a/src/Features/CheckImports/Reporters/AutoloadFiles.php +++ b/src/Features/CheckImports/Reporters/AutoloadFiles.php @@ -7,16 +7,15 @@ class AutoloadFiles use Reporting; /** - * @param string $basePath * @param \Generator $filesListGen * @return string */ - public static function getLines($basePath, $filesListGen) + public static function getLines($filesListGen) { $lines = ''; $total = 0; foreach ($filesListGen as $files) { - $linesArr = self::formatFiles($files, $basePath); + $linesArr = self::formatFiles($files); $total += count($linesArr); $lines .= implode('', $linesArr); } diff --git a/src/Features/CheckImports/Reporters/CheckImportReporter.php b/src/Features/CheckImports/Reporters/CheckImportReporter.php index fc64e3b9..527e6041 100644 --- a/src/Features/CheckImports/Reporters/CheckImportReporter.php +++ b/src/Features/CheckImports/Reporters/CheckImportReporter.php @@ -13,9 +13,9 @@ public static function totalImportsMsg() return 'Imports were checked under:'; } - public static function getRouteStats($basePath, $routeFiles) + public static function getRouteStats($routeFiles) { - $linesArr = self::formatFiles($routeFiles, $basePath); + $linesArr = self::formatFiles($routeFiles); $count = count($linesArr); $lines = implode('', $linesArr); diff --git a/src/Features/CheckImports/Reporters/Psr4Report.php b/src/Features/CheckImports/Reporters/Psr4Report.php index 3739d93c..64a6f513 100644 --- a/src/Features/CheckImports/Reporters/Psr4Report.php +++ b/src/Features/CheckImports/Reporters/Psr4Report.php @@ -2,6 +2,7 @@ namespace Imanghafoori\LaravelMicroscope\Features\CheckImports\Reporters; +use Generator; use JetBrains\PhpStorm\Pure; class Psr4Report @@ -16,20 +17,17 @@ class Psr4Report */ public static function printAutoload($psr4Stats, $classMapStats) { - $output = ''; + $callback = function ($composerPath, $psr4, $classMapStats) { + return self::present($composerPath, $psr4, $classMapStats); + }; + + $outputAll = ''; foreach ($psr4Stats as $composerPath => $psr4) { - $output .= PHP_EOL; - $output .= self::formatComposerPath($composerPath); - $output .= PHP_EOL; - $output .= self::hyphen('PSR-4 '); - $output .= self::formatPsr4Stats($psr4); - if (isset($classMapStats[$composerPath])) { - $lines = ClassMapStats::getMessage($classMapStats[$composerPath], self::$callback); - $lines && ($output .= PHP_EOL.$lines); - } + $output = $callback($composerPath, $psr4, $classMapStats); + $outputAll .= $output; } - return trim($output); + return trim($outputAll); } public static function formatComposerPath($composerPath) @@ -131,4 +129,20 @@ private static function concatinate($longest, array $lines) return implode('', $lines); } + + private static function present(string $composerPath, Generator $psr4, $classMapStats) + { + $output = ''; + $output .= PHP_EOL; + $output .= self::formatComposerPath($composerPath); + $output .= PHP_EOL; + $output .= self::hyphen('PSR-4 '); + $output .= self::formatPsr4Stats($psr4); + if (isset($classMapStats[$composerPath])) { + $lines = ClassMapStats::getMessage($classMapStats[$composerPath], self::$callback); + $lines && ($output .= PHP_EOL.$lines); + } + + return $output; + } } diff --git a/src/Features/CheckImports/Reporters/Reporting.php b/src/Features/CheckImports/Reporters/Reporting.php index 2c78d555..5e30c966 100644 --- a/src/Features/CheckImports/Reporters/Reporting.php +++ b/src/Features/CheckImports/Reporters/Reporting.php @@ -3,6 +3,7 @@ namespace Imanghafoori\LaravelMicroscope\Features\CheckImports\Reporters; use Imanghafoori\LaravelMicroscope\FileReaders\FilePath; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use JetBrains\PhpStorm\Pure; trait Reporting @@ -48,21 +49,19 @@ public static function normalize($dirPath) return str_replace(DIRECTORY_SEPARATOR, '/', $path).'/'; } - private static function formatLine($basePath, $absFilePath): string + private static function formatLine(PhpFileDescriptor $file): string { - $relPath = str_replace($basePath, '', $absFilePath); - $relPath = ltrim($relPath, DIRECTORY_SEPARATOR); - $relPath = str_replace(DIRECTORY_SEPARATOR, '/', $relPath); + $relPath = $file->path()->relativePath()->getWithUnixDirectorySeprator(); return PHP_EOL.' '.self::hyphen(''.$relPath.''); } #[Pure] - private static function formatFiles($files, string $basePath): array + private static function formatFiles($files) { $lines = []; - foreach ($files as $absFilePath) { - $lines[] = self::formatLine($basePath, $absFilePath); + foreach ($files as $file) { + $lines[] = self::formatLine($file); } return $lines; diff --git a/src/Features/CheckView/BladeFile.php b/src/Features/CheckView/BladeFile.php deleted file mode 100644 index 7e39b9f9..00000000 --- a/src/Features/CheckView/BladeFile.php +++ /dev/null @@ -1,10 +0,0 @@ -getTokens(); + $absPath = $file->getAbsolutePath(); + $staticCalls = [ 'View' => ['make', 0], 'Route' => ['view', 1], @@ -33,10 +36,10 @@ private static function checkViewParams($absPath, &$tokens, $i, $index) if (FunctionCall::isSolidString($paramTokens)) { self::$checkedCallsCount++; - $viewName = \trim($paramTokens[0][1], '\'\"'); - - $viewName = str_replace('.', '/', $viewName); - $viewName && ! View::exists($viewName) && BladeFile::warn($absPath, $paramTokens[0][2], $viewName); + $viewName = self::getViewName($paramTokens[0][1]); + if ($viewName && ! View::exists($viewName)) { + CheckView::viewError($absPath, $paramTokens[0][2], $viewName); + } } else { self::$skippedCallsCount++; } @@ -60,15 +63,22 @@ public static function checkViewCalls($tokens, $absPath, array $staticCalls) return $tokens; } - public static function viewError($absPath, $message, $lineNumber, $fileName) + public static function viewError($absPath, $lineNumber, $fileName) { ErrorPrinter::singleton()->simplePendError( $fileName.'.blade.php', $absPath, $lineNumber, 'missing_view', - \trim($message), + 'The blade file is missing:', ' does not exist' ); } + + private static function getViewName($string) + { + $viewName = trim($string, '\'\"'); + + return str_replace('.', '/', $viewName); + } } diff --git a/src/Features/CheckView/Check/CheckViewFilesExistence.php b/src/Features/CheckView/Check/CheckViewFilesExistence.php index 062ec3e3..ad1da52a 100644 --- a/src/Features/CheckView/Check/CheckViewFilesExistence.php +++ b/src/Features/CheckView/Check/CheckViewFilesExistence.php @@ -4,19 +4,22 @@ use Illuminate\Support\Facades\View; use Imanghafoori\LaravelMicroscope\Check; -use Imanghafoori\LaravelMicroscope\Features\CheckView\BladeFile; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; class CheckViewFilesExistence implements Check { - public static function check($tokens, $absPath) + public static function check(PhpFileDescriptor $file) { - $tCount = \count($tokens); + $tokens = $file->getTokens(); + $absPath = $file->getAbsolutePath(); + + $tCount = count($tokens); for ($i = 0; $i < $tCount; $i++) { if (! self::isEnvMake($tokens, $i)) { continue; } - $viewName = \trim($tokens[$i + 4][1], '\'\"'); + $viewName = trim($tokens[$i + 4][1], '\'\"'); CheckView::$checkedCallsCount++; if (! View::exists($viewName)) { self::error($tokens, $absPath, $i); @@ -44,7 +47,7 @@ private static function error($tokens, $absPath, $i) { $viewName = $tokens[$i + 4][1]; $viewName = str_replace('.', '/', trim($viewName, '\'\"')); - BladeFile::warn($absPath, $tokens[$i + 4][2], $viewName); + CheckView::viewError($absPath, $tokens[$i + 4][2], $viewName); } private static function isVariable($token, string $varName) diff --git a/src/Features/CheckView/CheckViewsCommand.php b/src/Features/CheckView/CheckViewsCommand.php index dcbcba19..2dadb865 100644 --- a/src/Features/CheckView/CheckViewsCommand.php +++ b/src/Features/CheckView/CheckViewsCommand.php @@ -4,6 +4,7 @@ use Illuminate\Console\Command; use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter; +use Imanghafoori\LaravelMicroscope\Features\CheckImports\Reporters\Psr4Report; use Imanghafoori\LaravelMicroscope\Features\CheckView\Check\CheckView; use Imanghafoori\LaravelMicroscope\Features\CheckView\Check\CheckViewFilesExistence; use Imanghafoori\LaravelMicroscope\FileReaders\FilePath; @@ -29,7 +30,13 @@ public function handle(ErrorPrinter $errorPrinter) $this->checkRoutePaths( FilePath::removeExtraPaths(RoutePaths::get(), $folder, $fileName) ); - $this->checkPsr4($fileName, $folder); + + $psr4Stats = ForPsr4LoadedClasses::check([CheckView::class], [], $fileName, $folder); + + $this->getOutput()->writeln(implode(PHP_EOL, [ + Psr4Report::printAutoload($psr4Stats, []), + ])); + $this->checkBladeFiles($fileName, $folder); $this->logErrors($errorPrinter); @@ -78,9 +85,4 @@ private function logErrors(ErrorPrinter $errorPrinter) $errorPrinter->printTime(); } - - private function checkPsr4(string $fileName, string $folder) - { - ForPsr4LoadedClasses::checkNow([CheckView::class], [], $fileName, $folder); - } } diff --git a/src/Features/CheckView/ViewsInstaller.php b/src/Features/CheckView/ViewsInstaller.php deleted file mode 100644 index 49711893..00000000 --- a/src/Features/CheckView/ViewsInstaller.php +++ /dev/null @@ -1,22 +0,0 @@ -data; - CheckView::viewError( - $data['absPath'], - 'The blade file is missing:', - $data['lineNumber'], - $data['name'] - ); - }); - } -} diff --git a/src/Features/ExtractsBladePartials/ExtractBladePartial.php b/src/Features/ExtractsBladePartials/ExtractBladePartial.php index d98feacd..97de1bb1 100644 --- a/src/Features/ExtractsBladePartials/ExtractBladePartial.php +++ b/src/Features/ExtractsBladePartials/ExtractBladePartial.php @@ -6,13 +6,17 @@ use Illuminate\Support\Facades\View; use Illuminate\Support\Str; use Imanghafoori\LaravelMicroscope\Check; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\FunctionCall; use InvalidArgumentException; class ExtractBladePartial implements Check { - public static function check($tokens, $absPath) + public static function check(PhpFileDescriptor $file) { + $tokens = $file->getTokens(); + $absPath = $file->getAbsolutePath(); + // we skip the very first tokens: 'getTokens()); return $imports[0] ?: [$imports[1]]; }; diff --git a/src/Features/FacadeAlias/FacadeAliasesCheck.php b/src/Features/FacadeAlias/FacadeAliasesCheck.php index f14fe14f..35a33f34 100644 --- a/src/Features/FacadeAlias/FacadeAliasesCheck.php +++ b/src/Features/FacadeAlias/FacadeAliasesCheck.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\AliasLoader; use Imanghafoori\LaravelMicroscope\Check; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; class FacadeAliasesCheck implements Check { @@ -17,8 +18,11 @@ class FacadeAliasesCheck implements Check */ public static $command; - public static function check($tokens, $absFilePath, $imports) + public static function check(PhpFileDescriptor $file, $imports) { + $tokens = $file->getTokens(); + $absFilePath = $file->getAbsolutePath(); + $aliases = AliasLoader::getInstance()->getAliases(); self::$handler::$command = self::$command; diff --git a/src/Features/ListModels/SubclassFinder.php b/src/Features/ListModels/SubclassFinder.php index 0c12b787..436ab4ad 100644 --- a/src/Features/ListModels/SubclassFinder.php +++ b/src/Features/ListModels/SubclassFinder.php @@ -25,7 +25,7 @@ public function getList($folder, $parentClass) return ComposerJson::make()->getClasslists($filter, $pathFilter); } - protected function getPathFilter(string $folder) + protected function getPathFilter($folder) { return function ($absFilePath, $fileName) use ($folder) { return strpos(str_replace(base_path(), '', $absFilePath), $folder); diff --git a/src/Features/Psr4/CheckPsr4ArtisanCommand.php b/src/Features/Psr4/CheckPsr4ArtisanCommand.php index c5b08dbf..5031a05a 100644 --- a/src/Features/Psr4/CheckPsr4ArtisanCommand.php +++ b/src/Features/Psr4/CheckPsr4ArtisanCommand.php @@ -27,31 +27,27 @@ public function handle() $this->line(''); $this->info('Started checking PSR-4 namespaces...'); $time = microtime(true); - - $errorPrinter = ErrorPrinter::singleton($this->output); + $printer = ErrorPrinter::singleton($this->output); $composer = ComposerJson::make(); start: $classLists = $this->getClassLists($composer); - $errorsLists = $this->getErrorsLists($composer, $classLists); - - $time = round(microtime(true) - $time, 5); - Psr4Errors::handle($errorsLists, $this); - $this->printReport($errorPrinter, $time, $composer->readAutoload(), $classLists); - $this->composerDumpIfNeeded($errorPrinter); - if ($this->option('watch')) { - sleep(8); + $duration = round(microtime(true) - $time, 5); + $this->printReport($printer, $duration, $composer->readAutoload(), $classLists); + $this->composerDumpIfNeeded($printer); - $errorPrinter->errorsList = []; - $errorPrinter->total = 0; - - goto start; - } else { - return $errorPrinter->total > 0 ? 1 : 0; + if (! $this->option('watch')) { + return $printer->total > 0 ? 1 : 0; } + sleep(8); + + $printer->errorsList = []; + $printer->total = 0; + + goto start; } private function composerDumpIfNeeded(ErrorPrinter $errorPrinter) @@ -65,14 +61,14 @@ private function composerDumpIfNeeded(ErrorPrinter $errorPrinter) private function printReport(ErrorPrinter $errorPrinter, $time, $autoload, $classLists) { - [$stats, $typesStats] = $this->countClasses($classLists); + $classListStatistics = self::countClasses($classLists); $errorPrinter->logErrors(); if (! $this->option('watch') && Str::startsWith(request()->server('argv')[1] ?? '', 'check:psr4')) { - $this->getOutput()->writeln(CheckPsr4Printer::reportResult($autoload, $stats, $time, $typesStats)); - $this->printMessages(CheckPsr4Printer::getErrorsCount($errorPrinter->total, $time)); + $this->write(CheckPsr4Printer::reportResult($autoload, $time, $classListStatistics)); + $this->printMessages(CheckPsr4Printer::getErrorsCount($errorPrinter->total)); } else { - $this->getOutput()->writeln(' - '.array_sum($stats).' namespaces were checked.'); + $this->write($this->getTotalCheckedMessage($classListStatistics->getTotalCount())); } } @@ -83,32 +79,23 @@ private function printMessages($messages) } } - private function countClasses($classLists) + private static function countClasses($classLists) { - $stats = []; - $typesStats = [ - 'enum' => 0, - 'interface' => 0, - 'class' => 0, - 'trait' => 0, - ]; + $type = new TypeStatistics(); foreach ($classLists as $composerPath => $classList) { - foreach ($classList as $namespace => $classes) { - $stats[$namespace] = count($classes); - foreach ($classes as $class) { - $class['type'] === T_INTERFACE && $typesStats['interface']++; - $class['type'] === T_CLASS && $typesStats['class']++; - $class['type'] === T_TRAIT && $typesStats['trait']++; - $class['type'] === T_ENUM && $typesStats['enum']++; + foreach ($classList as $namespace => $entities) { + $type->namespaceFiles($namespace, count($entities)); + foreach ($entities as $entity) { + $type->increment($entity['type']); } } } - return [$stats, $typesStats]; + return $type; } - private function getPathFilter(string $folder) + private function getPathFilter($folder) { return function ($absFilePath, $fileName) use ($folder) { return strpos($absFilePath, $folder); @@ -137,4 +124,14 @@ private function getErrorsLists(Comp $composer, $classLists) return $composer->getErrorsLists($classLists, $onCheck); } + + private function getTotalCheckedMessage($total): string + { + return ' - '.$total.' namespaces were checked.'; + } + + private function write($text): void + { + $this->getOutput()->writeln($text); + } } diff --git a/src/Features/Psr4/CheckPsr4Printer.php b/src/Features/Psr4/CheckPsr4Printer.php index b47e0be6..41395e6e 100644 --- a/src/Features/Psr4/CheckPsr4Printer.php +++ b/src/Features/Psr4/CheckPsr4Printer.php @@ -4,108 +4,88 @@ use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter; use Imanghafoori\LaravelMicroscope\ErrorReporters\PendingError; -use Symfony\Component\Console\Terminal; class CheckPsr4Printer extends ErrorPrinter { - public static function warnIncorrectNamespace($relativePath, $currentNamespace, $class) + public static function warnIncorrectNamespace($path, $currentNamespace, $className) { - $p = ErrorPrinter::singleton(); - $msg = 'Incorrect namespace: '.$p->color("namespace $currentNamespace;"); - PendingError::$maxLength = max(PendingError::$maxLength, strlen($msg)); - $p->end(); + $printer = ErrorPrinter::singleton(); + $msg = self::getHeader($currentNamespace, $className); - if ($currentNamespace) { - $header = 'Incorrect namespace: '.$p->color("namespace $currentNamespace;"); - } else { - $header = 'Namespace Not Found: '.$class; - } + PendingError::$maxLength = max(PendingError::$maxLength, strlen($msg) - 12); + $printer->end(); - $p->printHeader($header); - $p->printLink($relativePath, 3); + $printer->printHeader($msg); + $printer->printLink($path, 3); } - public static function ask($command, $correctNamespace) + private static function getHeader($currentNamespace, $className): string { - if ($command->option('nofix')) { - return false; - } - - if ($command->option('force')) { - return true; + if ($currentNamespace) { + $namespace = self::colorizer("$currentNamespace", 'blue'); + $header = "Incorrect namespace: '$namespace'"; + } else { + $header = 'Namespace Not Found for class: "'.self::colorizer($className, 'blue').'"'; } - return $command->getOutput()->confirm('Do you want to change it to: '.$correctNamespace.'', true); + return $header; } - public static function reportResult($autoload, $stats, $time, $typesStats) + public static function reportResult($autoload, $time, TypeStatistics $typesStats) { $messages = []; - $separator = function ($color) { - return ' '.str_repeat('_', (new Terminal)->getWidth() - 2).''; - }; - - $messages[] = $separator(config('microscope.colors.line_separator')); - $header = ' '.array_sum($stats).' entities are checked in:'; - $types = self::presentTypes($typesStats); + $messages[] = ErrorPrinter::lineSeparator(); + $messages[] = self::getHeaderLine($typesStats); + $messages[] = ''; $max = self::getMaxNamespaceLength($autoload); - $messages[] = $header.' '.$types; - $messages[] = ''; foreach ($autoload as $composerPath => $psr4) { - $output = ''; - $messages[] = ' ./'.trim($composerPath.'/', '/').'composer.json '; - foreach ($psr4 as $namespace => $path) { - $count = $stats[$namespace] ?? 0; - $path = implode(', ', (array) $path); - $output .= ' '.str_pad($count, 4).' - '.$namespace.str_repeat(' ', $max - strlen($namespace)).' (./'.$path.")\n"; - } - $messages[] = $output; + $messages[] = self::getComposerFileAddress($composerPath); + $messages[] = self::getNamespaces($psr4, $typesStats, $max); } - $messages[] = 'Finished In: '.$time.'(s)'; + $messages[] = self::getFinishMsg($time); return $messages; } - public static function noErrorFound($time) + public static function noErrorFound() { return [ [PHP_EOL.'All namespaces are correct! You rock \(^_^)/ ', 'line'], - [''.$time.'(s)', 'line'], ['', 'line'], ]; } - public static function getErrorsCount($errorCount, $time) + public static function getErrorsCount($errorCount) { if ($errorCount) { return [[PHP_EOL.$errorCount.' error(s) found.', 'warn']]; } else { - return CheckPsr4Printer::noErrorFound($time); + return CheckPsr4Printer::noErrorFound(); } } - public static function fixedNamespace($path, $wrong, $correct, $lineNumber = 4) + public static function fixedNamespace($file, $wrong, $correct, $class, $lineNumber = 4) { - $p = ErrorPrinter::singleton(); + $path = $file->relativePath(); $key = 'badNamespace'; - $header = 'Incorrect namespace: '.$p->color("namespace $wrong;"); - $errorData = ' namespace fixed to: '.$p->color("namespace $correct;"); + $printer = ErrorPrinter::singleton(); + + $errorData = ' Namespace of class "'.$class.'" fixed to: '.$printer->color("$correct"); - $p->addPendingError($path, $lineNumber, $key, $header, $errorData); + $printer->addPendingError($path, $lineNumber, $key, $errorData, ''); } public static function wrongFileName($path, $class, $file) { - $p = ErrorPrinter::singleton(); $key = 'badFileName'; $header = 'The file name and the class name are different.'; $errorData = 'Class name: '.$class.''.PHP_EOL.' File name: '.$file.''; - $p->addPendingError($path, 1, $key, $header, $errorData); + ErrorPrinter::singleton()->addPendingError($path, 1, $key, $header, $errorData); } private static function getMaxNamespaceLength($autoload): int @@ -121,14 +101,67 @@ private static function getMaxNamespaceLength($autoload): int return $max; } - private static function presentTypes($typesStats) + private static function presentTypes(TypeStatistics $typesStats) + { + $results = $typesStats->iterate(function ($type, $count) { + return " | $count $type"; + }); + + return implode('', $results).' |'; + } + + private static function header($stats): string + { + return " $stats entities are checked in:"; + } + + private static function detailLine(int $count, string $namespace, int $max, string $path): string { - $types = ''; - foreach ($typesStats as $type => $count) { - $types .= ' / '.$count.' '.$type.''; + $spacing = str_repeat(' ', $max - strlen($namespace)); + $paddedCount = str_pad($count, 4); + + $path = self::colorizer("./$path", 'green'); + // Since the namespace ends with a back-slash + // we have to include a space char so that + // the '' does not get scaped out. + $namespace = self::colorizer($namespace.' ', 'red'); + + return " $paddedCount - $namespace $spacing ($path)\n"; + } + + private static function colorizer($str, $color) + { + return ''.$str.''; + } + + private static function getNamespaces($psr4, TypeStatistics $typesStats, int $max): string + { + $output = ''; + + foreach ($psr4 as $namespace => $path) { + $count = $typesStats->namespaceCount[$namespace] ?? 0; + $path = implode(', ', (array) $path); + $output .= self::detailLine($count, $namespace, $max, $path); } - $types .= ' /'; - return $types; + return $output; + } + + private static function getComposerFileAddress($composerPath): string + { + return ' ./'.trim($composerPath.'/', '/').'composer.json '; + } + + private static function getHeaderLine(TypeStatistics $typesStats): string + { + $header = self::header($typesStats->getTotalCount()); + $types = self::presentTypes($typesStats); + + return $header.' '.PHP_EOL.$types; + } + + private static function getFinishMsg($time): string + { + return 'Finished In: '.$time.'(s)'; } } diff --git a/src/Features/Psr4/ClassRefCorrector.php b/src/Features/Psr4/ClassRefCorrector.php index 99573122..f663833c 100644 --- a/src/Features/Psr4/ClassRefCorrector.php +++ b/src/Features/Psr4/ClassRefCorrector.php @@ -2,16 +2,29 @@ namespace Imanghafoori\LaravelMicroscope\Features\Psr4; +use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; + class ClassRefCorrector { - private static $afterFix; + private static $afterFix = [self::class, 'afterReferenceFix']; + + private static $beforeFix = [self::class, 'beforeReferenceFix']; + + public static function fixOldRefs($from, $class, $to, $path, $beforeFix = null, $afterFix = null) + { + $afterFix && self::$afterFix = $afterFix; + $beforeFix && self::$beforeFix = $beforeFix; + + $changes = [ + $from.'\\'.$class => $to.'\\'.$class, + ]; - private static $beforeFix; + self::fixAllRefs($changes, $path); + } - public static function fixAllRefs($changes, $paths, $beforeFix, $afterFix) + private static function fixAllRefs($changes, $paths) { - self::$afterFix = $afterFix; - self::$beforeFix = $beforeFix; foreach ($paths as $path) { foreach ($path as $p) { self::applyFix($p, $changes); @@ -19,13 +32,26 @@ public static function fixAllRefs($changes, $paths, $beforeFix, $afterFix) } } + private static function applyFix($paths, $changes) + { + if (! is_string($paths)) { + foreach (iterator_to_array($paths) as $path) { + foreach ($path as $p) { + self::fix($p, $changes); + } + } + } else { + self::fix($paths, $changes); + } + } + private static function fix($path, $changes) { [$changedLineNums, $content] = self::fixRefs($path, $changes); if ($changedLineNums) { - $afterFix = self::$afterFix; - $afterFix($path, $changedLineNums, $content); + // calling the \Closure: + (self::$afterFix)($path, $changedLineNums, $content); } } @@ -49,46 +75,72 @@ private static function fixRefs($path, $changes) return [$changedLineNums, implode('', $lines)]; } - private static function possibleOccurrence($olds) + private static function afterReferenceFix() { - $keywords = ['(', '::', ';', '|', ')', "\r\n", "\n", "\r", '$', '?', ',', '&']; + return function (PhpFileDescriptor $file, $changedLineNums, $content) { + $file->putContents($content); + $path = $file->getAbsolutePath(); - foreach ($olds as $old) { - foreach ($keywords as $keyword) { - yield $old.$keyword; + $printer = ErrorPrinter::singleton(); + foreach ($changedLineNums as $line) { + $printer->simplePendError( + '', $path, $line, 'ns_replacement', 'Namespace replacement:' + ); } + }; + } + + private static function beforeReferenceFix($command) + { + if ($command->option('force-ref-fix')) { + return function () { + return true; + }; } + + return function (PhpFileDescriptor $file, $lineIndex, $lineContent) use ($command) { + $command->getOutput()->writeln( + ErrorPrinter::getLink($file->getAbsolutePath(), $lineIndex) + ); + + $command->warn($lineContent); + + return $command->confirm(self::getQuestion(), true); + }; + } + + private static function getQuestion(): string + { + return 'Do you want to update reference to the old namespace?'; } private static function hasReference($lineContent, array $olds) { - return self::str_contains( + return self::strContains( str_replace(' ', '', $lineContent), self::possibleOccurrence($olds) ); } - private static function str_contains($haystack, $needles) + private static function possibleOccurrence($olds) { - foreach ($needles as $needle) { - if (mb_strpos($haystack, $needle) !== false) { - return true; + $keywords = ['(', '::', ';', '|', ')', "\r\n", "\n", "\r", '$', '?', ',', '&']; + + foreach ($olds as $old) { + foreach ($keywords as $keyword) { + yield $old.$keyword; } } - - return false; } - private static function applyFix($path, $changes) + private static function strContains($haystack, $needles) { - if (! is_string($path)) { - foreach (iterator_to_array($path) as $p_p) { - foreach ($p_p as $t) { - self::fix($t, $changes); - } + foreach ($needles as $needle) { + if (mb_strpos($haystack, $needle) !== false) { + return true; } - } else { - self::fix($path, $changes); } + + return false; } } diff --git a/src/Features/Psr4/Confirm.php b/src/Features/Psr4/Confirm.php new file mode 100644 index 00000000..9f45f5d3 --- /dev/null +++ b/src/Features/Psr4/Confirm.php @@ -0,0 +1,24 @@ +option('nofix')) { + return false; + } + + if ($command->option('force')) { + return true; + } + + return $command->getOutput()->confirm(self::getQuestion($correctNamespace), true); + } + + private static function getQuestion($replacement) + { + return "Do you want to change it to: $replacement"; + } +} diff --git a/src/Features/Psr4/FilePathsForReferenceFix.php b/src/Features/Psr4/FilePathsForReferenceFix.php index d1a77e06..ee2fd691 100644 --- a/src/Features/Psr4/FilePathsForReferenceFix.php +++ b/src/Features/Psr4/FilePathsForReferenceFix.php @@ -10,11 +10,12 @@ class FilePathsForReferenceFix { - private static $pathsForReferenceFix = []; + public static $pathsForReferenceFix = []; public static function getFiles() { if (self::$pathsForReferenceFix) { + // Used for testing and memoization: return self::$pathsForReferenceFix; } diff --git a/src/Features/Psr4/NamespaceFixer.php b/src/Features/Psr4/NamespaceFixer.php index 78b5de32..9a931540 100644 --- a/src/Features/Psr4/NamespaceFixer.php +++ b/src/Features/Psr4/NamespaceFixer.php @@ -2,32 +2,32 @@ namespace Imanghafoori\LaravelMicroscope\Features\Psr4; -use Imanghafoori\Filesystem\FileManipulator; -use Imanghafoori\Filesystem\Filesystem; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\SearchReplace\Searcher; class NamespaceFixer { - public static function fix($absPath, $incorrectNamespace, $correctNamespace) + public static function fix(PhpFileDescriptor $file, $incorrectNamespace, $correctNamespace) { // decides to add namespace (in case there is no namespace) or edit the existing one. [$oldLine, $newline] = self::getNewLine($incorrectNamespace, $correctNamespace); $oldLine = \ltrim($oldLine, '\\'); - $tokens = token_get_all(file_get_contents($absPath)); + $tokens = $file->getTokens(); if ($oldLine !== 'putContents($newVersion); } elseif ($tokens[2][0] !== T_DECLARE) { // insertion - FileManipulator::replaceFirst($absPath, $oldLine, 'replaceFirst($oldLine, 'insertNewLine(PHP_EOL.$newline, $tokens[$i][2] + 1); } } diff --git a/src/Features/Psr4/Psr4Errors.php b/src/Features/Psr4/Psr4Errors.php index 207a9e36..8497b995 100644 --- a/src/Features/Psr4/Psr4Errors.php +++ b/src/Features/Psr4/Psr4Errors.php @@ -3,8 +3,7 @@ namespace Imanghafoori\LaravelMicroscope\Features\Psr4; use Illuminate\Console\Command; -use Imanghafoori\Filesystem\Filesystem; -use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; class Psr4Errors { @@ -13,80 +12,59 @@ class Psr4Errors */ private static $command; - public static function handle(array $errorsLists, Command $command) + public static $refCorrector = ClassRefCorrector::class; + + public static $confirm = Confirm::class; + + public static function handle(array $errorsLists, $command) { self::$command = $command; - $before = self::beforeReferenceFix(); - $after = self::afterReferenceFix(); foreach ($errorsLists as $errors) { - foreach ($errors as $wrong) { - self::handleError($wrong, $before, $after); + foreach ($errors as $error) { + self::handleError($error); } } } - private static function handleError($wrong, $beforeFix, $afterFix) + private static function handleError($error) { - if ($wrong['type'] === 'namespace') { - $absPath = $wrong['absFilePath']; - $from = $wrong['currentNamespace']; - $to = $wrong['correctNamespace']; - $class = $wrong['class']; - $relativePath = str_replace(base_path(), '', $absPath); - - CheckPsr4Printer::warnIncorrectNamespace($relativePath, $from, $class); - - if (CheckPsr4Printer::ask(self::$command, $to)) { - self::updateOldRefs($absPath, $from, $to, $class, $beforeFix, $afterFix, $relativePath); - } - } elseif ($wrong['type'] === 'filename') { - CheckPsr4Printer::wrongFileName($wrong['relativePath'], $wrong['class'], $wrong['fileName']); + if ($error['type'] === 'namespace') { + self::askAndFixNamespace($error); + } elseif ($error['type'] === 'filename') { + CheckPsr4Printer::wrongFileName( + $error['relativePath'], + $error['class'], + $error['fileName'] + ); } } - private static function afterReferenceFix() + private static function updateOldRefs($from, $to, $class) { - return function ($path, $changedLineNums, $content) { - Filesystem::$fileSystem::file_put_contents($path, $content); - - $p = ErrorPrinter::singleton(); - foreach ($changedLineNums as $line) { - $p->simplePendError('', $path, $line, 'ns_replacement', 'Namespace replacement:'); - } - }; + if ($from && ! self::$command->option('no-ref-fix')) { + self::$refCorrector::fixOldRefs($from, $class, $to, FilePathsForReferenceFix::getFiles()); + } } - private static function beforeReferenceFix() + private static function applyFixProcess(PhpFileDescriptor $file, $from, $class, $to) { - $command = self::$command; - if ($command->option('force-ref-fix')) { - return function () { - return true; - }; - } + CheckPsr4Printer::warnIncorrectNamespace($file->relativePath(), $from, $class); - return function ($path, $lineIndex, $lineContent) use ($command) { - $command->getOutput()->writeln(ErrorPrinter::getLink($path, $lineIndex)); - $command->warn($lineContent); - $msg = 'Do you want to update reference to the old namespace?'; - - return $command->confirm($msg, true); - }; + if (self::$confirm::ask(self::$command, $to)) { + NamespaceFixer::fix($file, $from, $to); + self::updateOldRefs($from, $to, $class); + CheckPsr4Printer::fixedNamespace($file, $from, $to, $class); + } } - private static function updateOldRefs($absPath, $from, $to, $class, $beforeFix, $afterFix, $relativePath) + private static function askAndFixNamespace($error) { - NamespaceFixer::fix($absPath, $from, $to); - $command = self::$command; - - if ($from && ! $command->option('no-ref-fix')) { - $changes = [ - $from.'\\'.$class => $to.'\\'.$class, - ]; - - ClassRefCorrector::fixAllRefs($changes, FilePathsForReferenceFix::getFiles(), $beforeFix, $afterFix); - } - CheckPsr4Printer::fixedNamespace($relativePath, $from, $to); + self::applyFixProcess( + PhpFileDescriptor::make($error['absFilePath']), + $error['currentNamespace'], + $error['class'], + $error['correctNamespace'] + ); } } diff --git a/src/Features/Psr4/TypeStatistics.php b/src/Features/Psr4/TypeStatistics.php new file mode 100644 index 00000000..4473d129 --- /dev/null +++ b/src/Features/Psr4/TypeStatistics.php @@ -0,0 +1,52 @@ +interface++; + } elseif ($type === T_CLASS) { + $this->class++; + } elseif ($type === T_TRAIT) { + $this->trait++; + } elseif ($type === T_ENUM) { + $this->enum++; + } + } + + public function iterate($callback) + { + $results = []; + foreach (['class', 'trait', 'interface', 'enum'] as $prop) { + $results[] = $callback($prop, $this->$prop); + } + + return $results; + } + + /** + * @return void + */ + public function namespaceFiles($namespace, int $count) + { + $this->namespaceCount[$namespace] = $count; + } + + public function getTotalCount() + { + return array_sum($this->namespaceCount); + } +} diff --git a/src/ForPsr4LoadedClasses.php b/src/ForPsr4LoadedClasses.php index ba69c7a4..1af62b09 100644 --- a/src/ForPsr4LoadedClasses.php +++ b/src/ForPsr4LoadedClasses.php @@ -23,10 +23,18 @@ public static function check($checks, $params = [], $includeFile = '', $includeF public static function checkNow($checks, $params = [], $includeFile = '', $includeFolder = '', $callback = null) { - foreach (self::check($checks, $params, $includeFile, $includeFolder) as $results) { - foreach (iterator_to_array($results) as $result) { + self::applyOnStats( + self::check($checks, $params, $includeFile, $includeFolder), + $callback + ); + } + + public static function applyOnStats(array $allStats, $callback = null) + { + foreach ($allStats as $path => $results) { + foreach (iterator_to_array($results) as $namespace => $result) { foreach ($result as $folder => $count) { - $callback && $callback($folder, $count); + $callback && $callback($folder, $count, $path, $namespace); } } } diff --git a/src/Foundations/Path.php b/src/Foundations/Path.php new file mode 100644 index 00000000..99b089fc --- /dev/null +++ b/src/Foundations/Path.php @@ -0,0 +1,66 @@ +path = $path; + } + + private static function normalizeDirectorySeparator($absolutePath): string + { + return str_replace('/\\', DIRECTORY_SEPARATOR, $absolutePath); + } + + public function relativePath() + { + $relPath = str_replace(self::$basePath, '', $this->path); + + return self::make(trim($relPath, DIRECTORY_SEPARATOR)); + } + + private static function removeTrailingSlash($path): string + { + return rtrim($path, DIRECTORY_SEPARATOR); + } + + public function __toString() + { + return $this->path; + } + + public function getWithUnixDirectorySeprator() + { + return str_replace('\\', '/', $this->path); + } + + public function getWithWindowsDirectorySeprator() + { + return str_replace('/', '\\', $this->path); + } +} diff --git a/src/Foundations/PhpFileDescriptor.php b/src/Foundations/PhpFileDescriptor.php new file mode 100644 index 00000000..4f09868f --- /dev/null +++ b/src/Foundations/PhpFileDescriptor.php @@ -0,0 +1,137 @@ +path = Path::make($absolutePath); + + return $obj; + } + + public function setTokenizer($tokenizer) + { + $this->tokenizer = $tokenizer; + } + + public function getTokens($reload = false) + { + if (! $this->tokens || $reload) { + $this->tokens = $this->tokenizer ? ($this->tokenizer)($this->path) : $this->tokenize(); + } + + return $this->tokens; + } + + public function getAbsolutePath() + { + return $this->path->__toString(); + } + + public function relativePath() + { + return $this->path->relativePath(); + } + + public function getLine(int $lineNumber) + { + return file($this->path)[$lineNumber - 1] ?? ''; + } + + public function getNamespace() + { + return ComposerJson::make()->getNamespacedClassFromPath($this->path); + } + + private function tokenize() + { + return token_get_all(file_get_contents($this->path)); + } + + public function setTokens($tokens) + { + $this->tokens = $tokens; + } + + public function path() + { + return $this->path; + } + + public function putContents($newVersion) + { + $this->tokens = []; + + return Filesystem::$fileSystem::file_put_contents($this->path, $newVersion); + } + + public function replaceFirst($search, $replace) + { + return $this->replaceFirstAtLine($search, $replace, null); + } + + public function replaceFirstAtLine($search, $replace, $line) + { + return FileManipulator::replaceFirst($this->path, $search, $replace, $line); + } + + public function replaceAtLine($search, $replace, $lineNum) + { + $this->tokens = []; + + return FileManipulator::replaceFirst($this->path, $search, $replace, $lineNum); + } + + public function searchReplacePatterns($search, $replace) + { + [$newVersion, $lines] = self::searchReplace($search, $replace, $this->tokens); + + $this->putContents($newVersion); + + return $lines; + } + + private static function pattern($search, $replace): array + { + return [ + 'fix' => [ + 'search' => $search, + 'replace' => $replace, + ], + ]; + } + + public static function searchReplace($search, $replace, $tokens): array + { + return Searcher::searchReplace(self::pattern($search, $replace), $tokens); + } + + public function insertNewLine($newLine, $atLine) + { + return FileManipulator::insertNewLine($this->path, $newLine, $atLine); + } +} diff --git a/src/Iterators/BaseIterator.php b/src/Iterators/BaseIterator.php index 7191a12e..1c7750ad 100644 --- a/src/Iterators/BaseIterator.php +++ b/src/Iterators/BaseIterator.php @@ -2,25 +2,23 @@ namespace Imanghafoori\LaravelMicroscope\Iterators; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; + abstract class BaseIterator { protected static function applyChecks($absFilePaths, $checks, $params) { foreach ($absFilePaths as $absFilePath) { - $tokens = token_get_all(file_get_contents($absFilePath)); + $file = PhpFileDescriptor::make($absFilePath); foreach ($checks as $check) { - $check::check( - $tokens, - $absFilePath, - self::processParams($tokens, $absFilePaths, $params) - ); + $check::check($file, self::processParams($file, $params)); } - yield $absFilePath; + yield $file; } } - private static function processParams($tokens, $absFilePaths, $params) + private static function processParams(PhpFileDescriptor $file, $params) { - return (! is_array($params) && is_callable($params)) ? $params($tokens, $absFilePaths) : $params; + return (! is_array($params) && is_callable($params)) ? $params($file) : $params; } } diff --git a/src/Iterators/BladeFiles/CheckBladePaths.php b/src/Iterators/BladeFiles/CheckBladePaths.php index fabbbd61..c803979e 100644 --- a/src/Iterators/BladeFiles/CheckBladePaths.php +++ b/src/Iterators/BladeFiles/CheckBladePaths.php @@ -3,6 +3,7 @@ namespace Imanghafoori\LaravelMicroscope\Iterators\BladeFiles; use Imanghafoori\LaravelMicroscope\Features\CheckUnusedBladeVars\ViewsData; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\LaravelMicroscope\Iterators\FiltersFiles; use Symfony\Component\Finder\Finder; @@ -12,6 +13,8 @@ class CheckBladePaths public static $scanned = []; + public static $readOnly = true; + /** * @param \Generator $dirs * @param array $checkers @@ -48,7 +51,7 @@ public static function findFiles($path, $fileName = null): Finder * @param string $path * @return bool */ - private static function shouldSkip(string $path) + private static function shouldSkip($path) { if (! is_dir($path)) { return true; @@ -82,12 +85,19 @@ private static function applyChecks($files, $paramsProvider, $checkers): int /** * @var \Symfony\Component\Finder\SplFileInfo $blade */ - $absPath = $blade->getPathname(); - $tokens = ViewsData::getBladeTokens($absPath); - $params1 = (! is_array($paramsProvider) && is_callable($paramsProvider)) ? $paramsProvider($tokens, $absPath) : $paramsProvider; + $absFilePath = $blade->getPathname(); + + $file = PhpFileDescriptor::make($absFilePath); + if (self::$readOnly) { + $file->setTokenizer(function ($absPath) { + return ViewsData::getBladeTokens($absPath); + }); + } + + $params1 = (! is_array($paramsProvider) && is_callable($paramsProvider)) ? $paramsProvider($file) : $paramsProvider; foreach ($checkers as $check) { - $check::check($tokens, $absPath, $params1); + $check::check($file, $params1); } } diff --git a/src/Iterators/Check.php b/src/Iterators/Check.php index 45b1115e..c746cef9 100644 --- a/src/Iterators/Check.php +++ b/src/Iterators/Check.php @@ -2,7 +2,9 @@ namespace Imanghafoori\LaravelMicroscope\Iterators; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; + abstract class Check { - abstract public static function check($tokens, $absFilePath, $processedParams, $phpFilePath, $psr4Path, $psr4Namespace); + abstract public static function check(PhpFileDescriptor $file, $processedParams = []); } diff --git a/src/Iterators/ChecksOnPsr4Classes.php b/src/Iterators/ChecksOnPsr4Classes.php index 2e0f20dc..38849a97 100644 --- a/src/Iterators/ChecksOnPsr4Classes.php +++ b/src/Iterators/ChecksOnPsr4Classes.php @@ -4,6 +4,7 @@ use Imanghafoori\LaravelMicroscope\Analyzers\ComposerJson; use Imanghafoori\LaravelMicroscope\FileReaders\PhpFinder; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Throwable; class ChecksOnPsr4Classes @@ -43,9 +44,9 @@ public static function apply($checks, $params, $includeFile, $includeFolder) return $stats; } - private static function getParams($params, array $tokens, $absFilePath, $psr4Path, $psr4Namespace) + private static function getParams($params, PhpFileDescriptor $file, $psr4Path, $psr4Namespace) { - return (! is_array($params) && is_callable($params)) ? $params($tokens, $absFilePath, $psr4Path, $psr4Namespace) : $params; + return (! is_array($params) && is_callable($params)) ? $params($file, $psr4Path, $psr4Namespace) : $params; } /** @@ -69,21 +70,22 @@ private static function applyChecksInPath($psr4Namespace, $psr4Path, $checks, $p return $filesCount; } - private static function applyChecks($phpFilePath, $params, $psr4Path, $psr4Namespace, $checks) + private static function applyChecks($phpFileObj, $params, $psr4Path, $psr4Namespace, $checks) { - $absFilePath = $phpFilePath->getRealPath(); - $tokens = token_get_all(file_get_contents($absFilePath)); + $absFilePath = $phpFileObj->getRealPath(); - $processedParams = self::getParams($params, $tokens, $absFilePath, $psr4Path, $psr4Namespace); + $file = PhpFileDescriptor::make($absFilePath); + + $processedParams = self::getParams($params, $file, $psr4Path, $psr4Namespace); foreach ($checks as $check) { try { /** * @var $check \Imanghafoori\LaravelMicroscope\Iterators\Check */ - $newTokens = $check::check($tokens, $absFilePath, $processedParams, $phpFilePath, $psr4Path, $psr4Namespace); + $newTokens = $check::check($file, $processedParams, $psr4Path, $psr4Namespace); if ($newTokens) { - $tokens = $newTokens; - $processedParams = self::getParams($params, $tokens, $absFilePath, $psr4Path, $psr4Namespace); + $file->setTokens($newTokens); + $processedParams = self::getParams($params, $file, $psr4Path, $psr4Namespace); } } catch (Throwable $exception) { self::$exceptions[] = $exception; diff --git a/src/LaravelMicroscopeServiceProvider.php b/src/LaravelMicroscopeServiceProvider.php index 008a64d1..b2b23806 100644 --- a/src/LaravelMicroscopeServiceProvider.php +++ b/src/LaravelMicroscopeServiceProvider.php @@ -12,44 +12,16 @@ use Imanghafoori\LaravelMicroscope\Features\CheckEvents\Installer; use Imanghafoori\LaravelMicroscope\Features\CheckUnusedBladeVars\UnusedVarsInstaller; use Imanghafoori\LaravelMicroscope\Features\CheckView\Check\CheckView; -use Imanghafoori\LaravelMicroscope\Features\ListModels\ListModelsArtisanCommand; use Imanghafoori\LaravelMicroscope\FileReaders\PhpFinder; +use Imanghafoori\LaravelMicroscope\Foundations\Path; +use Imanghafoori\LaravelMicroscope\ServiceProvider\CommandsRegistry; use Imanghafoori\LaravelMicroscope\SpyClasses\SpyBladeCompiler; use Imanghafoori\LaravelMicroscope\SpyClasses\SpyGate; use Imanghafoori\TokenAnalyzer\ImportsAnalyzer; class LaravelMicroscopeServiceProvider extends ServiceProvider { - private static $commandNames = [ - Features\CheckFacadeDocblocks\CheckFacadeDocblocks::class, - Features\CheckEvents\CheckEvents::class, - Commands\CheckGates::class, - Commands\CheckRoutes::class, - Features\CheckView\CheckViewsCommand::class, - Features\Psr4\CheckPsr4ArtisanCommand::class, - Features\CheckImports\CheckImportsCommand::class, - Features\FacadeAlias\CheckAliasesCommand::class, - Commands\CheckAll::class, - Commands\ClassifyStrings::class, - Features\CheckDD\CheckDDCommand::class, - Commands\CheckEarlyReturns::class, - Commands\CheckCompact::class, - Commands\CheckBladeQueries::class, - Features\ActionComments\CheckActionComments::class, - Features\CheckEnvCalls\CheckEnvCallsCommand::class, - Features\ExtractsBladePartials\CheckExtractBladeIncludesCommand::class, - Commands\PrettyPrintRoutes::class, - Features\ServiceProviderGenerator\CheckCodeGeneration::class, - Commands\CheckDeadControllers::class, - Features\CheckGenericDocBlocks\CheckGenericDocBlocksCommand::class, - Commands\CheckPsr12::class, - Commands\CheckEndIf::class, - Commands\EnforceQuery::class, - Commands\EnforceHelpers::class, - SearchReplace\CheckRefactorsCommand::class, - Commands\CheckDynamicWhereMethod::class, - ListModelsArtisanCommand::class, - ]; + use CommandsRegistry; public function boot() { @@ -67,20 +39,14 @@ public function boot() $this->resetCountersOnFinish(); - $this->commands(self::$commandNames); + $this->registerCommands(); ErrorPrinter::$ignored = config('microscope.ignore'); - $this->publishes([ - __DIR__.'/../config/config.php' => config_path('microscope.php'), - ], 'config'); - $this->publishes([ __DIR__.'/../templates' => base_path('resources/views/vendor/microscope'), ], 'microscope'); - $this->mergeConfigFrom(__DIR__.'/../config/config.php', 'microscope'); - ConsolePrinterInstaller::boot(); } @@ -90,24 +56,9 @@ public function register() return; } - ComposerJson::$composer = function () { - return Composer::make( - base_path(), - config('microscope.ignored_namespaces', []), - config('microscope.additional_composer_paths', []) - ); - }; - - PhpFinder::$basePath = base_path(); - - [$major] = explode('.', app()->version()); - - $color = (int) $major >= 8 ? 'gray' : 'blue'; - - config()->set('microscope.colors.line_separator', $color); - + $this->setBasePath(); + $this->setLineSeparatorColor(); $this->registerCompiler(); - $this->loadConfig(); app()->singleton(ErrorPrinter::class, function () { @@ -127,7 +78,10 @@ public function register() private function loadConfig() { - $this->mergeConfigFrom(__DIR__.'/../config/config.php', 'microscope'); + $configPath = __DIR__.'/../config/config.php'; + + $this->mergeConfigFrom($configPath, 'microscope'); + $this->publishes([$configPath => config_path('microscope.php')], 'config'); } private function canRun() @@ -136,11 +90,15 @@ private function canRun() return false; } + if (! config('microscope.is_enabled', true)) { + return false; + } + if (windows_os()) { return true; } - return config('microscope.is_enabled', true) && app()['env'] !== 'production'; + return app()['env'] !== 'production'; } private function registerCompiler() @@ -155,8 +113,28 @@ private function resetCountersOnFinish() Event::listen('microscope.finished.checks', function () { CheckView::$checkedCallsCount = 0; CheckView::$skippedCallsCount = 0; + }); + + Event::listen('microscope.finished.checks', function () { ImportsAnalyzer::$checkedRefCount = 0; Iterators\ChecksOnPsr4Classes::$checkedFilesCount = 0; }); } + + private function setLineSeparatorColor() + { + [$major] = explode('.', app()->version()); + $color = (int) $major >= 9 ? 'gray' : 'blue'; + config()->set('microscope.colors.line_separator', $color); + } + + private function setBasePath() + { + ComposerJson::$composer = function () { + return Composer::make(base_path(), config('microscope.ignored_namespaces', []), config('microscope.additional_composer_paths', [])); + }; + + PhpFinder::$basePath = base_path(); + Path::setBasePath(base_path()); + } } diff --git a/src/SearchReplace/PatternRefactorings.php b/src/SearchReplace/PatternRefactorings.php index 804fc594..8df20b33 100644 --- a/src/SearchReplace/PatternRefactorings.php +++ b/src/SearchReplace/PatternRefactorings.php @@ -6,6 +6,7 @@ use Imanghafoori\Filesystem\Filesystem; use Imanghafoori\LaravelMicroscope\Check; use Imanghafoori\LaravelMicroscope\ErrorReporters\ErrorPrinter; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\SearchReplace\Finder; use Imanghafoori\SearchReplace\Replacer; use Imanghafoori\SearchReplace\Stringify; @@ -15,8 +16,11 @@ class PatternRefactorings implements Check { public static $patternFound = false; - public static function check($tokens, $absFilePath, $patterns) + public static function check(PhpFileDescriptor $file, $patterns) { + $tokens = $file->getTokens(); + $absFilePath = $file->getAbsolutePath(); + foreach ($patterns[0] as $pattern) { if (isset($pattern['file']) && ! Str::endsWith($absFilePath, $pattern['file'])) { continue; diff --git a/src/ServiceProvider/CommandsRegistry.php b/src/ServiceProvider/CommandsRegistry.php new file mode 100644 index 00000000..f754f89c --- /dev/null +++ b/src/ServiceProvider/CommandsRegistry.php @@ -0,0 +1,46 @@ +commands(self::$commandNames); + } +} diff --git a/src/SpyClasses/RoutePaths.php b/src/SpyClasses/RoutePaths.php index 1fa930c9..07aa23da 100644 --- a/src/SpyClasses/RoutePaths.php +++ b/src/SpyClasses/RoutePaths.php @@ -5,6 +5,7 @@ use Illuminate\Support\Str; use Imanghafoori\LaravelMicroscope\Analyzers\ComposerJson; use Imanghafoori\LaravelMicroscope\FileReaders\FilePath; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\TokenAnalyzer\FunctionCall; use Throwable; @@ -72,7 +73,7 @@ private static function fullPath($calls, $providerClass, $path) private static function readLoadedRouteFiles($path) { - $tokens = token_get_all(file_get_contents(base_path($path).'.php')); + $tokens = PhpFileDescriptor::make(base_path($path).'.php')->getTokens(); foreach ($tokens as $i => $routeFileToken) { if (FunctionCall::isMethodCallOnThis('loadRoutesFrom', $tokens, $i)) { diff --git a/tests/CheckImports/CheckClassReferencesAreValidTest.php b/tests/CheckImports/CheckClassReferencesAreValidTest.php index ca012653..8c7449c9 100644 --- a/tests/CheckImports/CheckClassReferencesAreValidTest.php +++ b/tests/CheckImports/CheckClassReferencesAreValidTest.php @@ -3,6 +3,7 @@ namespace Imanghafoori\LaravelMicroscope\Tests\CheckImports; use Imanghafoori\LaravelMicroscope\Features\CheckImports\Checks\CheckClassReferencesAreValid; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; use Imanghafoori\LaravelMicroscope\Tests\CheckImports\MockExistenceChecker\AlwaysExistsMock; use Imanghafoori\TokenAnalyzer\ImportsAnalyzer; use Imanghafoori\TokenAnalyzer\ParseUseStatement; @@ -13,7 +14,8 @@ class CheckClassReferencesAreValidTest extends TestCase /** @test */ public function check() { - $absPath = __DIR__.'/wongImport.stub'; + $absPath = __DIR__.'/wrongImport.stub'; + $file = PhpFileDescriptor::make($absPath); $tokens = token_get_all(file_get_contents($absPath)); CheckClassReferencesAreValid::$extraCorrectImportsHandler = MockHandlers\MockExtraImportsHandler::class; CheckClassReferencesAreValid::$extraWrongImportsHandler = MockHandlers\MockerUnusedWrongImportsHandler::class; @@ -21,7 +23,7 @@ public function check() ImportsAnalyzer::$existenceChecker = AlwaysExistsMock::class; - CheckClassReferencesAreValid::check($tokens, $absPath, (function ($tokens) { + CheckClassReferencesAreValid::check($file, (function ($tokens) { $imports = ParseUseStatement::parseUseStatements($tokens); return $imports[0] ?: [$imports[1]]; @@ -37,18 +39,18 @@ public function check() 'doo' => ['doo', 5], 'Foooo' => ['Foooo', 6], ], - __DIR__.'/wongImport.stub', + __DIR__.'/wrongImport.stub', ], ], $extraImportHandler); $this->assertEquals([[ 0 => [], - 1 => __DIR__.'/wongImport.stub', + 1 => __DIR__.'/wrongImport.stub', ]], $unusedWrongImportsHandler); $this->assertEquals([[ 0 => [], - 1 => __DIR__.'/wongImport.stub', + 1 => __DIR__.'/wrongImport.stub', ]], $wrongClassRefsHandler); } } diff --git a/tests/CheckImports/wongImport.stub b/tests/CheckImports/wrongImport.stub similarity index 100% rename from tests/CheckImports/wongImport.stub rename to tests/CheckImports/wrongImport.stub diff --git a/tests/NamespaceCorrectorTest.php b/tests/NamespaceCorrectorTest.php index 5072edd5..0cc209ec 100644 --- a/tests/NamespaceCorrectorTest.php +++ b/tests/NamespaceCorrectorTest.php @@ -7,6 +7,7 @@ use Imanghafoori\Filesystem\Filesystem; use Imanghafoori\LaravelMicroscope\ClassListProvider; use Imanghafoori\LaravelMicroscope\Features\Psr4\NamespaceFixer; +use Imanghafoori\LaravelMicroscope\Foundations\PhpFileDescriptor; class NamespaceCorrectorTest extends BaseTestClass { @@ -39,7 +40,7 @@ public function fix_namespace() // fix namespace $correctNamespace = 'App\Http\Controllers\Foo'; $filePath = __DIR__.'/stubs/PostController.stub'; - NamespaceFixer::fix($filePath, 'App\Http\Controllers', $correctNamespace); + NamespaceFixer::fix(PhpFileDescriptor::make($filePath), 'App\Http\Controllers', $correctNamespace); // assert $pattern = '/[\n\s]*<\?php[\s\n]*namespace App\\\Http\\\Controllers\\\Foo;/'; $this->assertTrue(preg_match($pattern, FakeFilesystem::$putContent[$filePath]) == 1); @@ -55,7 +56,7 @@ public function fix_namespace_declare() $from = ''; $to = 'App\Http\Controllers\Foo'; $filePath = __DIR__.'/stubs/fix_namespace/declared_no_namespace.stub'; - NamespaceFixer::fix($filePath, $from, $to); + NamespaceFixer::fix(PhpFileDescriptor::make($filePath), $from, $to); // assert FakeFilesystem::$files[$filePath][5] = trim(FakeFilesystem::$files[$filePath][5]); $this->assertTrue(in_array('namespace '.$to.';', FakeFilesystem::$files[$filePath])); @@ -71,7 +72,7 @@ public function fix_namespace_class_with_no_namespace() $from = ''; $to = 'App\Http\Roo'; $filePath = __DIR__.'/stubs/fix_namespace/class_no_namespace.stub'; - NamespaceFixer::fix($filePath, $from, $to); + NamespaceFixer::fix(PhpFileDescriptor::make($filePath), $from, $to); // assert $pattern = '/[\n\s]*<\?php[\s\n]*namespace App\\\Http\\\Roo;/'; $this->assertTrue(preg_match($pattern, FakeFilesystem::$files[$filePath][0]) == 1); @@ -87,7 +88,7 @@ public function fix_namespace_class_with_bad_namespace() $from = 'App\Http\Controllers\Foo'; $to = 'App\Http\Roo'; $filePath = __DIR__.'/stubs/fix_namespace/class_with_namespace.stub'; - NamespaceFixer::fix($filePath, $from, $to); + NamespaceFixer::fix(PhpFileDescriptor::make($filePath), $from, $to); // assert $pattern = '/[\n\s]*<\?php[\s\n]*namespace App\\\Http\\\Roo;/'; $this->assertTrue(preg_match($pattern, FakeFilesystem::$putContent[$filePath]) == 1);