@@ -52,6 +52,8 @@ class JavaMethodNode:
5252 class_name : str | None
5353 source_text : str
5454 javadoc_start_line : int | None = None # Line where Javadoc comment starts
55+ formal_parameters_text : str | None = None # Raw formal parameters "(Type name, ...)" for matching
56+ is_class_nested : bool = False # True when the enclosing class is itself nested inside another class
5557
5658
5759@dataclass
@@ -182,6 +184,104 @@ def find_methods(
182184
183185 return methods
184186
187+ def find_constructors (self , source : str , class_name : str | None = None ) -> list [JavaMethodNode ]:
188+ """Find all constructor definitions in source code.
189+
190+ Args:
191+ source: The source code to analyze.
192+ class_name: Optional class name to filter constructors.
193+
194+ Returns:
195+ List of JavaMethodNode objects describing found constructors.
196+ The ``name`` field of each node is the constructor name (i.e. the class name).
197+
198+ """
199+ source_bytes = source .encode ("utf8" )
200+ tree = self .parse (source_bytes )
201+ constructors : list [JavaMethodNode ] = []
202+ self ._walk_tree_for_constructors (
203+ tree .root_node , source_bytes , constructors , current_class = None , target_class = class_name
204+ )
205+ return constructors
206+
207+ def _walk_tree_for_constructors (
208+ self ,
209+ node : Node ,
210+ source_bytes : bytes ,
211+ constructors : list [JavaMethodNode ],
212+ current_class : str | None ,
213+ target_class : str | None ,
214+ ) -> None :
215+ """Recursively walk the tree to find constructor declarations."""
216+ new_class = current_class
217+ type_declarations = ("class_declaration" , "interface_declaration" , "enum_declaration" )
218+ if node .type in type_declarations :
219+ name_node = node .child_by_field_name ("name" )
220+ if name_node :
221+ new_class = self .get_node_text (name_node , source_bytes )
222+
223+ if node .type == "constructor_declaration" :
224+ constructor_info = self ._extract_constructor_info (node , source_bytes , new_class )
225+ if constructor_info :
226+ if target_class is None or constructor_info .class_name == target_class :
227+ constructors .append (constructor_info )
228+
229+ for child in node .children :
230+ self ._walk_tree_for_constructors (
231+ child ,
232+ source_bytes ,
233+ constructors ,
234+ current_class = new_class if node .type in type_declarations else current_class ,
235+ target_class = target_class ,
236+ )
237+
238+ def _extract_constructor_info (
239+ self , node : Node , source_bytes : bytes , current_class : str | None
240+ ) -> JavaMethodNode | None :
241+ """Extract constructor information from a constructor_declaration node."""
242+ name_node = node .child_by_field_name ("name" )
243+ if not name_node :
244+ return None
245+ name = self .get_node_text (name_node , source_bytes )
246+
247+ is_public = False
248+ is_private = False
249+ is_protected = False
250+ for child in node .children :
251+ if child .type == "modifiers" :
252+ modifier_text = self .get_node_text (child , source_bytes )
253+ is_public = "public" in modifier_text
254+ is_private = "private" in modifier_text
255+ is_protected = "protected" in modifier_text
256+ break
257+
258+ # Extract formal parameters text for signature matching
259+ params_node = node .child_by_field_name ("parameters" )
260+ formal_parameters_text = self .get_node_text (params_node , source_bytes ) if params_node else "()"
261+
262+ source_text = self .get_node_text (node , source_bytes )
263+ javadoc_start_line = self ._find_preceding_javadoc (node , source_bytes )
264+
265+ return JavaMethodNode (
266+ name = name ,
267+ node = node ,
268+ start_line = node .start_point [0 ] + 1 ,
269+ end_line = node .end_point [0 ] + 1 ,
270+ start_col = node .start_point [1 ],
271+ end_col = node .end_point [1 ],
272+ is_static = False ,
273+ is_public = is_public ,
274+ is_private = is_private ,
275+ is_protected = is_protected ,
276+ is_abstract = False ,
277+ is_synchronized = False ,
278+ return_type = None ,
279+ class_name = current_class ,
280+ source_text = source_text ,
281+ javadoc_start_line = javadoc_start_line ,
282+ formal_parameters_text = formal_parameters_text ,
283+ )
284+
185285 def _walk_tree_for_methods (
186286 self ,
187287 node : Node ,
@@ -190,6 +290,7 @@ def _walk_tree_for_methods(
190290 include_private : bool ,
191291 include_static : bool ,
192292 current_class : str | None ,
293+ class_depth : int = 0 ,
193294 ) -> None :
194295 """Recursively walk the tree to find method definitions."""
195296 new_class = current_class
@@ -205,6 +306,10 @@ def _walk_tree_for_methods(
205306 method_info = self ._extract_method_info (node , source_bytes , current_class )
206307
207308 if method_info :
309+ # A method is nested when its enclosing class is itself inside another
310+ # class (class_depth >= 2: depth 1 = outermost class, depth 2+ = nested).
311+ method_info .is_class_nested = class_depth >= 2
312+
208313 # Apply filters
209314 should_include = True
210315
@@ -217,7 +322,7 @@ def _walk_tree_for_methods(
217322 if should_include :
218323 methods .append (method_info )
219324
220- # Recurse into children
325+ # Recurse into children, incrementing depth when entering a type declaration
221326 for child in node .children :
222327 self ._walk_tree_for_methods (
223328 child ,
@@ -226,6 +331,7 @@ def _walk_tree_for_methods(
226331 include_private = include_private ,
227332 include_static = include_static ,
228333 current_class = new_class if node .type in type_declarations else current_class ,
334+ class_depth = class_depth + 1 if node .type in type_declarations else class_depth ,
229335 )
230336
231337 def _extract_method_info (self , node : Node , source_bytes : bytes , current_class : str | None ) -> JavaMethodNode | None :
0 commit comments