From 12d8d68dcff8786890ce9b6e05aa49bc62a49b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Mat=C4=9Bj=C4=8Dek?= Date: Mon, 27 Nov 2023 11:40:47 +0100 Subject: [PATCH] WIP [ci skip] --- .../ComposerAutoloadAbsoluteAction.php | 50 +++++++ .../Actions/GetComposerJsonDataAction.php | 16 ++- .../Actions/GetNamespaceForStubsAction.php | 7 +- src/Testing/Actions/PathToClassAction.php | 38 ++++++ .../Commands/MakeExpectationCommand.php | 122 +++++++++--------- src/Testing/Factories/FinderFactory.php | 18 +-- src/Testing/TestServiceProvider.php | 2 +- .../GetDevNamespaceForStubsActionTest.php | 2 +- 8 files changed, 166 insertions(+), 89 deletions(-) create mode 100644 src/Testing/Actions/ComposerAutoloadAbsoluteAction.php create mode 100644 src/Testing/Actions/PathToClassAction.php diff --git a/src/Testing/Actions/ComposerAutoloadAbsoluteAction.php b/src/Testing/Actions/ComposerAutoloadAbsoluteAction.php new file mode 100644 index 00000000..530e97ed --- /dev/null +++ b/src/Testing/Actions/ComposerAutoloadAbsoluteAction.php @@ -0,0 +1,50 @@ + + */ + private readonly array $dirs; + + public function __construct( + GetComposerJsonDataAction $getComposerJsonDataAction, + GetBasePathForStubsActionContract $getBasePathForStubsAction, + ) { + $this->dirs = $this->makeDirs($getComposerJsonDataAction, $getBasePathForStubsAction); + } + + /** + * @return array + */ + public function execute(): array + { + return $this->dirs; + } + + /** + * @return array + */ + private function makeDirs( + GetComposerJsonDataAction $getComposerJsonDataAction, + GetBasePathForStubsActionContract $getBasePathForStubsAction + ): array { + $basePath = $getBasePathForStubsAction->execute(); + $data = $getComposerJsonDataAction->execute(); + $dirs = []; + + if (isset($data['autoload']['psr-4']) && is_array($data['autoload']['psr-4'])) { + foreach ($data['autoload']['psr-4'] as $ns => $path) { + $dirs[$ns] = $basePath . DIRECTORY_SEPARATOR . trim((string) $path, '\\/'); + } + } + + return $dirs; + } +} diff --git a/src/Testing/Actions/GetComposerJsonDataAction.php b/src/Testing/Actions/GetComposerJsonDataAction.php index e5c05ed1..a428698a 100644 --- a/src/Testing/Actions/GetComposerJsonDataAction.php +++ b/src/Testing/Actions/GetComposerJsonDataAction.php @@ -1,4 +1,6 @@ -cacheMeServiceContract->get( key: 'larasctrict.composer.json', getValue: function (): mixed { $basePath = $this->getBasePathAction->execute(); - return json_decode($this->filesystem->get($basePath . '/composer.json'), true, 512, JSON_THROW_ON_ERROR); + return json_decode( + $this->filesystem->get($basePath . '/composer.json'), + true, + 512, + JSON_THROW_ON_ERROR + ); }, strategy: CacheMeStrategy::Memory, ); } - } diff --git a/src/Testing/Actions/GetNamespaceForStubsAction.php b/src/Testing/Actions/GetNamespaceForStubsAction.php index 4bb0920f..14e940fa 100644 --- a/src/Testing/Actions/GetNamespaceForStubsAction.php +++ b/src/Testing/Actions/GetNamespaceForStubsAction.php @@ -5,7 +5,6 @@ namespace LaraStrict\Testing\Actions; use Illuminate\Console\Command; -use Illuminate\Filesystem\Filesystem; use LaraStrict\Testing\Constants\StubConstants; use LaraStrict\Testing\Contracts\GetNamespaceForStubsActionContract; use LaraStrict\Testing\Entities\NamespaceEntity; @@ -16,14 +15,11 @@ class GetNamespaceForStubsAction implements GetNamespaceForStubsActionContract final public const ComposerAutoLoadDev = 'autoload-dev'; final public const ComposerPsr4 = 'psr-4'; - public function __construct( private readonly GetComposerJsonDataAction $getComposerJsonDataAction, - ) - { + ) { } - public function execute(Command $command, string $inputClass): NamespaceEntity { // Ask for which namespace which to use for "tests" @@ -50,7 +46,6 @@ public function execute(Command $command, string $inputClass): NamespaceEntity return new NamespaceEntity($folder, $baseNamespace); } - private function getComposerDevAutoLoad(array $composer): array { if (isset($composer[self::ComposerAutoLoadDev]) diff --git a/src/Testing/Actions/PathToClassAction.php b/src/Testing/Actions/PathToClassAction.php new file mode 100644 index 00000000..cbfacce1 --- /dev/null +++ b/src/Testing/Actions/PathToClassAction.php @@ -0,0 +1,38 @@ +composerAutoloadAbsoluteAction->execute(); + foreach ($dirs as $ns => $dir) { + if (str_starts_with($path, $dir) === false) { + continue; + } + + $class = preg_replace_callback( + sprintf('~^%s[/\\\](?.*)\.php$~', preg_quote($dir, '~')), + static fn (array $matches) => $ns . strtr($matches['path'], [ + '/' => '\\', + ]), + $path + ); + assert(is_string($class)); + + return $class; + } + + throw new Exception(sprintf('Path "%s" not found in composer psr-4.', $path)); + } +} diff --git a/src/Testing/Commands/MakeExpectationCommand.php b/src/Testing/Commands/MakeExpectationCommand.php index fa349a62..fe96d8e4 100644 --- a/src/Testing/Commands/MakeExpectationCommand.php +++ b/src/Testing/Commands/MakeExpectationCommand.php @@ -7,21 +7,18 @@ use Illuminate\Console\Command; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Str; -use LaraStrict\Cache\Contracts\CacheMeServiceContract; -use LaraStrict\Testing\Actions\GetComposerJsonDataAction; use LaraStrict\Testing\Actions\ParsePhpDocAction; +use LaraStrict\Testing\Actions\PathToClassAction; use LaraStrict\Testing\Assert\AbstractExpectationCallsMap; +use LaraStrict\Testing\Attributes\TestAssert; use LaraStrict\Testing\Constants\StubConstants; +use LaraStrict\Testing\Contracts\FinderFactoryContract; use LaraStrict\Testing\Contracts\GetBasePathForStubsActionContract; use LaraStrict\Testing\Contracts\GetNamespaceForStubsActionContract; -use LaraStrict\Testing\Contracts\FinderFactoryContract; use LaraStrict\Testing\Entities\AssertFileStateEntity; use LaraStrict\Testing\Entities\PhpDocEntity; use LaraStrict\Testing\Enums\PhpType; use LogicException; -use Nette\InvalidArgumentException; -use Nette\InvalidStateException; -use Nette\PhpGenerator\ClassLike; use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\Factory; use Nette\PhpGenerator\Literal; @@ -39,7 +36,6 @@ use ReflectionUnionType; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Finder\Finder; -use Symfony\Component\Finder\SplFileInfo; #[AsCommand(name: 'make:expectation', description: 'Make expectation class for given class')] class MakeExpectationCommand extends Command @@ -50,13 +46,16 @@ class MakeExpectationCommand extends Command {class : Class name of path to class using PSR-4 specs or use all keyword} '; + public function handle( Filesystem $filesystem, GetBasePathForStubsActionContract $getBasePathAction, GetNamespaceForStubsActionContract $getFolderAndNamespaceForStubsAction, ParsePhpDocAction $parsePhpDocAction, FinderFactoryContract $finderFactory, - ): int { + PathToClassAction $pathToClassAction, + ): int + { if (class_exists(ClassType::class) === false) { $message = 'First install package that is required:'; @@ -73,11 +72,9 @@ public function handle( $class = (string) $this->input->getArgument('class'); if ($class === 'all') { - $inputClasses = $this->findAllClasses( - $finderFactory->create(), - ); + $inputClasses = $this->findAllClasses($finderFactory->create(), $pathToClassAction); } else { - $inClass = $this->normalizeToClass($class, $basePath, $filesystem); + $inClass = $this->normalizeToClass($class, $basePath, $filesystem, $pathToClassAction); $inputClasses = $inClass === null ? [] : [$inClass]; } @@ -98,6 +95,7 @@ public function handle( return 0; } + /** * @param class-string $inputClass */ @@ -107,7 +105,8 @@ public function generateExpectationFiles( string $basePath, Filesystem $filesystem, ParsePhpDocAction $parsePhpDocAction - ): void { + ): void + { $class = new ReflectionClass($inputClass); $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC); @@ -135,10 +134,10 @@ public function generateExpectationFiles( // Base namespace can contain $directory = $basePath . DIRECTORY_SEPARATOR . $namespace->folder . strtr( - $fileNamespace, - StubConstants::NameSpaceSeparator, - DIRECTORY_SEPARATOR - ); + $fileNamespace, + StubConstants::NameSpaceSeparator, + DIRECTORY_SEPARATOR + ); $fullNamespace = $namespace->baseNamespace . $fileNamespace; $filesystem->ensureDirectoryExists($directory); @@ -218,6 +217,7 @@ className: $assertClassName, } } + /** * Generates a method assert in assert class. Generates __construct if $expectationClassName is passed (requires * extending AbstractExpectationCallsMap). @@ -227,7 +227,8 @@ protected function generateExpectationMethodAssert( ReflectionMethod $method, string $expectationClassName, PhpDocEntity $phpDoc, - ): void { + ): void + { $parameters = $method->getParameters(); $assertMethod = (new Factory())->fromMethodReflection($method); @@ -289,6 +290,7 @@ protected function generateExpectationMethodAssert( } } + /** * @param ReflectionClass $class */ @@ -296,7 +298,8 @@ protected function createAssertFileAndClass( ReflectionClass $class, string $namespace, string $className, - ): ?AssertFileStateEntity { + ): ?AssertFileStateEntity + { if ($class->isInterface() === false) { return null; } @@ -319,13 +322,15 @@ protected function createAssertFileAndClass( return new AssertFileStateEntity($file, $assertClass, $assertConstructor); } + protected function getExpectationFileContents( PsrPrinter $printer, string $namespace, string $className, ReflectionMethod $method, PhpDocEntity $phpDoc, - ): string { + ): string + { $parameters = $method->getParameters(); $file = new PhpFile(); @@ -376,10 +381,12 @@ protected function getExpectationFileContents( return $printer->printFile($file); } + protected function setParameterType( ReflectionType|ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null $type, PromotedParameter $constructorParameter - ): string { + ): string + { $proposedType = ''; $allowNull = false; @@ -438,10 +445,12 @@ protected function setParameterType( return $proposedType; } + protected function setParameterDefaultValue( ReflectionParameter $parameter, PromotedParameter $constructorParameter - ): void { + ): void + { if ($parameter->isDefaultValueAvailable() === false) { return; } @@ -467,6 +476,7 @@ protected function setParameterDefaultValue( } } + protected function writeError(string $message): void { if (property_exists($this, 'components')) { @@ -477,12 +487,14 @@ protected function writeError(string $message): void } } + protected function writeFile( string $directory, string $className, Filesystem $filesystem, string $fileContents - ): void { + ): void + { $filePath = $directory . DIRECTORY_SEPARATOR . $className . '.php'; $filesystem->put($filePath, $fileContents); @@ -497,6 +509,7 @@ protected function writeFile( $this->newLine(); } + /** * @param ReflectionClass $class */ @@ -505,37 +518,17 @@ protected function getExpectationClassName(ReflectionClass $class, string $metho return $class->getShortName() . $methodSuffix . 'Expectation'; } + protected function canReturnExpectation(ReflectionNamedType $returnType): bool { return $returnType->getName() !== PhpType::Void - ->value + ->value && $returnType->getName() !== PhpType::Self ->value && $returnType->getName() !== PhpType::Static ->value; } - /** - * @return class-string|null - */ - private function fileToClass(SplFileInfo $fileInfo): ?string - { - try { - $file = PhpFile::fromCode($fileInfo->getContents()); - } catch (InvalidStateException|InvalidArgumentException $e) { - $this->writeError(sprintf('Error parse file [%s]: %s', $fileInfo->getPathname(), $e->getMessage())); - return null; - } - - /** @phpstan-var array $classes */ - $classes = $file->getClasses(); - if ($classes === []) { - $this->writeError(sprintf('Provided file does not contain any class [%s]', $fileInfo->getPathname())); - return null; - } - - return $this->checkInterface(array_keys($classes)[0]); - } /** * @return class-string|null @@ -550,10 +543,16 @@ private function checkInterface(string $class): ?string return $class; } + /** * @return class-string|null */ - private function normalizeToClass(string $class, string $basePath, Filesystem $filesystem): ?string + private function normalizeToClass( + string $class, + string $basePath, + Filesystem $filesystem, + PathToClassAction $pathToClassAction + ): ?string { if (str_ends_with($class, '.php')) { $fullPath = $basePath . '/' . $class; @@ -562,34 +561,35 @@ private function normalizeToClass(string $class, string $basePath, Filesystem $f $this->writeError(sprintf('File does not exists at [%s]', $class)); return null; } - return $this->fileToClass(new SplFileInfo($fullPath, 'not used', 'not used')); + + return $pathToClassAction->execute($fullPath); } return $this->checkInterface($class); } + /** * @return array */ - private function findAllClasses(Finder $finder): array + private function findAllClasses(Finder $finder, PathToClassAction $pathToClassAction): array { - $cacheKey = 'larastrict.make.expectation.interfaces'; - $cachedInterfaces = get_declared_interfaces(); $classes = []; - $interfaces = []; foreach ($finder as $file) { - $done = require_once $file; + $interface = $pathToClassAction->execute($file->getRealPath()); + require_once $file->getPathname(); - } + if (interface_exists($interface, false) === false) { + continue; + } -// $classReflection = new ReflectionClass($class); -// $attributes = $classReflection->getAttributes(TestAssert::class); -// if ($attributes === []) { -// $interfaces[$file->getPathname()] = $class; -// continue; -// } -// $classes[] = $class; -// $cacheMeService->set($cacheKey, $interfaces); + $classReflection = new ReflectionClass($interface); + $attributes = $classReflection->getAttributes(TestAssert::class); + if ($attributes === []) { + continue; + } + $classes[] = $interface; + } return $classes; } diff --git a/src/Testing/Factories/FinderFactory.php b/src/Testing/Factories/FinderFactory.php index 19bfc365..9c345427 100644 --- a/src/Testing/Factories/FinderFactory.php +++ b/src/Testing/Factories/FinderFactory.php @@ -4,32 +4,22 @@ namespace LaraStrict\Testing\Factories; -use LaraStrict\Testing\Actions\GetComposerJsonDataAction; +use LaraStrict\Testing\Actions\ComposerAutoloadAbsoluteAction; use LaraStrict\Testing\Contracts\FinderFactoryContract; -use LaraStrict\Testing\Contracts\GetBasePathForStubsActionContract; use Symfony\Component\Finder\Finder; final class FinderFactory implements FinderFactoryContract { public function __construct( - private readonly GetComposerJsonDataAction $getComposerJsonDataAction, - private readonly GetBasePathForStubsActionContract $getBasePathForStubsAction, - ) - { + private readonly ComposerAutoloadAbsoluteAction $composerAutoloadAbsoluteAction, + ) { } - public function create(): Finder { - $basePath = $this->getBasePathForStubsAction->execute(); - - $data = $this->getComposerJsonDataAction->execute(); - $data->autoload['psr-4']; - return Finder::create()->files() ->name('*.php') - ->in($this->getBasePathForStubsAction->execute()) - ->notPath(['node_modules', 'vendor', 'storage']) + ->in($this->composerAutoloadAbsoluteAction->execute()) ->notName('*.blade.php'); } } diff --git a/src/Testing/TestServiceProvider.php b/src/Testing/TestServiceProvider.php index 744f9e8e..ada45da2 100644 --- a/src/Testing/TestServiceProvider.php +++ b/src/Testing/TestServiceProvider.php @@ -11,9 +11,9 @@ use LaraStrict\Testing\Actions\GetBasePathForStubsAction; use LaraStrict\Testing\Actions\GetNamespaceForStubsAction; use LaraStrict\Testing\Commands\MakeExpectationCommand; +use LaraStrict\Testing\Contracts\FinderFactoryContract; use LaraStrict\Testing\Contracts\GetBasePathForStubsActionContract; use LaraStrict\Testing\Contracts\GetNamespaceForStubsActionContract; -use LaraStrict\Testing\Contracts\FinderFactoryContract; use LaraStrict\Testing\Core\Services\NoSleepService; use LaraStrict\Testing\Factories\FinderFactory; diff --git a/tests/Unit/Testing/Actions/GetDevNamespaceForStubsActionTest.php b/tests/Unit/Testing/Actions/GetDevNamespaceForStubsActionTest.php index 197948a7..256a889f 100644 --- a/tests/Unit/Testing/Actions/GetDevNamespaceForStubsActionTest.php +++ b/tests/Unit/Testing/Actions/GetDevNamespaceForStubsActionTest.php @@ -33,7 +33,7 @@ public function data(): array */ public function testNamespace(string $class, string $expectedBaseNamespace, string $expectedFolder): void { - $result = (new GetDevNamespaceForStubsAction())->execute(new Command(), 'test', $class); + $result = (new GetDevNamespaceForStubsAction())->execute(new Command(), $class); $this->assertEquals($expectedFolder, $result->folder); $this->assertEquals($expectedBaseNamespace, $result->baseNamespace); }