php_lsp/hover/
formatting.rs1use crate::document::ast::format_type_hint;
2use crate::types::resolve::Declaration;
3use php_ast::{MethodDecl, Param, Visibility};
4
5pub(crate) fn format_expr_literal(expr: &php_ast::Expr<'_, '_>) -> Option<String> {
7 use php_ast::ExprKind;
8 match &expr.kind {
9 ExprKind::Int(n) => Some(n.to_string()),
10 ExprKind::Float(f) => Some(f.to_string()),
11 ExprKind::Bool(b) => Some(if *b { "true" } else { "false" }.to_string()),
12 ExprKind::String(s) => Some(format!("'{}'", s)),
13 _ => None,
14 }
15}
16
17pub(crate) fn format_class_const(c: &php_ast::ClassConstDecl<'_, '_>) -> String {
19 use php_ast::ExprKind;
20 let type_str = c
21 .type_hint
22 .as_ref()
23 .map(|t| format!("{} ", format_type_hint(t)))
24 .or_else(|| match &c.value.kind {
25 ExprKind::Int(_) => Some("int ".to_string()),
26 ExprKind::String(_) => Some("string ".to_string()),
27 ExprKind::Float(_) => Some("float ".to_string()),
28 ExprKind::Bool(_) => Some("bool ".to_string()),
29 _ => None,
30 })
31 .unwrap_or_default();
32 let value_str = format_expr_literal(&c.value)
33 .map(|v| format!(" = {v}"))
34 .unwrap_or_default();
35 format!("const {}{}{}", type_str, c.name, value_str)
36}
37
38pub fn format_params_str(params: &[Param<'_, '_>]) -> String {
39 format_params(params)
40}
41
42pub(crate) fn format_params(params: &[Param<'_, '_>]) -> String {
43 params
44 .iter()
45 .map(|p| {
46 let mut s = String::new();
47 if p.by_ref {
48 s.push('&');
49 }
50 if let Some(t) = &p.type_hint {
51 s.push_str(&format!("{} ", format_type_hint(t)));
52 }
53 if p.variadic {
54 s.push_str("...");
55 }
56 s.push_str(&format!("${}", p.name));
57 if let Some(default) = &p.default {
58 s.push_str(&format!(" = {}", format_default_value(default)));
59 }
60 s
61 })
62 .collect::<Vec<_>>()
63 .join(", ")
64}
65
66pub(crate) fn format_default_value(expr: &php_ast::Expr<'_, '_>) -> String {
68 use php_ast::ExprKind;
69 match &expr.kind {
70 ExprKind::Int(n) => n.to_string(),
71 ExprKind::Float(f) => f.to_string(),
72 ExprKind::String(s) => format!("'{}'", s),
73 ExprKind::Bool(b) => {
74 if *b {
75 "true".to_string()
76 } else {
77 "false".to_string()
78 }
79 }
80 ExprKind::Null => "null".to_string(),
81 ExprKind::Array(items) => {
82 if items.is_empty() {
83 "[]".to_string()
84 } else {
85 "[...]".to_string()
86 }
87 }
88 _ => "...".to_string(),
89 }
90}
91
92pub(crate) fn wrap_php(sig: &str) -> String {
93 format!("```php\n{}\n```", sig)
94}
95
96pub(crate) fn method_signature(m: &MethodDecl<'_, '_>) -> String {
99 let prefix = format_method_prefix(
100 m.visibility.as_ref(),
101 m.is_static,
102 m.is_abstract,
103 m.is_final,
104 );
105 let params = format_params(&m.params);
106 let ret = m
107 .return_type
108 .as_ref()
109 .map(|r| format!(": {}", format_type_hint(r)))
110 .unwrap_or_default();
111 format!("{}function {}({}){}", prefix, m.name, params, ret)
112}
113
114pub(crate) fn declaration_signature(decl: &Declaration<'_>, word: &str) -> Option<String> {
117 let sig = match decl {
118 Declaration::Function { decl: f, .. } => {
119 let params = format_params(&f.params);
120 let ret = f
121 .return_type
122 .as_ref()
123 .map(|r| format!(": {}", format_type_hint(r)))
124 .unwrap_or_default();
125 format!("function {}({}){}", word, params, ret)
126 }
127 Declaration::Class { decl: c, .. } => {
128 let kw = if c.modifiers.is_abstract {
129 "abstract class"
130 } else if c.modifiers.is_final {
131 "final class"
132 } else if c.modifiers.is_readonly {
133 "readonly class"
134 } else {
135 "class"
136 };
137 let mut sig = format!("{} {}", kw, word);
138 if let Some(ext) = &c.extends {
139 sig.push_str(&format!(" extends {}", ext.to_string_repr()));
140 }
141 if !c.implements.is_empty() {
142 let ifaces: Vec<String> = c
143 .implements
144 .iter()
145 .map(|i| i.to_string_repr().into_owned())
146 .collect();
147 sig.push_str(&format!(" implements {}", ifaces.join(", ")));
148 }
149 sig
150 }
151 Declaration::Interface { .. } => format!("interface {}", word),
152 Declaration::Trait { .. } => format!("trait {}", word),
153 Declaration::Enum { decl: e, .. } => {
154 let mut sig = if let Some(scalar) = &e.scalar_type {
155 format!("enum {}: {}", word, scalar.to_string_repr())
156 } else {
157 format!("enum {}", word)
158 };
159 if !e.implements.is_empty() {
160 let ifaces: Vec<String> = e
161 .implements
162 .iter()
163 .map(|i| i.to_string_repr().into_owned())
164 .collect();
165 sig.push_str(&format!(" implements {}", ifaces.join(", ")));
166 }
167 sig
168 }
169 Declaration::Method { method, .. } => method_signature(method),
170 Declaration::ClassConst { konst, .. } => format_class_const(konst),
171 Declaration::EnumCase {
172 case, enum_name, ..
173 } => {
174 let value_str = case
175 .value
176 .as_ref()
177 .and_then(format_expr_literal)
178 .map(|v| format!(" = {v}"))
179 .unwrap_or_default();
180 format!("case {}::{}{}", enum_name, case.name, value_str)
181 }
182 Declaration::Property { .. } | Declaration::PromotedParam { .. } => return None,
183 };
184 Some(sig)
185}
186
187fn visibility_str(v: &Visibility) -> &'static str {
188 match v {
189 Visibility::Public => "public",
190 Visibility::Protected => "protected",
191 Visibility::Private => "private",
192 }
193}
194
195pub(crate) fn format_method_prefix(
196 visibility: Option<&Visibility>,
197 is_static: bool,
198 is_abstract: bool,
199 is_final: bool,
200) -> String {
201 let mut parts: Vec<&str> = Vec::new();
202 if let Some(v) = visibility {
203 parts.push(visibility_str(v));
204 }
205 if is_abstract {
206 parts.push("abstract");
207 }
208 if is_final {
209 parts.push("final");
210 }
211 if is_static {
212 parts.push("static");
213 }
214 if parts.is_empty() {
215 String::new()
216 } else {
217 parts.join(" ") + " "
218 }
219}
220
221pub(crate) fn format_prop_prefix(
222 visibility: Option<&Visibility>,
223 is_static: bool,
224 is_readonly: bool,
225) -> String {
226 let mut parts: Vec<&str> = Vec::new();
227 if let Some(v) = visibility {
228 parts.push(visibility_str(v));
229 }
230 if is_static {
231 parts.push("static");
232 }
233 if is_readonly {
234 parts.push("readonly");
235 }
236 if parts.is_empty() {
237 String::new()
238 } else {
239 parts.join(" ") + " "
240 }
241}