From a85cf723ecb392e4037df214ab7adc609fadd70d Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Thu, 27 Mar 2025 21:09:42 +0000 Subject: [PATCH 01/11] Add a repository for Livewire components Fixes N1ebieski/vs-code-extension#29 --- php-templates/livewire-components.php | 209 ++++++++++++++++++++++++ src/repositories/configs.ts | 4 + src/repositories/livewireComponents.ts | 49 ++++++ src/support/str.ts | 3 + src/templates/index.ts | 2 + src/templates/livewire-components.ts | 210 +++++++++++++++++++++++++ 6 files changed, 477 insertions(+) create mode 100644 php-templates/livewire-components.php create mode 100644 src/repositories/livewireComponents.ts create mode 100644 src/support/str.ts create mode 100644 src/templates/livewire-components.ts diff --git a/php-templates/livewire-components.php b/php-templates/livewire-components.php new file mode 100644 index 00000000..6906e5bc --- /dev/null +++ b/php-templates/livewire-components.php @@ -0,0 +1,209 @@ +getStandardComponents(), + $this->getVoltComponents() + ))->groupBy('key')->map(fn($items) => [ + 'isVendor' => $items->first()['isVendor'], + 'paths' => $items->pluck('path')->values(), + 'props' => $items->pluck('props')->values()->filter()->flatMap(fn($i) => $i), + ]); + + return [ + 'components' => $components + ]; + } + + /** + * @return array + */ + protected function findFiles(string $path, string $extension, \Closure $keyCallback): array + { + if (! is_dir($path)) { + return []; + } + + $files = \Symfony\Component\Finder\Finder::create() + ->files() + ->name("*." . $extension) + ->in($path); + $components = []; + $pathRealPath = realpath($path); + + foreach ($files as $file) { + $realPath = $file->getRealPath(); + + $key = str($realPath) + ->replace($pathRealPath, '') + ->ltrim('/\\') + ->replace('.' . $extension, '') + ->replace(['/', '\\'], '.') + ->pipe(fn(string $str): string => $str); + + $components[] = [ + "path" => LaravelVsCode::relativePath($realPath), + "isVendor" => LaravelVsCode::isVendor($realPath), + "key" => $keyCallback ? $keyCallback($key) : $key, + ]; + } + + return $components; + } + + protected function getStandardComponents(): array + { + /** @var string|null $classNamespace */ + $classNamespace = config('livewire.class_namespace'); + + if (! $classNamespace) { + return []; + } + + $path = str($classNamespace) + ->replace('\\', DIRECTORY_SEPARATOR) + ->replace('App', 'app') + ->toString(); + + $items = $this->findFiles( + $path, + 'php', + fn(\Illuminate\Support\Stringable $key): string => $key->explode('.') + ->map(fn(string $p): string => \Illuminate\Support\Str::kebab($p)) + ->implode('.'), + ); + + $components = []; + + foreach ($items as $item) { + $class = str($item['path']) + ->replace('.php', '') + ->replace('/', '\\') + ->ucfirst() + ->toString(); + + if (! class_exists($class)) { + continue; + } + + $reflection = new \ReflectionClass($class); + + if (! $reflection->isSubclassOf('Livewire\Component')) { + continue; + } + + $components[] = [ + ...$item, + 'props' => $this->getComponentProps($reflection), + ]; + } + + return $components; + } + + protected function getVoltComponents(): array + { + /** @var string|null $viewPath */ + $path = config('livewire.view_path'); + + if (! $path) { + return []; + } + + $items = $this->findFiles( + $path, + 'php', + fn(\Illuminate\Support\Stringable $key): string => $key->explode('.') + ->map(fn(string $p): string => \Illuminate\Support\Str::kebab($p)) + ->implode('.'), + ); + + $components = []; + + foreach ($items as $item) { + // This is ugly, I know, but I don't have better idea how to get + // anonymous classes from Volt components + ob_start(); + + try { + require_once $item['path']; + } catch (\Throwable $e) { + continue; + } + + ob_get_clean(); + + $declaredClasses = get_declared_classes(); + $class = end($declaredClasses); + + if (! \Illuminate\Support\Str::contains($class, '@anonymous')) { + continue; + } + + $reflection = new \ReflectionClass($class); + + if (! $reflection->isSubclassOf('Livewire\Volt\Component')) { + continue; + } + + $components[] = [ + ...$item, + 'props' => $this->getComponentProps($reflection), + ]; + } + + return $components; + } + + /** + * @return array + */ + protected function getComponentProps(ReflectionClass $reflection): array + { + $props = collect(); + + // Firstly we need to get the mount method parameters. Remember that + // Livewire components can have multiple mount methods in traits. + + $methods = $reflection->getMethods(); + + $mountMethods = array_filter( + $methods, + fn (\ReflectionMethod $method): bool => strpos($method->getName(), 'mount') === 0 + ); + + foreach ($mountMethods as $method) { + $parameters = $method->getParameters(); + + $parameters = collect($parameters) + ->map(fn(\ReflectionParameter $p) => [ + 'name' => \Illuminate\Support\Str::kebab($p->getName()), + 'type' => (string) ($p->getType() ?? 'mixed'), + 'default' => $p->isOptional() ? $p->getDefaultValue() : null + ]) + ->all(); + + $props = $props->merge($parameters); + } + + // Then we need to get the public properties + + $properties = collect($reflection->getProperties()) + ->filter(fn(\ReflectionProperty $p) => $p->isPublic() && $p->getDeclaringClass()->getName() === $reflection->getName()) + ->map(fn(\ReflectionProperty $p) => [ + 'name' => \Illuminate\Support\Str::kebab($p->getName()), + 'type' => (string) ($p->getType() ?? 'mixed'), + 'default' => $p->getDefaultValue() + ]) + ->all(); + + return $props + ->merge($properties) + ->unique('name') // Mount parameters always overwrite public properties + ->all(); + } +}; + +echo json_encode($components->all()); diff --git a/src/repositories/configs.ts b/src/repositories/configs.ts index cea56b83..69c2c426 100644 --- a/src/repositories/configs.ts +++ b/src/repositories/configs.ts @@ -2,6 +2,10 @@ import { repository } from "."; import { Config } from ".."; import { runInLaravel, template } from "../support/php"; +export const getConfigByName = (name: string): Config | undefined => { + return getConfigs().items.find((item) => item.name === name); +}; + export const getConfigs = repository({ load: () => { return runInLaravel(template("configs"), "Configs").then( diff --git a/src/repositories/livewireComponents.ts b/src/repositories/livewireComponents.ts new file mode 100644 index 00000000..4aaa9cc7 --- /dev/null +++ b/src/repositories/livewireComponents.ts @@ -0,0 +1,49 @@ +import { runInLaravel, template } from "@src/support/php"; +import { projectPath } from "@src/support/project"; +import { lcfirst } from "@src/support/str"; +import { waitForValue } from "@src/support/util"; +import { repository } from "."; +import { getConfigByName, getConfigs } from "./configs"; + +let livewirePaths: string[] | null = null; + +export interface LivewireComponents { + components: { + [key: string]: { + paths: string[]; + isVendor: boolean; + props: { + name: string; + type: string; + default: string | null; + }[]; + }; + }; +} + +const load = () => { + getConfigs().whenLoaded(() => { + livewirePaths = [ + projectPath(lcfirst(getConfigByName('livewire.class_namespace')?.value?.replace('\\', '/') ?? 'app/Livewire')), + getConfigByName('livewire.view_path')?.value ?? 'resources/views/livewire' + ]; + }); + + return runInLaravel(template("livewireComponents")); +}; + +export const getLivewireComponents = repository({ + load, + pattern: () => + waitForValue(() => livewirePaths).then((paths) => { + if (paths === null || paths.length === 0) { + return null; + } + + return paths.map(path => path + "/{*,**/*}"); + }), + itemsDefault: { + components: {}, + }, + fileWatcherEvents: ["create", "delete"], +}); diff --git a/src/support/str.ts b/src/support/str.ts new file mode 100644 index 00000000..ac64063e --- /dev/null +++ b/src/support/str.ts @@ -0,0 +1,3 @@ +export const lcfirst = (str: string): string => { + return str.charAt(0).toLowerCase() + str.slice(1); +}; \ No newline at end of file diff --git a/src/templates/index.ts b/src/templates/index.ts index 4b0572f3..40617165 100644 --- a/src/templates/index.ts +++ b/src/templates/index.ts @@ -5,6 +5,7 @@ import bladeDirectives from "./blade-directives"; import bootstrapLaravel from "./bootstrap-laravel"; import configs from "./configs"; import inertia from "./inertia"; +import livewireComponents from "./livewire-components"; import middleware from "./middleware"; import models from "./models"; import routes from "./routes"; @@ -14,6 +15,7 @@ import views from "./views"; const templates = { app, auth, + livewireComponents, bladeComponents, bladeDirectives, bootstrapLaravel, diff --git a/src/templates/livewire-components.ts b/src/templates/livewire-components.ts new file mode 100644 index 00000000..74f6689e --- /dev/null +++ b/src/templates/livewire-components.ts @@ -0,0 +1,210 @@ +// This file was generated from php-templates/livewire-components.php, do not edit directly +export default ` +$components = new class { + public function all(): array + { + $components = collect(array_merge( + $this->getStandardComponents(), + $this->getVoltComponents() + ))->groupBy('key')->map(fn($items) => [ + 'isVendor' => $items->first()['isVendor'], + 'paths' => $items->pluck('path')->values(), + 'props' => $items->pluck('props')->values()->filter()->flatMap(fn($i) => $i), + ]); + + return [ + 'components' => $components + ]; + } + + /** + * @return array + */ + protected function findFiles(string $path, string $extension, \\Closure $keyCallback): array + { + if (! is_dir($path)) { + return []; + } + + $files = \\Symfony\\Component\\Finder\\Finder::create() + ->files() + ->name("*." . $extension) + ->in($path); + $components = []; + $pathRealPath = realpath($path); + + foreach ($files as $file) { + $realPath = $file->getRealPath(); + + $key = str($realPath) + ->replace($pathRealPath, '') + ->ltrim('/\\\\') + ->replace('.' . $extension, '') + ->replace(['/', '\\\\'], '.') + ->pipe(fn(string $str): string => $str); + + $components[] = [ + "path" => LaravelVsCode::relativePath($realPath), + "isVendor" => LaravelVsCode::isVendor($realPath), + "key" => $keyCallback ? $keyCallback($key) : $key, + ]; + } + + return $components; + } + + protected function getStandardComponents(): array + { + /** @var string|null $classNamespace */ + $classNamespace = config('livewire.class_namespace'); + + if (! $classNamespace) { + return []; + } + + $path = str($classNamespace) + ->replace('\\\\', DIRECTORY_SEPARATOR) + ->replace('App', 'app') + ->toString(); + + $items = $this->findFiles( + $path, + 'php', + fn(\\Illuminate\\Support\\Stringable $key): string => $key->explode('.') + ->map(fn(string $p): string => \\Illuminate\\Support\\Str::kebab($p)) + ->implode('.'), + ); + + $components = []; + + foreach ($items as $item) { + $class = str($item['path']) + ->replace('.php', '') + ->replace('/', '\\\\') + ->ucfirst() + ->toString(); + + if (! class_exists($class)) { + continue; + } + + $reflection = new \\ReflectionClass($class); + + if (! $reflection->isSubclassOf('Livewire\\Component')) { + continue; + } + + $components[] = [ + ...$item, + 'props' => $this->getComponentProps($reflection), + ]; + } + + return $components; + } + + protected function getVoltComponents(): array + { + /** @var string|null $viewPath */ + $path = config('livewire.view_path'); + + if (! $path) { + return []; + } + + $items = $this->findFiles( + $path, + 'php', + fn(\\Illuminate\\Support\\Stringable $key): string => $key->explode('.') + ->map(fn(string $p): string => \\Illuminate\\Support\\Str::kebab($p)) + ->implode('.'), + ); + + $components = []; + + foreach ($items as $item) { + // This is ugly, I know, but I don't have better idea how to get + // anonymous classes from Volt components + ob_start(); + + try { + require_once $item['path']; + } catch (\\Throwable $e) { + continue; + } + + ob_get_clean(); + + $declaredClasses = get_declared_classes(); + $class = end($declaredClasses); + + if (! \\Illuminate\\Support\\Str::contains($class, '@anonymous')) { + continue; + } + + $reflection = new \\ReflectionClass($class); + + if (! $reflection->isSubclassOf('Livewire\\Volt\\Component')) { + continue; + } + + $components[] = [ + ...$item, + 'props' => $this->getComponentProps($reflection), + ]; + } + + return $components; + } + + /** + * @return array + */ + protected function getComponentProps(ReflectionClass $reflection): array + { + $props = collect(); + + // Firstly we need to get the mount method parameters. Remember that + // Livewire components can have multiple mount methods in traits. + + $methods = $reflection->getMethods(); + + $mountMethods = array_filter( + $methods, + fn (\\ReflectionMethod $method): bool => strpos($method->getName(), 'mount') === 0 + ); + + foreach ($mountMethods as $method) { + $parameters = $method->getParameters(); + + $parameters = collect($parameters) + ->map(fn(\\ReflectionParameter $p) => [ + 'name' => \\Illuminate\\Support\\Str::kebab($p->getName()), + 'type' => (string) ($p->getType() ?? 'mixed'), + 'default' => $p->isOptional() ? $p->getDefaultValue() : null + ]) + ->all(); + + $props = $props->merge($parameters); + } + + // Then we need to get the public properties + + $properties = collect($reflection->getProperties()) + ->filter(fn(\\ReflectionProperty $p) => $p->isPublic() && $p->getDeclaringClass()->getName() === $reflection->getName()) + ->map(fn(\\ReflectionProperty $p) => [ + 'name' => \\Illuminate\\Support\\Str::kebab($p->getName()), + 'type' => (string) ($p->getType() ?? 'mixed'), + 'default' => $p->getDefaultValue() + ]) + ->all(); + + return $props + ->merge($properties) + ->unique('name') // Mount parameters always overwrite public properties + ->all(); + } +}; + +echo json_encode($components->all()); +`; \ No newline at end of file From 9e40eecfa606400118c0b7bd9ea423935d716a80 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Thu, 27 Mar 2025 21:26:48 +0000 Subject: [PATCH 02/11] fix extension for volt components --- php-templates/livewire-components.php | 2 +- src/templates/livewire-components.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/php-templates/livewire-components.php b/php-templates/livewire-components.php index 6906e5bc..1ac44b34 100644 --- a/php-templates/livewire-components.php +++ b/php-templates/livewire-components.php @@ -114,7 +114,7 @@ protected function getVoltComponents(): array $items = $this->findFiles( $path, - 'php', + 'blade.php', fn(\Illuminate\Support\Stringable $key): string => $key->explode('.') ->map(fn(string $p): string => \Illuminate\Support\Str::kebab($p)) ->implode('.'), diff --git a/src/templates/livewire-components.ts b/src/templates/livewire-components.ts index 74f6689e..526d55b9 100644 --- a/src/templates/livewire-components.ts +++ b/src/templates/livewire-components.ts @@ -114,7 +114,7 @@ $components = new class { $items = $this->findFiles( $path, - 'php', + 'blade.php', fn(\\Illuminate\\Support\\Stringable $key): string => $key->explode('.') ->map(fn(string $p): string => \\Illuminate\\Support\\Str::kebab($p)) ->implode('.'), From 3dcb68106a3d01273afc4585d543b60b7a1867da Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Thu, 27 Mar 2025 22:25:42 +0000 Subject: [PATCH 03/11] fix duplicate props and refactoring --- php-templates/livewire-components.php | 129 ++++++++++++++++---------- src/templates/livewire-components.ts | 129 ++++++++++++++++---------- 2 files changed, 162 insertions(+), 96 deletions(-) diff --git a/php-templates/livewire-components.php b/php-templates/livewire-components.php index 1ac44b34..888c5827 100644 --- a/php-templates/livewire-components.php +++ b/php-templates/livewire-components.php @@ -1,11 +1,38 @@ getMessage()); + } +} + $components = new class { public function all(): array { $components = collect(array_merge( - $this->getStandardComponents(), - $this->getVoltComponents() + $this->getStandardClasses(), + $this->getStandardViews() ))->groupBy('key')->map(fn($items) => [ 'isVendor' => $items->first()['isVendor'], 'paths' => $items->pluck('path')->values(), @@ -53,7 +80,7 @@ protected function findFiles(string $path, string $extension, \Closure $keyCallb return $components; } - protected function getStandardComponents(): array + protected function getStandardClasses(): array { /** @var string|null $classNamespace */ $classNamespace = config('livewire.class_namespace'); @@ -75,35 +102,35 @@ protected function getStandardComponents(): array ->implode('.'), ); - $components = []; - - foreach ($items as $item) { - $class = str($item['path']) + return collect($items) + ->map(function ($item) { + $class = str($item['path']) ->replace('.php', '') ->replace('/', '\\') ->ucfirst() ->toString(); - if (! class_exists($class)) { - continue; - } + if (! class_exists($class)) { + return null; + } - $reflection = new \ReflectionClass($class); + $reflection = new \ReflectionClass($class); - if (! $reflection->isSubclassOf('Livewire\Component')) { - continue; - } + if (! $reflection->isSubclassOf('Livewire\Component')) { + return null; + } - $components[] = [ - ...$item, - 'props' => $this->getComponentProps($reflection), - ]; - } - - return $components; + return [ + ...$item, + 'props' => $this->getComponentProps($reflection), + ]; + }) + ->filter() + ->values() + ->all(); } - protected function getVoltComponents(): array + protected function getStandardViews(): array { /** @var string|null $viewPath */ $path = config('livewire.view_path'); @@ -120,41 +147,47 @@ protected function getVoltComponents(): array ->implode('.'), ); - $components = []; + $previousClass = null; - foreach ($items as $item) { - // This is ugly, I know, but I don't have better idea how to get - // anonymous classes from Volt components - ob_start(); + return collect($items) + ->map(function ($item) use (&$previousClass) { + // This is ugly, I know, but I don't have better idea how to get + // anonymous classes from Volt components + ob_start(); - try { - require_once $item['path']; - } catch (\Throwable $e) { - continue; - } + try { + require_once $item['path']; + } catch (\Throwable $e) { + return $item; + } - ob_get_clean(); + ob_clean(); - $declaredClasses = get_declared_classes(); - $class = end($declaredClasses); + $declaredClasses = get_declared_classes(); + $class = end($declaredClasses); - if (! \Illuminate\Support\Str::contains($class, '@anonymous')) { - continue; - } + if ($previousClass === $class) { + return $item; + } - $reflection = new \ReflectionClass($class); + $previousClass = $class; - if (! $reflection->isSubclassOf('Livewire\Volt\Component')) { - continue; - } + if (! \Illuminate\Support\Str::contains($class, '@anonymous')) { + return $item; + } - $components[] = [ - ...$item, - 'props' => $this->getComponentProps($reflection), - ]; - } + $reflection = new \ReflectionClass($class); - return $components; + if (! $reflection->isSubclassOf('Livewire\Volt\Component')) { + return $item; + } + + return [ + ...$item, + 'props' => $this->getComponentProps($reflection), + ]; + }) + ->all(); } /** diff --git a/src/templates/livewire-components.ts b/src/templates/livewire-components.ts index 526d55b9..a0e95284 100644 --- a/src/templates/livewire-components.ts +++ b/src/templates/livewire-components.ts @@ -1,11 +1,38 @@ // This file was generated from php-templates/livewire-components.php, do not edit directly export default ` +class LaravelVsCode +{ + public static function relativePath($path) + { + if (!str_contains($path, base_path())) { + return (string) $path; + } + + return ltrim(str_replace(base_path(), '', realpath($path) ?: $path), DIRECTORY_SEPARATOR); + } + + public static function isVendor($path) + { + return str_contains($path, base_path("vendor")); + } + + public static function outputMarker($key) + { + return '__VSCODE_LARAVEL_' . $key . '__'; + } + + public static function startupError(\\Throwable $e) + { + throw new Error(self::outputMarker('STARTUP_ERROR') . ': ' . $e->getMessage()); + } +} + $components = new class { public function all(): array { $components = collect(array_merge( - $this->getStandardComponents(), - $this->getVoltComponents() + $this->getStandardClasses(), + $this->getStandardViews() ))->groupBy('key')->map(fn($items) => [ 'isVendor' => $items->first()['isVendor'], 'paths' => $items->pluck('path')->values(), @@ -53,7 +80,7 @@ $components = new class { return $components; } - protected function getStandardComponents(): array + protected function getStandardClasses(): array { /** @var string|null $classNamespace */ $classNamespace = config('livewire.class_namespace'); @@ -75,35 +102,35 @@ $components = new class { ->implode('.'), ); - $components = []; - - foreach ($items as $item) { - $class = str($item['path']) + return collect($items) + ->map(function ($item) { + $class = str($item['path']) ->replace('.php', '') ->replace('/', '\\\\') ->ucfirst() ->toString(); - if (! class_exists($class)) { - continue; - } + if (! class_exists($class)) { + return null; + } - $reflection = new \\ReflectionClass($class); + $reflection = new \\ReflectionClass($class); - if (! $reflection->isSubclassOf('Livewire\\Component')) { - continue; - } + if (! $reflection->isSubclassOf('Livewire\\Component')) { + return null; + } - $components[] = [ - ...$item, - 'props' => $this->getComponentProps($reflection), - ]; - } - - return $components; + return [ + ...$item, + 'props' => $this->getComponentProps($reflection), + ]; + }) + ->filter() + ->values() + ->all(); } - protected function getVoltComponents(): array + protected function getStandardViews(): array { /** @var string|null $viewPath */ $path = config('livewire.view_path'); @@ -120,41 +147,47 @@ $components = new class { ->implode('.'), ); - $components = []; + $previousClass = null; - foreach ($items as $item) { - // This is ugly, I know, but I don't have better idea how to get - // anonymous classes from Volt components - ob_start(); + return collect($items) + ->map(function ($item) use (&$previousClass) { + // This is ugly, I know, but I don't have better idea how to get + // anonymous classes from Volt components + ob_start(); - try { - require_once $item['path']; - } catch (\\Throwable $e) { - continue; - } + try { + require_once $item['path']; + } catch (\\Throwable $e) { + return $item; + } - ob_get_clean(); + ob_clean(); - $declaredClasses = get_declared_classes(); - $class = end($declaredClasses); + $declaredClasses = get_declared_classes(); + $class = end($declaredClasses); - if (! \\Illuminate\\Support\\Str::contains($class, '@anonymous')) { - continue; - } + if ($previousClass === $class) { + return $item; + } - $reflection = new \\ReflectionClass($class); + $previousClass = $class; - if (! $reflection->isSubclassOf('Livewire\\Volt\\Component')) { - continue; - } + if (! \\Illuminate\\Support\\Str::contains($class, '@anonymous')) { + return $item; + } - $components[] = [ - ...$item, - 'props' => $this->getComponentProps($reflection), - ]; - } + $reflection = new \\ReflectionClass($class); - return $components; + if (! $reflection->isSubclassOf('Livewire\\Volt\\Component')) { + return $item; + } + + return [ + ...$item, + 'props' => $this->getComponentProps($reflection), + ]; + }) + ->all(); } /** From 0d9fc180527460092e26e729996c13992434174b Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Thu, 27 Mar 2025 22:26:43 +0000 Subject: [PATCH 04/11] fix --- php-templates/livewire-components.php | 27 --------------------------- src/templates/livewire-components.ts | 27 --------------------------- 2 files changed, 54 deletions(-) diff --git a/php-templates/livewire-components.php b/php-templates/livewire-components.php index 888c5827..9c4abc75 100644 --- a/php-templates/livewire-components.php +++ b/php-templates/livewire-components.php @@ -1,32 +1,5 @@ getMessage()); - } -} - $components = new class { public function all(): array { diff --git a/src/templates/livewire-components.ts b/src/templates/livewire-components.ts index a0e95284..84295742 100644 --- a/src/templates/livewire-components.ts +++ b/src/templates/livewire-components.ts @@ -1,32 +1,5 @@ // This file was generated from php-templates/livewire-components.php, do not edit directly export default ` -class LaravelVsCode -{ - public static function relativePath($path) - { - if (!str_contains($path, base_path())) { - return (string) $path; - } - - return ltrim(str_replace(base_path(), '', realpath($path) ?: $path), DIRECTORY_SEPARATOR); - } - - public static function isVendor($path) - { - return str_contains($path, base_path("vendor")); - } - - public static function outputMarker($key) - { - return '__VSCODE_LARAVEL_' . $key . '__'; - } - - public static function startupError(\\Throwable $e) - { - throw new Error(self::outputMarker('STARTUP_ERROR') . ': ' . $e->getMessage()); - } -} - $components = new class { public function all(): array { From fd9f181e6ea61f6c92bea2354709d79f725a9380 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 28 Mar 2025 08:06:10 +0000 Subject: [PATCH 05/11] refactoring --- php-templates/livewire-components.php | 33 +++++++++++++++------------ src/templates/livewire-components.ts | 33 +++++++++++++++------------ 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/php-templates/livewire-components.php b/php-templates/livewire-components.php index 9c4abc75..f5b966a4 100644 --- a/php-templates/livewire-components.php +++ b/php-templates/livewire-components.php @@ -6,10 +6,10 @@ public function all(): array $components = collect(array_merge( $this->getStandardClasses(), $this->getStandardViews() - ))->groupBy('key')->map(fn($items) => [ + ))->groupBy('key')->map(fn (\Illuminate\Support\Collection $items) => [ 'isVendor' => $items->first()['isVendor'], 'paths' => $items->pluck('path')->values(), - 'props' => $items->pluck('props')->values()->filter()->flatMap(fn($i) => $i), + 'props' => $items->pluck('props')->values()->filter()->flatMap(fn ($i) => $i), ]); return [ @@ -41,7 +41,7 @@ protected function findFiles(string $path, string $extension, \Closure $keyCallb ->ltrim('/\\') ->replace('.' . $extension, '') ->replace(['/', '\\'], '.') - ->pipe(fn(string $str): string => $str); + ->pipe(fn (string $str): string => $str); $components[] = [ "path" => LaravelVsCode::relativePath($realPath), @@ -64,24 +64,24 @@ protected function getStandardClasses(): array $path = str($classNamespace) ->replace('\\', DIRECTORY_SEPARATOR) - ->replace('App', 'app') + ->lcfirst() ->toString(); $items = $this->findFiles( $path, 'php', - fn(\Illuminate\Support\Stringable $key): string => $key->explode('.') - ->map(fn(string $p): string => \Illuminate\Support\Str::kebab($p)) + fn (\Illuminate\Support\Stringable $key): string => $key->explode('.') + ->map(fn (string $p): string => \Illuminate\Support\Str::kebab($p)) ->implode('.'), ); return collect($items) ->map(function ($item) { $class = str($item['path']) - ->replace('.php', '') - ->replace('/', '\\') - ->ucfirst() - ->toString(); + ->replace('.php', '') + ->replace(DIRECTORY_SEPARATOR, '\\') + ->ucfirst() + ->toString(); if (! class_exists($class)) { return null; @@ -115,7 +115,7 @@ protected function getStandardViews(): array $items = $this->findFiles( $path, 'blade.php', - fn(\Illuminate\Support\Stringable $key): string => $key->explode('.') + fn (\Illuminate\Support\Stringable $key): string => $key->explode('.') ->map(fn(string $p): string => \Illuminate\Support\Str::kebab($p)) ->implode('.'), ); @@ -177,14 +177,15 @@ protected function getComponentProps(ReflectionClass $reflection): array $mountMethods = array_filter( $methods, - fn (\ReflectionMethod $method): bool => strpos($method->getName(), 'mount') === 0 + fn (\ReflectionMethod $method): bool => + \Illuminate\Support\Str::startsWith($method->getName(), 'mount') ); foreach ($mountMethods as $method) { $parameters = $method->getParameters(); $parameters = collect($parameters) - ->map(fn(\ReflectionParameter $p) => [ + ->map(fn (\ReflectionParameter $p): array => [ 'name' => \Illuminate\Support\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), 'default' => $p->isOptional() ? $p->getDefaultValue() : null @@ -197,8 +198,10 @@ protected function getComponentProps(ReflectionClass $reflection): array // Then we need to get the public properties $properties = collect($reflection->getProperties()) - ->filter(fn(\ReflectionProperty $p) => $p->isPublic() && $p->getDeclaringClass()->getName() === $reflection->getName()) - ->map(fn(\ReflectionProperty $p) => [ + ->filter(fn (\ReflectionProperty $p): bool => + $p->isPublic() && $p->getDeclaringClass()->getName() === $reflection->getName() + ) + ->map(fn (\ReflectionProperty $p): array => [ 'name' => \Illuminate\Support\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), 'default' => $p->getDefaultValue() diff --git a/src/templates/livewire-components.ts b/src/templates/livewire-components.ts index 84295742..d6849036 100644 --- a/src/templates/livewire-components.ts +++ b/src/templates/livewire-components.ts @@ -6,10 +6,10 @@ $components = new class { $components = collect(array_merge( $this->getStandardClasses(), $this->getStandardViews() - ))->groupBy('key')->map(fn($items) => [ + ))->groupBy('key')->map(fn (\\Illuminate\\Support\\Collection $items) => [ 'isVendor' => $items->first()['isVendor'], 'paths' => $items->pluck('path')->values(), - 'props' => $items->pluck('props')->values()->filter()->flatMap(fn($i) => $i), + 'props' => $items->pluck('props')->values()->filter()->flatMap(fn ($i) => $i), ]); return [ @@ -41,7 +41,7 @@ $components = new class { ->ltrim('/\\\\') ->replace('.' . $extension, '') ->replace(['/', '\\\\'], '.') - ->pipe(fn(string $str): string => $str); + ->pipe(fn (string $str): string => $str); $components[] = [ "path" => LaravelVsCode::relativePath($realPath), @@ -64,24 +64,24 @@ $components = new class { $path = str($classNamespace) ->replace('\\\\', DIRECTORY_SEPARATOR) - ->replace('App', 'app') + ->lcfirst() ->toString(); $items = $this->findFiles( $path, 'php', - fn(\\Illuminate\\Support\\Stringable $key): string => $key->explode('.') - ->map(fn(string $p): string => \\Illuminate\\Support\\Str::kebab($p)) + fn (\\Illuminate\\Support\\Stringable $key): string => $key->explode('.') + ->map(fn (string $p): string => \\Illuminate\\Support\\Str::kebab($p)) ->implode('.'), ); return collect($items) ->map(function ($item) { $class = str($item['path']) - ->replace('.php', '') - ->replace('/', '\\\\') - ->ucfirst() - ->toString(); + ->replace('.php', '') + ->replace(DIRECTORY_SEPARATOR, '\\\\') + ->ucfirst() + ->toString(); if (! class_exists($class)) { return null; @@ -115,7 +115,7 @@ $components = new class { $items = $this->findFiles( $path, 'blade.php', - fn(\\Illuminate\\Support\\Stringable $key): string => $key->explode('.') + fn (\\Illuminate\\Support\\Stringable $key): string => $key->explode('.') ->map(fn(string $p): string => \\Illuminate\\Support\\Str::kebab($p)) ->implode('.'), ); @@ -177,14 +177,15 @@ $components = new class { $mountMethods = array_filter( $methods, - fn (\\ReflectionMethod $method): bool => strpos($method->getName(), 'mount') === 0 + fn (\\ReflectionMethod $method): bool => + \\Illuminate\\Support\\Str::startsWith($method->getName(), 'mount') ); foreach ($mountMethods as $method) { $parameters = $method->getParameters(); $parameters = collect($parameters) - ->map(fn(\\ReflectionParameter $p) => [ + ->map(fn (\\ReflectionParameter $p): array => [ 'name' => \\Illuminate\\Support\\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), 'default' => $p->isOptional() ? $p->getDefaultValue() : null @@ -197,8 +198,10 @@ $components = new class { // Then we need to get the public properties $properties = collect($reflection->getProperties()) - ->filter(fn(\\ReflectionProperty $p) => $p->isPublic() && $p->getDeclaringClass()->getName() === $reflection->getName()) - ->map(fn(\\ReflectionProperty $p) => [ + ->filter(fn (\\ReflectionProperty $p): bool => + $p->isPublic() && $p->getDeclaringClass()->getName() === $reflection->getName() + ) + ->map(fn (\\ReflectionProperty $p): array => [ 'name' => \\Illuminate\\Support\\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), 'default' => $p->getDefaultValue() From ced4cb6b183591ba2641ec79d13776e479626c31 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 28 Mar 2025 08:39:10 +0000 Subject: [PATCH 06/11] add isOptional to method parameters --- php-templates/livewire-components.php | 3 +++ src/templates/livewire-components.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/php-templates/livewire-components.php b/php-templates/livewire-components.php index f5b966a4..520b4894 100644 --- a/php-templates/livewire-components.php +++ b/php-templates/livewire-components.php @@ -188,6 +188,9 @@ protected function getComponentProps(ReflectionClass $reflection): array ->map(fn (\ReflectionParameter $p): array => [ 'name' => \Illuminate\Support\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), + // We need to add isOptional, because null can be also a default value, + // it can't be a flag of no default + 'isOptional' => $p->isOptional(), 'default' => $p->isOptional() ? $p->getDefaultValue() : null ]) ->all(); diff --git a/src/templates/livewire-components.ts b/src/templates/livewire-components.ts index d6849036..48760287 100644 --- a/src/templates/livewire-components.ts +++ b/src/templates/livewire-components.ts @@ -188,6 +188,9 @@ $components = new class { ->map(fn (\\ReflectionParameter $p): array => [ 'name' => \\Illuminate\\Support\\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), + // We need to add isOptional, because null can be also a default value, + // it can't be a flag of no default + 'isOptional' => $p->isOptional(), 'default' => $p->isOptional() ? $p->getDefaultValue() : null ]) ->all(); From 28f626d8b9e4b91de35da6ce0ba4de9f09c3eb2f Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 28 Mar 2025 08:42:00 +0000 Subject: [PATCH 07/11] rename isOptional to hasDefault --- php-templates/livewire-components.php | 5 +++-- src/templates/livewire-components.ts | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/php-templates/livewire-components.php b/php-templates/livewire-components.php index 520b4894..bfe8de78 100644 --- a/php-templates/livewire-components.php +++ b/php-templates/livewire-components.php @@ -188,9 +188,9 @@ protected function getComponentProps(ReflectionClass $reflection): array ->map(fn (\ReflectionParameter $p): array => [ 'name' => \Illuminate\Support\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), - // We need to add isOptional, because null can be also a default value, + // We need to add hasDefault, because null can be also a default value, // it can't be a flag of no default - 'isOptional' => $p->isOptional(), + 'hasDefault' => $p->isDefaultValueAvailable(), 'default' => $p->isOptional() ? $p->getDefaultValue() : null ]) ->all(); @@ -207,6 +207,7 @@ protected function getComponentProps(ReflectionClass $reflection): array ->map(fn (\ReflectionProperty $p): array => [ 'name' => \Illuminate\Support\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), + 'hasDefault' => $p->hasDefaultValue(), 'default' => $p->getDefaultValue() ]) ->all(); diff --git a/src/templates/livewire-components.ts b/src/templates/livewire-components.ts index 48760287..9cffa42d 100644 --- a/src/templates/livewire-components.ts +++ b/src/templates/livewire-components.ts @@ -190,7 +190,7 @@ $components = new class { 'type' => (string) ($p->getType() ?? 'mixed'), // We need to add isOptional, because null can be also a default value, // it can't be a flag of no default - 'isOptional' => $p->isOptional(), + 'hasDefault' => $p->isDefaultValueAvailable(), 'default' => $p->isOptional() ? $p->getDefaultValue() : null ]) ->all(); @@ -207,6 +207,7 @@ $components = new class { ->map(fn (\\ReflectionProperty $p): array => [ 'name' => \\Illuminate\\Support\\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), + 'hasDefault' => $p->hasDefaultValue(), 'default' => $p->getDefaultValue() ]) ->all(); From 830ef90e28ffd19a2279ced8dafe2cf3f8ddd454 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 28 Mar 2025 08:42:22 +0000 Subject: [PATCH 08/11] rename isOptional to hasDefault --- src/templates/livewire-components.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/livewire-components.ts b/src/templates/livewire-components.ts index 9cffa42d..a6ed3dc8 100644 --- a/src/templates/livewire-components.ts +++ b/src/templates/livewire-components.ts @@ -188,7 +188,7 @@ $components = new class { ->map(fn (\\ReflectionParameter $p): array => [ 'name' => \\Illuminate\\Support\\Str::kebab($p->getName()), 'type' => (string) ($p->getType() ?? 'mixed'), - // We need to add isOptional, because null can be also a default value, + // We need to add hasDefault, because null can be also a default value, // it can't be a flag of no default 'hasDefault' => $p->isDefaultValueAvailable(), 'default' => $p->isOptional() ? $p->getDefaultValue() : null From 0ef652444c940e0f5f9b12575f023d8bce830c80 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 28 Mar 2025 09:20:41 +0000 Subject: [PATCH 09/11] fix docblock --- php-templates/livewire-components.php | 2 +- src/templates/livewire-components.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/php-templates/livewire-components.php b/php-templates/livewire-components.php index bfe8de78..16230fcd 100644 --- a/php-templates/livewire-components.php +++ b/php-templates/livewire-components.php @@ -164,7 +164,7 @@ protected function getStandardViews(): array } /** - * @return array + * @return array */ protected function getComponentProps(ReflectionClass $reflection): array { diff --git a/src/templates/livewire-components.ts b/src/templates/livewire-components.ts index a6ed3dc8..f708577b 100644 --- a/src/templates/livewire-components.ts +++ b/src/templates/livewire-components.ts @@ -164,7 +164,7 @@ $components = new class { } /** - * @return array + * @return array */ protected function getComponentProps(ReflectionClass $reflection): array { From 574cdf74d79665a75f2aea81a4f185dd26ec3e73 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 28 Mar 2025 11:05:42 +0000 Subject: [PATCH 10/11] remove project path --- src/repositories/livewireComponents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repositories/livewireComponents.ts b/src/repositories/livewireComponents.ts index 4aaa9cc7..00295458 100644 --- a/src/repositories/livewireComponents.ts +++ b/src/repositories/livewireComponents.ts @@ -24,7 +24,7 @@ export interface LivewireComponents { const load = () => { getConfigs().whenLoaded(() => { livewirePaths = [ - projectPath(lcfirst(getConfigByName('livewire.class_namespace')?.value?.replace('\\', '/') ?? 'app/Livewire')), + lcfirst(getConfigByName('livewire.class_namespace')?.value?.replace('\\', '/') ?? 'app/Livewire'), getConfigByName('livewire.view_path')?.value ?? 'resources/views/livewire' ]; }); From c566ee5c88b3ee97b44967f0e402066efb83e8d5 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 28 Mar 2025 11:10:24 +0000 Subject: [PATCH 11/11] Add a repository for Livewire components Fixes N1ebieski/vs-code-extension#29 --- src/repositories/livewireComponents.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/repositories/livewireComponents.ts b/src/repositories/livewireComponents.ts index 00295458..7e57f1a0 100644 --- a/src/repositories/livewireComponents.ts +++ b/src/repositories/livewireComponents.ts @@ -1,5 +1,5 @@ import { runInLaravel, template } from "@src/support/php"; -import { projectPath } from "@src/support/project"; +import { relativePath } from "@src/support/project"; import { lcfirst } from "@src/support/str"; import { waitForValue } from "@src/support/util"; import { repository } from "."; @@ -15,6 +15,7 @@ export interface LivewireComponents { props: { name: string; type: string; + hasDefault: boolean; default: string | null; }[]; }; @@ -24,8 +25,8 @@ export interface LivewireComponents { const load = () => { getConfigs().whenLoaded(() => { livewirePaths = [ - lcfirst(getConfigByName('livewire.class_namespace')?.value?.replace('\\', '/') ?? 'app/Livewire'), - getConfigByName('livewire.view_path')?.value ?? 'resources/views/livewire' + lcfirst(getConfigByName('livewire.class_namespace')?.value?.replace(/\\/g, '/') ?? 'app/Livewire'), + relativePath(getConfigByName('livewire.view_path')?.value ?? 'resources/views/livewire') ]; });