1use php_ast::{ClassMemberKind, EnumMemberKind, ExprKind, NamespaceBody, Stmt, StmtKind};
2use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind};
3
4use crate::ast::{ParsedDoc, SourceView};
5
6use super::{build_function_sig, callable_item, docblock_docs, named_arg_item};
7
8pub fn symbol_completions(doc: &ParsedDoc) -> Vec<CompletionItem> {
9 let mut items = Vec::new();
10 collect_from_statements_with_doc(&doc.program().stmts, &mut items, Some(doc));
11 items
12}
13
14pub fn symbol_completions_before(doc: &ParsedDoc, line: u32) -> Vec<CompletionItem> {
17 let sv = doc.view();
18 let mut items = Vec::new();
19 collect_from_statements_before(&doc.program().stmts, &mut items, line, sv, Some(doc));
20 items
21}
22
23fn collect_from_statements_before(
24 stmts: &[Stmt<'_, '_>],
25 items: &mut Vec<CompletionItem>,
26 line: u32,
27 sv: SourceView<'_>,
28 doc: Option<&ParsedDoc>,
29) {
30 for stmt in stmts {
31 match &stmt.kind {
32 StmtKind::Expression(e) => {
33 let stmt_line = sv.position_of(stmt.span.start).line;
35 if stmt_line <= line {
36 collect_from_expression(e, items);
37 }
38 }
39 StmtKind::Namespace(ns) => {
40 if let NamespaceBody::Braced(inner) = &ns.body {
41 collect_from_statements_before(inner, items, line, sv, doc);
42 }
43 }
44 _ => {
46 collect_from_statements_with_doc(std::slice::from_ref(stmt), items, doc);
47 }
48 }
49 }
50}
51
52fn collect_from_statements_with_doc(
53 stmts: &[Stmt<'_, '_>],
54 items: &mut Vec<CompletionItem>,
55 doc: Option<&ParsedDoc>,
56) {
57 for stmt in stmts {
58 match &stmt.kind {
59 StmtKind::Function(f) => {
60 let sig = build_function_sig(f.name, &f.params, f.return_type.as_ref());
61 let documentation = doc.and_then(|d| docblock_docs(d, f.name));
62 let mut item =
63 callable_item(f.name, CompletionItemKind::FUNCTION, !f.params.is_empty());
64 item.detail = Some(sig);
65 item.documentation = documentation;
66 items.push(item);
67 if let Some(named) = named_arg_item(f.name, CompletionItemKind::FUNCTION, &f.params)
68 {
69 items.push(named);
70 }
71 for param in f.params.iter() {
72 items.push(CompletionItem {
73 label: format!("${}", param.name),
74 kind: Some(CompletionItemKind::VARIABLE),
75 ..Default::default()
76 });
77 }
78 }
79 StmtKind::Class(c) => {
80 let class_name = c.name.unwrap_or("");
81 if !class_name.is_empty() {
82 items.push(CompletionItem {
83 label: class_name.to_string(),
84 kind: Some(CompletionItemKind::CLASS),
85 ..Default::default()
86 });
87 }
88 for member in c.members.iter() {
89 match &member.kind {
90 ClassMemberKind::Method(m) => {
91 let sig = build_function_sig(m.name, &m.params, m.return_type.as_ref());
92 let documentation = doc.and_then(|d| docblock_docs(d, m.name));
93 let mut item = callable_item(
94 m.name,
95 CompletionItemKind::METHOD,
96 !m.params.is_empty(),
97 );
98 item.detail = Some(sig);
99 item.documentation = documentation;
100 items.push(item);
101 if let Some(named) =
102 named_arg_item(m.name, CompletionItemKind::METHOD, &m.params)
103 {
104 items.push(named);
105 }
106 }
107 ClassMemberKind::Property(p) => {
108 items.push(CompletionItem {
109 label: format!("${}", p.name),
110 kind: Some(CompletionItemKind::PROPERTY),
111 ..Default::default()
112 });
113 }
114 ClassMemberKind::ClassConst(c) => {
115 items.push(CompletionItem {
116 label: c.name.to_string(),
117 kind: Some(CompletionItemKind::CONSTANT),
118 ..Default::default()
119 });
120 }
121 _ => {}
122 }
123 }
124 }
125 StmtKind::Interface(i) => {
126 items.push(CompletionItem {
127 label: i.name.to_string(),
128 kind: Some(CompletionItemKind::INTERFACE),
129 ..Default::default()
130 });
131 }
132 StmtKind::Trait(t) => {
133 items.push(CompletionItem {
134 label: t.name.to_string(),
135 kind: Some(CompletionItemKind::CLASS),
136 ..Default::default()
137 });
138 }
139 StmtKind::Enum(e) => {
140 items.push(CompletionItem {
141 label: e.name.to_string(),
142 kind: Some(CompletionItemKind::ENUM),
143 ..Default::default()
144 });
145 for member in e.members.iter() {
146 if let EnumMemberKind::Case(c) = &member.kind {
147 items.push(CompletionItem {
148 label: format!("{}::{}", e.name, c.name),
149 kind: Some(CompletionItemKind::ENUM_MEMBER),
150 ..Default::default()
151 });
152 }
153 }
154 }
155 StmtKind::Namespace(ns) => {
156 if let NamespaceBody::Braced(inner) = &ns.body {
157 collect_from_statements_with_doc(inner, items, doc);
158 }
159 }
160 StmtKind::Expression(e) => {
161 collect_from_expression(e, items);
162 }
163 _ => {}
164 }
165 }
166}
167
168fn collect_from_expression(expr: &php_ast::Expr<'_, '_>, items: &mut Vec<CompletionItem>) {
169 if let ExprKind::Assign(assign) = &expr.kind {
170 match &assign.target.kind {
171 ExprKind::Variable(name) => {
172 let label = format!("${}", name.as_str());
173 if label != "$this" {
174 items.push(CompletionItem {
175 label,
176 kind: Some(CompletionItemKind::VARIABLE),
177 ..Default::default()
178 });
179 }
180 }
181 ExprKind::Array(elements) => {
183 for elem in elements.iter() {
184 if let ExprKind::Variable(name) = &elem.value.kind {
185 let label = format!("${}", name.as_str());
186 if label != "$this" {
187 items.push(CompletionItem {
188 label,
189 kind: Some(CompletionItemKind::VARIABLE),
190 ..Default::default()
191 });
192 }
193 }
194 }
195 }
196 _ => {}
197 }
198 collect_from_expression(assign.value, items);
199 }
200}
201
202const PHP_BUILTINS: &[&str] = &[
203 "strlen",
205 "strpos",
206 "strrpos",
207 "substr",
208 "str_replace",
209 "str_contains",
210 "str_starts_with",
211 "str_ends_with",
212 "str_split",
213 "explode",
214 "implode",
215 "join",
216 "trim",
217 "ltrim",
218 "rtrim",
219 "strtolower",
220 "strtoupper",
221 "ucfirst",
222 "lcfirst",
223 "ucwords",
224 "sprintf",
225 "printf",
226 "vsprintf",
227 "number_format",
228 "nl2br",
229 "htmlspecialchars",
230 "htmlentities",
231 "strip_tags",
232 "addslashes",
233 "stripslashes",
234 "str_pad",
235 "str_repeat",
236 "str_word_count",
237 "strcmp",
238 "strcasecmp",
239 "strncmp",
240 "strncasecmp",
241 "substr_count",
242 "substr_replace",
243 "strstr",
244 "stristr",
245 "preg_match",
246 "preg_match_all",
247 "preg_replace",
248 "preg_split",
249 "preg_quote",
250 "md5",
251 "sha1",
252 "hash",
253 "base64_encode",
254 "base64_decode",
255 "urlencode",
256 "urldecode",
257 "rawurlencode",
258 "rawurldecode",
259 "http_build_query",
260 "parse_str",
261 "parse_url",
262 "count",
264 "array_key_exists",
265 "in_array",
266 "array_search",
267 "array_merge",
268 "array_replace",
269 "array_push",
270 "array_pop",
271 "array_shift",
272 "array_unshift",
273 "array_splice",
274 "array_slice",
275 "array_chunk",
276 "array_combine",
277 "array_diff",
278 "array_intersect",
279 "array_unique",
280 "array_flip",
281 "array_reverse",
282 "array_keys",
283 "array_values",
284 "array_map",
285 "array_filter",
286 "array_reduce",
287 "array_walk",
288 "array_fill",
289 "array_fill_keys",
290 "array_pad",
291 "sort",
292 "rsort",
293 "asort",
294 "arsort",
295 "ksort",
296 "krsort",
297 "usort",
298 "uasort",
299 "uksort",
300 "compact",
301 "extract",
302 "list",
303 "range",
304 "abs",
306 "ceil",
307 "floor",
308 "round",
309 "max",
310 "min",
311 "pow",
312 "sqrt",
313 "log",
314 "exp",
315 "rand",
316 "mt_rand",
317 "random_int",
318 "fmod",
319 "intdiv",
320 "intval",
321 "floatval",
322 "is_nan",
323 "is_infinite",
324 "is_finite",
325 "pi",
326 "sin",
327 "cos",
328 "tan",
329 "asin",
330 "acos",
331 "atan",
332 "atan2",
333 "isset",
335 "empty",
336 "unset",
337 "is_null",
338 "is_bool",
339 "is_int",
340 "is_integer",
341 "is_long",
342 "is_float",
343 "is_double",
344 "is_string",
345 "is_array",
346 "is_object",
347 "is_callable",
348 "is_numeric",
349 "is_a",
350 "instanceof",
351 "gettype",
352 "settype",
353 "intval",
354 "floatval",
355 "strval",
356 "boolval",
357 "var_dump",
358 "var_export",
359 "print_r",
360 "serialize",
361 "unserialize",
362 "file_get_contents",
364 "file_put_contents",
365 "file_exists",
366 "is_file",
367 "is_dir",
368 "is_readable",
369 "is_writable",
370 "mkdir",
371 "rmdir",
372 "unlink",
373 "rename",
374 "copy",
375 "realpath",
376 "dirname",
377 "basename",
378 "pathinfo",
379 "glob",
380 "scandir",
381 "opendir",
382 "readdir",
383 "closedir",
384 "fopen",
385 "fclose",
386 "fread",
387 "fwrite",
388 "fgets",
389 "fputs",
390 "feof",
391 "fseek",
392 "ftell",
393 "rewind",
394 "time",
396 "microtime",
397 "mktime",
398 "strtotime",
399 "date",
400 "date_create",
401 "date_format",
402 "date_diff",
403 "date_add",
404 "date_sub",
405 "checkdate",
406 "defined",
408 "define",
409 "constant",
410 "class_exists",
411 "interface_exists",
412 "function_exists",
413 "method_exists",
414 "property_exists",
415 "get_class",
416 "get_parent_class",
417 "is_subclass_of",
418 "header",
419 "headers_sent",
420 "setcookie",
421 "session_start",
422 "session_destroy",
423 "ob_start",
424 "ob_get_clean",
425 "ob_end_clean",
426 "json_encode",
427 "json_decode",
428 "call_user_func",
429 "call_user_func_array",
430 "array_walk_recursive",
431 "array_map",
432 "compact",
433 "extract",
434 "sleep",
435 "usleep",
436 "exit",
437 "die",
438];
439
440pub fn builtin_completions() -> Vec<CompletionItem> {
441 let mut seen = std::collections::HashSet::new();
442 PHP_BUILTINS
443 .iter()
444 .filter(|&&f| seen.insert(f))
445 .map(|f| callable_item(f, CompletionItemKind::FUNCTION, true))
446 .collect()
447}
448
449const PHP_SUPERGLOBALS: &[&str] = &[
450 "$_SERVER",
451 "$_GET",
452 "$_POST",
453 "$_FILES",
454 "$_COOKIE",
455 "$_SESSION",
456 "$_REQUEST",
457 "$_ENV",
458 "$GLOBALS",
459];
460
461pub fn superglobal_completions() -> Vec<CompletionItem> {
462 PHP_SUPERGLOBALS
463 .iter()
464 .map(|&name| CompletionItem {
465 label: name.to_string(),
466 kind: Some(CompletionItemKind::VARIABLE),
467 detail: Some("superglobal".to_string()),
468 ..Default::default()
469 })
470 .collect()
471}