22import logging
33import re
44from typing import Any , Dict , List
5+ from pathlib import Path
56
67from ..core .database import DatabaseManager
78
@@ -116,6 +117,56 @@ def find_related_code(self, user_query: str) -> Dict[str, Any]:
116117
117118 return results
118119
120+ def find_functions_by_argument (self , argument_name : str , file_path : str = None ) -> List [Dict ]:
121+ """Find functions that take a specific argument name."""
122+ with self .driver .session () as session :
123+ if file_path :
124+ query = """
125+ MATCH (f:Function)-[:HAS_PARAMETER]->(p:Parameter)
126+ WHERE p.name = $argument_name AND f.file_path = $file_path
127+ RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
128+ f.docstring AS docstring, f.is_dependency AS is_dependency
129+ ORDER BY f.is_dependency ASC, f.file_path, f.line_number
130+ LIMIT 20
131+ """
132+ result = session .run (query , argument_name = argument_name , file_path = file_path )
133+ else :
134+ query = """
135+ MATCH (f:Function)-[:HAS_PARAMETER]->(p:Parameter)
136+ WHERE p.name = $argument_name
137+ RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
138+ f.docstring AS docstring, f.is_dependency AS is_dependency
139+ ORDER BY f.is_dependency ASC, f.file_path, f.line_number
140+ LIMIT 20
141+ """
142+ result = session .run (query , argument_name = argument_name )
143+ return [dict (record ) for record in result ]
144+
145+ def find_functions_by_decorator (self , decorator_name : str , file_path : str = None ) -> List [Dict ]:
146+ """Find functions that have a specific decorator applied to them."""
147+ with self .driver .session () as session :
148+ if file_path :
149+ query = """
150+ MATCH (f:Function)
151+ WHERE f.file_path = $file_path AND $decorator_name IN f.decorators
152+ RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
153+ f.docstring AS docstring, f.is_dependency AS is_dependency, f.decorators AS decorators
154+ ORDER BY f.is_dependency ASC, f.file_path, f.line_number
155+ LIMIT 20
156+ """
157+ result = session .run (query , decorator_name = decorator_name , file_path = file_path )
158+ else :
159+ query = """
160+ MATCH (f:Function)
161+ WHERE $decorator_name IN f.decorators
162+ RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
163+ f.docstring AS docstring, f.is_dependency AS is_dependency, f.decorators AS decorators
164+ ORDER BY f.is_dependency ASC, f.file_path, f.line_number
165+ LIMIT 20
166+ """
167+ result = session .run (query , decorator_name = decorator_name )
168+ return [dict (record ) for record in result ]
169+
119170 def who_calls_function (self , function_name : str , file_path : str = None ) -> List [Dict ]:
120171 """Find what functions call a specific function using CALLS relationships with improved matching"""
121172 with self .driver .session () as session :
@@ -230,7 +281,7 @@ def who_imports_module(self, module_name: str) -> List[Dict]:
230281 with self .driver .session () as session :
231282 result = session .run ("""
232283 MATCH (file:File)-[imp:IMPORTS]->(module:Module)
233- WHERE module.name CONTAINS $module_name OR module.name = $module_name
284+ WHERE module.name = $module_name OR module.full_import_name CONTAINS $module_name
234285 OPTIONAL MATCH (repo:Repository)-[:CONTAINS]->(file)
235286 RETURN DISTINCT
236287 file.name as file_name,
@@ -344,15 +395,19 @@ def find_function_overrides(self, function_name: str) -> List[Dict]:
344395
345396 return [dict (record ) for record in result ]
346397
347- def find_dead_code (self ) -> Dict [str , Any ]:
348- """Find potentially unused functions (not called by other functions in the project)"""
398+ def find_dead_code (self , exclude_decorated_with : List [str ] = None ) -> Dict [str , Any ]:
399+ """Find potentially unused functions (not called by other functions in the project), optionally excluding those with specific decorators."""
400+ if exclude_decorated_with is None :
401+ exclude_decorated_with = []
402+
349403 with self .driver .session () as session :
350404 result = session .run ("""
351405 MATCH (func:Function)
352406 WHERE func.is_dependency = false
353407 AND NOT func.name IN ['main', '__init__', '__main__', 'setup', 'run', '__new__', '__del__']
354408 AND NOT func.name STARTS WITH '_test'
355409 AND NOT func.name STARTS WITH 'test_'
410+ AND ALL(decorator_name IN $exclude_decorated_with WHERE NOT decorator_name IN func.decorators)
356411 WITH func
357412 OPTIONAL MATCH (caller:Function)-[:CALLS]->(func)
358413 WHERE caller.is_dependency = false
@@ -368,37 +423,84 @@ def find_dead_code(self) -> Dict[str, Any]:
368423 file.name as file_name
369424 ORDER BY func.file_path, func.line_number
370425 LIMIT 50
371- """ )
426+ """ , exclude_decorated_with = exclude_decorated_with )
372427
373428 return {
374429 "potentially_unused_functions" : [dict (record ) for record in result ],
375430 "note" : "These functions might be unused, but could be entry points, callbacks, or called dynamically"
376431 }
377432
433+ def find_all_callers (self , function_name : str , file_path : str = None ) -> List [Dict ]:
434+ """Find all direct and indirect callers of a specific function."""
435+ with self .driver .session () as session :
436+ if file_path :
437+ # Find functions within the specified file_path that call the target function
438+ query = """
439+ MATCH (f:Function)-[:CALLS*]->(target:Function {name: $function_name})
440+ WHERE f.file_path = $file_path
441+ RETURN DISTINCT f.name AS caller_name, f.file_path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
442+ ORDER BY f.is_dependency ASC, f.file_path, f.line_number
443+ LIMIT 50
444+ """
445+ result = session .run (query , function_name = function_name , file_path = file_path )
446+ else :
447+ # If no file_path (context) is provided, find all callers of the function by name
448+ query = """
449+ MATCH (f:Function)-[:CALLS*]->(target:Function {name: $function_name})
450+ RETURN DISTINCT f.name AS caller_name, f.file_path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
451+ ORDER BY f.is_dependency ASC, f.file_path, f.line_number
452+ LIMIT 50
453+ """
454+ result = session .run (query , function_name = function_name )
455+ return [dict (record ) for record in result ]
456+
457+ def find_all_callees (self , function_name : str , file_path : str = None ) -> List [Dict ]:
458+ """Find all direct and indirect callees of a specific function."""
459+ with self .driver .session () as session :
460+ if file_path :
461+ query = """
462+ MATCH (caller:Function {name: $function_name, file_path: $file_path})
463+ MATCH (caller)-[:CALLS*]->(f:Function)
464+ RETURN DISTINCT f.name AS callee_name, f.file_path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
465+ ORDER BY f.is_dependency ASC, f.file_path, f.line_number
466+ LIMIT 50
467+ """
468+ result = session .run (query , function_name = function_name , file_path = file_path )
469+ else :
470+ query = """
471+ MATCH (caller:Function {name: $function_name})
472+ MATCH (caller)-[:CALLS*]->(f:Function)
473+ RETURN DISTINCT f.name AS callee_name, f.file_path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
474+ ORDER BY f.is_dependency ASC, f.file_path, f.line_number
475+ LIMIT 50
476+ """
477+ result = session .run (query , function_name = function_name )
478+ return [dict (record ) for record in result ]
479+
378480 def find_function_call_chain (self , start_function : str , end_function : str , max_depth : int = 5 ) -> List [Dict ]:
379481 """Find call chains between two functions"""
380482 with self .driver .session () as session :
381- result = session .run ("""
483+ result = session .run (f """
382484 MATCH path = shortestPath(
383- (start:Function {name: $start_function})-[:CALLS*1..$ max_depth]->(end:Function {name: $end_function})
485+ (start:Function {{ name: $start_function}} )-[:CALLS*1..{ max_depth } ]->(end:Function {{ name: $end_function} })
384486 )
385487 WITH path, nodes(path) as func_nodes, relationships(path) as call_rels
386488 RETURN
387- [node in func_nodes | {
489+ [node in func_nodes | {{
388490 name: node.name,
389491 file_path: node.file_path,
390492 line_number: node.line_number,
391493 is_dependency: node.is_dependency
392- }] as function_chain,
393- [rel in call_rels | {
494+ }} ] as function_chain,
495+ [rel in call_rels | {{
394496 call_line: rel.line_number,
395497 args: rel.args,
396498 full_call_name: rel.full_call_name
397- }] as call_details,
499+ }} ] as call_details,
398500 length(path) as chain_length
399501 ORDER BY chain_length ASC
400502 LIMIT 10
401- """ , start_function = start_function , end_function = end_function , max_depth = max_depth )
503+ """ , start_function = start_function , end_function = end_function )
402504
403505 return [dict (record ) for record in result ]
404506
@@ -494,6 +596,20 @@ def analyze_code_relationships(self, query_type: str, target: str, context: str
494596 "summary" : f"Found { len (results )} files that import '{ target } '"
495597 }
496598
599+ elif query_type == "find_functions_by_argument" :
600+ results = self .find_functions_by_argument (target , context )
601+ return {
602+ "query_type" : "find_functions_by_argument" , "target" : target , "context" : context , "results" : results ,
603+ "summary" : f"Found { len (results )} functions that take '{ target } ' as an argument"
604+ }
605+
606+ elif query_type == "find_functions_by_decorator" :
607+ results = self .find_functions_by_decorator (target , context )
608+ return {
609+ "query_type" : "find_functions_by_decorator" , "target" : target , "context" : context , "results" : results ,
610+ "summary" : f"Found { len (results )} functions decorated with '{ target } '"
611+ }
612+
497613 elif query_type in ["who_modifies" , "modifies" , "mutations" , "changes" , "variable_usage" ]:
498614 results = self .who_modifies_variable (target )
499615 return {
@@ -530,13 +646,29 @@ def analyze_code_relationships(self, query_type: str, target: str, context: str
530646 "summary" : f"Found the top { len (results )} most complex functions"
531647 }
532648
649+ elif query_type == "find_all_callers" :
650+ results = self .find_all_callers (target , context )
651+ return {
652+ "query_type" : "find_all_callers" , "target" : target , "context" : context , "results" : results ,
653+ "summary" : f"Found { len (results )} direct and indirect callers of '{ target } '"
654+ }
655+
656+ elif query_type == "find_all_callees" :
657+ results = self .find_all_callees (target , context )
658+ return {
659+ "query_type" : "find_all_callees" , "target" : target , "context" : context , "results" : results ,
660+ "summary" : f"Found { len (results )} direct and indirect callees of '{ target } '"
661+ }
662+
533663 elif query_type in ["call_chain" , "path" , "chain" ]:
534664 if '->' in target :
535665 start_func , end_func = target .split ('->' , 1 )
536- results = self .find_function_call_chain (start_func .strip (), end_func .strip ())
666+ # max_depth can be passed as context, default to 5 if not provided or invalid
667+ max_depth = int (context ) if context and context .isdigit () else 5
668+ results = self .find_function_call_chain (start_func .strip (), end_func .strip (), max_depth )
537669 return {
538670 "query_type" : "call_chain" , "target" : target , "results" : results ,
539- "summary" : f"Found { len (results )} call chains from '{ start_func .strip ()} ' to '{ end_func .strip ()} '"
671+ "summary" : f"Found { len (results )} call chains from '{ start_func .strip ()} ' to '{ end_func .strip ()} ' (max depth: { max_depth } ) "
540672 }
541673 else :
542674 return {
0 commit comments