1use php_ast::{Param, Visibility};
2use tower_lsp::lsp_types::{Hover, HoverContents, MarkupContent, MarkupKind};
3
4use crate::ast::format_type_hint;
5use crate::util::{is_php_builtin, php_doc_url};
6
7pub(crate) fn format_expr_literal(expr: &php_ast::Expr<'_, '_>) -> Option<String> {
9 use php_ast::ExprKind;
10 match &expr.kind {
11 ExprKind::Int(n) => Some(n.to_string()),
12 ExprKind::Float(f) => Some(f.to_string()),
13 ExprKind::Bool(b) => Some(if *b { "true" } else { "false" }.to_string()),
14 ExprKind::String(s) => Some(format!("'{}'", s)),
15 _ => None,
16 }
17}
18
19pub(crate) fn format_class_const(c: &php_ast::ClassConstDecl<'_, '_>) -> String {
21 use php_ast::ExprKind;
22 let type_str = c
23 .type_hint
24 .as_ref()
25 .map(|t| format!("{} ", format_type_hint(t)))
26 .or_else(|| match &c.value.kind {
27 ExprKind::Int(_) => Some("int ".to_string()),
28 ExprKind::String(_) => Some("string ".to_string()),
29 ExprKind::Float(_) => Some("float ".to_string()),
30 ExprKind::Bool(_) => Some("bool ".to_string()),
31 _ => None,
32 })
33 .unwrap_or_default();
34 let value_str = format_expr_literal(&c.value)
35 .map(|v| format!(" = {v}"))
36 .unwrap_or_default();
37 format!("const {}{}{}", type_str, c.name, value_str)
38}
39
40pub fn format_params_str(params: &[Param<'_, '_>]) -> String {
41 format_params(params)
42}
43
44pub(crate) fn format_params(params: &[Param<'_, '_>]) -> String {
45 params
46 .iter()
47 .map(|p| {
48 let mut s = String::new();
49 if p.by_ref {
50 s.push('&');
51 }
52 if let Some(t) = &p.type_hint {
53 s.push_str(&format!("{} ", format_type_hint(t)));
54 }
55 if p.variadic {
56 s.push_str("...");
57 }
58 s.push_str(&format!("${}", p.name));
59 if let Some(default) = &p.default {
60 s.push_str(&format!(" = {}", format_default_value(default)));
61 }
62 s
63 })
64 .collect::<Vec<_>>()
65 .join(", ")
66}
67
68pub(crate) fn format_default_value(expr: &php_ast::Expr<'_, '_>) -> String {
70 use php_ast::ExprKind;
71 match &expr.kind {
72 ExprKind::Int(n) => n.to_string(),
73 ExprKind::Float(f) => f.to_string(),
74 ExprKind::String(s) => format!("'{}'", s),
75 ExprKind::Bool(b) => {
76 if *b {
77 "true".to_string()
78 } else {
79 "false".to_string()
80 }
81 }
82 ExprKind::Null => "null".to_string(),
83 ExprKind::Array(items) => {
84 if items.is_empty() {
85 "[]".to_string()
86 } else {
87 "[...]".to_string()
88 }
89 }
90 _ => "...".to_string(),
91 }
92}
93
94pub(crate) fn wrap_php(sig: &str) -> String {
95 format!("```php\n{}\n```", sig)
96}
97
98fn visibility_str(v: &Visibility) -> &'static str {
99 match v {
100 Visibility::Public => "public",
101 Visibility::Protected => "protected",
102 Visibility::Private => "private",
103 }
104}
105
106pub(crate) fn format_method_prefix(
107 visibility: Option<&Visibility>,
108 is_static: bool,
109 is_abstract: bool,
110 is_final: bool,
111) -> String {
112 let mut parts: Vec<&str> = Vec::new();
113 if let Some(v) = visibility {
114 parts.push(visibility_str(v));
115 }
116 if is_abstract {
117 parts.push("abstract");
118 }
119 if is_final {
120 parts.push("final");
121 }
122 if is_static {
123 parts.push("static");
124 }
125 if parts.is_empty() {
126 String::new()
127 } else {
128 parts.join(" ") + " "
129 }
130}
131
132pub(crate) fn format_prop_prefix(
133 visibility: Option<&Visibility>,
134 is_static: bool,
135 is_readonly: bool,
136) -> String {
137 let mut parts: Vec<&str> = Vec::new();
138 if let Some(v) = visibility {
139 parts.push(visibility_str(v));
140 }
141 if is_static {
142 parts.push("static");
143 }
144 if is_readonly {
145 parts.push("readonly");
146 }
147 if parts.is_empty() {
148 String::new()
149 } else {
150 parts.join(" ") + " "
151 }
152}
153
154pub fn signature_for_symbol_from_index(
156 name: &str,
157 indexes: &[(
158 tower_lsp::lsp_types::Url,
159 std::sync::Arc<crate::file_index::FileIndex>,
160 )],
161) -> Option<String> {
162 for (_, idx) in indexes {
163 for f in &idx.functions {
164 if f.name.as_ref() == name {
165 let params_str = f
166 .params
167 .iter()
168 .map(|p| {
169 let mut s = String::new();
170 if let Some(t) = &p.type_hint {
171 s.push_str(&format!("{} ", t));
172 }
173 if p.variadic {
174 s.push_str("...");
175 }
176 s.push_str(&format!("${}", p.name));
177 s
178 })
179 .collect::<Vec<_>>()
180 .join(", ");
181 let ret = f
182 .return_type
183 .as_deref()
184 .map(|r| format!(": {}", r))
185 .unwrap_or_default();
186 return Some(format!("function {}({}){}", name, params_str, ret));
187 }
188 }
189 for cls in &idx.classes {
190 for m in &cls.methods {
191 if m.name.as_ref() == name {
192 let params_str = m
193 .params
194 .iter()
195 .map(|p| {
196 let mut s = String::new();
197 if let Some(t) = &p.type_hint {
198 s.push_str(&format!("{} ", t));
199 }
200 if p.variadic {
201 s.push_str("...");
202 }
203 s.push_str(&format!("${}", p.name));
204 s
205 })
206 .collect::<Vec<_>>()
207 .join(", ");
208 let ret = m
209 .return_type
210 .as_deref()
211 .map(|r| format!(": {}", r))
212 .unwrap_or_default();
213 return Some(format!("function {}({}){}", name, params_str, ret));
214 }
215 }
216 }
217 }
218 None
219}
220
221pub fn docs_for_symbol_from_index(
223 name: &str,
224 indexes: &[(
225 tower_lsp::lsp_types::Url,
226 std::sync::Arc<crate::file_index::FileIndex>,
227 )],
228) -> Option<String> {
229 if let Some(sig) = signature_for_symbol_from_index(name, indexes) {
230 let mut value = wrap_php(&sig);
231 for (_, idx) in indexes {
232 for f in &idx.functions {
233 if f.name.as_ref() == name {
234 if let Some(raw) = &f.doc {
235 let db = crate::docblock::parse_docblock(raw);
236 let md = db.to_markdown();
237 if !md.is_empty() {
238 value.push_str("\n\n---\n\n");
239 value.push_str(&md);
240 }
241 }
242 break;
243 }
244 }
245 for cls in &idx.classes {
246 for m in &cls.methods {
247 if m.name.as_ref() == name {
248 if let Some(raw) = &m.doc {
249 let db = crate::docblock::parse_docblock(raw);
250 let md = db.to_markdown();
251 if !md.is_empty() {
252 value.push_str("\n\n---\n\n");
253 value.push_str(&md);
254 }
255 }
256 break;
257 }
258 }
259 }
260 }
261 if is_php_builtin(name) {
262 value.push_str(&format!(
263 "\n\n[php.net documentation]({})",
264 php_doc_url(name)
265 ));
266 }
267 return Some(value);
268 }
269 if is_php_builtin(name) {
270 return Some(format!(
271 "```php\nfunction {}()\n```\n\n[php.net documentation]({})",
272 name,
273 php_doc_url(name)
274 ));
275 }
276 None
277}
278
279pub fn class_hover_from_index(
281 word: &str,
282 indexes: &[(
283 tower_lsp::lsp_types::Url,
284 std::sync::Arc<crate::file_index::FileIndex>,
285 )],
286) -> Option<Hover> {
287 use crate::file_index::ClassKind;
288
289 for (_, idx) in indexes {
290 for cls in &idx.classes {
291 if cls.name.as_ref() == word || cls.fqn.as_ref().trim_start_matches('\\') == word {
292 let kw = match cls.kind {
293 ClassKind::Interface => "interface",
294 ClassKind::Trait => "trait",
295 ClassKind::Enum => "enum",
296 ClassKind::Class => {
297 if cls.is_abstract {
298 "abstract class"
299 } else {
300 "class"
301 }
302 }
303 };
304 let mut sig = format!("{} {}", kw, &cls.name.to_string());
305 if let Some(parent) = &cls.parent {
306 sig.push_str(&format!(" extends {}", parent));
307 }
308 if !cls.implements.is_empty() {
309 let list: Vec<&str> = cls.implements.iter().map(|s| s.as_ref()).collect();
310 sig.push_str(&format!(" implements {}", list.join(", ")));
311 }
312 return Some(Hover {
313 contents: HoverContents::Markup(MarkupContent {
314 kind: MarkupKind::Markdown,
315 value: wrap_php(&sig),
316 }),
317 range: None,
318 });
319 }
320 }
321 }
322 None
323}