Skip to content

Commit 4e4da08

Browse files
barryvdhlaravel-ide-helper
and
laravel-ide-helper
authored
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]>
1 parent 66ea6aa commit 4e4da08

File tree

11 files changed

+547
-46
lines changed

11 files changed

+547
-46
lines changed

php-templates/LICENSE.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) Taylor Otwell
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

php-templates/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
The templates here are based on the official VS Code extension by Laravel https://github.com/laravel/vs-code-extension
2+
3+
Modifications:
4+
- return instead of echo
5+
- do not serialize to JSON

php-templates/configs.php

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
$local = collect(glob(config_path('/*.php')))
4+
->merge(glob(config_path('**/*.php')))
5+
->map(fn ($path) => [
6+
(string) Illuminate\Support\Str::of($path)
7+
->replace([config_path('/'), '.php'], '')
8+
->replace('/', '.'),
9+
$path,
10+
]);
11+
12+
$vendor = collect(glob(base_path('vendor/**/**/config/*.php')))->map(fn (
13+
$path
14+
) => [
15+
(string) Illuminate\Support\Str::of($path)
16+
->afterLast('/config/')
17+
->replace('.php', '')
18+
->replace('/', '.'),
19+
$path,
20+
]);
21+
22+
$configPaths = $local
23+
->merge($vendor)
24+
->groupBy(0)
25+
->map(fn ($items) => $items->pluck(1));
26+
27+
$cachedContents = [];
28+
$cachedParsed = [];
29+
30+
function vsCodeGetConfigValue($value, $key, $configPaths)
31+
{
32+
$parts = explode('.', $key);
33+
$toFind = $key;
34+
$found = null;
35+
36+
while (count($parts) > 0) {
37+
array_pop($parts);
38+
$toFind = implode('.', $parts);
39+
40+
if ($configPaths->has($toFind)) {
41+
$found = $toFind;
42+
break;
43+
}
44+
}
45+
46+
if ($found === null) {
47+
return null;
48+
}
49+
50+
$file = null;
51+
$line = null;
52+
53+
if ($found === $key) {
54+
$file = $configPaths->get($found)[0];
55+
} else {
56+
foreach ($configPaths->get($found) as $path) {
57+
$cachedContents[$path] ??= file_get_contents($path);
58+
$cachedParsed[$path] ??= token_get_all($cachedContents[$path]);
59+
60+
$keysToFind = Illuminate\Support\Str::of($key)
61+
->replaceFirst($found, '')
62+
->ltrim('.')
63+
->explode('.');
64+
65+
if (is_numeric($keysToFind->last())) {
66+
$index = $keysToFind->pop();
67+
68+
if ($index !== '0') {
69+
return null;
70+
}
71+
72+
$key = collect(explode('.', $key));
73+
$key->pop();
74+
$key = $key->implode('.');
75+
$value = 'array(...)';
76+
}
77+
78+
$nextKey = $keysToFind->shift();
79+
$expectedDepth = 1;
80+
81+
$depth = 0;
82+
83+
foreach ($cachedParsed[$path] as $token) {
84+
if ($token === '[') {
85+
$depth++;
86+
}
87+
88+
if ($token === ']') {
89+
$depth--;
90+
}
91+
92+
if (!is_array($token)) {
93+
continue;
94+
}
95+
96+
$str = trim($token[1], '"\'');
97+
98+
if (
99+
$str === $nextKey &&
100+
$depth === $expectedDepth &&
101+
$token[0] === T_CONSTANT_ENCAPSED_STRING
102+
) {
103+
$nextKey = $keysToFind->shift();
104+
$expectedDepth++;
105+
106+
if ($nextKey === null) {
107+
$file = $path;
108+
$line = $token[2];
109+
break;
110+
}
111+
}
112+
}
113+
114+
if ($file) {
115+
break;
116+
}
117+
}
118+
}
119+
120+
return [
121+
'name' => $key,
122+
'value' => $value,
123+
'file' => $file === null ? null : str_replace(base_path('/'), '', $file),
124+
'line' => $line,
125+
];
126+
}
127+
128+
return collect(Illuminate\Support\Arr::dot(config()->all()))
129+
->map(fn ($value, $key) => vsCodeGetConfigValue($value, $key, $configPaths))
130+
->filter()
131+
->values();

php-templates/routes.php

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
function vsCodeGetRouterReflection(Illuminate\Routing\Route $route)
4+
{
5+
if ($route->getActionName() === 'Closure') {
6+
return new ReflectionFunction($route->getAction()['uses']);
7+
}
8+
9+
if (!str_contains($route->getActionName(), '@')) {
10+
return new ReflectionClass($route->getActionName());
11+
}
12+
13+
try {
14+
return new ReflectionMethod($route->getControllerClass(), $route->getActionMethod());
15+
} catch (Throwable $e) {
16+
$namespace = app(Illuminate\Routing\UrlGenerator::class)->getRootControllerNamespace()
17+
?? (app()->getNamespace() . 'Http\Controllers');
18+
19+
return new ReflectionMethod(
20+
$namespace . '\\' . ltrim($route->getControllerClass(), '\\'),
21+
$route->getActionMethod(),
22+
);
23+
}
24+
}
25+
26+
return collect(app('router')->getRoutes()->getRoutes())
27+
->map(function (Illuminate\Routing\Route $route) {
28+
try {
29+
$reflection = vsCodeGetRouterReflection($route);
30+
} catch (Throwable $e) {
31+
$reflection = null;
32+
}
33+
34+
return [
35+
'method' => collect($route->methods())->filter(function ($method) {
36+
return $method !== 'HEAD';
37+
})->implode('|'),
38+
'uri' => $route->uri(),
39+
'name' => $route->getName(),
40+
'action' => $route->getActionName(),
41+
'parameters' => $route->parameterNames(),
42+
'filename' => $reflection ? $reflection->getFileName() : null,
43+
'line' => $reflection ? $reflection->getStartLine() : null,
44+
];
45+
})
46+
;

