From 8489bf394b9cb4baa7132fb0956425b9f75dfb4c Mon Sep 17 00:00:00 2001 From: Shift Date: Sat, 5 Feb 2022 15:35:35 +0000 Subject: [PATCH 01/45] Bump dependencies for Laravel 9 --- composer.json | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 7946a00..f2083a9 100644 --- a/composer.json +++ b/composer.json @@ -17,17 +17,17 @@ ], "require": { "php": "^8.0", - "illuminate/contracts": "^8.37", + "illuminate/contracts": "^9.0", "riimu/kit-pathjoin": "^1.2", "spatie/laravel-package-tools": "^1.4.3" }, "require-dev": { - "brianium/paratest": "^6.2", - "nunomaduro/collision": "^5.3", - "orchestra/testbench": "^6.15", - "phpunit/phpunit": "^9.3", - "spatie/laravel-ray": "^1.9", - "vimeo/psalm": "^4.4" + "brianium/paratest": "^6.4", + "nunomaduro/collision": "^6.0", + "orchestra/testbench": "^7.0", + "phpunit/phpunit": "^9.5.10", + "spatie/laravel-ray": "^1.29", + "vimeo/psalm": "^4.20" }, "autoload": { "psr-4": { @@ -40,8 +40,7 @@ "Wulfheart\\LaravelActionsIdeHelper\\Tests\\": "tests" } }, - "scripts": { - }, + "scripts": [], "config": { "sort-packages": true }, From 1b88b71b1e561f1ceabb379618339ba024964ef8 Mon Sep 17 00:00:00 2001 From: "P. K. Tharindu" Date: Thu, 10 Feb 2022 13:10:59 +0530 Subject: [PATCH 02/45] add composer/pcre --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 7946a00..44e818d 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": "^8.0", + "composer/pcre": "^1.0", "illuminate/contracts": "^8.37", "riimu/kit-pathjoin": "^1.2", "spatie/laravel-package-tools": "^1.4.3" From a9daff51c98790feb541e08845455ad9cc0886e1 Mon Sep 17 00:00:00 2001 From: "P. K. Tharindu" Date: Thu, 10 Feb 2022 13:15:12 +0530 Subject: [PATCH 03/45] Create ClassMapGenerator.php --- src/ClassMapGenerator.php | 415 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 src/ClassMapGenerator.php diff --git a/src/ClassMapGenerator.php b/src/ClassMapGenerator.php new file mode 100644 index 0000000..0aa6241 --- /dev/null +++ b/src/ClassMapGenerator.php @@ -0,0 +1,415 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * This file is copied from the Symfony package to composer/composer and then copied again here. + * + * (c) Fabien Potencier + */ + +namespace Wulfheart\LaravelActionsIdeHelper; + +use Composer\Pcre\Preg; +use Symfony\Component\Finder\Finder; + +/** + * ClassMapGenerator + * + * @author Gyula Sallai + * @author Jordi Boggiano + */ +class ClassMapGenerator +{ + /** @var array */ + private static $typeConfig; + + /** @var non-empty-string */ + private static $restPattern; + + /** + * Generate a class map file + * + * @param \Traversable|array $dirs Directories or a single path to search in + * @param string $file The name of the class map file + * @return void + */ + public static function dump($dirs, $file) + { + $maps = []; + + foreach ($dirs as $dir) { + $maps = array_merge($maps, static::createMap($dir)); + } + + file_put_contents($file, sprintf(' + * @author Johannes M. Schmitt + */ + public static function isAbsolutePath($path) + { + return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':' || substr($path, 0, 2) === '\\\\'; + } + + /** + * Normalize a path. This replaces backslashes with slashes, removes ending + * slash and collapses redundant separators and up-level references. + * + * @param string $path Path to the file or directory + * @return string + * + * @see \Composer\Util\Filesystem + * @author Jordi Boggiano + * @author Johannes M. Schmitt + */ + public static function normalizePath($path) + { + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = false; + + // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: + if (preg_match('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, strlen($prefix)); + } + + if (substr($path, 0, 1) === '/') { + $absolute = true; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && ($absolute || $up)) { + array_pop($parts); + $up = !(empty($parts) || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + return $prefix . ($absolute ? '/' : '') . implode('/', $parts); + } + + /** + * Iterate over all files in the given directory searching for classes + * + * @param \Traversable<\SplFileInfo>|string|array $path The path to search in or an iterator + * @param string $excluded Regex that matches file paths to be excluded from the classmap + * @param ?\Composer\IO\IOInterface $io IO object + * @param ?string $namespace Optional namespace prefix to filter by + * @param ?string $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules + * @param array $scannedFiles + * @return array A class map array + * @throws \RuntimeException When the path is neither an existing file nor directory + */ + public static function createMap($path, $excluded = null, $io = null, $namespace = null, $autoloadType = null, &$scannedFiles = []) + { + $basePath = $path; + if (is_string($path)) { + if (is_file($path)) { + $path = [new \SplFileInfo($path)]; + } elseif (is_dir($path) || strpos($path, '*') !== false) { + $path = Finder::create()->files()->followLinks()->name('/\.(php|inc|hh)$/')->in($path); + } else { + throw new \RuntimeException( + 'Could not scan for classes inside "' . $path . + '" which does not appear to be a file nor a folder' + ); + } + } elseif (null !== $autoloadType) { + throw new \RuntimeException('Path must be a string when specifying an autoload type'); + } + + $map = []; + $cwd = realpath(getcwd()); + + foreach ($path as $file) { + $filePath = $file->getPathname(); + if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), ['php', 'inc', 'hh'])) { + continue; + } + + if (!self::isAbsolutePath($filePath)) { + $filePath = $cwd . '/' . $filePath; + $filePath = self::normalizePath($filePath); + } else { + $filePath = Preg::replace('{[\\\\/]{2,}}', '/', $filePath); + } + + $realPath = realpath($filePath); + + // if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings + // in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already + if (isset($scannedFiles[$realPath])) { + continue; + } + + // check the realpath of the file against the excluded paths as the path might be a symlink and the excluded path is realpath'd so symlink are resolved + if ($excluded && Preg::isMatch($excluded, strtr($realPath, '\\', '/'))) { + continue; + } + // check non-realpath of file for directories symlink in project dir + if ($excluded && Preg::isMatch($excluded, strtr($filePath, '\\', '/'))) { + continue; + } + + $classes = self::findClasses($filePath); + if (null !== $autoloadType) { + $classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io); + + // if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later + if ($classes) { + $scannedFiles[$realPath] = true; + } + } else { + // classmap autoload rules always collect all classes so for these we definitely do not want to scan again + $scannedFiles[$realPath] = true; + } + + foreach ($classes as $class) { + // skip classes not within the given namespace prefix + if (null === $autoloadType && null !== $namespace && '' !== $namespace && 0 !== strpos($class, $namespace)) { + continue; + } + + if (!isset($map[$class])) { + $map[$class] = $filePath; + } elseif ($io && $map[$class] !== $filePath && !Preg::isMatch('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { + $io->writeError( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.' + ); + } + } + } + + return $map; + } + + /** + * Remove classes which could not have been loaded by namespace autoloaders + * + * @param array $classes found classes in given file + * @param string $filePath current file + * @param string $baseNamespace prefix of given autoload mapping + * @param string $namespaceType psr-0|psr-4 + * @param string $basePath root directory of given autoload mapping + * @param ?\Composer\IO\IOInterface $io IO object + * @return array valid classes + */ + private static function filterByNamespace($classes, $filePath, $baseNamespace, $namespaceType, $basePath, $io) + { + $validClasses = []; + $rejectedClasses = []; + + $realSubPath = substr($filePath, strlen($basePath) + 1); + $dotPosition = strrpos($realSubPath, '.'); + $realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition); + + foreach ($classes as $class) { + // silently skip if ns doesn't have common root + if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) { + continue; + } + // transform class name to file path and validate + if ('psr-0' === $namespaceType) { + $namespaceLength = strrpos($class, '\\'); + if (false !== $namespaceLength) { + $namespace = substr($class, 0, $namespaceLength + 1); + $className = substr($class, $namespaceLength + 1); + $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) + . str_replace('_', DIRECTORY_SEPARATOR, $className); + } else { + $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class); + } + } elseif ('psr-4' === $namespaceType) { + $subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class; + $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace); + } else { + throw new \RuntimeException("namespaceType must be psr-0 or psr-4, $namespaceType given"); + } + if ($subPath === $realSubPath) { + $validClasses[] = $class; + } else { + $rejectedClasses[] = $class; + } + } + // warn only if no valid classes, else silently skip invalid + if (empty($validClasses)) { + foreach ($rejectedClasses as $class) { + if ($io) { + $io->writeError("Class $class located in " . Preg::replace('{^' . preg_quote(getcwd()) . '}', '.', $filePath, 1) . " does not comply with $namespaceType autoloading standard. Skipping."); + } + } + + return []; + } + + return $validClasses; + } + + /** + * Extract the classes in the given file + * + * @param string $path The file to check + * @throws \RuntimeException + * @return array The found classes + */ + private static function findClasses($path) + { + $extraTypes = self::getExtraTypes(); + + // Use @ here instead of Silencer to actively suppress 'unhelpful' output + // @link https://github.com/composer/composer/pull/4886 + $contents = @php_strip_whitespace($path); + if (!$contents) { + if (!file_exists($path)) { + $message = 'File at "%s" does not exist, check your classmap definitions'; + } elseif (!is_readable($path)) { + $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim((string) file_get_contents($path))) { + // The input file was really empty and thus contains no classes + return []; + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; + } + $error = error_get_last(); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + throw new \RuntimeException(sprintf($message, $path)); + } + + // return early if there is no chance of matching anything in this file + Preg::matchAll('{\b(?:class|interface|trait' . $extraTypes . ')\s}i', $contents, $matches); + if (!$matches) { + return []; + } + + // strip heredocs/nowdocs + $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents); + // strip strings + $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); + // strip leading non-php code if needed + if (substr($contents, 0, 2) !== '(?:[^<]++|<(?!\?))*+<\?}s', '?>'); + if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface|trait' . $extraTypes . ') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] + ) + }ix', $contents, $matches); + + $classes = []; + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { + if (!empty($matches['ns'][$i])) { + $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + // skip anon classes extending/implementing + if ($name === 'extends' || $name === 'implements') { + continue; + } + if ($name[0] === ':') { + // This is an XHP class, https://github.com/facebook/xhp + $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); + } elseif ($matches['type'][$i] === 'enum') { + // something like: + // enum Foo: int { HERP = '123'; } + // The regex above captures the colon, which isn't part of + // the class name. + // or: + // enum Foo:int { HERP = '123'; } + // The regex above captures the colon and type, which isn't part of + // the class name. + $colonPos = strrpos($name, ':'); + if (false !== $colonPos) { + $name = substr($name, 0, $colonPos); + } + } + $classes[] = ltrim($namespace . $name, '\\'); + } + } + + return $classes; + } + + /** + * @return string + */ + private static function getExtraTypes() + { + static $extraTypes = null; + + if (null === $extraTypes) { + $extraTypes = ''; + if (PHP_VERSION_ID >= 80100 || (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) { + $extraTypes .= '|enum'; + } + + self::setTypeConfig(array_merge(['class', 'interface', 'trait'], array_filter(explode('|', $extraTypes)))); + } + + return $extraTypes; + } + + /** + * @param string[] $types + * @return void + * + * @see \Composer\Autoload\PhpFileCleaner + * @author Jordi Boggiano + * @author Johannes M. Schmitt + */ + private static function setTypeConfig($types) + { + foreach ($types as $type) { + self::$typeConfig[$type[0]] = [ + 'name' => $type, + 'length' => strlen($type), + 'pattern' => '{.\b(?])' . $type . '\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais', + ]; + } + + self::$restPattern = '{[^?"\' Date: Thu, 10 Feb 2022 13:17:56 +0530 Subject: [PATCH 04/45] Replace composer with copy of classmap generator --- src/Commands/LaravelActionsIdeHelperCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/LaravelActionsIdeHelperCommand.php b/src/Commands/LaravelActionsIdeHelperCommand.php index 813d9dd..7841751 100644 --- a/src/Commands/LaravelActionsIdeHelperCommand.php +++ b/src/Commands/LaravelActionsIdeHelperCommand.php @@ -2,7 +2,6 @@ namespace Wulfheart\LaravelActionsIdeHelper\Commands; -use Composer\Autoload\ClassMapGenerator; use Illuminate\Console\Command; use phpDocumentor\Reflection\Php\Factory\Type; use phpDocumentor\Reflection\TypeResolver; @@ -12,6 +11,7 @@ use ReflectionClass; use Riimu\Kit\PathJoin\Path; use Symfony\Component\Finder\Finder; +use Wulfheart\LaravelActionsIdeHelper\ClassMapGenerator; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; use Wulfheart\LaravelActionsIdeHelper\Service\BuildIdeHelper; use Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\AsObjectGenerator; From c9fa8555ece160c567c874fb88e512da4805029d Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 11:46:55 +0100 Subject: [PATCH 05/45] WIP --- composer.json | 9 ++++- .../LaravelActionsIdeHelperCommand.php | 5 +-- tests/ExampleTest.php | 37 ++++++++++++++----- tests/Pest.php | 3 ++ tests/TestCase.php | 19 ---------- tests/stubs/TestAction.php | 16 ++++++++ 6 files changed, 55 insertions(+), 34 deletions(-) create mode 100644 tests/Pest.php create mode 100644 tests/stubs/TestAction.php diff --git a/composer.json b/composer.json index cbb32b7..3beaa53 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^8.0", "illuminate/contracts": "^9.0", - "composer/pcre": "^1.0", + "phpdocumentor/reflection": "^5.1", "riimu/kit-pathjoin": "^1.2", "spatie/laravel-package-tools": "^1.4.3" }, @@ -26,6 +26,7 @@ "brianium/paratest": "^6.4", "nunomaduro/collision": "^6.0", "orchestra/testbench": "^7.0", + "pestphp/pest": "^1.21", "phpunit/phpunit": "^9.5.10", "spatie/laravel-ray": "^1.29", "vimeo/psalm": "^4.20" @@ -44,7 +45,11 @@ "scripts": { }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "composer/package-versions-deprecated": true, + "pestphp/pest-plugin": true + } }, "extra": { "laravel": { diff --git a/src/Commands/LaravelActionsIdeHelperCommand.php b/src/Commands/LaravelActionsIdeHelperCommand.php index 7841751..6be6a5f 100644 --- a/src/Commands/LaravelActionsIdeHelperCommand.php +++ b/src/Commands/LaravelActionsIdeHelperCommand.php @@ -3,9 +3,7 @@ namespace Wulfheart\LaravelActionsIdeHelper\Commands; use Illuminate\Console\Command; -use phpDocumentor\Reflection\Php\Factory\Type; -use phpDocumentor\Reflection\TypeResolver; -use phpDocumentor\Reflection\Types\Nullable; +use phpDocumentor\Reflection\File\LocalFile; use PhpParser\BuilderFactory; use PhpParser\PrettyPrinter\Standard; use ReflectionClass; @@ -36,7 +34,6 @@ protected function traverseFiles() ->name('*.php'); $map = collect(ClassMapGenerator::createMap($finder->getIterator())); - // dd($map); $classes = $map->keys(); $infos = []; diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php index 750dd52..0f0ba37 100644 --- a/tests/ExampleTest.php +++ b/tests/ExampleTest.php @@ -1,12 +1,31 @@ assertTrue(true); +use Funkyproject\ReflectionFile; +use phpDocumentor\Reflection\File\LocalFile; +use phpDocumentor\Reflection\ProjectFactory; +use Symfony\Component\Finder\Finder; +use Wulfheart\LaravelActionsIdeHelper\ClassMapGenerator; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\TestAction; + +it('can test', function (){ + /** @var \Illuminate\Foundation\Application $app */ + $app = $this->app; + $app->make(TestAction::class); + $iterator = Finder::create()->in(__DIR__ . '/stubs/'); + $map = collect(ClassMapGenerator::createMap($iterator)); + foreach ($map as $item => $value) { + + $file = new LocalFile($value); + $projectFactory = \phpDocumentor\Reflection\Php\ProjectFactory::createInstance(); + + $project = $projectFactory->create('Test', [$file]); + + $_ = 3; + + + + } -} + $m = $map; +}); + diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..39e84b0 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,3 @@ +in(__DIR__); \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index eaaeaa5..4c3e578 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,21 +2,11 @@ namespace Wulfheart\LaravelActionsIdeHelper\Tests; -use Illuminate\Database\Eloquent\Factories\Factory; use Orchestra\Testbench\TestCase as Orchestra; use Wulfheart\LaravelActionsIdeHelper\LaravelActionsIdeHelperServiceProvider; class TestCase extends Orchestra { - public function setUp(): void - { - parent::setUp(); - - Factory::guessFactoryNamesUsing( - fn (string $modelName) => 'Wulfheart\\LaravelActionsIdeHelper\\Database\\Factories\\'.class_basename($modelName).'Factory' - ); - } - protected function getPackageProviders($app) { return [ @@ -24,13 +14,4 @@ protected function getPackageProviders($app) ]; } - public function getEnvironmentSetUp($app) - { - config()->set('database.default', 'testing'); - - /* - include_once __DIR__.'/../database/migrations/create_laravel-actions-ide-helper_table.php.stub'; - (new \CreatePackageTable())->up(); - */ - } } diff --git a/tests/stubs/TestAction.php b/tests/stubs/TestAction.php new file mode 100644 index 0000000..2b902ca --- /dev/null +++ b/tests/stubs/TestAction.php @@ -0,0 +1,16 @@ + Date: Sat, 19 Feb 2022 15:44:34 +0100 Subject: [PATCH 06/45] ActionFactory Test start --- src/Service/ActionInfoFactory.php | 47 +++++++++++++++++++++++++++++++ tests/ActionInfoFactoryTest.php | 19 +++++++++++++ tests/stubs/BaseAction.php | 12 ++++++++ tests/stubs/NewAction.php | 11 ++++++++ tests/stubs/TestAction.php | 3 ++ 5 files changed, 92 insertions(+) create mode 100644 src/Service/ActionInfoFactory.php create mode 100644 tests/ActionInfoFactoryTest.php create mode 100644 tests/stubs/BaseAction.php create mode 100644 tests/stubs/NewAction.php diff --git a/src/Service/ActionInfoFactory.php b/src/Service/ActionInfoFactory.php new file mode 100644 index 0000000..9ab7c73 --- /dev/null +++ b/src/Service/ActionInfoFactory.php @@ -0,0 +1,47 @@ + */ + public static function create(string $path): array + { + + return []; + + + } + + /** @return array> */ + protected function loadFromPath(string $path) + { + $res = Lody::classes($path)->isNotAbstract(); + /** @var array> $traits */ + return collect(ActionInfo::ALL_TRAITS) + ->map(fn($trait, $key) => [$trait => $res->hasTrait($trait)->all()]) + ->collapse() + ->map(function ($item, $key) { + return collect($item) + ->map(fn($i) => [ + 'item' => $i, + 'group' => $key, + ]) + ->toArray(); + }) + ->values() + ->collapse() + ->groupBy('item') + ->map(fn($item) => $item->pluck('group')->toArray()) + ->toArray(); + } + +} \ No newline at end of file diff --git a/tests/ActionInfoFactoryTest.php b/tests/ActionInfoFactoryTest.php new file mode 100644 index 0000000..31f57de --- /dev/null +++ b/tests/ActionInfoFactoryTest.php @@ -0,0 +1,19 @@ +loadFromPath(__DIR__ . '/stubs'); + + expect($result)->toBeArray()->toMatchArray([ + BaseAction::class => [AsObject::class], + NewAction::class => [AsObject::class, AsJob::class], + TestAction::class => ActionInfo::ALL_TRAITS, + ]); +}); \ No newline at end of file diff --git a/tests/stubs/BaseAction.php b/tests/stubs/BaseAction.php new file mode 100644 index 0000000..88d4681 --- /dev/null +++ b/tests/stubs/BaseAction.php @@ -0,0 +1,12 @@ + Date: Sat, 19 Feb 2022 16:23:40 +0100 Subject: [PATCH 07/45] ActionFactory creation --- src/Service/ActionInfo.php | 75 +++++++++++++++---------------- src/Service/ActionInfoFactory.php | 17 ++++++- tests/stubs/NotAnAction.php | 8 ++++ 3 files changed, 58 insertions(+), 42 deletions(-) create mode 100644 tests/stubs/NotAnAction.php diff --git a/src/Service/ActionInfo.php b/src/Service/ActionInfo.php index 43a0132..f4692ec 100644 --- a/src/Service/ActionInfo.php +++ b/src/Service/ActionInfo.php @@ -3,6 +3,12 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service; use JetBrains\PhpStorm\Pure; +use Lorisleiva\Actions\Concerns\AsCommand; +use Lorisleiva\Actions\Concerns\AsController; +use Lorisleiva\Actions\Concerns\AsFake; +use Lorisleiva\Actions\Concerns\AsJob; +use Lorisleiva\Actions\Concerns\AsListener; +use Lorisleiva\Actions\Concerns\AsObject; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\TypeResolver; use PhpParser\BuilderFactory; @@ -26,52 +32,41 @@ final class ActionInfo /** @var array $functionInfos */ public array $functionInfos = []; - - const AS_ACTION_NAME = "Lorisleiva\Actions\Concerns\AsAction"; - const AS_OBJECT_NAME = "Lorisleiva\Actions\Concerns\AsObject"; - const AS_CONTROLLER_NAME = "Lorisleiva\Actions\Concerns\AsController"; - const AS_LISTENER_NAME = "Lorisleiva\Actions\Concerns\AsListener"; - const AS_JOB_NAME = "Lorisleiva\Actions\Concerns\AsJob"; - const AS_COMMAND_NAME = "Lorisleiva\Actions\Concerns\AsCommand"; - const AS_FAKE_NAME = "Lorisleiva\Actions\Concerns\AsFake"; + const ALL_TRAITS = [ + AsObject::class, + AsController::class, + AsJob::class, + AsListener::class, + AsCommand::class, + AsFake::class, + ]; #[Pure] public static function create(): ActionInfo { return new ActionInfo(); } - public static function createFromReflectionClass(ReflectionClass $reflection): ?ActionInfo - { - $traits = collect(ActionInfo::getAllTraits($reflection)); - - $intersection = $traits->intersect([ - // Constants that are hard-coded for now - self::AS_OBJECT_NAME, - self::AS_CONTROLLER_NAME, - self::AS_LISTENER_NAME, - self::AS_JOB_NAME, - self::AS_COMMAND_NAME, - ]); - - if ($intersection->count() <= 0) { - return null; - } - - return self::create() - ->setName($reflection->getName()) - ->setAsObject($intersection->contains(self::AS_OBJECT_NAME)) - ->setAsController($intersection->contains(self::AS_CONTROLLER_NAME)) - ->setAsListener($intersection->contains(self::AS_LISTENER_NAME)) - ->setAsJob($intersection->contains(self::AS_JOB_NAME)) - ->setAsCommand($intersection->contains(self::AS_COMMAND_NAME)) - ->setFunctionInfos([ - self::AS_OBJECT_NAME => self::resolveFunctionInfo($reflection), - self::AS_CONTROLLER_NAME => self::resolveFunctionInfo($reflection, 'asController'), - self::AS_LISTENER_NAME => self::resolveFunctionInfo($reflection, 'asListener'), - self::AS_JOB_NAME => self::resolveFunctionInfo($reflection, 'asJob'), - self::AS_COMMAND_NAME => self::resolveFunctionInfo($reflection, 'asCommand'), - ]); - } + // public static function createFromReflectionClass(ReflectionClass $reflection): ?ActionInfo + // { + // // if ($intersection->count() <= 0) { + // // return null; + // // } + // // + // // return self::create() + // // ->setName($reflection->getName()) + // // ->setAsObject($intersection->contains(self::AS_OBJECT_NAME)) + // // ->setAsController($intersection->contains(self::AS_CONTROLLER_NAME)) + // // ->setAsListener($intersection->contains(self::AS_LISTENER_NAME)) + // // ->setAsJob($intersection->contains(self::AS_JOB_NAME)) + // // ->setAsCommand($intersection->contains(self::AS_COMMAND_NAME)) + // // ->setFunctionInfos([ + // // self::AS_OBJECT_NAME => self::resolveFunctionInfo($reflection), + // // self::AS_CONTROLLER_NAME => self::resolveFunctionInfo($reflection, 'asController'), + // // self::AS_LISTENER_NAME => self::resolveFunctionInfo($reflection, 'asListener'), + // // self::AS_JOB_NAME => self::resolveFunctionInfo($reflection, 'asJob'), + // // self::AS_COMMAND_NAME => self::resolveFunctionInfo($reflection, 'asCommand'), + // // ]); + // } /** diff --git a/src/Service/ActionInfoFactory.php b/src/Service/ActionInfoFactory.php index 9ab7c73..e2e40f4 100644 --- a/src/Service/ActionInfoFactory.php +++ b/src/Service/ActionInfoFactory.php @@ -15,8 +15,21 @@ class ActionInfoFactory /** @return array<\Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo> */ public static function create(string $path): array { - - return []; + $factory = new self(); + $classes = $factory->loadFromPath($path); + + $ais = []; + foreach ($classes as $class => $traits){ + $tc = collect($traits); + $ais[] = ActionInfo::create() + ->setName($class) + ->setAsObject($tc->contains(AsObject::class)) + ->setAsCommand($tc->contains(AsCommand::class)) + ->setAsController($tc->contains(AsController::class)) + ->setAsJob($tc->contains(AsJob::class)) + ->setAsListener($tc->contains(AsListener::class)); + } + return $ais; } diff --git a/tests/stubs/NotAnAction.php b/tests/stubs/NotAnAction.php new file mode 100644 index 0000000..725266b --- /dev/null +++ b/tests/stubs/NotAnAction.php @@ -0,0 +1,8 @@ + Date: Sat, 19 Feb 2022 16:23:54 +0100 Subject: [PATCH 08/45] Expectations --- tests/ActionInfoFactoryTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/ActionInfoFactoryTest.php b/tests/ActionInfoFactoryTest.php index 31f57de..7bb087b 100644 --- a/tests/ActionInfoFactoryTest.php +++ b/tests/ActionInfoFactoryTest.php @@ -6,6 +6,7 @@ use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfoFactory; use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\BaseAction; use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\NewAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\NotAnAction; use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\TestAction; it('creates a correct trait lookup', function() { @@ -16,4 +17,16 @@ NewAction::class => [AsObject::class, AsJob::class], TestAction::class => ActionInfo::ALL_TRAITS, ]); + + expect(collect($result)->keys()->toArray())->not()->toContain(NotAnAction::class); +}); + +it('creates correct ActionInfos', function (){ + $result = ActionInfoFactory::create(__DIR__ . '/stubs'); + + /** @var ActionInfo $ai */ + $ai = collect($result)->filter(fn(ActionInfo $a) => $a->name == BaseAction::class)->first(); + + expect($ai->asObject)->toBeTrue(); + expect($ai->asCommand)->toBeFalse(); }); \ No newline at end of file From ce3e658b33187409ba5ed47f6661dafc231fd306 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 16:43:02 +0100 Subject: [PATCH 09/45] Creation of classmap from PhpDocumentor Reflection --- src/Service/ActionInfoFactory.php | 22 +++++++++++++++++++++- tests/ActionInfoFactoryTest.php | 6 ++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Service/ActionInfoFactory.php b/src/Service/ActionInfoFactory.php index e2e40f4..4ed1796 100644 --- a/src/Service/ActionInfoFactory.php +++ b/src/Service/ActionInfoFactory.php @@ -9,6 +9,11 @@ use Lorisleiva\Actions\Concerns\AsListener; use Lorisleiva\Actions\Concerns\AsObject; use Lorisleiva\Lody\Lody; +use phpDocumentor\Reflection\File\LocalFile; +use phpDocumentor\Reflection\Php\File; +use phpDocumentor\Reflection\Php\ProjectFactory; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; class ActionInfoFactory { @@ -21,13 +26,15 @@ public static function create(string $path): array $ais = []; foreach ($classes as $class => $traits){ $tc = collect($traits); + $reflection = new \ReflectionClass($class); $ais[] = ActionInfo::create() ->setName($class) ->setAsObject($tc->contains(AsObject::class)) ->setAsCommand($tc->contains(AsCommand::class)) ->setAsController($tc->contains(AsController::class)) ->setAsJob($tc->contains(AsJob::class)) - ->setAsListener($tc->contains(AsListener::class)); + ->setAsListener($tc->contains(AsListener::class)) + ; } return $ais; @@ -57,4 +64,17 @@ protected function loadFromPath(string $path) ->toArray(); } + /** @return array<\phpDocumentor\Reflection\Php\Class_> + * @throws \phpDocumentor\Reflection\Exception + */ + protected function loadPhpDocumentorReflectionClassMap(string $path): array{ + $finder = Finder::create()->files()->in($path)->name('*.php'); + $files = collect($finder)->map(fn(SplFileInfo $file) => new LocalFile($file->getRealPath()))->toArray(); + + /** @var \phpDocumentor\Reflection\Php\Project $project */ + $project = ProjectFactory::createInstance()->create('Laravel Actions IDE Helper', $files); + return collect($project->getFiles())->map(fn(File $f) => $f->getClasses())->collapse()->toArray(); + + } + } \ No newline at end of file diff --git a/tests/ActionInfoFactoryTest.php b/tests/ActionInfoFactoryTest.php index 7bb087b..4fc7cd6 100644 --- a/tests/ActionInfoFactoryTest.php +++ b/tests/ActionInfoFactoryTest.php @@ -29,4 +29,10 @@ expect($ai->asObject)->toBeTrue(); expect($ai->asCommand)->toBeFalse(); +}); + +it('parses the classes correctly', function() { + $result = invade(new ActionInfoFactory())->loadPhpDocumentorReflectionClassMap(__DIR__ . '/stubs'); + + expect(collect($result)->keys()->toArray())->not()->toContain(NotAnAction::class, BaseAction::class, NotAnAction::class, TestAction::class); }); \ No newline at end of file From 25023a356401b78b904134fc393a90889b2fd049 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 16:55:12 +0100 Subject: [PATCH 10/45] Fix in ClassMap Lookup --- src/Service/ActionInfoFactory.php | 11 ++++++++--- tests/ActionInfoFactoryTest.php | 7 ++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Service/ActionInfoFactory.php b/src/Service/ActionInfoFactory.php index 4ed1796..c126cb5 100644 --- a/src/Service/ActionInfoFactory.php +++ b/src/Service/ActionInfoFactory.php @@ -2,6 +2,7 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service; +use Illuminate\Support\Str; use Lorisleiva\Actions\Concerns\AsCommand; use Lorisleiva\Actions\Concerns\AsController; use Lorisleiva\Actions\Concerns\AsFake; @@ -22,7 +23,7 @@ public static function create(string $path): array { $factory = new self(); $classes = $factory->loadFromPath($path); - + $classMap = $factory->loadPhpDocumentorReflectionClassMap($path); $ais = []; foreach ($classes as $class => $traits){ $tc = collect($traits); @@ -34,7 +35,7 @@ public static function create(string $path): array ->setAsController($tc->contains(AsController::class)) ->setAsJob($tc->contains(AsJob::class)) ->setAsListener($tc->contains(AsListener::class)) - ; + ->setClassInfo($classMap[$class]); } return $ais; @@ -73,7 +74,11 @@ protected function loadPhpDocumentorReflectionClassMap(string $path): array{ /** @var \phpDocumentor\Reflection\Php\Project $project */ $project = ProjectFactory::createInstance()->create('Laravel Actions IDE Helper', $files); - return collect($project->getFiles())->map(fn(File $f) => $f->getClasses())->collapse()->toArray(); + return collect($project->getFiles()) + ->map(fn(File $f) => $f->getClasses()) + ->collapse() + ->mapWithKeys(fn($item, string $key) => [Str::of($key)->ltrim("\\")->toString() => $item]) + ->toArray(); } diff --git a/tests/ActionInfoFactoryTest.php b/tests/ActionInfoFactoryTest.php index 4fc7cd6..3f5c694 100644 --- a/tests/ActionInfoFactoryTest.php +++ b/tests/ActionInfoFactoryTest.php @@ -2,6 +2,7 @@ use Lorisleiva\Actions\Concerns\AsJob; use Lorisleiva\Actions\Concerns\AsObject; +use phpDocumentor\Reflection\Php\Class_; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfoFactory; use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\BaseAction; @@ -29,10 +30,14 @@ expect($ai->asObject)->toBeTrue(); expect($ai->asCommand)->toBeFalse(); + + expect($ai->classInfo instanceof Class_)->toBeTrue(); + expect($ai->classInfo)->not()->toBeNull(); }); it('parses the classes correctly', function() { $result = invade(new ActionInfoFactory())->loadPhpDocumentorReflectionClassMap(__DIR__ . '/stubs'); - expect(collect($result)->keys()->toArray())->not()->toContain(NotAnAction::class, BaseAction::class, NotAnAction::class, TestAction::class); + $keys = collect($result)->keys()->toArray(); + expect($keys)->toContain(NotAnAction::class, BaseAction::class, NotAnAction::class, TestAction::class); }); \ No newline at end of file From cd274b2024a94970adf7170647149f3d82887cab Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 16:57:38 +0100 Subject: [PATCH 11/45] ActionInfo Cleanup --- src/Service/ActionInfo.php | 141 ++----------------------------------- 1 file changed, 7 insertions(+), 134 deletions(-) diff --git a/src/Service/ActionInfo.php b/src/Service/ActionInfo.php index f4692ec..cf2322b 100644 --- a/src/Service/ActionInfo.php +++ b/src/Service/ActionInfo.php @@ -9,6 +9,7 @@ use Lorisleiva\Actions\Concerns\AsJob; use Lorisleiva\Actions\Concerns\AsListener; use Lorisleiva\Actions\Concerns\AsObject; +use phpDocumentor\Reflection\Php\Class_; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\TypeResolver; use PhpParser\BuilderFactory; @@ -29,8 +30,7 @@ final class ActionInfo public bool $asJob; public bool $asListener; public bool $asCommand; - /** @var array $functionInfos */ - public array $functionInfos = []; + public Class_ $classInfo; const ALL_TRAITS = [ AsObject::class, @@ -41,44 +41,11 @@ final class ActionInfo AsFake::class, ]; - #[Pure] public static function create(): ActionInfo + public static function create(): ActionInfo { return new ActionInfo(); } - // public static function createFromReflectionClass(ReflectionClass $reflection): ?ActionInfo - // { - // // if ($intersection->count() <= 0) { - // // return null; - // // } - // // - // // return self::create() - // // ->setName($reflection->getName()) - // // ->setAsObject($intersection->contains(self::AS_OBJECT_NAME)) - // // ->setAsController($intersection->contains(self::AS_CONTROLLER_NAME)) - // // ->setAsListener($intersection->contains(self::AS_LISTENER_NAME)) - // // ->setAsJob($intersection->contains(self::AS_JOB_NAME)) - // // ->setAsCommand($intersection->contains(self::AS_COMMAND_NAME)) - // // ->setFunctionInfos([ - // // self::AS_OBJECT_NAME => self::resolveFunctionInfo($reflection), - // // self::AS_CONTROLLER_NAME => self::resolveFunctionInfo($reflection, 'asController'), - // // self::AS_LISTENER_NAME => self::resolveFunctionInfo($reflection, 'asListener'), - // // self::AS_JOB_NAME => self::resolveFunctionInfo($reflection, 'asJob'), - // // self::AS_COMMAND_NAME => self::resolveFunctionInfo($reflection, 'asCommand'), - // // ]); - // } - - - /** - * @param array $functionInfos - */ - public function setFunctionInfos(array $functionInfos): ActionInfo - { - $this->functionInfos = $functionInfos; - return $this; - } - - public function setName(string $name): ActionInfo { $this->name = $name; @@ -121,104 +88,12 @@ public function setAsCommand(bool $asCommand): ActionInfo return $this; } - public function setReturnTypehint(?string $returnTypehint): ActionInfo + public function setClassInfo(Class_ $classInfo): ActionInfo { - $this->returnTypehint = $returnTypehint ?? ''; - + $this->classInfo = $classInfo; return $this; } - public function addParameter(ParameterInfo $pi): ActionInfo - { - $this->parameters[] = $pi; - - return $this; - } - - public function getNamespace(): string - { - $name = explode('\\', $this->name); - array_pop($name); - - return implode('\\', $name); - } - - public function getClass(): string - { - $name = explode('\\', $this->name); - - return $name[array_key_last($name)]; - } - - public function getReturnType(): ?Type - { - return (new TypeResolver())->resolve($this->returnTypehint); - } - - protected static function getAllTraits(ReflectionClass $reflection): array - { - $traitNames = []; - $traits = $reflection->getTraits(); - foreach ($traits as $trait) { - array_push($traitNames, $trait->getName()); - - // Get all child traits - array_push($traitNames, ...ActionInfo::getAllTraits($trait)); - } - - return $traitNames; - } - - protected static function resolveFunctionInfo(ReflectionClass $reflection, string $decorator = null): ?FunctionInfo - { - if ($decorator) { - $namesToTry = [$decorator, 'handle']; - } else { - $namesToTry = ['handle']; - } - foreach ($namesToTry as $name) { - try { - $function = $reflection->getMethod($name); - $fi = FunctionInfo::create(); - $rt = $function->getReturnType()?->getName(); - if (!is_null($rt)) { - $fi->setReturnType($function->getReturnType()?->getName() ?? ""); - } - foreach ($function->getParameters() as $parameter) { - try { - $default = $parameter->getDefaultValue(); - $defaultSet = true; - $factory = new BuilderFactory(); - $node = $factory->param($parameter->getName())->setDefault($default)->getNode(); - $printer = new Standard(); - $name = ltrim($printer->prettyPrint([$node]), '$'); - } catch (\Throwable) { - $name = $parameter->getName(); - } - - $pi = ParameterInfo::create() - ->setName($name) - ->setNullable($parameter->allowsNull()) - ->setPosition($parameter->getPosition()) - ->setVariadic($parameter->isVariadic()); - $temp = $parameter->getName(); - $defaultSet = false; - if ($parameter->hasType()) { - $pi->setTypehint($parameter->getType()?->getName()); - } - if ($parameter->isOptional()) { - $pi->setDefault((string) $parameter->getDefaultValue()); - } - $fi->addParameter($pi); - } - - return $fi; - } catch (\Throwable) { - - } - } - return null; - } /** * @return \Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\DocBlockGeneratorInterface[] @@ -234,8 +109,6 @@ public function getGenerators(): array ); } - public function getFunctionInfosByContext(string $ctx): ?FunctionInfo - { - return $this->functionInfos[$ctx] ?? null; - } + + } From c1ab4ed660b1199fc3a03d39e6fe68e0a5afea5b Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 16:59:10 +0100 Subject: [PATCH 12/45] Unneeded classes removed --- src/Service/FunctionInfo.php | 47 ------------------ src/Service/ParameterInfo.php | 93 ----------------------------------- 2 files changed, 140 deletions(-) delete mode 100644 src/Service/FunctionInfo.php delete mode 100644 src/Service/ParameterInfo.php diff --git a/src/Service/FunctionInfo.php b/src/Service/FunctionInfo.php deleted file mode 100644 index 4f2faf4..0000000 --- a/src/Service/FunctionInfo.php +++ /dev/null @@ -1,47 +0,0 @@ -returnType = null; - } - - public function setReturnType(string $returnType): FunctionInfo - { - $this->returnType = (new TypeResolver())->resolve($returnType); - return $this; - } - - public function setParameterInfos(array $parameterInfos): FunctionInfo - { - $this->parameterInfos = $parameterInfos; - return $this; - } - - public function addParameter(ParameterInfo $param): FunctionInfo - { - array_push($this->parameterInfos, $param); - return $this; - } - - - - -} \ No newline at end of file diff --git a/src/Service/ParameterInfo.php b/src/Service/ParameterInfo.php deleted file mode 100644 index 5bbc3c8..0000000 --- a/src/Service/ParameterInfo.php +++ /dev/null @@ -1,93 +0,0 @@ -name = $name; - - return $this; - } - - public function setTypehint(string $typehint): ParameterInfo - { - $this->typehint = $typehint; - - return $this; - } - - public function setNullable(bool $nullable): ParameterInfo - { - $this->nullable = $nullable; - - return $this; - } - - public function setDefault(string $default): ParameterInfo - { - $this->default = $default; - - return $this; - } - - public function setVariadic(bool $variadic): ParameterInfo - { - $this->variadic = $variadic; - - return $this; - } - - public function setPosition(int $position): ParameterInfo - { - $this->position = $position; - - return $this; - } - - public function isOptional(): bool - { - return isset($this->default) && $this->default !== ''; - } - - public function getParameter(): Param - { - $type = (new TypeResolver())->resolve($this->typehint); - // TODO: Support default parameters - // For now I decided to not include them. It should (!) work to include them - // in the name like "name = 'default'" - return new Param($this->name, $type, $this->variadic); - } - - public function getArgumentArray(): array { - $type = null; - if(!is_null($this->typehint)){ - - - $type = (new TypeResolver())->resolve($this->typehint); - - } - // TODO: Support default parameters - // For now I decided to not include them. It should (!) work to include them - // in the name like "name = 'default'" - return['name' => $this->name, 'type' => $type]; - } -} From 3c5ebeaf938f71df47cd69dfffb9803b366bc8d5 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 17:07:47 +0100 Subject: [PATCH 13/45] Tests moved --- tests/ActionInfoFactoryTest.php | 14 ++++----- tests/ExampleTest.php | 31 ------------------- tests/stubs/{ => GeneralTests}/BaseAction.php | 2 +- tests/stubs/{ => GeneralTests}/NewAction.php | 2 +- tests/stubs/GeneralTests/NotAnAction.php | 8 +++++ tests/stubs/{ => GeneralTests}/TestAction.php | 2 +- tests/stubs/NotAnAction.php | 8 ----- 7 files changed, 18 insertions(+), 49 deletions(-) delete mode 100644 tests/ExampleTest.php rename tests/stubs/{ => GeneralTests}/BaseAction.php (63%) rename tests/stubs/{ => GeneralTests}/NewAction.php (65%) create mode 100644 tests/stubs/GeneralTests/NotAnAction.php rename tests/stubs/{ => GeneralTests}/TestAction.php (76%) delete mode 100644 tests/stubs/NotAnAction.php diff --git a/tests/ActionInfoFactoryTest.php b/tests/ActionInfoFactoryTest.php index 3f5c694..708ff5b 100644 --- a/tests/ActionInfoFactoryTest.php +++ b/tests/ActionInfoFactoryTest.php @@ -5,13 +5,13 @@ use phpDocumentor\Reflection\Php\Class_; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfoFactory; -use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\BaseAction; -use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\NewAction; -use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\NotAnAction; -use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\TestAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\GeneralTests\BaseAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\GeneralTests\NewAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\GeneralTests\NotAnAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\GeneralTests\TestAction; it('creates a correct trait lookup', function() { - $result = invade(new ActionInfoFactory())->loadFromPath(__DIR__ . '/stubs'); + $result = invade(new ActionInfoFactory())->loadFromPath(__DIR__ . '/stubs/GeneralTests'); expect($result)->toBeArray()->toMatchArray([ BaseAction::class => [AsObject::class], @@ -23,7 +23,7 @@ }); it('creates correct ActionInfos', function (){ - $result = ActionInfoFactory::create(__DIR__ . '/stubs'); + $result = ActionInfoFactory::create(__DIR__ . '/stubs/GeneralTests'); /** @var ActionInfo $ai */ $ai = collect($result)->filter(fn(ActionInfo $a) => $a->name == BaseAction::class)->first(); @@ -36,7 +36,7 @@ }); it('parses the classes correctly', function() { - $result = invade(new ActionInfoFactory())->loadPhpDocumentorReflectionClassMap(__DIR__ . '/stubs'); + $result = invade(new ActionInfoFactory())->loadPhpDocumentorReflectionClassMap(__DIR__ . '/stubs/GeneralTests'); $keys = collect($result)->keys()->toArray(); expect($keys)->toContain(NotAnAction::class, BaseAction::class, NotAnAction::class, TestAction::class); diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 0f0ba37..0000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,31 +0,0 @@ -app; - $app->make(TestAction::class); - $iterator = Finder::create()->in(__DIR__ . '/stubs/'); - $map = collect(ClassMapGenerator::createMap($iterator)); - foreach ($map as $item => $value) { - - $file = new LocalFile($value); - $projectFactory = \phpDocumentor\Reflection\Php\ProjectFactory::createInstance(); - - $project = $projectFactory->create('Test', [$file]); - - $_ = 3; - - - - - } - $m = $map; -}); - diff --git a/tests/stubs/BaseAction.php b/tests/stubs/GeneralTests/BaseAction.php similarity index 63% rename from tests/stubs/BaseAction.php rename to tests/stubs/GeneralTests/BaseAction.php index 88d4681..9ae179f 100644 --- a/tests/stubs/BaseAction.php +++ b/tests/stubs/GeneralTests/BaseAction.php @@ -1,6 +1,6 @@ Date: Sat, 19 Feb 2022 17:09:21 +0100 Subject: [PATCH 14/45] Tests moved again --- tests/ActionInfoFactoryTest.php | 14 +++++++------- tests/stubs/GeneralTests/NotAnAction.php | 8 -------- tests/stubs/{GeneralTests => }/NewAction.php | 2 +- tests/stubs/NotAnAction.php | 8 ++++++++ tests/stubs/{GeneralTests => }/TestAction.php | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 tests/stubs/GeneralTests/NotAnAction.php rename tests/stubs/{GeneralTests => }/NewAction.php (65%) create mode 100644 tests/stubs/NotAnAction.php rename tests/stubs/{GeneralTests => }/TestAction.php (76%) diff --git a/tests/ActionInfoFactoryTest.php b/tests/ActionInfoFactoryTest.php index 708ff5b..3f5c694 100644 --- a/tests/ActionInfoFactoryTest.php +++ b/tests/ActionInfoFactoryTest.php @@ -5,13 +5,13 @@ use phpDocumentor\Reflection\Php\Class_; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfoFactory; -use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\GeneralTests\BaseAction; -use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\GeneralTests\NewAction; -use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\GeneralTests\NotAnAction; -use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\GeneralTests\TestAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\BaseAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\NewAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\NotAnAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\TestAction; it('creates a correct trait lookup', function() { - $result = invade(new ActionInfoFactory())->loadFromPath(__DIR__ . '/stubs/GeneralTests'); + $result = invade(new ActionInfoFactory())->loadFromPath(__DIR__ . '/stubs'); expect($result)->toBeArray()->toMatchArray([ BaseAction::class => [AsObject::class], @@ -23,7 +23,7 @@ }); it('creates correct ActionInfos', function (){ - $result = ActionInfoFactory::create(__DIR__ . '/stubs/GeneralTests'); + $result = ActionInfoFactory::create(__DIR__ . '/stubs'); /** @var ActionInfo $ai */ $ai = collect($result)->filter(fn(ActionInfo $a) => $a->name == BaseAction::class)->first(); @@ -36,7 +36,7 @@ }); it('parses the classes correctly', function() { - $result = invade(new ActionInfoFactory())->loadPhpDocumentorReflectionClassMap(__DIR__ . '/stubs/GeneralTests'); + $result = invade(new ActionInfoFactory())->loadPhpDocumentorReflectionClassMap(__DIR__ . '/stubs'); $keys = collect($result)->keys()->toArray(); expect($keys)->toContain(NotAnAction::class, BaseAction::class, NotAnAction::class, TestAction::class); diff --git a/tests/stubs/GeneralTests/NotAnAction.php b/tests/stubs/GeneralTests/NotAnAction.php deleted file mode 100644 index b5c9d9d..0000000 --- a/tests/stubs/GeneralTests/NotAnAction.php +++ /dev/null @@ -1,8 +0,0 @@ - Date: Sat, 19 Feb 2022 20:17:26 +0100 Subject: [PATCH 15/45] Updated AsObjectGenerator --- composer.json | 3 +++ .../Generator/DocBlock/AsObjectGenerator.php | 15 ++++++------ .../DocBlock/DocBlockGeneratorBase.php | 21 ++++++++-------- tests/Generators/ObjectGeneratorTest.php | 24 +++++++++++++++++++ tests/Pest.php | 11 ++++++++- tests/stubs/BaseAction.php | 14 +++++++++++ tests/stubs/GeneralTests/BaseAction.php | 12 ---------- tests/stubs/UnionTypeAction.php | 15 ++++++++++++ tests/stubs/VoidAction.php | 13 ++++++++++ tests/stubs/VoidActionWithNoReturnType.php | 13 ++++++++++ 10 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 tests/Generators/ObjectGeneratorTest.php create mode 100644 tests/stubs/BaseAction.php delete mode 100644 tests/stubs/GeneralTests/BaseAction.php create mode 100644 tests/stubs/UnionTypeAction.php create mode 100644 tests/stubs/VoidAction.php create mode 100644 tests/stubs/VoidActionWithNoReturnType.php diff --git a/composer.json b/composer.json index 3beaa53..0306d2f 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,8 @@ "require": { "php": "^8.0", "illuminate/contracts": "^9.0", + "lorisleiva/laravel-actions": "^2.3", + "lorisleiva/lody": "^0.3.0", "phpdocumentor/reflection": "^5.1", "riimu/kit-pathjoin": "^1.2", "spatie/laravel-package-tools": "^1.4.3" @@ -28,6 +30,7 @@ "orchestra/testbench": "^7.0", "pestphp/pest": "^1.21", "phpunit/phpunit": "^9.5.10", + "spatie/invade": "^1.0", "spatie/laravel-ray": "^1.29", "vimeo/psalm": "^4.20" }, diff --git a/src/Service/Generator/DocBlock/AsObjectGenerator.php b/src/Service/Generator/DocBlock/AsObjectGenerator.php index 86b0146..7f58eb0 100644 --- a/src/Service/Generator/DocBlock/AsObjectGenerator.php +++ b/src/Service/Generator/DocBlock/AsObjectGenerator.php @@ -3,15 +3,16 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock; +use Lorisleiva\Actions\Concerns\AsObject; use phpDocumentor\Reflection\DocBlock\Tags\Method; use phpDocumentor\Reflection\DocBlock\Tags\Param; +use phpDocumentor\Reflection\Php\Argument; use phpDocumentor\Reflection\TypeResolver; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; -use Wulfheart\LaravelActionsIdeHelper\Service\ParameterInfo; class AsObjectGenerator extends DocBlockGeneratorBase implements DocBlockGeneratorInterface { - protected string $context = ActionInfo::AS_OBJECT_NAME; + protected string $context = AsObject::class; /** * @param \Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo $info @@ -19,11 +20,11 @@ class AsObjectGenerator extends DocBlockGeneratorBase implements DocBlockGenerat */ public function generate(ActionInfo $info): array { - $functionInfo = $info->getFunctionInfosByContext($this->context); - $params = array_map(function (ParameterInfo $parameterInfo) { - return $parameterInfo->getArgumentArray(); - }, $functionInfo?->parameterInfos ?? []); + /** @var Method $method */ + $method = collect($info->classInfo->getMethods()) + ->filter(fn(\phpDocumentor\Reflection\Php\Method $m) => $m->getName() == 'handle') + ->firstOrFail(); - return [new Method('run', $params, $functionInfo?->returnType, true)]; + return [new Method('run', $this->convertArguments($method->getArguments()), $method->getReturnType(), true)]; } } diff --git a/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php b/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php index d11563c..12766b0 100644 --- a/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php +++ b/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php @@ -4,29 +4,28 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock; use JetBrains\PhpStorm\Pure; +use phpDocumentor\Reflection\Php\Argument; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\TypeResolver; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; class DocBlockGeneratorBase implements DocBlockGeneratorInterface { - #[Pure] public static function create(): static + public static function create(): static { return new static(); } - protected function resolveType(string $type): Type - { - return (new TypeResolver())->resolve($type); - } - - protected function resolveAsUnionType(string ...$types): Type - { - return (new TypeResolver())->resolve(implode('|', $types)); - } - public function generate(ActionInfo $info): array { return []; } + + /** + * @param array $arguments + * @phpstan-return array> + */ + protected function convertArguments(array $arguments): array { + return collect($arguments)->transform(fn(Argument $arg) => ['name' => $arg->getName(),'type' => $arg->getType()])->toArray(); + } } \ No newline at end of file diff --git a/tests/Generators/ObjectGeneratorTest.php b/tests/Generators/ObjectGeneratorTest.php new file mode 100644 index 0000000..633601a --- /dev/null +++ b/tests/Generators/ObjectGeneratorTest.php @@ -0,0 +1,24 @@ +generate($ai))->first(); + expect($docblock)->toBeInstanceOf(Method::class); + + expect($docblock->render())->toEqual($docblockExpectation); + +})->with([ + "BaseAction" => [BaseAction::class, '@method static string run()'], + "UnionTypeAction" => [UnionTypeAction::class, '@method static int|string run(string $string, float|int $number)'], + "VoidAction" => [VoidAction::class, '@method static void run(int $i)'], + "VoidActionWithNoReturnType" => [VoidActionWithNoReturnType::class, '@method static mixed run()'], +]); \ No newline at end of file diff --git a/tests/Pest.php b/tests/Pest.php index 39e84b0..babd93e 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,3 +1,12 @@ in(__DIR__); \ No newline at end of file +use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; +use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfoFactory; + +uses(\Wulfheart\LaravelActionsIdeHelper\Tests\TestCase::class)->in(__DIR__); + +/** @param class-string $class */ +function getActionInfo(string $class): ActionInfo { + $actionInfos = collect(ActionInfoFactory::create(__DIR__ . '/stubs')); + return $actionInfos->filter(fn(ActionInfo $ai) => $ai->name == $class)->firstOrFail(); +} \ No newline at end of file diff --git a/tests/stubs/BaseAction.php b/tests/stubs/BaseAction.php new file mode 100644 index 0000000..bf3732a --- /dev/null +++ b/tests/stubs/BaseAction.php @@ -0,0 +1,14 @@ + Date: Sat, 19 Feb 2022 20:18:42 +0100 Subject: [PATCH 16/45] Updated AsControllerGenerator [do nothing] --- src/Service/Generator/DocBlock/AsControllerGenerator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service/Generator/DocBlock/AsControllerGenerator.php b/src/Service/Generator/DocBlock/AsControllerGenerator.php index 3d37338..2c78bbc 100644 --- a/src/Service/Generator/DocBlock/AsControllerGenerator.php +++ b/src/Service/Generator/DocBlock/AsControllerGenerator.php @@ -3,11 +3,12 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock; +use Lorisleiva\Actions\Concerns\AsController; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; class AsControllerGenerator extends DocBlockGeneratorBase implements DocBlockGeneratorInterface { - protected string $context = ActionInfo::AS_CONTROLLER_NAME; + protected string $context = AsController::class; /** * @inheritDoc */ From d34d15d60e15d97a05a6f07b42bb67c2939c3364 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 20:39:48 +0100 Subject: [PATCH 17/45] DocBlockGenerator base Find Method Helper --- .../DocBlock/DocBlockGeneratorBase.php | 15 ++++++++-- tests/Generators/BaseGeneratorTest.php | 28 +++++++++++++++++++ tests/stubs/EmptyAction.php | 11 ++++++++ tests/stubs/Jobs/WithDecoratorAction.php | 15 ++++++++++ tests/stubs/Jobs/WithoutDecoratorAction.php | 13 +++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 tests/Generators/BaseGeneratorTest.php create mode 100644 tests/stubs/EmptyAction.php create mode 100644 tests/stubs/Jobs/WithDecoratorAction.php create mode 100644 tests/stubs/Jobs/WithoutDecoratorAction.php diff --git a/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php b/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php index 12766b0..b3d8dd2 100644 --- a/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php +++ b/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php @@ -3,10 +3,8 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock; -use JetBrains\PhpStorm\Pure; use phpDocumentor\Reflection\Php\Argument; use phpDocumentor\Reflection\Type; -use phpDocumentor\Reflection\TypeResolver; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; class DocBlockGeneratorBase implements DocBlockGeneratorInterface @@ -22,10 +20,23 @@ public function generate(ActionInfo $info): array } /** + * Needed because otherwise a docblock method is not able to get parsed * @param array $arguments * @phpstan-return array> */ protected function convertArguments(array $arguments): array { return collect($arguments)->transform(fn(Argument $arg) => ['name' => $arg->getName(),'type' => $arg->getType()])->toArray(); } + + protected function findMethod(ActionInfo $info, string ...$methods): ?\phpDocumentor\Reflection\Php\Method { + foreach ($methods as $method){ + $m = collect($info->classInfo->getMethods()) + ->filter(fn(\phpDocumentor\Reflection\Php\Method $m) => $m->getName() == $method) + ->first(); + if(!empty($m)){ + return $m; + } + } + return null; + } } \ No newline at end of file diff --git a/tests/Generators/BaseGeneratorTest.php b/tests/Generators/BaseGeneratorTest.php new file mode 100644 index 0000000..a333470 --- /dev/null +++ b/tests/Generators/BaseGeneratorTest.php @@ -0,0 +1,28 @@ +findMethod($ai, 'handle'); + expect($method)->toBeNull(); + + +}); + +it('can find a method in the correct precedence', function () { + $ai = $ai = getActionInfo(WithDecoratorAction::class); + /** @var \phpDocumentor\Reflection\Php\Method $method */ + $method = invade(new DocBlockGeneratorBase())->findMethod($ai, 'asJob', 'handle'); + expect($method->getName())->toBe('asJob'); +}); + +it('can find a method in the correct precedence even when one is not present', function () { + $ai = $ai = getActionInfo(WithoutDecoratorAction::class); + /** @var \phpDocumentor\Reflection\Php\Method $method */ + $method = invade(new DocBlockGeneratorBase())->findMethod($ai, 'asJob', 'handle'); + expect($method->getName())->toBe('handle'); +}); \ No newline at end of file diff --git a/tests/stubs/EmptyAction.php b/tests/stubs/EmptyAction.php new file mode 100644 index 0000000..8edab01 --- /dev/null +++ b/tests/stubs/EmptyAction.php @@ -0,0 +1,11 @@ + Date: Sat, 19 Feb 2022 20:41:20 +0100 Subject: [PATCH 18/45] Refactored AsObject to FindMethod helper --- src/Service/Generator/DocBlock/AsObjectGenerator.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Service/Generator/DocBlock/AsObjectGenerator.php b/src/Service/Generator/DocBlock/AsObjectGenerator.php index 7f58eb0..e31a4ef 100644 --- a/src/Service/Generator/DocBlock/AsObjectGenerator.php +++ b/src/Service/Generator/DocBlock/AsObjectGenerator.php @@ -21,10 +21,7 @@ class AsObjectGenerator extends DocBlockGeneratorBase implements DocBlockGenerat public function generate(ActionInfo $info): array { /** @var Method $method */ - $method = collect($info->classInfo->getMethods()) - ->filter(fn(\phpDocumentor\Reflection\Php\Method $m) => $m->getName() == 'handle') - ->firstOrFail(); - - return [new Method('run', $this->convertArguments($method->getArguments()), $method->getReturnType(), true)]; + $method = $this->findMethod($info, 'handle'); + return $method == null ? [] : [new Method('run', $this->convertArguments($method->getArguments()), $method->getReturnType(), true)]; } } From a52ebbf87f68c138cc23b38e8a421de4ced05e43 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 21:04:05 +0100 Subject: [PATCH 19/45] AsJobGenerator tested --- .../Generator/DocBlock/AsJobGenerator.php | 45 +++++++++++++------ .../DocBlock/DocBlockGeneratorBase.php | 11 +++++ tests/Generators/JobGeneratorTest.php | 34 ++++++++++++++ 3 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 tests/Generators/JobGeneratorTest.php diff --git a/src/Service/Generator/DocBlock/AsJobGenerator.php b/src/Service/Generator/DocBlock/AsJobGenerator.php index bc15ef1..485dd61 100644 --- a/src/Service/Generator/DocBlock/AsJobGenerator.php +++ b/src/Service/Generator/DocBlock/AsJobGenerator.php @@ -3,33 +3,52 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock; +use Illuminate\Foundation\Bus\PendingDispatch; +use Illuminate\Support\Fluent; +use Lorisleiva\Actions\Concerns\AsJob; +use Lorisleiva\Actions\Decorators\JobDecorator; +use Lorisleiva\Actions\Decorators\UniqueJobDecorator; use phpDocumentor\Reflection\DocBlock\Tags\Method; use phpDocumentor\Reflection\DocBlock\Tags\Param; +use phpDocumentor\Reflection\Types\Boolean; +use PhpParser\Node\UnionType; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; -use Wulfheart\LaravelActionsIdeHelper\Service\ParameterInfo; class AsJobGenerator extends DocBlockGeneratorBase implements DocBlockGeneratorInterface { - protected string $context = ActionInfo::AS_JOB_NAME; + protected string $context = AsJob::class; + /** * @inheritDoc */ public function generate(ActionInfo $info): array { - $params = array_map(function (ParameterInfo $parameterInfo) { - return $parameterInfo->getArgumentArray(); - }, $info->getFunctionInfosByContext($this->context)?->parameterInfos ?? []); + + $method = $this->findMethod($info, 'asJob', 'handle'); + + if ($method == null) { + return []; + } + + $args = $this->convertArguments($method->getArguments()); return [ - new Method('makeJob', $params, $this->resolveAsUnionType('\Lorisleiva\Actions\Decorators\JobDecorator', '\Lorisleiva\Actions\Decorators\UniqueJobDecorator'), true), - new Method('makeUniqueJob', $params, $this->resolveType('\Lorisleiva\Actions\Decorators\UniqueJobDecorator'), true), - new Method('dispatch', $params, $this->resolveType('\Illuminate\Foundation\Bus\PendingDispatch'), true), - new Method('dispatchIf', collect($params)->prepend(ParameterInfo::create()->setName('boolean')->setTypehint('bool')->getArgumentArray())->toArray(), $this->resolveAsUnionType('\Illuminate\Foundation\Bus\PendingDispatch', '\Illuminate\Support\Fluent'), true), - new Method('dispatchUnless', collect($params)->prepend(ParameterInfo::create()->setName('boolean')->setTypehint('bool')->getArgumentArray())->toArray(), $this->resolveAsUnionType('\Illuminate\Foundation\Bus\PendingDispatch', '\Illuminate\Support\Fluent'), true), - new Method('dispatchSync', $params, null, true), - new Method('dispatchNow', $params, null, true), - new Method('dispatchAfterResponse', $params, null, true), + new Method('makeJob', $args, $this->resolveAsUnionType(JobDecorator::class, UniqueJobDecorator::class), + true), + new Method('makeUniqueJob', $args, $this->resolveType(UniqueJobDecorator::class), true), + new Method('dispatch', $args, $this->resolveType(PendingDispatch::class), true), + new Method('dispatchIf', + collect($args)->prepend(['name' => 'boolean', 'type' => Boolean::class])->toArray(), + $this->resolveAsUnionType(PendingDispatch::class, Fluent::class), + true), + new Method('dispatchUnless', + collect($args)->prepend(['name' => 'boolean', 'type' => Boolean::class])->toArray(), + $this->resolveAsUnionType(PendingDispatch::class, Fluent::class), + true), + new Method('dispatchSync', $args, null, true), + new Method('dispatchNow', $args, null, true), + new Method('dispatchAfterResponse', $args, null, true), ]; } diff --git a/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php b/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php index b3d8dd2..a2dfcfe 100644 --- a/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php +++ b/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php @@ -5,6 +5,7 @@ use phpDocumentor\Reflection\Php\Argument; use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; class DocBlockGeneratorBase implements DocBlockGeneratorInterface @@ -14,6 +15,16 @@ public static function create(): static return new static(); } + protected function resolveType(string $type): Type + { + return (new TypeResolver())->resolve($type); + } + + protected function resolveAsUnionType(string ...$types): Type + { + return (new TypeResolver())->resolve(implode('|', $types)); + } + public function generate(ActionInfo $info): array { return []; diff --git a/tests/Generators/JobGeneratorTest.php b/tests/Generators/JobGeneratorTest.php new file mode 100644 index 0000000..a397dbb --- /dev/null +++ b/tests/Generators/JobGeneratorTest.php @@ -0,0 +1,34 @@ +generate($ai)); + + expect($docblock->count())->toEqual(8); + + $all = $docblock->map(fn($item) => $item->render())->implode(PHP_EOL); + foreach ($expectations as $expectation) { + expect($all)->toContain($expectation); + } +})->with([ + // Just one Method as an example + 'with decorator' => [ + WithDecoratorAction::class, [ + '@method static \\'.JobDecorator::class.'|\\'.UniqueJobDecorator::class.' makeJob(int $i)', + ], + ], + 'without decorator' => [ + WithoutDecoratorAction::class, [ + '@method static \\'.JobDecorator::class.'|\\'.UniqueJobDecorator::class.' makeJob()', + ], + ], + +]); \ No newline at end of file From 54402f11582e0fa42cc29b0a6bb069815d2ac9f3 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 21:05:42 +0100 Subject: [PATCH 20/45] AsListenerGenerator and AsCommandGenerator do not need any action --- src/Service/Generator/DocBlock/AsCommandGenerator.php | 3 ++- src/Service/Generator/DocBlock/AsListenerGenerator.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Service/Generator/DocBlock/AsCommandGenerator.php b/src/Service/Generator/DocBlock/AsCommandGenerator.php index 6591ed9..cd3d482 100644 --- a/src/Service/Generator/DocBlock/AsCommandGenerator.php +++ b/src/Service/Generator/DocBlock/AsCommandGenerator.php @@ -3,11 +3,12 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock; +use Lorisleiva\Actions\Concerns\AsCommand; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; class AsCommandGenerator extends DocBlockGeneratorBase implements DocBlockGeneratorInterface { - protected string $context = ActionInfo::AS_COMMAND_NAME; + protected string $context = AsCommand::class; /** * @inheritDoc */ diff --git a/src/Service/Generator/DocBlock/AsListenerGenerator.php b/src/Service/Generator/DocBlock/AsListenerGenerator.php index 3ea068b..a98f4af 100644 --- a/src/Service/Generator/DocBlock/AsListenerGenerator.php +++ b/src/Service/Generator/DocBlock/AsListenerGenerator.php @@ -3,11 +3,12 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock; +use Lorisleiva\Actions\Concerns\AsListener; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; class AsListenerGenerator extends DocBlockGeneratorBase implements DocBlockGeneratorInterface { - protected string $context = ActionInfo::AS_LISTENER_NAME; + protected string $context = AsListener::class; /** * @inheritDoc */ From fa8d1f58392fbb5f8964d4ae8a0eb56fafb00210 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 21:29:59 +0100 Subject: [PATCH 21/45] Namespace added --- src/Service/ActionInfo.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Service/ActionInfo.php b/src/Service/ActionInfo.php index cf2322b..28c55ff 100644 --- a/src/Service/ActionInfo.php +++ b/src/Service/ActionInfo.php @@ -2,6 +2,7 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service; +use Illuminate\Support\Str; use JetBrains\PhpStorm\Pure; use Lorisleiva\Actions\Concerns\AsCommand; use Lorisleiva\Actions\Concerns\AsController; @@ -10,12 +11,6 @@ use Lorisleiva\Actions\Concerns\AsListener; use Lorisleiva\Actions\Concerns\AsObject; use phpDocumentor\Reflection\Php\Class_; -use phpDocumentor\Reflection\Type; -use phpDocumentor\Reflection\TypeResolver; -use PhpParser\BuilderFactory; -use PhpParser\PrettyPrinter\Standard; -use PhpParser\PrettyPrinterAbstract; -use ReflectionClass; use Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\AsCommandGenerator; use Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\AsControllerGenerator; use Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\AsJobGenerator; @@ -25,6 +20,7 @@ final class ActionInfo { public string $name; + public string $namespace; public bool $asObject; public bool $asController; public bool $asJob; @@ -48,7 +44,8 @@ public static function create(): ActionInfo public function setName(string $name): ActionInfo { - $this->name = $name; + $this->namespace = $name; + $this->name = class_basename($name); return $this; } From 4c1695368e7e3693a891f0c0ddb71c0f7738758e Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 21:30:35 +0100 Subject: [PATCH 22/45] BuildIdeHelper.php reformatted --- src/Service/BuildIdeHelper.php | 37 +++++++++++++--------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/Service/BuildIdeHelper.php b/src/Service/BuildIdeHelper.php index 1c4aaf9..36cecef 100644 --- a/src/Service/BuildIdeHelper.php +++ b/src/Service/BuildIdeHelper.php @@ -3,41 +3,30 @@ namespace Wulfheart\LaravelActionsIdeHelper\Service; -use JetBrains\PhpStorm\Pure; -use Lorisleiva\Actions\Concerns\AsController; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Serializer; use phpDocumentor\Reflection\DocBlock\Tag; -use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\TypeResolver; -use PhpParser\Builder\Method; use PhpParser\Builder\Trait_; use PhpParser\BuilderFactory; use PhpParser\Comment\Doc; -use PhpParser\Node\Param; -use PhpParser\Node\Stmt\Expression; use PhpParser\PrettyPrinter\Standard; class BuildIdeHelper { - #[Pure] - public static function create(): BuildIdeHelper - { - return new BuildIdeHelper(); - } + public static function create(): BuildIdeHelper + { + return new BuildIdeHelper(); + } /** * @param \Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo[] $actionInfos */ - - public function build(array $actionInfos): string { - - $groups = collect($actionInfos)->groupBy(function (ActionInfo $item) { - return $item->getNamespace(); + return $item->namespace; })->toArray(); $nodes = []; @@ -48,10 +37,10 @@ public function build(array $actionInfos): string $factory = new BuilderFactory(); foreach ($groups as $namespace => $items) { $ns = $factory->namespace($namespace); - foreach ($items as $item){ - $ns->addStmt($factory->class($item->getClass())->setDocComment(new Doc($this->generateDocBlocks($item)))); + foreach ($items as $item) { + $ns->addStmt($factory->class($item->classInfo->getName())->setDocComment(new Doc($this->generateDocBlocks($item)))); } - $nodes[] = $ns->getNode(); + $nodes[] = $ns->getNode(); } $nodes[] = $this->getTraitIdeHelpers($factory); $printer = new Standard(); @@ -70,7 +59,8 @@ protected function generateDocBlocks(ActionInfo $info): string return $this->serializeDocBlocks(...$tags); } - protected function serializeDocBlocks(Tag ...$tags): string { + protected function serializeDocBlocks(Tag ...$tags): string + { $db = new DocBlock('', null, $tags); $serializer = new Serializer(); @@ -87,7 +77,8 @@ protected function resolveAsUnionType(string ...$types): Type return (new TypeResolver())->resolve(implode('|', $types)); } - protected function getTraitIdeHelpers(BuilderFactory $factory): \PhpParser\Node{ + protected function getTraitIdeHelpers(BuilderFactory $factory): \PhpParser\Node + { return $factory->namespace("Lorisleiva\Actions\Concerns") ->addStmt( (new Trait_("AsController"))->setDocComment( @@ -112,8 +103,8 @@ protected function getTraitIdeHelpers(BuilderFactory $factory): \PhpParser\Node{ ->addStmt( (new Trait_("AsCommand"))->setDocComment( $this->serializeDocBlocks( - new DocBlock\Tags\Method('asCommand',arguments: [ - ['name' => 'command', 'type' => $this->resolveType("\Illuminate\Console\Command")] + new DocBlock\Tags\Method('asCommand', arguments: [ + ['name' => 'command', 'type' => $this->resolveType("\Illuminate\Console\Command")], ], returnType: $this->resolveType('void')) ) ) From 6e2b9d52094622c06a7d5bf073277fada2a0191a Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 21:45:20 +0100 Subject: [PATCH 23/45] ActionInfo store FQSEN --- src/Service/ActionInfo.php | 4 +++- tests/ActionInfoFactoryTest.php | 5 +---- tests/Pest.php | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Service/ActionInfo.php b/src/Service/ActionInfo.php index 28c55ff..eadaf11 100644 --- a/src/Service/ActionInfo.php +++ b/src/Service/ActionInfo.php @@ -21,6 +21,7 @@ final class ActionInfo { public string $name; public string $namespace; + public string $fqsen; public bool $asObject; public bool $asController; public bool $asJob; @@ -44,8 +45,9 @@ public static function create(): ActionInfo public function setName(string $name): ActionInfo { - $this->namespace = $name; + $this->fqsen = $name; $this->name = class_basename($name); + $this->namespace = Str::of($name)->beforeLast('/' . $this->name); return $this; } diff --git a/tests/ActionInfoFactoryTest.php b/tests/ActionInfoFactoryTest.php index 3f5c694..ad708ed 100644 --- a/tests/ActionInfoFactoryTest.php +++ b/tests/ActionInfoFactoryTest.php @@ -23,10 +23,7 @@ }); it('creates correct ActionInfos', function (){ - $result = ActionInfoFactory::create(__DIR__ . '/stubs'); - - /** @var ActionInfo $ai */ - $ai = collect($result)->filter(fn(ActionInfo $a) => $a->name == BaseAction::class)->first(); + $ai = getActionInfo(BaseAction::class); expect($ai->asObject)->toBeTrue(); expect($ai->asCommand)->toBeFalse(); diff --git a/tests/Pest.php b/tests/Pest.php index babd93e..d4f11bb 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -8,5 +8,5 @@ /** @param class-string $class */ function getActionInfo(string $class): ActionInfo { $actionInfos = collect(ActionInfoFactory::create(__DIR__ . '/stubs')); - return $actionInfos->filter(fn(ActionInfo $ai) => $ai->name == $class)->firstOrFail(); + return $actionInfos->filter(fn(ActionInfo $ai) => $ai->fqsen == $class)->firstOrFail(); } \ No newline at end of file From 5333b9db5fc0f638b32f86c552de18debc4c90fe Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 21:51:29 +0100 Subject: [PATCH 24/45] Snapshot testing added --- composer.json | 1 + .../LaravelActionsIdeHelperCommand.php | 40 ++--- src/Service/ActionInfo.php | 2 +- tests/IdeHelperOutputTest.php | 13 ++ ...r_correctly_an_action_with_AsObject__1.txt | 146 ++++++++++++++++++ tests/stubs/Object/ObjectAction.php | 15 ++ 6 files changed, 187 insertions(+), 30 deletions(-) create mode 100644 tests/IdeHelperOutputTest.php create mode 100644 tests/__snapshots__/IdeHelperOutputTest__it_can_render_correctly_an_action_with_AsObject__1.txt create mode 100644 tests/stubs/Object/ObjectAction.php diff --git a/composer.json b/composer.json index 0306d2f..36a7f83 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "phpunit/phpunit": "^9.5.10", "spatie/invade": "^1.0", "spatie/laravel-ray": "^1.29", + "spatie/pest-plugin-snapshots": "^1.1", "vimeo/psalm": "^4.20" }, "autoload": { diff --git a/src/Commands/LaravelActionsIdeHelperCommand.php b/src/Commands/LaravelActionsIdeHelperCommand.php index 6be6a5f..754448d 100644 --- a/src/Commands/LaravelActionsIdeHelperCommand.php +++ b/src/Commands/LaravelActionsIdeHelperCommand.php @@ -3,6 +3,7 @@ namespace Wulfheart\LaravelActionsIdeHelper\Commands; use Illuminate\Console\Command; +use Illuminate\Support\Str; use phpDocumentor\Reflection\File\LocalFile; use PhpParser\BuilderFactory; use PhpParser\PrettyPrinter\Standard; @@ -11,6 +12,7 @@ use Symfony\Component\Finder\Finder; use Wulfheart\LaravelActionsIdeHelper\ClassMapGenerator; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; +use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfoFactory; use Wulfheart\LaravelActionsIdeHelper\Service\BuildIdeHelper; use Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\AsObjectGenerator; @@ -22,35 +24,15 @@ class LaravelActionsIdeHelperCommand extends Command public function handle() { - $this->traverseFiles(); - $this->comment('IDE Helpers generated for Laravel Actions at ./_ide_helper_actions.php'); - } - protected function traverseFiles() - { - $finder = Finder::create() - ->files() - ->in(app_path()) - ->name('*.php'); - - $map = collect(ClassMapGenerator::createMap($finder->getIterator())); - $classes = $map->keys(); - - $infos = []; - - foreach ($classes as $class) { - // Fail gracefully if there is any problem with a reflection class - try { - $reflection = new ReflectionClass($class); - $ai = ActionInfo::createFromReflectionClass($reflection); - if (! is_null($ai)) { - $infos[] = $ai; - } - } catch (\Throwable) { - } - } - - $result = BuildIdeHelper::create()->build($infos); - file_put_contents(Path::join([base_path(), '_ide_helper_actions.php']), $result); + $actionsPath = Path::join(app_path()); + + $outfile = Path::join(base_path(), '/_ide_helper_actions.php'); + + $actionInfos = ActionInfoFactory::create($actionsPath); + + $result = BuildIdeHelper::create()->build($actionInfos); + + $this->comment('IDE Helpers generated for Laravel Actions at ' . Str::of($outfile)); } } diff --git a/src/Service/ActionInfo.php b/src/Service/ActionInfo.php index eadaf11..b7c80fd 100644 --- a/src/Service/ActionInfo.php +++ b/src/Service/ActionInfo.php @@ -47,7 +47,7 @@ public function setName(string $name): ActionInfo { $this->fqsen = $name; $this->name = class_basename($name); - $this->namespace = Str::of($name)->beforeLast('/' . $this->name); + $this->namespace = Str::of($name)->beforeLast('\\' . $this->name); return $this; } diff --git a/tests/IdeHelperOutputTest.php b/tests/IdeHelperOutputTest.php new file mode 100644 index 0000000..aabe5f1 --- /dev/null +++ b/tests/IdeHelperOutputTest.php @@ -0,0 +1,13 @@ +build($actionInfos); + + assertMatchesSnapshot($result); +}); \ No newline at end of file diff --git a/tests/__snapshots__/IdeHelperOutputTest__it_can_render_correctly_an_action_with_AsObject__1.txt b/tests/__snapshots__/IdeHelperOutputTest__it_can_render_correctly_an_action_with_AsObject__1.txt new file mode 100644 index 0000000..08c0233 --- /dev/null +++ b/tests/__snapshots__/IdeHelperOutputTest__it_can_render_correctly_an_action_with_AsObject__1.txt @@ -0,0 +1,146 @@ + Date: Sat, 19 Feb 2022 21:52:56 +0100 Subject: [PATCH 25/45] Command updated --- src/Commands/LaravelActionsIdeHelperCommand.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Commands/LaravelActionsIdeHelperCommand.php b/src/Commands/LaravelActionsIdeHelperCommand.php index 754448d..0b72993 100644 --- a/src/Commands/LaravelActionsIdeHelperCommand.php +++ b/src/Commands/LaravelActionsIdeHelperCommand.php @@ -25,7 +25,7 @@ class LaravelActionsIdeHelperCommand extends Command public function handle() { - $actionsPath = Path::join(app_path()); + $actionsPath = Path::join(app_path() . '/Actions'); $outfile = Path::join(base_path(), '/_ide_helper_actions.php'); @@ -33,6 +33,8 @@ public function handle() $result = BuildIdeHelper::create()->build($actionInfos); + file_put_contents($outfile, $result); + $this->comment('IDE Helpers generated for Laravel Actions at ' . Str::of($outfile)); } } From 07a5cf12272bb58b8f8af21606692a7e55f8e783 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 21:56:02 +0100 Subject: [PATCH 26/45] ClassMapGenerator.php removed --- src/ClassMapGenerator.php | 415 -------------------------------------- 1 file changed, 415 deletions(-) delete mode 100644 src/ClassMapGenerator.php diff --git a/src/ClassMapGenerator.php b/src/ClassMapGenerator.php deleted file mode 100644 index 0aa6241..0000000 --- a/src/ClassMapGenerator.php +++ /dev/null @@ -1,415 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/* - * This file is copied from the Symfony package to composer/composer and then copied again here. - * - * (c) Fabien Potencier - */ - -namespace Wulfheart\LaravelActionsIdeHelper; - -use Composer\Pcre\Preg; -use Symfony\Component\Finder\Finder; - -/** - * ClassMapGenerator - * - * @author Gyula Sallai - * @author Jordi Boggiano - */ -class ClassMapGenerator -{ - /** @var array */ - private static $typeConfig; - - /** @var non-empty-string */ - private static $restPattern; - - /** - * Generate a class map file - * - * @param \Traversable|array $dirs Directories or a single path to search in - * @param string $file The name of the class map file - * @return void - */ - public static function dump($dirs, $file) - { - $maps = []; - - foreach ($dirs as $dir) { - $maps = array_merge($maps, static::createMap($dir)); - } - - file_put_contents($file, sprintf(' - * @author Johannes M. Schmitt - */ - public static function isAbsolutePath($path) - { - return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':' || substr($path, 0, 2) === '\\\\'; - } - - /** - * Normalize a path. This replaces backslashes with slashes, removes ending - * slash and collapses redundant separators and up-level references. - * - * @param string $path Path to the file or directory - * @return string - * - * @see \Composer\Util\Filesystem - * @author Jordi Boggiano - * @author Johannes M. Schmitt - */ - public static function normalizePath($path) - { - $parts = []; - $path = strtr($path, '\\', '/'); - $prefix = ''; - $absolute = false; - - // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: - if (preg_match('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { - $prefix = $match[1]; - $path = substr($path, strlen($prefix)); - } - - if (substr($path, 0, 1) === '/') { - $absolute = true; - $path = substr($path, 1); - } - - $up = false; - foreach (explode('/', $path) as $chunk) { - if ('..' === $chunk && ($absolute || $up)) { - array_pop($parts); - $up = !(empty($parts) || '..' === end($parts)); - } elseif ('.' !== $chunk && '' !== $chunk) { - $parts[] = $chunk; - $up = '..' !== $chunk; - } - } - - return $prefix . ($absolute ? '/' : '') . implode('/', $parts); - } - - /** - * Iterate over all files in the given directory searching for classes - * - * @param \Traversable<\SplFileInfo>|string|array $path The path to search in or an iterator - * @param string $excluded Regex that matches file paths to be excluded from the classmap - * @param ?\Composer\IO\IOInterface $io IO object - * @param ?string $namespace Optional namespace prefix to filter by - * @param ?string $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules - * @param array $scannedFiles - * @return array A class map array - * @throws \RuntimeException When the path is neither an existing file nor directory - */ - public static function createMap($path, $excluded = null, $io = null, $namespace = null, $autoloadType = null, &$scannedFiles = []) - { - $basePath = $path; - if (is_string($path)) { - if (is_file($path)) { - $path = [new \SplFileInfo($path)]; - } elseif (is_dir($path) || strpos($path, '*') !== false) { - $path = Finder::create()->files()->followLinks()->name('/\.(php|inc|hh)$/')->in($path); - } else { - throw new \RuntimeException( - 'Could not scan for classes inside "' . $path . - '" which does not appear to be a file nor a folder' - ); - } - } elseif (null !== $autoloadType) { - throw new \RuntimeException('Path must be a string when specifying an autoload type'); - } - - $map = []; - $cwd = realpath(getcwd()); - - foreach ($path as $file) { - $filePath = $file->getPathname(); - if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), ['php', 'inc', 'hh'])) { - continue; - } - - if (!self::isAbsolutePath($filePath)) { - $filePath = $cwd . '/' . $filePath; - $filePath = self::normalizePath($filePath); - } else { - $filePath = Preg::replace('{[\\\\/]{2,}}', '/', $filePath); - } - - $realPath = realpath($filePath); - - // if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings - // in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already - if (isset($scannedFiles[$realPath])) { - continue; - } - - // check the realpath of the file against the excluded paths as the path might be a symlink and the excluded path is realpath'd so symlink are resolved - if ($excluded && Preg::isMatch($excluded, strtr($realPath, '\\', '/'))) { - continue; - } - // check non-realpath of file for directories symlink in project dir - if ($excluded && Preg::isMatch($excluded, strtr($filePath, '\\', '/'))) { - continue; - } - - $classes = self::findClasses($filePath); - if (null !== $autoloadType) { - $classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io); - - // if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later - if ($classes) { - $scannedFiles[$realPath] = true; - } - } else { - // classmap autoload rules always collect all classes so for these we definitely do not want to scan again - $scannedFiles[$realPath] = true; - } - - foreach ($classes as $class) { - // skip classes not within the given namespace prefix - if (null === $autoloadType && null !== $namespace && '' !== $namespace && 0 !== strpos($class, $namespace)) { - continue; - } - - if (!isset($map[$class])) { - $map[$class] = $filePath; - } elseif ($io && $map[$class] !== $filePath && !Preg::isMatch('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { - $io->writeError( - 'Warning: Ambiguous class resolution, "' . $class . '"' . - ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.' - ); - } - } - } - - return $map; - } - - /** - * Remove classes which could not have been loaded by namespace autoloaders - * - * @param array $classes found classes in given file - * @param string $filePath current file - * @param string $baseNamespace prefix of given autoload mapping - * @param string $namespaceType psr-0|psr-4 - * @param string $basePath root directory of given autoload mapping - * @param ?\Composer\IO\IOInterface $io IO object - * @return array valid classes - */ - private static function filterByNamespace($classes, $filePath, $baseNamespace, $namespaceType, $basePath, $io) - { - $validClasses = []; - $rejectedClasses = []; - - $realSubPath = substr($filePath, strlen($basePath) + 1); - $dotPosition = strrpos($realSubPath, '.'); - $realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition); - - foreach ($classes as $class) { - // silently skip if ns doesn't have common root - if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) { - continue; - } - // transform class name to file path and validate - if ('psr-0' === $namespaceType) { - $namespaceLength = strrpos($class, '\\'); - if (false !== $namespaceLength) { - $namespace = substr($class, 0, $namespaceLength + 1); - $className = substr($class, $namespaceLength + 1); - $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) - . str_replace('_', DIRECTORY_SEPARATOR, $className); - } else { - $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class); - } - } elseif ('psr-4' === $namespaceType) { - $subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class; - $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace); - } else { - throw new \RuntimeException("namespaceType must be psr-0 or psr-4, $namespaceType given"); - } - if ($subPath === $realSubPath) { - $validClasses[] = $class; - } else { - $rejectedClasses[] = $class; - } - } - // warn only if no valid classes, else silently skip invalid - if (empty($validClasses)) { - foreach ($rejectedClasses as $class) { - if ($io) { - $io->writeError("Class $class located in " . Preg::replace('{^' . preg_quote(getcwd()) . '}', '.', $filePath, 1) . " does not comply with $namespaceType autoloading standard. Skipping."); - } - } - - return []; - } - - return $validClasses; - } - - /** - * Extract the classes in the given file - * - * @param string $path The file to check - * @throws \RuntimeException - * @return array The found classes - */ - private static function findClasses($path) - { - $extraTypes = self::getExtraTypes(); - - // Use @ here instead of Silencer to actively suppress 'unhelpful' output - // @link https://github.com/composer/composer/pull/4886 - $contents = @php_strip_whitespace($path); - if (!$contents) { - if (!file_exists($path)) { - $message = 'File at "%s" does not exist, check your classmap definitions'; - } elseif (!is_readable($path)) { - $message = 'File at "%s" is not readable, check its permissions'; - } elseif ('' === trim((string) file_get_contents($path))) { - // The input file was really empty and thus contains no classes - return []; - } else { - $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; - } - $error = error_get_last(); - if (isset($error['message'])) { - $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; - } - throw new \RuntimeException(sprintf($message, $path)); - } - - // return early if there is no chance of matching anything in this file - Preg::matchAll('{\b(?:class|interface|trait' . $extraTypes . ')\s}i', $contents, $matches); - if (!$matches) { - return []; - } - - // strip heredocs/nowdocs - $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents); - // strip strings - $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); - // strip leading non-php code if needed - if (substr($contents, 0, 2) !== '(?:[^<]++|<(?!\?))*+<\?}s', '?>'); - if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface|trait' . $extraTypes . ') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) - | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] - ) - }ix', $contents, $matches); - - $classes = []; - $namespace = ''; - - for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { - if (!empty($matches['ns'][$i])) { - $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; - } else { - $name = $matches['name'][$i]; - // skip anon classes extending/implementing - if ($name === 'extends' || $name === 'implements') { - continue; - } - if ($name[0] === ':') { - // This is an XHP class, https://github.com/facebook/xhp - $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); - } elseif ($matches['type'][$i] === 'enum') { - // something like: - // enum Foo: int { HERP = '123'; } - // The regex above captures the colon, which isn't part of - // the class name. - // or: - // enum Foo:int { HERP = '123'; } - // The regex above captures the colon and type, which isn't part of - // the class name. - $colonPos = strrpos($name, ':'); - if (false !== $colonPos) { - $name = substr($name, 0, $colonPos); - } - } - $classes[] = ltrim($namespace . $name, '\\'); - } - } - - return $classes; - } - - /** - * @return string - */ - private static function getExtraTypes() - { - static $extraTypes = null; - - if (null === $extraTypes) { - $extraTypes = ''; - if (PHP_VERSION_ID >= 80100 || (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) { - $extraTypes .= '|enum'; - } - - self::setTypeConfig(array_merge(['class', 'interface', 'trait'], array_filter(explode('|', $extraTypes)))); - } - - return $extraTypes; - } - - /** - * @param string[] $types - * @return void - * - * @see \Composer\Autoload\PhpFileCleaner - * @author Jordi Boggiano - * @author Johannes M. Schmitt - */ - private static function setTypeConfig($types) - { - foreach ($types as $type) { - self::$typeConfig[$type[0]] = [ - 'name' => $type, - 'length' => strlen($type), - 'pattern' => '{.\b(?])' . $type . '\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais', - ]; - } - - self::$restPattern = '{[^?"\' Date: Sat, 19 Feb 2022 21:56:40 +0100 Subject: [PATCH 27/45] Test updated --- .../Generator/DocBlock/AsJobGenerator.php | 4 +- ...ender_the_output_so_it_matches_the___1.txt | 146 ++++++++++++++++++ 2 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 tests/__snapshots__/IdeHelperOutputTest__it_can_render_the_output_so_it_matches_the___1.txt diff --git a/src/Service/Generator/DocBlock/AsJobGenerator.php b/src/Service/Generator/DocBlock/AsJobGenerator.php index 485dd61..cc5e7dd 100644 --- a/src/Service/Generator/DocBlock/AsJobGenerator.php +++ b/src/Service/Generator/DocBlock/AsJobGenerator.php @@ -39,11 +39,11 @@ public function generate(ActionInfo $info): array new Method('makeUniqueJob', $args, $this->resolveType(UniqueJobDecorator::class), true), new Method('dispatch', $args, $this->resolveType(PendingDispatch::class), true), new Method('dispatchIf', - collect($args)->prepend(['name' => 'boolean', 'type' => Boolean::class])->toArray(), + collect($args)->prepend(['name' => 'boolean', 'type' => 'bool'])->toArray(), $this->resolveAsUnionType(PendingDispatch::class, Fluent::class), true), new Method('dispatchUnless', - collect($args)->prepend(['name' => 'boolean', 'type' => Boolean::class])->toArray(), + collect($args)->prepend(['name' => 'boolean', 'type' => 'bool'])->toArray(), $this->resolveAsUnionType(PendingDispatch::class, Fluent::class), true), new Method('dispatchSync', $args, null, true), diff --git a/tests/__snapshots__/IdeHelperOutputTest__it_can_render_the_output_so_it_matches_the___1.txt b/tests/__snapshots__/IdeHelperOutputTest__it_can_render_the_output_so_it_matches_the___1.txt new file mode 100644 index 0000000..b64c713 --- /dev/null +++ b/tests/__snapshots__/IdeHelperOutputTest__it_can_render_the_output_so_it_matches_the___1.txt @@ -0,0 +1,146 @@ + Date: Sat, 19 Feb 2022 22:13:56 +0100 Subject: [PATCH 28/45] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9860703..bc55ae5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Laravel Actions IDE Helper -This packages generates IDE helpers for [Laravel Actions v2](https://github.com/lorisleiva/laravel-actions). Currently under development 🚧. Feedback appreciated. Discussion at https://github.com/lorisleiva/laravel-actions/issues/117. +This packages generates IDE helpers for [Laravel Actions v2](https://github.com/lorisleiva/laravel-actions). Feedback appreciated. Discussion at https://github.com/lorisleiva/laravel-actions/issues/117. ## Installation @@ -11,4 +11,4 @@ composer require --dev wulfheart/laravel-actions-ide-helper ## Usage ``` php artisan ide-helper:actions -``` \ No newline at end of file +``` From d23914970153dc00871efadcfe46929887742362 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sat, 19 Feb 2022 22:53:34 +0100 Subject: [PATCH 29/45] Failing Intersection Type --- tests/IdeHelperOutputTest.php | 8 ++++++++ tests/IntersectionTypes/IntersectionAction.php | 15 +++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/IntersectionTypes/IntersectionAction.php diff --git a/tests/IdeHelperOutputTest.php b/tests/IdeHelperOutputTest.php index aabe5f1..7dc5596 100644 --- a/tests/IdeHelperOutputTest.php +++ b/tests/IdeHelperOutputTest.php @@ -9,5 +9,13 @@ $result = BuildIdeHelper::create()->build($actionInfos); + assertMatchesSnapshot($result); +}); + +it('can render intersection types ', function() { + $actionInfos = ActionInfoFactory::create(__DIR__ . '/stubs/IntersectionTypes'); + + $result = BuildIdeHelper::create()->build($actionInfos); + assertMatchesSnapshot($result); }); \ No newline at end of file diff --git a/tests/IntersectionTypes/IntersectionAction.php b/tests/IntersectionTypes/IntersectionAction.php new file mode 100644 index 0000000..016be9a --- /dev/null +++ b/tests/IntersectionTypes/IntersectionAction.php @@ -0,0 +1,15 @@ + Date: Sun, 20 Feb 2022 14:08:00 +0100 Subject: [PATCH 30/45] Still failing --- tests/IntersectionTypes/IntersectionAction.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/IntersectionTypes/IntersectionAction.php b/tests/IntersectionTypes/IntersectionAction.php index 016be9a..4088d55 100644 --- a/tests/IntersectionTypes/IntersectionAction.php +++ b/tests/IntersectionTypes/IntersectionAction.php @@ -3,12 +3,13 @@ namespace Wulfheart\LaravelActionsIdeHelper\Tests\IntersectionTypes; use Lorisleiva\Actions\Concerns\AsObject; +use PhpParser\Node\Expr\Array_; class IntersectionAction { use AsObject; - public function handle(\Permissionable&\Model $permissionable): ?\Permission{ + public function handle(\stdClass&Array_ $permissionable): ?\stdClass{ return null; } From 24684db41c4d36c5cd43daa0f6a99d3c935231c9 Mon Sep 17 00:00:00 2001 From: Alexander Wulf Date: Sun, 20 Feb 2022 14:08:13 +0100 Subject: [PATCH 31/45] Still failing --- vendor/Expectation.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 vendor/Expectation.php diff --git a/vendor/Expectation.php b/vendor/Expectation.php new file mode 100644 index 0000000..652a1c6 --- /dev/null +++ b/vendor/Expectation.php @@ -0,0 +1,17 @@ + Date: Sun, 20 Feb 2022 15:36:16 +0100 Subject: [PATCH 32/45] Added default parameters in methods (custom method class) --- composer.json | 1 + .../Generator/DocBlock/AsJobGenerator.php | 11 ++- .../Generator/DocBlock/AsObjectGenerator.php | 2 +- .../Generator/DocBlock/Custom/Method.php | 82 +++++++++++++++++++ .../DocBlock/DocBlockGeneratorBase.php | 4 +- tests/Generators/ObjectGeneratorTest.php | 17 +++- ...__it_can_render_intersection_types___1.txt | 36 ++++++++ ...ender_the_output_so_it_matches_the___1.txt | 76 +++++++++++------ tests/stubs/DefaultParameterValuesAction.php | 15 ++++ .../IntersectionTypes/IntersectionAction.php | 2 +- 10 files changed, 208 insertions(+), 38 deletions(-) create mode 100644 src/Service/Generator/DocBlock/Custom/Method.php create mode 100644 tests/__snapshots__/IdeHelperOutputTest__it_can_render_intersection_types___1.txt create mode 100644 tests/stubs/DefaultParameterValuesAction.php rename tests/{ => stubs}/IntersectionTypes/IntersectionAction.php (75%) diff --git a/composer.json b/composer.json index 36a7f83..ee4c6f4 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "lorisleiva/laravel-actions": "^2.3", "lorisleiva/lody": "^0.3.0", "phpdocumentor/reflection": "^5.1", + "ridzhi/docbuilder": "^1.0", "riimu/kit-pathjoin": "^1.2", "spatie/laravel-package-tools": "^1.4.3" }, diff --git a/src/Service/Generator/DocBlock/AsJobGenerator.php b/src/Service/Generator/DocBlock/AsJobGenerator.php index cc5e7dd..e4b8c57 100644 --- a/src/Service/Generator/DocBlock/AsJobGenerator.php +++ b/src/Service/Generator/DocBlock/AsJobGenerator.php @@ -8,10 +8,9 @@ use Lorisleiva\Actions\Concerns\AsJob; use Lorisleiva\Actions\Decorators\JobDecorator; use Lorisleiva\Actions\Decorators\UniqueJobDecorator; -use phpDocumentor\Reflection\DocBlock\Tags\Method; -use phpDocumentor\Reflection\DocBlock\Tags\Param; +use phpDocumentor\Reflection\Php\Argument; use phpDocumentor\Reflection\Types\Boolean; -use PhpParser\Node\UnionType; +use Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\Custom\Method; use Wulfheart\LaravelActionsIdeHelper\Service\ActionInfo; class AsJobGenerator extends DocBlockGeneratorBase implements DocBlockGeneratorInterface @@ -30,7 +29,7 @@ public function generate(ActionInfo $info): array return []; } - $args = $this->convertArguments($method->getArguments()); + $args = $method->getArguments(); return [ @@ -39,11 +38,11 @@ public function generate(ActionInfo $info): array new Method('makeUniqueJob', $args, $this->resolveType(UniqueJobDecorator::class), true), new Method('dispatch', $args, $this->resolveType(PendingDispatch::class), true), new Method('dispatchIf', - collect($args)->prepend(['name' => 'boolean', 'type' => 'bool'])->toArray(), + collect($args)->prepend(new Argument('boolean', new Boolean()))->toArray(), $this->resolveAsUnionType(PendingDispatch::class, Fluent::class), true), new Method('dispatchUnless', - collect($args)->prepend(['name' => 'boolean', 'type' => 'bool'])->toArray(), + collect($args)->prepend(new Argument('boolean', new Boolean()))->toArray(), $this->resolveAsUnionType(PendingDispatch::class, Fluent::class), true), new Method('dispatchSync', $args, null, true), diff --git a/src/Service/Generator/DocBlock/AsObjectGenerator.php b/src/Service/Generator/DocBlock/AsObjectGenerator.php index e31a4ef..d78c0bf 100644 --- a/src/Service/Generator/DocBlock/AsObjectGenerator.php +++ b/src/Service/Generator/DocBlock/AsObjectGenerator.php @@ -22,6 +22,6 @@ public function generate(ActionInfo $info): array { /** @var Method $method */ $method = $this->findMethod($info, 'handle'); - return $method == null ? [] : [new Method('run', $this->convertArguments($method->getArguments()), $method->getReturnType(), true)]; + return $method == null ? [] : [new \Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\Custom\Method('run', $method->getArguments(), $method->getReturnType(), true)]; } } diff --git a/src/Service/Generator/DocBlock/Custom/Method.php b/src/Service/Generator/DocBlock/Custom/Method.php new file mode 100644 index 0000000..783f46a --- /dev/null +++ b/src/Service/Generator/DocBlock/Custom/Method.php @@ -0,0 +1,82 @@ + $arguments + * @param \phpDocumentor\Reflection\Type|null $returnType + * @param bool $static + * @param \phpDocumentor\Reflection\DocBlock\Description|null $description + */ + public function __construct( + protected string $methodName, + protected array $arguments = [], + protected ?Type $returnType = null, + protected bool $static = false, + protected $description = null + ) { + + } + + public static function create(string $body) + { + // TODO: Implement create() method. + } + + public function __toString(): string + { + $s = ''; + if($this->static){ + $s .= 'static '; + } + + if($this->returnType){ + $s .= (string) $this->returnType . ' '; + } + + + $s .= $this->methodName . '('; + + $s .= collect($this->arguments)->map(fn(Argument $arg) => $this->stringifyArgument($arg))->implode(', '); + + $s .= ')'; + + return $s; + } + + protected function stringifyArgument(Argument $argument): string + { + $s = ""; + $type = $argument->getType(); + if ($type) { + $s .= (string) $type." "; + } + + if ($argument->isVariadic()) { + $s .= "..."; + } + + if ($argument->isByReference()) { + $s .= "&"; + } + + $s .= '$'.$argument->getName(); + + $default = $argument->getDefault(); + if ($default) { + $s .= ' = ' . $default; + } + + return $s; + } +} \ No newline at end of file diff --git a/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php b/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php index a2dfcfe..f0e24d1 100644 --- a/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php +++ b/src/Service/Generator/DocBlock/DocBlockGeneratorBase.php @@ -36,7 +36,9 @@ public function generate(ActionInfo $info): array * @phpstan-return array> */ protected function convertArguments(array $arguments): array { - return collect($arguments)->transform(fn(Argument $arg) => ['name' => $arg->getName(),'type' => $arg->getType()])->toArray(); + return collect($arguments) + ->transform(fn(Argument $arg) => ['name' => $arg->getName(),'type' => $arg->getType()]) + ->toArray(); } protected function findMethod(ActionInfo $info, string ...$methods): ?\phpDocumentor\Reflection\Php\Method { diff --git a/tests/Generators/ObjectGeneratorTest.php b/tests/Generators/ObjectGeneratorTest.php index 633601a..8996446 100644 --- a/tests/Generators/ObjectGeneratorTest.php +++ b/tests/Generators/ObjectGeneratorTest.php @@ -3,6 +3,7 @@ use phpDocumentor\Reflection\DocBlock\Tags\Method; use Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\AsObjectGenerator; use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\BaseAction; +use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\DefaultParameterValuesAction; use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\UnionTypeAction; use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\VoidAction; use Wulfheart\LaravelActionsIdeHelper\Tests\stubs\VoidActionWithNoReturnType; @@ -12,7 +13,7 @@ /** @var \phpDocumentor\Reflection\DocBlock\Tag $docblock */ $docblock = collect((new AsObjectGenerator())->generate($ai))->first(); - expect($docblock)->toBeInstanceOf(Method::class); + expect($docblock)->toBeInstanceOf(\Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\Custom\Method::class); expect($docblock->render())->toEqual($docblockExpectation); @@ -21,4 +22,16 @@ "UnionTypeAction" => [UnionTypeAction::class, '@method static int|string run(string $string, float|int $number)'], "VoidAction" => [VoidAction::class, '@method static void run(int $i)'], "VoidActionWithNoReturnType" => [VoidActionWithNoReturnType::class, '@method static mixed run()'], -]); \ No newline at end of file +]); + +it('can render the run method with default parameter values', function(){ + $ai = getActionInfo(DefaultParameterValuesAction::class); + + /** @var \phpDocumentor\Reflection\DocBlock\Tag $docblock */ + $docblock = collect((new AsObjectGenerator())->generate($ai))->first(); + expect($docblock)->toBeInstanceOf(\Wulfheart\LaravelActionsIdeHelper\Service\Generator\DocBlock\Custom\Method::class); + + $docblockExpectation = '@method static int run(string $s, bool $var = false)'; + expect($docblock->render())->toEqual($docblockExpectation); + +}); \ No newline at end of file diff --git a/tests/__snapshots__/IdeHelperOutputTest__it_can_render_intersection_types___1.txt b/tests/__snapshots__/IdeHelperOutputTest__it_can_render_intersection_types___1.txt new file mode 100644 index 0000000..6bbf317 --- /dev/null +++ b/tests/__snapshots__/IdeHelperOutputTest__it_can_render_intersection_types___1.txt @@ -0,0 +1,36 @@ + Date: Mon, 30 Jan 2023 20:16:43 +0000 Subject: [PATCH 33/45] Bump dependencies for Laravel 10 --- composer.json | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index ee4c6f4..81a6803 100644 --- a/composer.json +++ b/composer.json @@ -16,25 +16,25 @@ } ], "require": { - "php": "^8.0", - "illuminate/contracts": "^9.0", + "php": "^8.1", + "illuminate/contracts": "^10.0", "lorisleiva/laravel-actions": "^2.3", "lorisleiva/lody": "^0.3.0", "phpdocumentor/reflection": "^5.1", "ridzhi/docbuilder": "^1.0", "riimu/kit-pathjoin": "^1.2", - "spatie/laravel-package-tools": "^1.4.3" + "spatie/laravel-package-tools": "^1.14" }, "require-dev": { - "brianium/paratest": "^6.4", - "nunomaduro/collision": "^6.0", - "orchestra/testbench": "^7.0", - "pestphp/pest": "^1.21", + "brianium/paratest": "^6.8", + "nunomaduro/collision": "^6.1", + "orchestra/testbench": "^8.0", + "pestphp/pest": "^1.22", "phpunit/phpunit": "^9.5.10", - "spatie/invade": "^1.0", - "spatie/laravel-ray": "^1.29", + "spatie/invade": "^1.1", + "spatie/laravel-ray": "^1.32", "spatie/pest-plugin-snapshots": "^1.1", - "vimeo/psalm": "^4.20" + "vimeo/psalm": "^5.6" }, "autoload": { "psr-4": { @@ -47,8 +47,7 @@ "Wulfheart\\LaravelActionsIdeHelper\\Tests\\": "tests" } }, - "scripts": { - }, + "scripts": [], "config": { "sort-packages": true, "allow-plugins": { From c2dfd789995df983fe1999e3455f94eb27dc6086 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Mar 2023 18:48:02 +0100 Subject: [PATCH 34/45] Update composer.json Co-authored-by: Vincent Prat --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 81a6803..047521c 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": "^8.1", "illuminate/contracts": "^10.0", "lorisleiva/laravel-actions": "^2.3", - "lorisleiva/lody": "^0.3.0", + "lorisleiva/lody": "^0.4.0", "phpdocumentor/reflection": "^5.1", "ridzhi/docbuilder": "^1.0", "riimu/kit-pathjoin": "^1.2", From 4e23738b3aa53704da14737dab5bf74d56e21afb Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 26 Jun 2023 14:15:35 +0200 Subject: [PATCH 35/45] Update composer.json --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 047521c..4dbbba5 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ "lorisleiva/laravel-actions": "^2.3", "lorisleiva/lody": "^0.4.0", "phpdocumentor/reflection": "^5.1", - "ridzhi/docbuilder": "^1.0", "riimu/kit-pathjoin": "^1.2", "spatie/laravel-package-tools": "^1.14" }, From 052e2e3094e8c3e85840ea440ab56775d7bd3d16 Mon Sep 17 00:00:00 2001 From: goper-leo Date: Sat, 28 Oct 2023 13:18:47 +0800 Subject: [PATCH 36/45] Add support for PHP 8.2 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 4dbbba5..98d2149 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.1|^8.2", "illuminate/contracts": "^10.0", "lorisleiva/laravel-actions": "^2.3", "lorisleiva/lody": "^0.4.0", @@ -46,7 +46,7 @@ "Wulfheart\\LaravelActionsIdeHelper\\Tests\\": "tests" } }, - "scripts": [], + "scripts": {}, "config": { "sort-packages": true, "allow-plugins": { From 5ead8c591ae78b747942bf95606f905392ba04cf Mon Sep 17 00:00:00 2001 From: Shift Date: Fri, 1 Mar 2024 22:51:26 +0000 Subject: [PATCH 37/45] Bump dependencies for Laravel 11 --- composer.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 98d2149..3e34809 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^8.1|^8.2", - "illuminate/contracts": "^10.0", + "illuminate/contracts": "^10.0|^11.0", "lorisleiva/laravel-actions": "^2.3", "lorisleiva/lody": "^0.4.0", "phpdocumentor/reflection": "^5.1", @@ -25,14 +25,14 @@ "spatie/laravel-package-tools": "^1.14" }, "require-dev": { - "brianium/paratest": "^6.8", - "nunomaduro/collision": "^6.1", - "orchestra/testbench": "^8.0", - "pestphp/pest": "^1.22", - "phpunit/phpunit": "^9.5.10", - "spatie/invade": "^1.1", + "brianium/paratest": "^6.8|^7.4", + "nunomaduro/collision": "^6.1|^8.0", + "orchestra/testbench": "^8.0|^9.0", + "pestphp/pest": "^1.22|^2.34", + "phpunit/phpunit": "^9.5.10|^10.5", + "spatie/invade": "^1.1|^2.0", "spatie/laravel-ray": "^1.32", - "spatie/pest-plugin-snapshots": "^1.1", + "spatie/pest-plugin-snapshots": "^1.1|^2.1", "vimeo/psalm": "^5.6" }, "autoload": { @@ -46,7 +46,7 @@ "Wulfheart\\LaravelActionsIdeHelper\\Tests\\": "tests" } }, - "scripts": {}, + "scripts": [], "config": { "sort-packages": true, "allow-plugins": { From 62a7fd38d25e902580a075f79d91d65d2b8fa364 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 15 Mar 2024 22:32:22 +0000 Subject: [PATCH 38/45] update dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3e34809..7ed02d5 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": "^8.1|^8.2", "illuminate/contracts": "^10.0|^11.0", "lorisleiva/laravel-actions": "^2.3", - "lorisleiva/lody": "^0.4.0", + "lorisleiva/lody": "^0.5.0", "phpdocumentor/reflection": "^5.1", "riimu/kit-pathjoin": "^1.2", "spatie/laravel-package-tools": "^1.14" From 5326a1bb3f7d2b02f96588c0b60d79c2beaa7cff Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 17 Mar 2024 17:44:45 +0100 Subject: [PATCH 39/45] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index bc55ae5..269c444 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ This packages generates IDE helpers for [Laravel Actions v2](https://github.com/lorisleiva/laravel-actions). Feedback appreciated. Discussion at https://github.com/lorisleiva/laravel-actions/issues/117. +## YOLO-ware + +As I don't use Laravel Actions anymore I decided to go into `YOLO-mode` with this project. That means: + +1. **No guarantees** +2. **Community-powered fixes**: Something doesn't work, you notice it you fix it. +3. **Trust in the community:** I won't test your changes, either they do work or they don't. I just merge them. +4. **Looking for a maintainer:** This situation is less than ideal. Therefore, I am looking for a new maintainer to take over this project. + ## Installation ``` From 3f03d397593a3c2a98543b427f082b03859cb53d Mon Sep 17 00:00:00 2001 From: olieady Date: Fri, 12 Apr 2024 11:51:40 +0100 Subject: [PATCH 40/45] fix incompatible types with phpdocumentor/reflection-docblock (5.4.0) --- composer.json | 2 +- src/Service/Generator/DocBlock/Custom/Method.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 7ed02d5..07b7dc5 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "Wulfheart\\LaravelActionsIdeHelper\\Tests\\": "tests" } }, - "scripts": [], + "scripts": {}, "config": { "sort-packages": true, "allow-plugins": { diff --git a/src/Service/Generator/DocBlock/Custom/Method.php b/src/Service/Generator/DocBlock/Custom/Method.php index 783f46a..8d50e98 100644 --- a/src/Service/Generator/DocBlock/Custom/Method.php +++ b/src/Service/Generator/DocBlock/Custom/Method.php @@ -9,7 +9,7 @@ class Method extends BaseTag { - protected $name = 'method'; + protected string $name = 'method'; /** * @param string $methodName @@ -23,7 +23,7 @@ public function __construct( protected array $arguments = [], protected ?Type $returnType = null, protected bool $static = false, - protected $description = null + protected ?Description $description = null ) { } From aebcf838027183ede1ce35374ada2953e2fb463d Mon Sep 17 00:00:00 2001 From: olieady Date: Fri, 12 Apr 2024 12:52:52 +0100 Subject: [PATCH 41/45] change scripts back to an arra --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 07b7dc5..7ed02d5 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "Wulfheart\\LaravelActionsIdeHelper\\Tests\\": "tests" } }, - "scripts": {}, + "scripts": [], "config": { "sort-packages": true, "allow-plugins": { From 97c00b394d6f1947d954908aeba8a4baae302efe Mon Sep 17 00:00:00 2001 From: Edwin van de Pol Date: Fri, 26 Apr 2024 15:39:03 +0200 Subject: [PATCH 42/45] Updated Composer JSON structure I found out while validating the packages I use in my project, that this project had an invalid script value. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7ed02d5..07b7dc5 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "Wulfheart\\LaravelActionsIdeHelper\\Tests\\": "tests" } }, - "scripts": [], + "scripts": {}, "config": { "sort-packages": true, "allow-plugins": { From c8714b39c0f4f10c9ebd4c338a7d54a0d3a10eb8 Mon Sep 17 00:00:00 2001 From: Nick Potts Date: Tue, 27 Aug 2024 22:06:24 +0800 Subject: [PATCH 43/45] Bump phpdocumentor/reflection --- composer.json | 2 +- phpunit.xml.dist | 58 ++++++++++++++++++------------------------------ 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/composer.json b/composer.json index 07b7dc5..01c9d3c 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "illuminate/contracts": "^10.0|^11.0", "lorisleiva/laravel-actions": "^2.3", "lorisleiva/lody": "^0.5.0", - "phpdocumentor/reflection": "^5.1", + "phpdocumentor/reflection": "^5.1|^6.0", "riimu/kit-pathjoin": "^1.2", "spatie/laravel-package-tools": "^1.14" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9e2f33e..d6cf188 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,39 +1,23 @@ - - - - tests - - - - - ./src - - - - - - - - - - + + + + tests + + + + + + + + + + + + + + + ./src + + From aaecb90c23ebb198bfad2f90d3a9e8d819aa058a Mon Sep 17 00:00:00 2001 From: Shift Date: Sun, 16 Feb 2025 21:28:37 +0000 Subject: [PATCH 44/45] Bump dependencies for Laravel 12 --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 01c9d3c..d621c6c 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^8.1|^8.2", - "illuminate/contracts": "^10.0|^11.0", + "illuminate/contracts": "^10.0|^11.0|^12.0", "lorisleiva/laravel-actions": "^2.3", "lorisleiva/lody": "^0.5.0", "phpdocumentor/reflection": "^5.1|^6.0", @@ -27,13 +27,13 @@ "require-dev": { "brianium/paratest": "^6.8|^7.4", "nunomaduro/collision": "^6.1|^8.0", - "orchestra/testbench": "^8.0|^9.0", - "pestphp/pest": "^1.22|^2.34", - "phpunit/phpunit": "^9.5.10|^10.5", + "orchestra/testbench": "^8.0|^9.0|^10.0", + "pestphp/pest": "^1.22|^2.34|^3.7", + "phpunit/phpunit": "^9.5.10|^10.5|^11.5.3", "spatie/invade": "^1.1|^2.0", "spatie/laravel-ray": "^1.32", "spatie/pest-plugin-snapshots": "^1.1|^2.1", - "vimeo/psalm": "^5.6" + "vimeo/psalm": "^5.6|^6.6" }, "autoload": { "psr-4": { @@ -46,7 +46,7 @@ "Wulfheart\\LaravelActionsIdeHelper\\Tests\\": "tests" } }, - "scripts": {}, + "scripts": [], "config": { "sort-packages": true, "allow-plugins": { From aaf89971050430051ac728f4432efab8cbbbd671 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sun, 2 Mar 2025 00:29:57 -0500 Subject: [PATCH 45/45] Bump lorisleiva/lody --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d621c6c..67164ae 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": "^8.1|^8.2", "illuminate/contracts": "^10.0|^11.0|^12.0", "lorisleiva/laravel-actions": "^2.3", - "lorisleiva/lody": "^0.5.0", + "lorisleiva/lody": "^0.5.0|^0.6.0", "phpdocumentor/reflection": "^5.1|^6.0", "riimu/kit-pathjoin": "^1.2", "spatie/laravel-package-tools": "^1.14"