@@ -8,14 +8,56 @@ use bumpalo::Bump;
88use mago_syntax:: ast:: * ;
99use mago_syntax:: parser:: parse_file_content;
1010
11+ /// Stores extracted parameter information from a parsed PHP method.
12+ #[ derive( Debug , Clone ) ]
13+ pub struct ParameterInfo {
14+ /// The parameter name including the `$` prefix (e.g. "$text").
15+ pub name : String ,
16+ /// Whether this parameter is required (no default value and not variadic).
17+ pub is_required : bool ,
18+ /// Optional type hint string (e.g. "string", "int", "?Foo").
19+ pub type_hint : Option < String > ,
20+ /// Whether this parameter is variadic (has `...`).
21+ pub is_variadic : bool ,
22+ /// Whether this parameter is passed by reference (has `&`).
23+ pub is_reference : bool ,
24+ }
25+
26+ /// Stores extracted method information from a parsed PHP class.
27+ #[ derive( Debug , Clone ) ]
28+ pub struct MethodInfo {
29+ /// The method name (e.g. "updateText").
30+ pub name : String ,
31+ /// The parameters of the method.
32+ pub parameters : Vec < ParameterInfo > ,
33+ /// Optional return type hint string (e.g. "void", "string", "?int").
34+ pub return_type : Option < String > ,
35+ /// Whether the method is static.
36+ pub is_static : bool ,
37+ }
38+
39+ /// Stores extracted property information from a parsed PHP class.
40+ #[ derive( Debug , Clone ) ]
41+ pub struct PropertyInfo {
42+ /// The property name WITHOUT the `$` prefix (e.g. "name", "age").
43+ /// This matches PHP access syntax: `$this->name` not `$this->$name`.
44+ pub name : String ,
45+ /// Optional type hint string (e.g. "string", "int").
46+ pub type_hint : Option < String > ,
47+ /// Whether the property is static.
48+ pub is_static : bool ,
49+ }
50+
1151/// Stores extracted class information from a parsed PHP file.
1252/// All data is owned so we don't depend on the parser's arena lifetime.
1353#[ derive( Debug , Clone ) ]
1454pub struct ClassInfo {
1555 /// The name of the class (e.g. "User").
1656 pub name : String ,
17- /// The names of methods defined directly in this class.
18- pub methods : Vec < String > ,
57+ /// The methods defined directly in this class.
58+ pub methods : Vec < MethodInfo > ,
59+ /// The properties defined directly in this class.
60+ pub properties : Vec < PropertyInfo > ,
1961 /// Byte offset where the class body starts (left brace).
2062 pub start_offset : u32 ,
2163 /// Byte offset where the class body ends (right brace).
@@ -88,6 +130,99 @@ impl Backend {
88130 }
89131 }
90132
133+ /// Extract a string representation of a type hint from the AST.
134+ fn extract_hint_string ( hint : & Hint ) -> String {
135+ match hint {
136+ Hint :: Identifier ( ident) => ident. value ( ) . to_string ( ) ,
137+ Hint :: Nullable ( nullable) => {
138+ format ! ( "?{}" , Self :: extract_hint_string( nullable. hint) )
139+ }
140+ Hint :: Union ( union) => {
141+ let left = Self :: extract_hint_string ( union. left ) ;
142+ let right = Self :: extract_hint_string ( union. right ) ;
143+ format ! ( "{}|{}" , left, right)
144+ }
145+ Hint :: Intersection ( intersection) => {
146+ let left = Self :: extract_hint_string ( intersection. left ) ;
147+ let right = Self :: extract_hint_string ( intersection. right ) ;
148+ format ! ( "{}&{}" , left, right)
149+ }
150+ Hint :: Void ( ident)
151+ | Hint :: Never ( ident)
152+ | Hint :: Float ( ident)
153+ | Hint :: Bool ( ident)
154+ | Hint :: Integer ( ident)
155+ | Hint :: String ( ident)
156+ | Hint :: Object ( ident)
157+ | Hint :: Mixed ( ident)
158+ | Hint :: Iterable ( ident) => ident. value . to_string ( ) ,
159+ Hint :: Null ( keyword)
160+ | Hint :: True ( keyword)
161+ | Hint :: False ( keyword)
162+ | Hint :: Array ( keyword)
163+ | Hint :: Callable ( keyword)
164+ | Hint :: Static ( keyword)
165+ | Hint :: Self_ ( keyword)
166+ | Hint :: Parent ( keyword) => keyword. value . to_string ( ) ,
167+ Hint :: Parenthesized ( paren) => {
168+ format ! ( "({})" , Self :: extract_hint_string( paren. hint) )
169+ }
170+ }
171+ }
172+
173+ /// Extract parameter information from a method's parameter list.
174+ fn extract_parameters ( parameter_list : & FunctionLikeParameterList ) -> Vec < ParameterInfo > {
175+ parameter_list
176+ . parameters
177+ . iter ( )
178+ . map ( |param| {
179+ let name = param. variable . name . to_string ( ) ;
180+ let is_variadic = param. ellipsis . is_some ( ) ;
181+ let is_reference = param. ampersand . is_some ( ) ;
182+ let has_default = param. default_value . is_some ( ) ;
183+ let is_required = !has_default && !is_variadic;
184+
185+ let type_hint = param. hint . as_ref ( ) . map ( |h| Self :: extract_hint_string ( h) ) ;
186+
187+ ParameterInfo {
188+ name,
189+ is_required,
190+ type_hint,
191+ is_variadic,
192+ is_reference,
193+ }
194+ } )
195+ . collect ( )
196+ }
197+
198+ /// Extract property information from a class member Property node.
199+ fn extract_property_info ( property : & Property ) -> Vec < PropertyInfo > {
200+ let is_static = property. modifiers ( ) . iter ( ) . any ( |m| m. is_static ( ) ) ;
201+
202+ let type_hint = property. hint ( ) . map ( |h| Self :: extract_hint_string ( h) ) ;
203+
204+ property
205+ . variables ( )
206+ . iter ( )
207+ . map ( |var| {
208+ let raw_name = var. name . to_string ( ) ;
209+ // Strip the leading `$` for property names since PHP access
210+ // syntax is `$this->name` not `$this->$name`.
211+ let name = if let Some ( stripped) = raw_name. strip_prefix ( '$' ) {
212+ stripped. to_string ( )
213+ } else {
214+ raw_name
215+ } ;
216+
217+ PropertyInfo {
218+ name,
219+ type_hint : type_hint. clone ( ) ,
220+ is_static,
221+ }
222+ } )
223+ . collect ( )
224+ }
225+
91226 /// Parse PHP source text and extract class information.
92227 /// Returns a Vec of ClassInfo for all classes found in the file.
93228 pub fn parse_php ( & self , content : & str ) -> Vec < ClassInfo > {
@@ -113,9 +248,31 @@ impl Backend {
113248 let class_name = class. name . value . to_string ( ) ;
114249
115250 let mut methods = Vec :: new ( ) ;
251+ let mut properties = Vec :: new ( ) ;
252+
116253 for member in class. members . iter ( ) {
117- if let ClassLikeMember :: Method ( method) = member {
118- methods. push ( method. name . value . to_string ( ) ) ;
254+ match member {
255+ ClassLikeMember :: Method ( method) => {
256+ let name = method. name . value . to_string ( ) ;
257+ let parameters = Self :: extract_parameters ( & method. parameter_list ) ;
258+ let return_type = method
259+ . return_type_hint
260+ . as_ref ( )
261+ . map ( |rth| Self :: extract_hint_string ( & rth. hint ) ) ;
262+ let is_static = method. modifiers . iter ( ) . any ( |m| m. is_static ( ) ) ;
263+
264+ methods. push ( MethodInfo {
265+ name,
266+ parameters,
267+ return_type,
268+ is_static,
269+ } ) ;
270+ }
271+ ClassLikeMember :: Property ( property) => {
272+ let mut prop_infos = Self :: extract_property_info ( property) ;
273+ properties. append ( & mut prop_infos) ;
274+ }
275+ _ => { }
119276 }
120277 }
121278
@@ -125,6 +282,7 @@ impl Backend {
125282 classes. push ( ClassInfo {
126283 name : class_name,
127284 methods,
285+ properties,
128286 start_offset,
129287 end_offset,
130288 } ) ;
@@ -175,6 +333,43 @@ impl Backend {
175333 . find ( |c| offset >= c. start_offset && offset <= c. end_offset )
176334 }
177335
336+ /// Build the label showing the full method signature.
337+ ///
338+ /// Example: `regularCode(string $text, $frogs = false): string`
339+ fn build_method_label ( method : & MethodInfo ) -> String {
340+ let params: Vec < String > = method
341+ . parameters
342+ . iter ( )
343+ . map ( |p| {
344+ let mut parts = Vec :: new ( ) ;
345+ if let Some ( ref th) = p. type_hint {
346+ parts. push ( th. clone ( ) ) ;
347+ }
348+ if p. is_reference {
349+ parts. push ( format ! ( "&{}" , p. name) ) ;
350+ } else if p. is_variadic {
351+ parts. push ( format ! ( "...{}" , p. name) ) ;
352+ } else {
353+ parts. push ( p. name . clone ( ) ) ;
354+ }
355+ let param_str = parts. join ( " " ) ;
356+ if !p. is_required && !p. is_variadic {
357+ format ! ( "{} = ..." , param_str)
358+ } else {
359+ param_str
360+ }
361+ } )
362+ . collect ( ) ;
363+
364+ let ret = method
365+ . return_type
366+ . as_ref ( )
367+ . map ( |r| format ! ( ": {}" , r) )
368+ . unwrap_or_default ( ) ;
369+
370+ format ! ( "{}({}){}" , method. name, params. join( ", " ) , ret)
371+ }
372+
178373 /// Public helper for tests: get the ast_map for a given URI.
179374 pub fn get_classes_for_uri ( & self , uri : & str ) -> Option < Vec < ClassInfo > > {
180375 if let Ok ( map) = self . ast_map . lock ( ) {
@@ -329,17 +524,38 @@ impl LanguageServer for Backend {
329524 && let Some ( offset) = Self :: position_to_offset ( & content, position)
330525 && let Some ( class_info) = Self :: find_class_at_offset ( & classes, offset)
331526 {
332- let items: Vec < CompletionItem > = class_info
333- . methods
334- . iter ( )
335- . map ( |method_name| CompletionItem {
336- label : method_name. clone ( ) ,
527+ let mut items: Vec < CompletionItem > = Vec :: new ( ) ;
528+
529+ // Add method completions
530+ for method in & class_info. methods {
531+ let label = Self :: build_method_label ( method) ;
532+
533+ items. push ( CompletionItem {
534+ label,
337535 kind : Some ( CompletionItemKind :: METHOD ) ,
338536 detail : Some ( format ! ( "Class: {}" , class_info. name) ) ,
339- insert_text : Some ( method_name. clone ( ) ) ,
537+ insert_text : Some ( method. name . clone ( ) ) ,
538+ filter_text : Some ( method. name . clone ( ) ) ,
340539 ..CompletionItem :: default ( )
341- } )
342- . collect ( ) ;
540+ } ) ;
541+ }
542+
543+ // Add property completions
544+ for property in & class_info. properties {
545+ let detail = if let Some ( ref th) = property. type_hint {
546+ format ! ( "Class: {} — {}" , class_info. name, th)
547+ } else {
548+ format ! ( "Class: {}" , class_info. name)
549+ } ;
550+
551+ items. push ( CompletionItem {
552+ label : property. name . clone ( ) ,
553+ kind : Some ( CompletionItemKind :: PROPERTY ) ,
554+ detail : Some ( detail) ,
555+ insert_text : Some ( property. name . clone ( ) ) ,
556+ ..CompletionItem :: default ( )
557+ } ) ;
558+ }
343559
344560 if !items. is_empty ( ) {
345561 return Ok ( Some ( CompletionResponse :: Array ( items) ) ) ;
0 commit comments