php-templates/translations.php

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
function vsCodeGetTranslationsFromFile($file, $path, $namespace)
4+
{
5+
$key = pathinfo($file, PATHINFO_FILENAME);
6+
7+
if ($namespace) {
8+
$key = "{$namespace}::{$key}";
9+
}
10+
11+
$lang = collect(explode(DIRECTORY_SEPARATOR, str_replace($path, '', $file)))
12+
->filter()
13+
->first();
14+
15+
$fileLines = Illuminate\Support\Facades\File::lines($file);
16+
$lines = [];
17+
$inComment = false;
18+
19+
foreach ($fileLines as $index => $line) {
20+
$trimmed = trim($line);
21+
22+
if (substr($trimmed, 0, 2) === '/*') {
23+
$inComment = true;
24+
continue;
25+
}
26+
27+
if ($inComment) {
28+
if (substr($trimmed, -2) !== '*/') {
29+
continue;
30+
}
31+
32+
$inComment = false;
33+
}
34+
35+
if (substr($trimmed, 0, 2) === '//') {
36+
continue;
37+
}
38+
39+
$lines[] = [$index + 1, $trimmed];
40+
}
41+
42+
return [
43+
'k' => $key,
44+
'la' => $lang,
45+
'vs' => collect(Illuminate\Support\Arr::dot((Illuminate\Support\Arr::wrap(__($key, [], $lang)))))
46+
->map(
47+
fn ($value, $key) => vsCodeTranslationValue(
48+
$key,
49+
$value,
50+
str_replace(base_path(DIRECTORY_SEPARATOR), '', $file),
51+
$lines
52+
)
53+
)
54+
->filter(),
55+
];
56+
}
57+
58+
function vsCodeTranslationValue($key, $value, $file, $lines): ?array
59+
{
60+
if (is_array($value)) {
61+
return null;
62+
}
63+
64+
$lineNumber = 1;
65+
$keys = explode('.', $key);
66+
$index = 0;
67+
$currentKey = array_shift($keys);
68+
69+
foreach ($lines as $index => $line) {
70+
if (
71+
strpos($line[1], '"' . $currentKey . '"', 0) !== false ||
72+
strpos($line[1], "'" . $currentKey . "'", 0) !== false
73+
) {
74+
$lineNumber = $line[0];
75+
$currentKey = array_shift($keys);
76+
}
77+
78+
if ($currentKey === null) {
79+
break;
80+
}
81+
}
82+
83+
return [
84+
'v' => $value,
85+
'p' => $file,
86+
'li' => $lineNumber,
87+
'pa' => preg_match_all("/\:([A-Za-z0-9_]+)/", $value, $matches)
88+
? $matches[1]
89+
: [],
90+
];
91+
}
92+
93+
function vscodeCollectTranslations(string $path, ?string $namespace = null)
94+
{
95+
$realPath = realpath($path);
96+
97+
if (!is_dir($realPath)) {
98+
return collect();
99+
}
100+
101+
return collect(Illuminate\Support\Facades\File::allFiles($realPath))->map(
102+
fn ($file) => vsCodeGetTranslationsFromFile($file, $path, $namespace)
103+
);
104+
}
105+
106+
$loader = app('translator')->getLoader();
107+
$namespaces = $loader->namespaces();
108+
109+
$reflection = new ReflectionClass($loader);
110+
$property = $reflection->hasProperty('paths')
111+
? $reflection->getProperty('paths')
112+
: $reflection->getProperty('path');
113+
$property->setAccessible(true);
114+
115+
$paths = Illuminate\Support\Arr::wrap($property->getValue($loader));
116+
117+
$default = collect($paths)->flatMap(
118+
fn ($path) => vscodeCollectTranslations($path)
119+
);
120+
121+
$namespaced = collect($namespaces)->flatMap(
122+
fn ($path, $namespace) => vscodeCollectTranslations($path, $namespace)
123+
);
124+
125+
$final = [];
126+
127+
foreach ($default->merge($namespaced) as $value) {
128+
foreach ($value['vs'] as $key => $v) {
129+
$dotKey = "{$value['k']}.{$key}";
130+
131+
if (!isset($final[$dotKey])) {
132+
$final[$dotKey] = [];
133+
}
134+
135+
$final[$dotKey][$value['la']] = $v;
136+
137+
if ($value['la'] === Illuminate\Support\Facades\App::currentLocale()) {
138+
$final[$dotKey]['default'] = $v;
139+
}
140+
}
141+
}
142+
143+
return collect($final);

0 commit comments

Comments
 (0)