Compare commits

...
Author SHA1 Message Date
laravel-ide-helper 4c2f68f14a composer fix-style 2025-01-08 10:00:25 +00:00
Barry vd. Heuvel a11dd14ee5 Check if macro is valid 2025-01-08 10:59:45 +01:00
barryvdh 088968c3fc Update CHANGELOG 2025-01-06 15:42:30 +00:00
Barry vd. Heuvel 655e057626 Fix empty closure 2025-01-06 16:29:58 +01:00
barryvdh c8d85a0d34 Update CHANGELOG 2025-01-06 14:09:45 +00:00
Barry vd. Heuvel 4c656cc71d Remove duplicate config, fix ->can() (#1650) 2025-01-06 15:08:47 +01:00
barryvdh 0fe9774dfb Update CHANGELOG 2025-01-06 13:53:52 +00:00
Jesper Noordsij 030f6f1f35 Add special 'dev' to composer keywords (#1649) 2025-01-03 15:21:18 +01:00
laravel-ide-helper 154300fb06 composer fix-style 2025-01-01 12:17:43 +00:00
Mathieu TUDISCOandBarry vd. Heuvel 05d9c3cd65 Allow adding custom Macroable classes. (#1629)
* Allow adding custom Macroable classes.

* Remove duplication, tweak config

* Remove test

---------

Co-authored-by: Barry vd. Heuvel <[email protected]>
2025-01-01 13:17:20 +01:00
Barry vd. Heuvelandlaravel-ide-helper beda399e7e Feat guess macro types (#1648)
* Add default class for HttpClient macro

* composer fix-style

---------

Co-authored-by: laravel-ide-helper <[email protected]>
2025-01-01 10:44:43 +01:00
Nereo Berardozzi 0bbca8f5e2 Fixed generating PHPDoc for methods with class templates (#1647)
* Fixed generating PHPDoc for methods with class templates

* Fix style

* Use getter for alias template names

* Fixed missing class in static analysis
2024-12-31 17:18:28 +01:00
Barry vd. Heuvelandlaravel-ide-helper f8381565ad Add more metadata (#1646)
* Add more metadata

* composer fix-style

* Parse .env

* composer fix-style

* Check if .env exists

---------

Co-authored-by: laravel-ide-helper <[email protected]>
2024-12-30 15:06:36 +01:00
Barry vd. Heuvel cffcd07e98 Also add eloquent template tags from base class (#1645) 2024-12-30 12:58:28 +01:00
Barry vd. Heuvelandlaravel-ide-helper 9c6380da4a Remove php parser (#1644)
* Remove php parser

* composer fix-style

---------

Co-authored-by: laravel-ide-helper <[email protected]>
2024-12-30 12:42:35 +01:00
Barry vd. Heuvelandlaravel-ide-helper e509790805 Use forked ContextFactory (#1643)
* Use forked ContextFactory

* composer fix-style

* Trim slashes

---------

Co-authored-by: laravel-ide-helper <[email protected]>
2024-12-30 12:03:45 +01:00
Barry vd. Heuvelandlaravel-ide-helper 791ba555f4 Add meta override for user return types (#1642)
* Add meta override for user return types

* composer fix-style

---------

Co-authored-by: laravel-ide-helper <[email protected]>
2024-12-30 10:31:30 +01:00
Barry vd. Heuvel c324438731 Bump to 3.5 2024-12-29 19:44:30 +01:00
Barry vd. Heuvelandlaravel-ide-helper 4e4da0825a Add phpstorm meta argument hints (#1640)
* Add phpstorm meta hints

* composer fix-style

* Use php-templates from vs-code

* Fix view instance

* composer fix-style

* Add translations

* composer fix-style

* map to string

* composer fix-style

* Fix test

---------

Co-authored-by: laravel-ide-helper <[email protected]>
2024-12-29 19:44:08 +01:00
barryvdh 66ea6aa57b Update CHANGELOG 2024-12-29 13:26:59 +00:00
25 changed files with 982 additions and 306 deletions
+67
View File
@@ -1,5 +1,69 @@
# Changelog # Changelog
## v3.5.2 - 2025-01-06
### Fixes
Fix empty/anonymous closure in meta command.
**Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.1...v3.5.2
## v3.5.1 - 2025-01-06
### What's Changed
* Remove duplicate config, fix ->can() by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1650
**Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.5.0...v3.5.1
## v3.5.0 - 2025-01-06
### What's Changed
* Add phpstorm meta argument hints by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1640
* Add meta override for user return types by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1642
* Use forked ContextFactory by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1643
* Remove php parser by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1644
* Also add eloquent template tags from base class by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1645
* Add more metadata by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1646
* Fixed generating PHPDoc for methods with class templates by @chack1172 in https://github.com/barryvdh/laravel-ide-helper/pull/1647
* Feat guess macro types by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1648
* Allow adding custom Macroable classes. by @mathieutu in https://github.com/barryvdh/laravel-ide-helper/pull/1629
* Add special `dev` to composer keywords by @jnoordsij in https://github.com/barryvdh/laravel-ide-helper/pull/1649
### New Contributors
* @chack1172 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1647
* @mathieutu made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1629
* @jnoordsij made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1649
**Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.4.0...v3.5.0
## v3.4.0 - 2024-12-29
### What's Changed
* fix: add @template TModel of static for Eloquent by @imzyf in https://github.com/barryvdh/laravel-ide-helper/pull/1631
* Add templates to Eloquent by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1634
* Update testsuite for Generator, simplify service provider and mock by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1635
* Add option for only eloquent by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1636
* Add weak generics for array type objects by @LauJosefsen in https://github.com/barryvdh/laravel-ide-helper/pull/1621
* Make all "note" in README apply quote style by @hms5232 in https://github.com/barryvdh/laravel-ide-helper/pull/1590
* Update README.md by @Mtillmann in https://github.com/barryvdh/laravel-ide-helper/pull/1587
* Rename view var by @barryvdh and @pb30 in https://github.com/barryvdh/laravel-ide-helper/pull/1637 and https://github.com/barryvdh/laravel-ide-helper/pull/1563
* Format IDE helper by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1638
* Add TLDR section, update options by @barryvdh in https://github.com/barryvdh/laravel-ide-helper/pull/1639
### New Contributors
* @imzyf made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1631
* @LauJosefsen made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1621
* @hms5232 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1590
* @Mtillmann made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1587
* @pb30 made their first contribution in https://github.com/barryvdh/laravel-ide-helper/pull/1563
**Full Changelog**: https://github.com/barryvdh/laravel-ide-helper/compare/v3.3.0...v3.4.0
## v3.3.0 - 2024-12-18 ## v3.3.0 - 2024-12-18
### What's Changed ### What's Changed
@@ -54,11 +118,14 @@
- Fix type of hashed model property to `string` - Fix type of hashed model property to `string`
### Changed ### Changed
- Update view "version" variable name to avoid potential conflicts - Update view "version" variable name to avoid potential conflicts
- Add support for EloquentBuilder generics introduced in Laravel 11.15. - Add support for EloquentBuilder generics introduced in Laravel 11.15.
- Drop support for Laravel versions earlier than 11.15. - Drop support for Laravel versions earlier than 11.15.
### Added ### Added
- Introduce `enforce_nullable_relationships` configuration option to control how nullable Eloquent relationships are enforced during static analysis. This provides flexibility for scenarios where application logic ensures data integrity without relying on database constraints. [#1580 / jeramyhing](https://github.com/barryvdh/laravel-ide-helper/pull/1580) - Introduce `enforce_nullable_relationships` configuration option to control how nullable Eloquent relationships are enforced during static analysis. This provides flexibility for scenarios where application logic ensures data integrity without relying on database constraints. [#1580 / jeramyhing](https://github.com/barryvdh/laravel-ide-helper/pull/1580)
+2
View File
@@ -115,6 +115,8 @@ Str::macro('concat', function(string $str1, string $str2) : string {
}); });
``` ```
You can add any custom Macroable traits to detect in the `macroable_traits` config option.
### Automatic PHPDocs for models ### Automatic PHPDocs for models
If you don't want to write your properties yourself, you can use the command `php artisan ide-helper:models` to generate If you don't want to write your properties yourself, you can use the command `php artisan ide-helper:models` to generate
+7 -7
View File
@@ -11,7 +11,8 @@
"netbeans", "netbeans",
"sublime", "sublime",
"codeintel", "codeintel",
"phpdoc" "phpdoc",
"dev"
], ],
"authors": [ "authors": [
{ {
@@ -22,14 +23,12 @@
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"ext-json": "*", "ext-json": "*",
"barryvdh/reflection-docblock": "^2.2", "barryvdh/reflection-docblock": "^2.3",
"composer/class-map-generator": "^1.0", "composer/class-map-generator": "^1.0",
"illuminate/console": "^11.15", "illuminate/console": "^11.15",
"illuminate/database": "^11.15", "illuminate/database": "^11.15",
"illuminate/filesystem": "^11.15", "illuminate/filesystem": "^11.15",
"illuminate/support": "^11.15", "illuminate/support": "^11.15"
"nikic/php-parser": "^4.18 || ^5",
"phpdocumentor/type-resolver": "^1.1.0"
}, },
"require-dev": { "require-dev": {
"ext-pdo_sqlite": "*", "ext-pdo_sqlite": "*",
@@ -40,7 +39,8 @@
"orchestra/testbench": "^9.2", "orchestra/testbench": "^9.2",
"phpunit/phpunit": "^10.5", "phpunit/phpunit": "^10.5",
"spatie/phpunit-snapshot-assertions": "^4 || ^5", "spatie/phpunit-snapshot-assertions": "^4 || ^5",
"vimeo/psalm": "^5.4" "vimeo/psalm": "^5.4",
"vlucas/phpdotenv": "^5"
}, },
"suggest": { "suggest": {
"illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9|^10|^11)." "illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9|^10|^11)."
@@ -65,7 +65,7 @@
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.4-dev" "dev-master": "3.5-dev"
}, },
"laravel": { "laravel": {
"providers": [ "providers": [
+14
View File
@@ -334,4 +334,18 @@ return [
// 'ide-helper:models --nowrite', // 'ide-helper:models --nowrite',
], ],
/*
|--------------------------------------------------------------------------
| Macroable Traits
|--------------------------------------------------------------------------
|
| Define which traits should be considered capable of adding Macro.
| You can add any custom trait that behaves like the original Laravel one.
|
*/
'macroable_traits' => [
Filament\Support\Concerns\Macroable::class,
Spatie\Macroable\Macroable::class,
],
]; ];
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+5
View File
@@ -0,0 +1,5 @@
The templates here are based on the official VS Code extension by Laravel https://github.com/laravel/vs-code-extension
Modifications:
- return instead of echo
- do not serialize to JSON
+43
View File
@@ -0,0 +1,43 @@
<?php
return collect(Illuminate\Support\Facades\Gate::abilities())
->map(function ($policy, $key) {
$reflection = new ReflectionFunction($policy);
$policyClass = null;
$closureThis = $reflection->getClosureThis();
if ($closureThis && get_class($closureThis) === Illuminate\Auth\Access\Gate::class) {
$vars = $reflection->getClosureUsedVariables();
if (isset($vars['callback'])) {
[$policyClass, $method] = explode('@', $vars['callback']);
$reflection = new ReflectionMethod($policyClass, $method);
}
}
return [
'key' => $key,
'uri' => $reflection->getFileName(),
'policy_class' => $policyClass,
'lineNumber' => $reflection->getStartLine(),
];
})
->merge(
collect(Illuminate\Support\Facades\Gate::policies())->flatMap(function ($policy, $model) {
$methods = (new ReflectionClass($policy))->getMethods();
return collect($methods)->map(function (ReflectionMethod $method) use ($policy) {
return [
'key' => $method->getName(),
'uri' => $method->getFileName(),
'policy_class' => $policy,
'lineNumber' => $method->getStartLine(),
];
})->filter(function ($ability) {
return !in_array($ability['key'], ['allow', 'deny']);
});
}),
)
->values()
->groupBy('key');
+131
View File
@@ -0,0 +1,131 @@
<?php
$local = collect(glob(config_path('/*.php')))
->merge(glob(config_path('**/*.php')))
->map(fn ($path) => [
(string) Illuminate\Support\Str::of($path)
->replace([config_path('/'), '.php'], '')
->replace('/', '.'),
$path,
]);
$vendor = collect(glob(base_path('vendor/**/**/config/*.php')))->map(fn (
$path
) => [
(string) Illuminate\Support\Str::of($path)
->afterLast('/config/')
->replace('.php', '')
->replace('/', '.'),
$path,
]);
$configPaths = $local
->merge($vendor)
->groupBy(0)
->map(fn ($items) => $items->pluck(1));
$cachedContents = [];
$cachedParsed = [];
function vsCodeGetConfigValue($value, $key, $configPaths)
{
$parts = explode('.', $key);
$toFind = $key;
$found = null;
while (count($parts) > 0) {
array_pop($parts);
$toFind = implode('.', $parts);
if ($configPaths->has($toFind)) {
$found = $toFind;
break;
}
}
if ($found === null) {
return null;
}
$file = null;
$line = null;
if ($found === $key) {
$file = $configPaths->get($found)[0];
} else {
foreach ($configPaths->get($found) as $path) {
$cachedContents[$path] ??= file_get_contents($path);
$cachedParsed[$path] ??= token_get_all($cachedContents[$path]);
$keysToFind = Illuminate\Support\Str::of($key)
->replaceFirst($found, '')
->ltrim('.')
->explode('.');
if (is_numeric($keysToFind->last())) {
$index = $keysToFind->pop();
if ($index !== '0') {
return null;
}
$key = collect(explode('.', $key));
$key->pop();
$key = $key->implode('.');
$value = 'array(...)';
}
$nextKey = $keysToFind->shift();
$expectedDepth = 1;
$depth = 0;
foreach ($cachedParsed[$path] as $token) {
if ($token === '[') {
$depth++;
}
if ($token === ']') {
$depth--;
}
if (!is_array($token)) {
continue;
}
$str = trim($token[1], '"\'');
if (
$str === $nextKey &&
$depth === $expectedDepth &&
$token[0] === T_CONSTANT_ENCAPSED_STRING
) {
$nextKey = $keysToFind->shift();
$expectedDepth++;
if ($nextKey === null) {
$file = $path;
$line = $token[2];
break;
}
}
}
if ($file) {
break;
}
}
}
return [
'name' => $key,
'value' => $value,
'file' => $file === null ? null : str_replace(base_path('/'), '', $file),
'line' => $line,
];
}
return collect(Illuminate\Support\Arr::dot(config()->all()))
->map(fn ($value, $key) => vsCodeGetConfigValue($value, $key, $configPaths))
->filter()
->values();
+64
View File
@@ -0,0 +1,64 @@
<?php
return collect(app("Illuminate\Contracts\Http\Kernel")->getMiddlewareGroups())
->merge(app("Illuminate\Contracts\Http\Kernel")->getRouteMiddleware())
->map(function ($middleware, $key) {
$result = [
'class' => null,
'uri' => null,
'startLine' => null,
'parameters' => null,
'groups' => [],
];
if (is_array($middleware)) {
$result['groups'] = collect($middleware)->map(function ($m) {
if (!class_exists($m)) {
return [
'class' => $m,
'uri' => null,
'startLine' => null,
];
}
$reflected = new ReflectionClass($m);
$reflectedMethod = $reflected->getMethod('handle');
return [
'class' => $m,
'uri' => $reflected->getFileName(),
'startLine' =>
$reflectedMethod->getFileName() === $reflected->getFileName()
? $reflectedMethod->getStartLine()
: null,
];
})->all();
return $result;
}
$reflected = new ReflectionClass($middleware);
$reflectedMethod = $reflected->getMethod('handle');
$result = array_merge($result, [
'class' => $middleware,
'uri' => $reflected->getFileName(),
'startLine' => $reflectedMethod->getStartLine(),
]);
$parameters = collect($reflectedMethod->getParameters())
->filter(function ($rc) {
return $rc->getName() !== 'request' && $rc->getName() !== 'next';
})
->map(function ($rc) {
return $rc->getName() . ($rc->isVariadic() ? '...' : '');
});
if ($parameters->isEmpty()) {
return $result;
}
return array_merge($result, [
'parameters' => $parameters->implode(','),
]);
});
+46
View File
@@ -0,0 +1,46 @@
<?php
function vsCodeGetRouterReflection(Illuminate\Routing\Route $route)
{
if ($route->getActionName() === 'Closure') {
return new ReflectionFunction($route->getAction()['uses']);
}
if (!str_contains($route->getActionName(), '@')) {
return new ReflectionClass($route->getActionName());
}
try {
return new ReflectionMethod($route->getControllerClass(), $route->getActionMethod());
} catch (Throwable $e) {
$namespace = app(Illuminate\Routing\UrlGenerator::class)->getRootControllerNamespace()
?? (app()->getNamespace() . 'Http\Controllers');
return new ReflectionMethod(
$namespace . '\\' . ltrim($route->getControllerClass(), '\\'),
$route->getActionMethod(),
);
}
}
return collect(app('router')->getRoutes()->getRoutes())
->map(function (Illuminate\Routing\Route $route) {
try {
$reflection = vsCodeGetRouterReflection($route);
} catch (Throwable $e) {
$reflection = null;
}
return [
'method' => collect($route->methods())->filter(function ($method) {
return $method !== 'HEAD';
})->implode('|'),
'uri' => $route->uri(),
'name' => $route->getName(),
'action' => $route->getActionName(),
'parameters' => $route->parameterNames(),
'filename' => $reflection ? $reflection->getFileName() : null,
'line' => $reflection ? $reflection->getStartLine() : null,
];
})
;
+143
View File
@@ -0,0 +1,143 @@
<?php
function vsCodeGetTranslationsFromFile($file, $path, $namespace)
{
$key = pathinfo($file, PATHINFO_FILENAME);
if ($namespace) {
$key = "{$namespace}::{$key}";
}
$lang = collect(explode(DIRECTORY_SEPARATOR, str_replace($path, '', $file)))
->filter()
->first();
$fileLines = Illuminate\Support\Facades\File::lines($file);
$lines = [];
$inComment = false;
foreach ($fileLines as $index => $line) {
$trimmed = trim($line);
if (substr($trimmed, 0, 2) === '/*') {
$inComment = true;
continue;
}
if ($inComment) {
if (substr($trimmed, -2) !== '*/') {
continue;
}
$inComment = false;
}
if (substr($trimmed, 0, 2) === '//') {
continue;
}
$lines[] = [$index + 1, $trimmed];
}
return [
'k' => $key,
'la' => $lang,
'vs' => collect(Illuminate\Support\Arr::dot((Illuminate\Support\Arr::wrap(__($key, [], $lang)))))
->map(
fn ($value, $key) => vsCodeTranslationValue(
$key,
$value,
str_replace(base_path(DIRECTORY_SEPARATOR), '', $file),
$lines
)
)
->filter(),
];
}
function vsCodeTranslationValue($key, $value, $file, $lines): ?array
{
if (is_array($value)) {
return null;
}
$lineNumber = 1;
$keys = explode('.', $key);
$index = 0;
$currentKey = array_shift($keys);
foreach ($lines as $index => $line) {
if (
strpos($line[1], '"' . $currentKey . '"', 0) !== false ||
strpos($line[1], "'" . $currentKey . "'", 0) !== false
) {
$lineNumber = $line[0];
$currentKey = array_shift($keys);
}
if ($currentKey === null) {
break;
}
}
return [
'v' => $value,
'p' => $file,
'li' => $lineNumber,
'pa' => preg_match_all("/\:([A-Za-z0-9_]+)/", $value, $matches)
? $matches[1]
: [],
];
}
function vscodeCollectTranslations(string $path, ?string $namespace = null)
{
$realPath = realpath($path);
if (!is_dir($realPath)) {
return collect();
}
return collect(Illuminate\Support\Facades\File::allFiles($realPath))->map(
fn ($file) => vsCodeGetTranslationsFromFile($file, $path, $namespace)
);
}
$loader = app('translator')->getLoader();
$namespaces = $loader->namespaces();
$reflection = new ReflectionClass($loader);
$property = $reflection->hasProperty('paths')
? $reflection->getProperty('paths')
: $reflection->getProperty('path');
$property->setAccessible(true);
$paths = Illuminate\Support\Arr::wrap($property->getValue($loader));
$default = collect($paths)->flatMap(
fn ($path) => vscodeCollectTranslations($path)
);
$namespaced = collect($namespaces)->flatMap(
fn ($path, $namespace) => vscodeCollectTranslations($path, $namespace)
);
$final = [];
foreach ($default->merge($namespaced) as $value) {
foreach ($value['vs'] as $key => $v) {
$dotKey = "{$value['k']}.{$key}";
if (!isset($final[$dotKey])) {
$final[$dotKey] = [];
}
$final[$dotKey][$value['la']] = $v;
if ($value['la'] === Illuminate\Support\Facades\App::currentLocale()) {
$final[$dotKey]['default'] = $v;
}
}
}
return collect($final);
+61
View File
@@ -0,0 +1,61 @@
<?php
function vsCodeFindBladeFiles($path)
{
$paths = [];
if (!is_dir($path)) {
return $paths;
}
foreach (
Symfony\Component\Finder\Finder::create()
->files()
->name('*.blade.php')
->in($path) as $file
) {
$paths[] = [
'path' => str_replace(base_path(DIRECTORY_SEPARATOR), '', $file->getRealPath()),
'isVendor' => str_contains($file->getRealPath(), base_path('vendor')),
'key' => Illuminate\Support\Str::of($file->getRealPath())
->replace(realpath($path), '')
->replace('.blade.php', '')
->ltrim(DIRECTORY_SEPARATOR)
->replace(DIRECTORY_SEPARATOR, '.'),
];
}
return $paths;
}
$paths = collect(
app('view')
->getFinder()
->getPaths()
)->flatMap(function ($path) {
return vsCodeFindBladeFiles($path);
});
$hints = collect(
app('view')
->getFinder()
->getHints()
)->flatMap(function ($paths, $key) {
return collect($paths)->flatMap(function ($path) use ($key) {
return collect(vsCodeFindBladeFiles($path))->map(function ($value) use (
$key
) {
return array_merge($value, ['key' => "{$key}::{$value['key']}"]);
});
});
});
[$local, $vendor] = $paths
->merge($hints)
->values()
->partition(function ($v) {
return !$v['isVendor'];
});
return $local
->sortBy('key', SORT_NATURAL)
->merge($vendor->sortBy('key', SORT_NATURAL));
+41
View File
@@ -21,6 +21,20 @@ namespace PHPSTORM_META {
])); ]));
<?php endforeach; ?> <?php endforeach; ?>
<?php foreach ($userMethods as $method) : ?>
override(<?= $method ?>, map([
'' => \<?= $userModel ?>::class,
]));
<?php endforeach; ?>
<?php foreach ($configMethods as $method) : ?>
override(<?= $method ?>, map([
<?php foreach ($configValues as $name => $value) : ?>
'<?= $name ?>' => '<?= $value ?>',
<?php endforeach; ?>
]));
<?php endforeach; ?>
<?php if (count($factories)) : ?> <?php if (count($factories)) : ?>
override(\factory(0), map([ override(\factory(0), map([
'' => '@FactoryBuilder', '' => '@FactoryBuilder',
@@ -65,4 +79,31 @@ namespace PHPSTORM_META {
override(\tap(0), type(0)); override(\tap(0), type(0));
override(\optional(0), type(0)); override(\optional(0), type(0));
<?php if (isset($expectedArgumentSets)): ?>
<?php foreach ($expectedArgumentSets as $name => $argumentsList) : ?>
registerArgumentsSet('<?= $name ?>', <?php foreach ($argumentsList as $i => $arg) : ?><?php if($i % 5 == 0) {
echo "\n";
} ?><?= var_export($arg, true); ?>,<?php endforeach; ?>);
<?php endforeach; ?>
<?php endif ?>
<?php if (isset($expectedArguments)) : ?>
<?php foreach ($expectedArguments as $arguments) : ?>
<?php
$classes = isset($arguments['class']) ? (array) $arguments['class'] : [null];
$index = $arguments['index'] ?? 0;
$argumentSet = $arguments['argumentSet'];
$functions = [];
foreach ($classes as $class) {
foreach ((array) $arguments['method'] as $method) {
$functions[] = '\\' . ($class ? ltrim($class, '\\') . '::' : '') . $method . '()';
}
}
?>
<?php foreach ($functions as $function) : ?>
expectedArguments(<?= $function ?>, <?= $index ?>, argumentsSet('<?= $argumentSet ?>'));
<?php endforeach; ?>
<?php endforeach; ?>
<?php endif; ?>
} }
+60 -13
View File
@@ -13,8 +13,10 @@ namespace Barryvdh\LaravelIdeHelper;
use Barryvdh\Reflection\DocBlock; use Barryvdh\Reflection\DocBlock;
use Barryvdh\Reflection\DocBlock\Context; use Barryvdh\Reflection\DocBlock\Context;
use Barryvdh\Reflection\DocBlock\ContextFactory;
use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer;
use Barryvdh\Reflection\DocBlock\Tag\MethodTag; use Barryvdh\Reflection\DocBlock\Tag\MethodTag;
use Barryvdh\Reflection\DocBlock\Tag\TemplateTag;
use Closure; use Closure;
use Illuminate\Config\Repository as ConfigRepository; use Illuminate\Config\Repository as ConfigRepository;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
@@ -44,9 +46,14 @@ class Alias
protected $phpdoc = null; protected $phpdoc = null;
protected $classAliases = []; protected $classAliases = [];
protected $isMacroable = false;
/** @var ConfigRepository */ /** @var ConfigRepository */
protected $config; protected $config;
/** @var string[] */
protected $templateNames;
/** /**
* @param ConfigRepository $config * @param ConfigRepository $config
* @param string $alias * @param string $alias
@@ -55,12 +62,13 @@ class Alias
* @param array $magicMethods * @param array $magicMethods
* @param array $interfaces * @param array $interfaces
*/ */
public function __construct($config, $alias, $facade, $magicMethods = [], $interfaces = []) public function __construct($config, $alias, $facade, $magicMethods = [], $interfaces = [], $isMacroable = false)
{ {
$this->alias = $alias; $this->alias = $alias;
$this->magicMethods = $magicMethods; $this->magicMethods = $magicMethods;
$this->interfaces = $interfaces; $this->interfaces = $interfaces;
$this->config = $config; $this->config = $config;
$this->isMacroable = $isMacroable;
// Make the class absolute // Make the class absolute
$facade = '\\' . ltrim($facade, '\\'); $facade = '\\' . ltrim($facade, '\\');
@@ -82,7 +90,7 @@ class Alias
if (!empty($this->namespace)) { if (!empty($this->namespace)) {
try { try {
$this->classAliases = (new UsesResolver())->loadFromClass($this->root); $this->classAliases = (new ContextFactory())->createFromReflector(new ReflectionClass($this->root))->getNamespaceAliases();
} catch (Throwable $e) { } catch (Throwable $e) {
$this->classAliases = []; $this->classAliases = [];
} }
@@ -346,7 +354,8 @@ class Alias
$magic, $magic,
$this->interfaces, $this->interfaces,
$this->classAliases, $this->classAliases,
$this->getReturnTypeNormalizers($class) $this->getReturnTypeNormalizers($class),
$this->getTemplateNames()
); );
} }
$this->usedMethods[] = $magic; $this->usedMethods[] = $magic;
@@ -378,7 +387,8 @@ class Alias
$method->name, $method->name,
$this->interfaces, $this->interfaces,
$this->classAliases, $this->classAliases,
$this->getReturnTypeNormalizers($reflection) $this->getReturnTypeNormalizers($reflection),
$this->getTemplateNames(),
); );
} }
$this->usedMethods[] = $method->name; $this->usedMethods[] = $method->name;
@@ -388,15 +398,20 @@ class Alias
// Check if the class is macroable // Check if the class is macroable
// (Eloquent\Builder is also macroable but doesn't use Macroable trait) // (Eloquent\Builder is also macroable but doesn't use Macroable trait)
$traits = collect($reflection->getTraitNames()); if ($this->isMacroable || $class === EloquentBuilder::class) {
if ($traits->contains('Illuminate\Support\Traits\Macroable') || $class === EloquentBuilder::class) {
$properties = $reflection->getStaticProperties(); $properties = $reflection->getStaticProperties();
$macros = isset($properties['macros']) ? $properties['macros'] : []; $macros = isset($properties['macros']) ? $properties['macros'] : [];
foreach ($macros as $macro_name => $macro_func) { foreach ($macros as $macro_name => $macro_func) {
if (!in_array($macro_name, $this->usedMethods)) { if (!in_array($macro_name, $this->usedMethods)) {
try {
$method = $this->getMacroFunction($macro_func);
} catch (Throwable $e) {
// Invalid method, skip
continue;
}
// Add macros // Add macros
$this->methods[] = new Macro( $this->methods[] = new Macro(
$this->getMacroFunction($macro_func), $method,
$this->alias, $this->alias,
$reflection, $reflection,
$macro_name, $macro_name,
@@ -476,30 +491,62 @@ class Alias
/** /**
* @param $prefix * @param $prefix
* @return string * @return string
* @throws \ReflectionException
*/ */
public function getPhpDocTemplates($prefix = "\t\t") public function getPhpDocTemplates($prefix = "\t\t")
{ {
$templateDoc = new DocBlock(''); $templateDoc = new DocBlock('');
$serializer = new DocBlockSerializer(1, $prefix); $serializer = new DocBlockSerializer(1, $prefix);
foreach ($this->getTemplateNames() as $templateName) {
$template = new TemplateTag('template', $templateName);
$template->setBound('static');
$template->setDocBlock($templateDoc);
$templateDoc->appendTag($template);
}
return $serializer->getDocComment($templateDoc);
}
/**
* @return string[]
*/
public function getTemplateNames()
{
if (!isset($this->templateNames)) {
$this->detectTemplateNames();
}
return $this->templateNames;
}
/**
* @return void
* @throws \ReflectionException
*/
protected function detectTemplateNames()
{
$templateNames = [];
foreach ($this->classes as $class) { foreach ($this->classes as $class) {
$reflection = new ReflectionClass($class); $reflection = new ReflectionClass($class);
$traits = collect($reflection->getTraitNames()); $traits = collect($reflection->getTraitNames());
$phpdoc = new DocBlock($reflection);
$templates = $phpdoc->getTagsByName('template');
/** @var TemplateTag $template */
foreach ($templates as $template) {
$templateNames[] = $template->getTemplateName();
}
foreach ($traits as $trait) { foreach ($traits as $trait) {
$phpdoc = new DocBlock(new ReflectionClass($trait)); $phpdoc = new DocBlock(new ReflectionClass($trait));
$templates = $phpdoc->getTagsByName('template'); $templates = $phpdoc->getTagsByName('template');
/** @var DocBlock\Tag\TemplateTag $template */ /** @var TemplateTag $template */
foreach ($templates as $template) { foreach ($templates as $template) {
$template->setBound('static'); $templateNames[] = $template->getTemplateName();
$template->setDocBlock($templateDoc);
$templateDoc->appendTag($template);
} }
} }
} }
return $serializer->getDocComment($templateDoc); $this->templateNames = $templateNames;
} }
/** /**
+182 -16
View File
@@ -12,10 +12,13 @@
namespace Barryvdh\LaravelIdeHelper\Console; namespace Barryvdh\LaravelIdeHelper\Console;
use Barryvdh\LaravelIdeHelper\Factories; use Barryvdh\LaravelIdeHelper\Factories;
use Dotenv\Parser\Entry;
use Dotenv\Parser\Parser;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Config\Repository; use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Collection;
use RuntimeException; use RuntimeException;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@@ -51,19 +54,36 @@ class MetaCommand extends Command
protected $config; protected $config;
protected $methods = [ protected $methods = [
'new \Illuminate\Contracts\Container\Container', 'new \Illuminate\Contracts\Container\Container',
'\Illuminate\Container\Container::makeWith(0)', '\Illuminate\Container\Container::makeWith(0)',
'\Illuminate\Contracts\Container\Container::get(0)', '\Illuminate\Contracts\Container\Container::get(0)',
'\Illuminate\Contracts\Container\Container::make(0)', '\Illuminate\Contracts\Container\Container::make(0)',
'\Illuminate\Contracts\Container\Container::makeWith(0)', '\Illuminate\Contracts\Container\Container::makeWith(0)',
'\App::get(0)', '\App::get(0)',
'\App::make(0)', '\App::make(0)',
'\App::makeWith(0)', '\App::makeWith(0)',
'\app(0)', '\app(0)',
'\resolve(0)', '\resolve(0)',
'\Psr\Container\ContainerInterface::get(0)', '\Psr\Container\ContainerInterface::get(0)',
]; ];
protected $configMethods = [
'\config()',
'\Illuminate\Config\Repository::get()',
'\Illuminate\Support\Facades\Config::get()',
];
protected $userMethods = [
'\auth()->user()',
'\Illuminate\Contracts\Auth\Guard::user()',
'\Illuminate\Support\Facades\Auth::user()',
'\request()->user()',
'\Illuminate\Http\Request::user()',
'\Illuminate\Support\Facades\Request::user()',
];
protected $templateCache = [];
/** /**
* *
* @param Filesystem $files * @param Filesystem $files
@@ -108,7 +128,7 @@ class MetaCommand extends Command
} }
$reflectionClass = new \ReflectionClass($concrete); $reflectionClass = new \ReflectionClass($concrete);
if (is_object($concrete) && !$reflectionClass->isAnonymous()) { if (is_object($concrete) && !$reflectionClass->isAnonymous() && $abstract !== get_class($concrete)) {
$bindings[$abstract] = get_class($concrete); $bindings[$abstract] = get_class($concrete);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -120,10 +140,22 @@ class MetaCommand extends Command
$this->unregisterClassAutoloadExceptions($ourAutoloader); $this->unregisterClassAutoloadExceptions($ourAutoloader);
$content = $this->view->make('meta', [ $configValues = $this->loadTemplate('configs')->pluck('value', 'name')->map(function ($value, $key) {
'bindings' => $bindings, return gettype($value);
'methods' => $this->methods, });
'factories' => $factories,
$defaultUserModel = $this->config->get('auth.providers.users.model', $this->config->get('auth.model', 'App\User'));
$content = $this->view->make('ide-helper::meta', [
'bindings' => $bindings,
'methods' => $this->methods,
'factories' => $factories,
'configMethods' => $this->configMethods,
'configValues' => $configValues,
'expectedArgumentSets' => $this->getExpectedArgumentSets(),
'expectedArguments' => $this->getExpectedArguments(),
'userModel' => $defaultUserModel,
'userMethods' => $this->userMethods,
])->render(); ])->render();
$filename = $this->option('filename'); $filename = $this->option('filename');
@@ -167,6 +199,125 @@ class MetaCommand extends Command
return $autoloader; return $autoloader;
} }
protected function getExpectedArgumentSets()
{
return [
'auth' => $this->loadTemplate('auth')->keys()->filter()->toArray(),
'configs' => $this->loadTemplate('configs')->pluck('name')->filter()->toArray(),
'middleware' => $this->loadTemplate('middleware')->keys()->filter()->toArray(),
'routes' => $this->loadTemplate('routes')->pluck('name')->filter()->toArray(),
'views' => $this->loadTemplate('views')->pluck('key')->filter()->map(function ($value) {
return (string) $value;
})->toArray(),
'translations' => $this->loadTemplate('translations')->filter()->keys()->toArray(),
'env' => $this->getEnv(),
];
}
protected function getExpectedArguments()
{
return [
[
'class' => '\Illuminate\Support\Facades\Gate',
'method' => [
'has',
'allows',
'denies',
'check',
'any',
'none',
'authorize',
'inspect',
],
'argumentSet' => 'auth',
],
[
'class' => ['\Illuminate\Support\Facades\Route', '\Illuminate\Support\Facades\Auth', 'Illuminate\Foundation\Auth\Access\Authorizable'],
'method' => ['can', 'cannot'],
'argumentSet' => 'auth',
],
[
'class' => ['\Illuminate\Config\Repository', '\Illuminate\Support\Facades\Config'],
'method' => [
// 'get', // config() and Config::Get() are added with return type hints already
'getMany',
'set',
'string',
'integer',
'boolean',
'float',
'array',
'prepend',
'push',
],
'argumentSet' => 'configs',
],
[
'class' => ['\Illuminate\Support\Facades\Route', '\Illuminate\Routing\Router'],
'method' => ['middleware', 'withoutMiddleware'],
'argumentSet' => 'middleware',
],
[
'method' => ['route', 'to_route', 'signedRoute'],
'argumentSet' => 'routes',
],
[
'class' => [
'\Illuminate\Support\Facades\Redirect',
'\Illuminate\Support\Facades\URL',
'\Illuminate\Routing\Redirector',
'\Illuminate\Routing\UrlGenerator',
],
'method' => ['route', 'signedRoute', 'temporarySignedRoute'],
'argumentSet' => 'routes',
],
[
'method' => 'view',
'argumentSet' => 'views',
],
[
'class' => ['\Illuminate\Support\Facades\View', '\Illuminate\View\Factory'],
'method' => 'make',
'argumentSet' => 'views',
],
[
'method' => ['__', 'trans'],
'argumentSet' => 'translations',
],
[
'class' => ['\Illuminate\Contracts\Translation\Translator'],
'method' => ['get'],
'argumentSet' => 'translations',
],
[
'method' => 'env',
'argumentSet' => 'env',
],
[
'class' => '\Illuminate\Support\Env',
'method' => 'get',
'argumentSet' => 'env',
],
];
}
/**
* @return Collection
*/
protected function loadTemplate($name)
{
if (!isset($this->templateCache[$name])) {
$file = __DIR__ . '/../../php-templates/' . basename($name) . '.php';
$value = $this->files->requireOnce($file) ?: [];
if (!$value instanceof Collection) {
$value = collect($value);
}
$this->templateCache[$name] = $value;
}
return $this->templateCache[$name];
}
/** /**
* Get the console command options. * Get the console command options.
* *
@@ -181,6 +332,21 @@ class MetaCommand extends Command
]; ];
} }
protected function getEnv()
{
$envPath = base_path('.env');
if (!file_exists($envPath)) {
return [];
}
$parser = new Parser();
$entries = $parser->parse(file_get_contents($envPath));
return collect($entries)->map(function (Entry $entry) {
return $entry->getName();
});
}
/** /**
* Remove our custom autoloader that we pushed onto the autoload stack * Remove our custom autoloader that we pushed onto the autoload stack
* *
+4 -2
View File
@@ -16,6 +16,7 @@ use Barryvdh\LaravelIdeHelper\Generator;
use Barryvdh\LaravelIdeHelper\Parsers\PhpDocReturnTypeParser; use Barryvdh\LaravelIdeHelper\Parsers\PhpDocReturnTypeParser;
use Barryvdh\Reflection\DocBlock; use Barryvdh\Reflection\DocBlock;
use Barryvdh\Reflection\DocBlock\Context; use Barryvdh\Reflection\DocBlock\Context;
use Barryvdh\Reflection\DocBlock\ContextFactory;
use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer;
use Barryvdh\Reflection\DocBlock\Tag; use Barryvdh\Reflection\DocBlock\Tag;
use Composer\ClassMapGenerator\ClassMapGenerator; use Composer\ClassMapGenerator\ClassMapGenerator;
@@ -49,7 +50,6 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\View\Factory as ViewFactory; use Illuminate\View\Factory as ViewFactory;
use phpDocumentor\Reflection\Types\ContextFactory;
use ReflectionClass; use ReflectionClass;
use ReflectionNamedType; use ReflectionNamedType;
use ReflectionObject; use ReflectionObject;
@@ -1571,7 +1571,9 @@ class ModelsCommand extends Command
*/ */
protected function getUsedClassNames(ReflectionClass $reflection): array protected function getUsedClassNames(ReflectionClass $reflection): array
{ {
$namespaceAliases = array_flip((new ContextFactory())->createFromReflector($reflection)->getNamespaceAliases()); $namespaceAliases = array_flip(array_map(function ($alias) {
return ltrim($alias, '\\');
}, (new ContextFactory())->createFromReflector($reflection)->getNamespaceAliases()));
$namespaceAliases[$reflection->getName()] = $reflection->getShortName(); $namespaceAliases[$reflection->getName()] = $reflection->getShortName();
return $namespaceAliases; return $namespaceAliases;
+29 -29
View File
@@ -17,10 +17,6 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\Facade;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\Macroable;
use PhpParser\Lexer\Emulative;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Parser\Php7;
use ReflectionClass; use ReflectionClass;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@@ -39,6 +35,7 @@ class Generator
protected $magic = []; protected $magic = [];
protected $interfaces = []; protected $interfaces = [];
protected $helpers; protected $helpers;
protected array $macroableTraits = [];
/** /**
* @param \Illuminate\Config\Repository $config * @param \Illuminate\Config\Repository $config
@@ -79,7 +76,7 @@ class Generator
public function generate() public function generate()
{ {
$app = app(); $app = app();
return $this->view->make('helper') return $this->view->make('ide-helper::helper')
->with('namespaces_by_extends_ns', $this->getAliasesByExtendsNamespace()) ->with('namespaces_by_extends_ns', $this->getAliasesByExtendsNamespace())
->with('namespaces_by_alias_ns', $this->getAliasesByAliasNamespace()) ->with('namespaces_by_alias_ns', $this->getAliasesByAliasNamespace())
->with('real_time_facades', $this->getRealTimeFacades()) ->with('real_time_facades', $this->getRealTimeFacades())
@@ -105,7 +102,7 @@ class Generator
} }
$app = app(); $app = app();
return $this->view->make('helper') return $this->view->make('ide-helper::helper')
->with('namespaces_by_extends_ns', []) ->with('namespaces_by_extends_ns', [])
->with('namespaces_by_alias_ns', ['__root' => [$alias]]) ->with('namespaces_by_alias_ns', ['__root' => [$alias]])
->with('real_time_facades', []) ->with('real_time_facades', [])
@@ -213,8 +210,10 @@ class Generator
foreach ($realTimeFacadeFiles as $file) { foreach ($realTimeFacadeFiles as $file) {
try { try {
$name = $this->getFullyQualifiedClassNameInFile($file); $name = $this->getFullyQualifiedClassNameInFile($file);
$facades[$name] = $name; if ($name) {
} catch (\Exception $e) { $facades[$name] = $name;
}
} catch (\Throwable $e) {
continue; continue;
} }
} }
@@ -226,26 +225,17 @@ class Generator
{ {
$contents = file_get_contents($path); $contents = file_get_contents($path);
$parsers = new Php7(new Emulative()); // Match namespace
preg_match('/namespace\s+([^;]+);/', $contents, $namespaceMatch);
$namespace = isset($namespaceMatch[1]) ? $namespaceMatch[1] : '';
$parsed = collect($parsers->parse($contents) ?: []); // Match class name
preg_match('/class\s+([a-zA-Z0-9_]+)/', $contents, $classMatch);
$className = isset($classMatch[1]) ? $classMatch[1] : '';
$namespace = $parsed->first(function ($node) { // Combine namespace and class name
return $node instanceof Namespace_; if ($namespace && $className) {
}); return $namespace . '\\' . $className;
if ($namespace) {
$name = $namespace->name->toString();
$class = collect($namespace->stmts)->first(function ($node) {
return $node instanceof Class_;
});
if ($class) {
$name .= '\\' . $class->name->toString();
}
return $name;
} }
} }
@@ -352,7 +342,7 @@ class Generator
continue; continue;
} }
$aliases[] = new Alias($this->config, $class, $class, [], $this->interfaces); $aliases[] = new Alias($this->config, $class, $class, [], $this->interfaces, true);
} }
} }
@@ -374,8 +364,18 @@ class Generator
->filter(function ($class) { ->filter(function ($class) {
$traits = class_uses_recursive($class); $traits = class_uses_recursive($class);
// Filter only classes with the macroable trait if (isset($traits[Macroable::class])) {
return isset($traits[Macroable::class]); return true;
}
// Filter only classes with a macroable trait
foreach ($this->config->get('ide-helper.macroable_traits', []) as $trait) {
if (isset($traits[$trait])) {
return true;
}
}
return false;
}) })
->filter(function ($class) use ($aliases) { ->filter(function ($class) use ($aliases) {
$class = Str::start($class, '\\'); $class = Str::start($class, '\\');
-26
View File
@@ -20,10 +20,6 @@ use Illuminate\Console\Events\CommandFinished;
use Illuminate\Contracts\Support\DeferrableProvider; use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Database\Events\MigrationsEnded; use Illuminate\Database\Events\MigrationsEnded;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Engines\PhpEngine;
use Illuminate\View\Factory;
use Illuminate\View\FileViewFinder;
class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProvider class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProvider
{ {
@@ -65,12 +61,6 @@ class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProv
$configPath = __DIR__ . '/../config/ide-helper.php'; $configPath = __DIR__ . '/../config/ide-helper.php';
$this->mergeConfigFrom($configPath, 'ide-helper'); $this->mergeConfigFrom($configPath, 'ide-helper');
$this->app->when([GeneratorCommand::class, MetaCommand::class, ModelsCommand::class])
->needs(\Illuminate\Contracts\View\Factory::class)
->give(function () {
return $this->createLocalViewFactory();
});
$this->commands( $this->commands(
[ [
GeneratorCommand::class, GeneratorCommand::class,
@@ -95,20 +85,4 @@ class IdeHelperServiceProvider extends ServiceProvider implements DeferrableProv
EloquentCommand::class, EloquentCommand::class,
]; ];
} }
/**
* @return Factory
*/
private function createLocalViewFactory()
{
$resolver = new EngineResolver();
$resolver->register('php', function () {
return new PhpEngine($this->app['files']);
});
$finder = new FileViewFinder($this->app['files'], [__DIR__ . '/../resources/views']);
$factory = new Factory($resolver, $finder, $this->app['events']);
$factory->addExtension('php', 'php');
return $factory;
}
} }
+11
View File
@@ -5,10 +5,15 @@ namespace Barryvdh\LaravelIdeHelper;
use Barryvdh\Reflection\DocBlock; use Barryvdh\Reflection\DocBlock;
use Barryvdh\Reflection\DocBlock\Tag; use Barryvdh\Reflection\DocBlock\Tag;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class Macro extends Method class Macro extends Method
{ {
protected $macroDefaults = [
\Illuminate\Http\Client\Factory::class => PendingRequest::class,
];
/** /**
* Macro constructor. * Macro constructor.
* *
@@ -77,6 +82,12 @@ class Macro extends Method
$this->phpdoc->appendTag(Tag::createInstance("@return {$type}")); $this->phpdoc->appendTag(Tag::createInstance("@return {$type}"));
} }
$class = ltrim($this->declaringClassName, '\\');
if (!$this->phpdoc->hasTag('return') && isset($this->macroDefaults[$class])) {
$type = $this->macroDefaults[$class];
$this->phpdoc->appendTag(Tag::createInstance("@return {$type}"));
}
} }
protected function concatReflectionTypes(?\ReflectionType $type): string protected function concatReflectionTypes(?\ReflectionType $type): string
+8 -3
View File
@@ -39,6 +39,9 @@ class Method
protected $classAliases; protected $classAliases;
protected $returnTypeNormalizers; protected $returnTypeNormalizers;
/** @var string[] */
protected $templateNames = [];
/** /**
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method * @param \ReflectionMethod|\ReflectionFunctionAbstract $method
* @param string $alias * @param string $alias
@@ -47,8 +50,9 @@ class Method
* @param array $interfaces * @param array $interfaces
* @param array $classAliases * @param array $classAliases
* @param array $returnTypeNormalizers * @param array $returnTypeNormalizers
* @param string[] $templateNames
*/ */
public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = []) public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [], array $returnTypeNormalizers = [], array $templateNames = [])
{ {
$this->method = $method; $this->method = $method;
$this->interfaces = $interfaces; $this->interfaces = $interfaces;
@@ -56,6 +60,7 @@ class Method
$this->returnTypeNormalizers = $returnTypeNormalizers; $this->returnTypeNormalizers = $returnTypeNormalizers;
$this->name = $methodName ?: $method->name; $this->name = $methodName ?: $method->name;
$this->real_name = $method->isClosure() ? $this->name : $method->name; $this->real_name = $method->isClosure() ? $this->name : $method->name;
$this->templateNames = $templateNames;
$this->initClassDefinedProperties($method, $class); $this->initClassDefinedProperties($method, $class);
//Reference the 'real' function in the declaring class //Reference the 'real' function in the declaring class
@@ -84,7 +89,7 @@ class Method
*/ */
protected function initPhpDoc($method) protected function initPhpDoc($method)
{ {
$this->phpdoc = new DocBlock($method, new Context($this->namespace, $this->classAliases)); $this->phpdoc = new DocBlock($method, new Context($this->namespace, $this->classAliases, generics: $this->templateNames));
} }
/** /**
@@ -386,7 +391,7 @@ class Method
} }
if ($method) { if ($method) {
$namespace = $method->getDeclaringClass()->getNamespaceName(); $namespace = $method->getDeclaringClass()->getNamespaceName();
$phpdoc = new DocBlock($method, new Context($namespace, $this->classAliases)); $phpdoc = new DocBlock($method, new Context($namespace, $this->classAliases, generics: $this->templateNames));
if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) { if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) {
//Not at the end yet, try another parent/interface.. //Not at the end yet, try another parent/interface..
-124
View File
@@ -1,124 +0,0 @@
<?php
/**
* Laravel IDE Helper Generator
*
* @author Barry vd. Heuvel <barryvdh@gmail.com>
* @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link https://github.com/barryvdh/laravel-ide-helper
*/
namespace Barryvdh\LaravelIdeHelper;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use PhpParser\ParserFactory;
class UsesResolver
{
/**
* @param string $classFQN
* @return array
*/
public function loadFromClass(string $classFQN): array
{
return $this->loadFromFile(
$classFQN,
(new \ReflectionClass($classFQN))->getFileName()
);
}
/**
* @param string $classFQN
* @param string $filename
* @return array
*/
public function loadFromFile(string $classFQN, string $filename): array
{
return $this->loadFromCode(
$classFQN,
file_get_contents(
$filename
)
);
}
/**
* @param string $classFQN
* @param string $code
* @return array
*/
public function loadFromCode(string $classFQN, string $code): array
{
$classFQN = ltrim($classFQN, '\\');
$namespace = rtrim(
preg_replace(
'/([^\\\\]+)$/',
'',
$classFQN
),
'\\'
);
$parser = (new ParserFactory())->createForHostVersion();
$namespaceData = null;
foreach ($parser->parse($code) as $node) {
if ($node instanceof Namespace_ && $node->name->toCodeString() === $namespace) {
$namespaceData = $node;
break;
}
}
if ($namespaceData === null) {
return [];
}
/** @var Namespace_ $namespaceData */
$aliases = [];
foreach ($namespaceData->stmts as $stmt) {
if ($stmt instanceof Use_) {
if ($stmt->type !== Use_::TYPE_NORMAL) {
continue;
}
foreach ($stmt->uses as $use) {
/** @var UseUse $use */
$alias = $use->alias ?
$use->alias->name :
self::classBasename($use->name->toCodeString());
$aliases[$alias] = '\\' . $use->name->toCodeString();
}
} elseif ($stmt instanceof GroupUse) {
foreach ($stmt->uses as $use) {
/** @var UseUse $use */
$alias = $use->alias ?
$use->alias->name :
self::classBasename($use->name->toCodeString());
$aliases[$alias] = '\\' . $stmt->prefix->toCodeString() . '\\' . $use->name->toCodeString();
}
}
}
return $aliases;
}
/**
* @param string $classFQN
* @return string
*/
protected static function classBasename(string $classFQN): string
{
return preg_replace('/^.*\\\\([^\\\\]+)$/', '$1', $classFQN);
}
}
+17 -27
View File
@@ -7,7 +7,6 @@ namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\Alias; use Barryvdh\LaravelIdeHelper\Alias;
use Barryvdh\LaravelIdeHelper\Macro; use Barryvdh\LaravelIdeHelper\Macro;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
/** /**
@@ -16,31 +15,6 @@ use Illuminate\Support\Arr;
*/ */
class AliasTest extends TestCase class AliasTest extends TestCase
{ {
/**
* @covers ::detectMethods
*/
public function testDetectMethodsMacroableMacros(): void
{
// Mock
$macro = __FUNCTION__;
$alias = new AliasMock();
// Macros
Builder::macro(
$macro,
function () {
// empty
}
);
// Prepare
$alias->setClasses([Builder::class]);
$alias->detectMethods();
// Test
$this->assertNotNull($this->getAliasMacro($alias, Builder::class, $macro));
}
/** /**
* @covers ::detectMethods * @covers ::detectMethods
*/ */
@@ -50,7 +24,7 @@ class AliasTest extends TestCase
$macro = __FUNCTION__; $macro = __FUNCTION__;
$alias = new AliasMock(); $alias = new AliasMock();
// Macros // Macrosx
EloquentBuilder::macro( EloquentBuilder::macro(
$macro, $macro,
function () { function () {
@@ -66,6 +40,21 @@ class AliasTest extends TestCase
$this->assertNotNull($this->getAliasMacro($alias, EloquentBuilder::class, $macro)); $this->assertNotNull($this->getAliasMacro($alias, EloquentBuilder::class, $macro));
} }
/**
* @covers ::detectTemplateNames
*/
public function testTemplateNamesAreDetected(): void
{
// Mock
$alias = new AliasMock();
// Prepare
$alias->setClasses([EloquentBuilder::class]);
// Test
$this->assertSame(['TModel', 'TValue'], $alias->getTemplateNames());
}
protected function getAliasMacro(Alias $alias, string $class, string $method): ?Macro protected function getAliasMacro(Alias $alias, string $class, string $method): ?Macro
{ {
return Arr::first( return Arr::first(
@@ -82,6 +71,7 @@ class AliasTest extends TestCase
/** /**
* @internal * @internal
* @noinspection PhpMultipleClassesDeclarationsInOneFile * @noinspection PhpMultipleClassesDeclarationsInOneFile
* @template TValue
*/ */
class AliasMock extends Alias class AliasMock extends Alias
{ {
@@ -27,7 +27,7 @@ class MetaCommandTest extends TestCase
$mockFileSystem $mockFileSystem
->shouldReceive('getRequire') ->shouldReceive('getRequire')
->andReturnUsing(function ($__path, $__data) { ->andReturnUsing(function ($__path, $__data = []) {
return (static function () use ($__path, $__data) { return (static function () use ($__path, $__data) {
extract($__data, EXTR_SKIP); extract($__data, EXTR_SKIP);
@@ -60,7 +60,7 @@ class MetaCommandTest extends TestCase
$mockFileSystem $mockFileSystem
->shouldReceive('getRequire') ->shouldReceive('getRequire')
->andReturnUsing(function ($__path, $__data) { ->andReturnUsing(function ($__path, $__data = []) {
return (static function () use ($__path, $__data) { return (static function () use ($__path, $__data) {
extract($__data, EXTR_SKIP); extract($__data, EXTR_SKIP);
+23
View File
@@ -193,6 +193,29 @@ DOC;
$this->assertSame([], $method->getParamsWithDefault(false)); $this->assertSame([], $method->getParamsWithDefault(false));
$this->assertTrue($method->shouldReturn()); $this->assertTrue($method->shouldReturn());
} }
public function testEloquentBuilderWithTemplates()
{
$reflectionClass = new \ReflectionClass(EloquentBuilder::class);
$reflectionMethod = $reflectionClass->getMethod('firstOr');
$method = new Method($reflectionMethod, 'Builder', $reflectionClass, null, [], [], [], ['TModel']);
$output = <<<'DOC'
/**
* Execute the query and get the first result or call a callback.
*
* @template TValue
* @param (\Closure(): TValue)|list<string> $columns
* @param (\Closure(): TValue)|null $callback
* @return TModel|TValue
* @static
*/
DOC;
$this->assertSame($output, $method->getDocComment(''));
$this->assertSame('firstOr', $method->getName());
$this->assertSame('\\' . EloquentBuilder::class, $method->getDeclaringClass());
}
} }
class ExampleClass class ExampleClass
-56
View File
@@ -1,56 +0,0 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\UsesResolver;
use PHPUnit\Framework\TestCase;
class UsesResolverTest extends TestCase
{
/**
* Test that we can correctly load uses from supplied code
*/
public function testLoadFromCode()
{
$usesResolver = new UsesResolver();
$code = <<<'DOC'
<?php
namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\UsesResolver as MyUsesResolver;
use PHPUnit\Framework\TestCase;
class UsesResolverTest extends TestCase
{
//
}
DOC;
$this->assertEquals(
$usesResolver->loadFromCode('Barryvdh\\LaravelIdeHelper\\Tests\\UsesResolverTest', $code),
[
'MyUsesResolver' => '\\Barryvdh\\LaravelIdeHelper\\UsesResolver',
'TestCase' => '\\PHPUnit\Framework\TestCase',
]
);
}
/**
* Test that we can correctly load uses from a class
*/
public function testLoadFromClass()
{
$usesResolver = new UsesResolver();
$this->assertEquals(
$usesResolver->loadFromClass(self::class),
[
'UsesResolver' => '\\Barryvdh\\LaravelIdeHelper\\UsesResolver',
'TestCase' => '\\PHPUnit\Framework\TestCase',
]
);
}
}