Skip to content

Commit 71bbba1

Browse files
committed
Merge remote-tracking branch 'upstream/main' into blade-space
2 parents 14a2f2e + 69ce58e commit 71bbba1

File tree

8 files changed

+448
-309
lines changed

8 files changed

+448
-309
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ All notable changes to the Laravel extension will be documented in this file.
44

55
## [Unreleased]
66

7-
- Nothing yet
7+
- Better support for Blade component index files ([#284](https://github.com/laravel/vs-code-extension/pull/284))
8+
- Enable Eloquent Model property auto-complete after sole, find, first, or firstOrFail ([#274](https://github.com/laravel/vs-code-extension/pull/274))
9+
- Added cast custom return type from get method ([#283](https://github.com/laravel/vs-code-extension/pull/283))
810

911
## [v1.0.0]
1012

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@
440440
"watch": "npm-run-all -p watch:*",
441441
"watch:esbuild": "node esbuild.js --watch",
442442
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
443-
"vscode:prepublish": "php generate-templates.php && ./precheck && npm run package",
443+
"vscode:prepublish": "php generate-templates.php && php generate-config.php && ./precheck && npm run package",
444444
"package": "npm run check-types && node esbuild.js --production",
445445
"installLocal": "vsce package && code --install-extension ./vscode-laravel-$npm_package_version.vsix"
446446
},

php-templates/models.php

Lines changed: 154 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,183 @@
11
<?php
22

3-
collect(glob(base_path('**/Models/*.php')))->each(fn($file) => include_once($file));
4-
53
if (class_exists('\phpDocumentor\Reflection\DocBlockFactory')) {
64
$factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
75
} else {
86
$factory = null;
97
}
108

11-
function getMethodDocblocks($method, $factory)
12-
{
13-
if ($factory !== null) {
14-
$docblock = $factory->create($method->getDocComment());
15-
$params = collect($docblock->getTagsByName("param"))->map(fn($p) => (string) $p)->all();
16-
$return = (string) $docblock->getTagsByName("return")[0] ?? null;
9+
$docblocks = new class($factory) {
10+
public function __construct(protected $factory) {}
11+
12+
public function forMethod($method)
13+
{
14+
if ($this->factory !== null) {
15+
$docblock = $this->factory->create($method->getDocComment());
16+
$params = collect($docblock->getTagsByName("param"))->map(fn($p) => (string) $p)->all();
17+
$return = (string) $docblock->getTagsByName("return")[0] ?? null;
18+
19+
return [$params, $return];
20+
}
21+
22+
23+
$params = collect($method->getParameters())
24+
->map(function (\ReflectionParameter $param) {
25+
$types = match ($param?->getType()) {
26+
null => [],
27+
default => method_exists($param->getType(), "getTypes")
28+
? $param->getType()->getTypes()
29+
: [$param->getType()]
30+
};
31+
32+
$types = collect($types)
33+
->filter()
34+
->values()
35+
->map(fn($t) => $t->getName());
36+
37+
return trim($types->join("|") . " $" . $param->getName());
38+
})
39+
->all();
40+
41+
$return = $method->getReturnType()?->getName();
1742

1843
return [$params, $return];
1944
}
45+
};
2046

47+
$models = new class($factory) {
48+
protected $output;
2149

22-
$params = collect($method->getParameters())
23-
->map(function (\ReflectionParameter $param) {
24-
$types = match ($param?->getType()) {
25-
null => [],
26-
default => method_exists($param->getType(), "getTypes")
27-
? $param->getType()->getTypes()
28-
: [$param->getType()]
29-
};
50+
public function __construct(protected $factory)
51+
{
52+
$this->output = new \Symfony\Component\Console\Output\BufferedOutput();
53+
}
3054

31-
$types = collect($types)
32-
->filter()
33-
->values()
34-
->map(fn($t) => $t->getName());
55+
public function all()
56+
{
57+
collect(glob(base_path('**/Models/*.php')))->each(fn($file) => include_once($file));
3558

36-
return trim($types->join("|") . " $" . $param->getName());
37-
})
38-
->all();
59+
return collect(get_declared_classes())
60+
->filter(fn($class) => is_subclass_of($class, \Illuminate\Database\Eloquent\Model::class))
61+
->filter(fn($class) => !in_array($class, [\Illuminate\Database\Eloquent\Relations\Pivot::class, \Illuminate\Foundation\Auth\User::class]))
62+
->values()
63+
->flatMap(fn(string $className) => $this->getInfo($className))
64+
->filter();
65+
}
3966

40-
$return = $method->getReturnType()?->getName();
67+
protected function getCastReturnType($className)
68+
{
69+
if ($className === null) {
70+
return null;
71+
}
4172

42-
return [$params, $return];
43-
}
73+
try {
74+
$method = (new \ReflectionClass($className))->getMethod('get');
4475

45-
function getBuilderMethod($method, $factory)
46-
{
47-
[$params, $return] = getMethodDocblocks($method, $factory);
76+
if ($method->hasReturnType()) {
77+
return $method->getReturnType()->getName();
78+
}
4879

49-
return [
50-
"name" => $method->getName(),
51-
"parameters" => $params,
52-
"return" => $return,
53-
];
54-
};
80+
return $className;
81+
} catch (\Exception | \Throwable $e) {
82+
return $className;
83+
}
84+
}
5585

56-
function getModelInfo($className, $factory)
57-
{
58-
$output = new \Symfony\Component\Console\Output\BufferedOutput();
59-
60-
try {
61-
\Illuminate\Support\Facades\Artisan::call(
62-
"model:show",
63-
[
64-
"model" => $className,
65-
"--json" => true,
66-
],
67-
$output
68-
);
69-
} catch (\Exception | \Throwable $e) {
70-
return null;
86+
protected function fromArtisan($className)
87+
{
88+
try {
89+
\Illuminate\Support\Facades\Artisan::call(
90+
"model:show",
91+
[
92+
"model" => $className,
93+
"--json" => true,
94+
],
95+
$this->output
96+
);
97+
} catch (\Exception | \Throwable $e) {
98+
return null;
99+
}
100+
101+
return json_decode($this->output->fetch(), true);
71102
}
72103

73-
$data = json_decode($output->fetch(), true);
104+
protected function collectExistingProperties($reflection)
105+
{
106+
if ($this->factory === null) {
107+
return collect();
108+
}
109+
110+
if ($comment = $reflection->getDocComment()) {
111+
$docblock = $this->factory->create($comment);
112+
113+
$existingProperties = collect($docblock->getTagsByName("property"))->map(fn($p) => $p->getVariableName());
114+
$existingReadProperties = collect($docblock->getTagsByName("property-read"))->map(fn($p) => $p->getVariableName());
74115

75-
if ($data === null) {
76-
return null;
116+
return $existingProperties->merge($existingReadProperties);
117+
}
118+
119+
return collect();
77120
}
78121

79-
$reflection = (new \ReflectionClass($className));
122+
protected function getInfo($className)
123+
{
124+
if (($data = $this->fromArtisan($className)) === null) {
125+
return null;
126+
}
127+
128+
$reflection = (new \ReflectionClass($className));
129+
130+
$existingProperties = $this->collectExistingProperties($reflection);
131+
132+
$data['attributes'] = collect($data['attributes'])
133+
->map(fn($attrs) => array_merge($attrs, [
134+
'title_case' => \Illuminate\Support\Str::of($attrs['name'])->title()->replace('_', '')->toString(),
135+
'documented' => $existingProperties->contains($attrs['name']),
136+
'cast' => $this->getCastReturnType($attrs['cast'])
137+
]))
138+
->toArray();
80139

81-
if ($factory !== null && ($comment = $reflection->getDocComment())) {
82-
$docblock = $factory->create($comment);
83-
$existingProperties = collect($docblock->getTagsByName("property"))->map(fn($p) => $p->getVariableName());
84-
$existingReadProperties = collect($docblock->getTagsByName("property-read"))->map(fn($p) => $p->getVariableName());
85-
$existingProperties = $existingProperties->merge($existingReadProperties);
86-
} else {
87-
$existingProperties = collect();
140+
$data['scopes'] = collect($reflection->getMethods())
141+
->filter(fn($method) => $method->isPublic() && !$method->isStatic() && str_starts_with($method->name, 'scope'))
142+
->map(fn($method) => \Illuminate\Support\Str::of($method->name)->replace('scope', '')->lcfirst()->toString())
143+
->values()
144+
->toArray();
145+
146+
$data['uri'] = $reflection->getFileName();
147+
148+
return [
149+
$className => $data,
150+
];
88151
}
152+
};
89153

90-
$data['attributes'] = collect($data['attributes'])
91-
->map(fn($attrs) => array_merge($attrs, [
92-
'title_case' => str_replace('_', '', \Illuminate\Support\Str::title($attrs['name'])),
93-
'documented' => $existingProperties->contains($attrs['name']),
94-
]))
95-
->toArray();
96-
97-
$data['scopes'] = collect($reflection->getMethods())
98-
->filter(fn($method) => $method->isPublic() && !$method->isStatic() && $method->name !== '__construct')
99-
->filter(fn($method) => str_starts_with($method->name, 'scope'))
100-
->map(fn($method) => str_replace('scope', '', $method->name))
101-
->map(fn($method) => strtolower(substr($method, 0, 1)) . substr($method, 1))
102-
->values()
103-
->toArray();
104-
105-
$data['uri'] = $reflection->getFileName();
106-
107-
return [
108-
$className => $data,
109-
];
110-
}
154+
$builder = new class($docblocks) {
155+
public function __construct(protected $docblocks) {}
156+
157+
public function methods()
158+
{
159+
$reflection = new \ReflectionClass(\Illuminate\Database\Query\Builder::class);
160+
161+
return collect($reflection->getMethods(\ReflectionMethod::IS_PUBLIC))
162+
->filter(fn(ReflectionMethod $method) => !str_starts_with($method->getName(), "__"))
163+
->map(fn(\ReflectionMethod $method) => $this->getMethodInfo($method))
164+
->filter()
165+
->values();
166+
}
167+
168+
protected function getMethodInfo($method)
169+
{
170+
[$params, $return] = $this->docblocks->forMethod($method);
171+
172+
return [
173+
"name" => $method->getName(),
174+
"parameters" => $params,
175+
"return" => $return,
176+
];
177+
}
178+
};
111179

112-
$reflection = new \ReflectionClass(\Illuminate\Database\Query\Builder::class);
113-
$builderMethods = collect($reflection->getMethods(\ReflectionMethod::IS_PUBLIC))
114-
->filter(fn(ReflectionMethod $method) => !str_starts_with($method->getName(), "__"))
115-
->map(fn(\ReflectionMethod $method) => getBuilderMethod($method, $factory))
116-
->filter()
117-
->values();
118-
119-
echo collect([
120-
'builderMethods' => $builderMethods,
121-
'models' => collect(get_declared_classes())
122-
->filter(fn($class) => is_subclass_of($class, \Illuminate\Database\Eloquent\Model::class))
123-
->filter(fn($class) => !in_array($class, [\Illuminate\Database\Eloquent\Relations\Pivot::class, \Illuminate\Foundation\Auth\User::class]))
124-
->values()
125-
->flatMap(fn(string $className) => getModelInfo($className, $factory))
126-
->filter(),
127-
])->toJson();
180+
echo json_encode([
181+
'builderMethods' => $builder->methods(),
182+
'models' => $models->all(),
183+
]);

php-templates/views.php

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,57 @@
11
<?php
22

3-
function vsCodeFindBladeFiles($path)
4-
{
5-
$paths = [];
3+
$blade = new class {
4+
public function findFiles($path)
5+
{
6+
$paths = [];
67

7-
if (!is_dir($path)) {
8-
return $paths;
9-
}
8+
if (!is_dir($path)) {
9+
return $paths;
10+
}
1011

11-
foreach (
12-
\Symfony\Component\Finder\Finder::create()
12+
$files = \Symfony\Component\Finder\Finder::create()
1313
->files()
1414
->name("*.blade.php")
15-
->in($path)
16-
as $file
17-
) {
18-
$paths[] = [
19-
"path" => str_replace(base_path(DIRECTORY_SEPARATOR), '', $file->getRealPath()),
20-
"isVendor" => str_contains($file->getRealPath(), base_path("vendor")),
21-
"key" => \Illuminate\Support\Str::of($file->getRealPath())
22-
->replace(realpath($path), "")
23-
->replace(".blade.php", "")
24-
->ltrim(DIRECTORY_SEPARATOR)
25-
->replace(DIRECTORY_SEPARATOR, ".")
26-
];
15+
->in($path);
16+
17+
foreach ($files as $file) {
18+
$paths[] = [
19+
"path" => str_replace(base_path(DIRECTORY_SEPARATOR), '', $file->getRealPath()),
20+
"isVendor" => str_contains($file->getRealPath(), base_path("vendor")),
21+
"key" => \Illuminate\Support\Str::of($file->getRealPath())
22+
->replace(realpath($path), "")
23+
->replace(".blade.php", "")
24+
->ltrim(DIRECTORY_SEPARATOR)
25+
->replace(DIRECTORY_SEPARATOR, ".")
26+
];
27+
}
28+
29+
return $paths;
2730
}
31+
};
2832

29-
return $paths;
30-
}
3133
$paths = collect(
3234
app("view")
3335
->getFinder()
3436
->getPaths()
35-
)->flatMap(function ($path) {
36-
return vsCodeFindBladeFiles($path);
37-
});
37+
)->flatMap(fn($path) => $blade->findFiles($path));
3838

3939
$hints = collect(
4040
app("view")
4141
->getFinder()
4242
->getHints()
43-
)->flatMap(function ($paths, $key) {
44-
return collect($paths)->flatMap(function ($path) use ($key) {
45-
return collect(vsCodeFindBladeFiles($path))->map(function ($value) use (
46-
$key
47-
) {
48-
return array_merge($value, ["key" => "{$key}::{$value["key"]}"]);
49-
});
50-
});
51-
});
43+
)->flatMap(
44+
fn($paths, $key) => collect($paths)->flatMap(
45+
fn($path) => collect($blade->findFiles($path))->map(
46+
fn($value) => array_merge($value, ["key" => "{$key}::{$value["key"]}"])
47+
)
48+
)
49+
);
5250

5351
[$local, $vendor] = $paths
5452
->merge($hints)
5553
->values()
56-
->partition(function ($v) {
57-
return !$v["isVendor"];
58-
});
54+
->partition(fn($v) => !$v["isVendor"]);
5955

6056
echo $local
6157
->sortBy("key", SORT_NATURAL)

0 commit comments

Comments
 (0)