Skip to content

Commit 191a4de

Browse files
committed
auth feature improvements
1 parent c7b6845 commit 191a4de

File tree

4 files changed

+223
-84
lines changed

4 files changed

+223
-84
lines changed

php-templates/auth.php

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
<?php
22

3+
collect(glob(base_path('**/Models/*.php')))->each(fn($file) => include_once($file));
4+
5+
$modelPolicies = collect(get_declared_classes())
6+
->filter(fn($class) => is_subclass_of($class, \Illuminate\Database\Eloquent\Model::class))
7+
->filter(fn($class) => !in_array($class, [
8+
\Illuminate\Database\Eloquent\Relations\Pivot::class,
9+
\Illuminate\Foundation\Auth\User::class,
10+
]))
11+
->flatMap(fn($class) => [
12+
$class => Gate::getPolicyFor($class),
13+
])
14+
->filter(fn($policy) => $policy !== null);
15+
16+
function vsCodeGetPolicyInfo($policy, $model)
17+
{
18+
$methods = (new ReflectionClass($policy))->getMethods();
19+
20+
return collect($methods)->map(fn(ReflectionMethod $method) => [
21+
'key' => $method->getName(),
22+
'uri' => $method->getFileName(),
23+
'policy' => is_string($policy) ? $policy : get_class($policy),
24+
'model' => $model,
25+
'line' => $method->getStartLine(),
26+
])->filter(fn($ability) => !in_array($ability['key'], ['allow', 'deny']));
27+
}
28+
329
echo collect(\Illuminate\Support\Facades\Gate::abilities())
430
->map(function ($policy, $key) {
531
$reflection = new \ReflectionFunction($policy);
@@ -21,26 +47,17 @@
2147
return [
2248
'key' => $key,
2349
'uri' => $reflection->getFileName(),
24-
'policy_class' => $policyClass,
25-
'lineNumber' => $reflection->getStartLine(),
50+
'policy' => $policyClass,
51+
'line' => $reflection->getStartLine(),
2652
];
2753
})
2854
->merge(
29-
collect(\Illuminate\Support\Facades\Gate::policies())->flatMap(function ($policy, $model) {
30-
$methods = (new ReflectionClass($policy))->getMethods();
31-
32-
return collect($methods)->map(function (ReflectionMethod $method) use ($policy) {
33-
return [
34-
'key' => $method->getName(),
35-
'uri' => $method->getFileName(),
36-
'policy_class' => $policy,
37-
'lineNumber' => $method->getStartLine(),
38-
];
39-
})->filter(function ($ability) {
40-
return !in_array($ability['key'], ['allow', 'deny']);
41-
});
42-
}),
55+
collect(\Illuminate\Support\Facades\Gate::policies())->flatMap(fn($policy, $model) => vsCodeGetPolicyInfo($policy, $model)),
56+
)
57+
->merge(
58+
$modelPolicies->flatMap(fn($policy, $model) => vsCodeGetPolicyInfo($policy, $model)),
4359
)
4460
->values()
4561
->groupBy('key')
62+
->map(fn($item) => $item->map(fn($i) => \Illuminate\Support\Arr::except($i, 'key')))
4663
->toJson();

src/features/auth.ts

Lines changed: 150 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { notFound } from "@src/diagnostic";
22
import AutocompleteResult from "@src/parser/AutocompleteResult";
3-
import { getPolicies } from "@src/repositories/auth";
3+
import { AuthItem, getPolicies } from "@src/repositories/auth";
44
import { config } from "@src/support/config";
55
import { findHoverMatchesInDoc } from "@src/support/doc";
66
import { detectedRange, detectInDoc } from "@src/support/parser";
@@ -45,57 +45,127 @@ export const linkProvider: LinkProvider = (doc: vscode.TextDocument) => {
4545
doc,
4646
toFind,
4747
getPolicies,
48-
({ param }) => {
48+
({ param, item, index }) => {
4949
const policy = getPolicies().items[param.value];
5050

5151
if (!policy || policy.length === 0) {
5252
return null;
5353
}
5454

55-
return policy.map((item) => {
56-
return new vscode.DocumentLink(
57-
detectedRange(param),
58-
vscode.Uri.file(item.uri).with({
59-
fragment: `L${item.lineNumber}`,
60-
}),
61-
);
62-
});
55+
if (item.type !== "methodCall" || !item.methodName || index !== 0) {
56+
return null;
57+
}
58+
59+
if (["has"].includes(item.methodName)) {
60+
return formattedLink(policy, param);
61+
}
62+
63+
if (item.arguments.children.length < 2) {
64+
// We don't have a second argument, just ignore it for now
65+
return null;
66+
}
67+
68+
// @ts-ignore
69+
const nextArg = item.arguments.children[1].children[0];
70+
const classArg = nextArg?.className;
71+
72+
if (!classArg) {
73+
// If it's not a class we can even identify, just ignore it
74+
return null;
75+
}
76+
77+
const found = policy.find((item) => item.model === classArg);
78+
79+
if (!found) {
80+
return null;
81+
}
82+
83+
return formattedLink([found], param);
6384
},
6485
);
6586
};
6687

88+
const formattedLink = (items: AuthItem[], param: any) => {
89+
return items.map((item) => {
90+
return new vscode.DocumentLink(
91+
detectedRange(param),
92+
vscode.Uri.file(item.uri).with({
93+
fragment: `L${item.line}`,
94+
}),
95+
);
96+
});
97+
};
98+
99+
const formattedHover = (items: AuthItem[]) => {
100+
const text = items.map((item) => {
101+
if (item.policy) {
102+
return [
103+
"`" + item.policy + "`",
104+
relativeMarkdownLink(
105+
vscode.Uri.file(item.uri).with({
106+
fragment: `L${item.line}`,
107+
}),
108+
),
109+
].join("\n\n");
110+
}
111+
112+
return relativeMarkdownLink(
113+
vscode.Uri.file(item.uri).with({
114+
fragment: `L${item.line}`,
115+
}),
116+
);
117+
});
118+
119+
return new vscode.Hover(new vscode.MarkdownString(text.join("\n\n")));
120+
};
121+
67122
export const hoverProvider: HoverProvider = (
68123
doc: vscode.TextDocument,
69124
pos: vscode.Position,
70125
): vscode.ProviderResult<vscode.Hover> => {
71-
return findHoverMatchesInDoc(doc, pos, toFind, getPolicies, (match) => {
72-
const items = getPolicies().items[match];
126+
return findHoverMatchesInDoc(
127+
doc,
128+
pos,
129+
toFind,
130+
getPolicies,
131+
(match, { index, item }) => {
132+
const items = getPolicies().items[match];
73133

74-
if (!items || items.length === 0) {
75-
return null;
76-
}
134+
if (!items || items.length === 0) {
135+
return null;
136+
}
77137

78-
const text = items.map((item) => {
79-
if (item.policy_class) {
80-
return [
81-
"`" + item.policy_class + "`",
82-
relativeMarkdownLink(
83-
vscode.Uri.file(item.uri).with({
84-
fragment: `L${item.lineNumber}`,
85-
}),
86-
),
87-
].join("\n\n");
88-
}
89-
90-
return relativeMarkdownLink(
91-
vscode.Uri.file(item.uri).with({
92-
fragment: `L${item.lineNumber}`,
93-
}),
94-
);
95-
});
138+
if (item.type !== "methodCall" || !item.methodName || index !== 0) {
139+
return null;
140+
}
96141

97-
return new vscode.Hover(new vscode.MarkdownString(text.join("\n\n")));
98-
});
142+
if (["has"].includes(item.methodName)) {
143+
return formattedHover(items);
144+
}
145+
146+
if (item.arguments.children.length < 2) {
147+
// We don't have a second argument, just ignore it for now
148+
return null;
149+
}
150+
151+
// @ts-ignore
152+
const nextArg = item.arguments.children[1].children[0];
153+
const classArg = nextArg?.className;
154+
155+
if (!classArg) {
156+
// If it's not a class we can even identify, just ignore it
157+
return null;
158+
}
159+
160+
const found = items.find((item) => item.model === classArg);
161+
162+
if (!found) {
163+
return null;
164+
}
165+
166+
return formattedHover([found]);
167+
},
168+
);
99169
};
100170

101171
export const diagnosticProvider = (
@@ -105,17 +175,52 @@ export const diagnosticProvider = (
105175
doc,
106176
toFind,
107177
getPolicies,
108-
({ param }) => {
109-
if (getPolicies().items[param.value]) {
178+
({ param, item, index }) => {
179+
if (item.type !== "methodCall" || !item.methodName || index !== 0) {
110180
return null;
111181
}
112182

113-
return notFound(
114-
"Policy",
115-
param.value,
116-
detectedRange(param),
117-
"auth",
118-
);
183+
const policy = getPolicies().items[param.value];
184+
185+
if (!policy) {
186+
return notFound(
187+
"Policy",
188+
param.value,
189+
detectedRange(param),
190+
"auth",
191+
);
192+
}
193+
194+
if (["has"].includes(item.methodName)) {
195+
return null;
196+
}
197+
198+
if (item.arguments.children.length < 2) {
199+
// We don't have a second argument, just ignore it for now
200+
return null;
201+
}
202+
203+
// @ts-ignore
204+
const nextArg = item.arguments.children[1].children[0];
205+
const classArg = nextArg?.className;
206+
207+
if (!classArg) {
208+
// If it's not a class we can even identify, just ignore it
209+
return null;
210+
}
211+
212+
const found = policy.find((item) => item.model === classArg);
213+
214+
if (!found) {
215+
return notFound(
216+
"Policy/Model match",
217+
classArg,
218+
detectedRange(param),
219+
"auth",
220+
);
221+
}
222+
223+
return null;
119224
},
120225
);
121226
};
@@ -142,7 +247,7 @@ export const completionProvider: CompletionProvider = {
142247

143248
return Object.entries(getPolicies().items).map(([key, value]) => {
144249
let completeItem = new vscode.CompletionItem(
145-
value[0].key,
250+
key,
146251
vscode.CompletionItemKind.Value,
147252
);
148253

@@ -152,7 +257,7 @@ export const completionProvider: CompletionProvider = {
152257
);
153258

154259
const policyClasses = value
155-
.map((item) => item.policy_class)
260+
.map((item) => item.policy)
156261
.filter(String);
157262

158263
if (policyClasses.length > 0) {

src/repositories/auth.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { repository } from ".";
22
import { runInLaravel, template } from "./../support/php";
33

4-
interface AuthItems {
4+
type AuthItems = {
55
[key: string]: AuthItem[];
6-
}
6+
};
77

8-
interface AuthItem {
9-
key: string;
10-
policy_class: string | null;
8+
export type AuthItem = {
9+
policy: string | null;
1110
uri: string;
12-
lineNumber: number;
13-
}
11+
line: number;
12+
model: string | null;
13+
};
1414

1515
const load = () => {
1616
return runInLaravel<AuthItems>(template("auth"), "Auth Data");

0 commit comments

Comments
 (0)