Skip to content

Commit 3df90cb

Browse files
3 more cli commands added
1 parent 9d621bd commit 3df90cb

2 files changed

Lines changed: 307 additions & 4 deletions

File tree

src/codegraphcontext/cli/main.py

Lines changed: 305 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,12 +708,15 @@ def find_by_name(
708708
# Search all
709709
funcs = code_finder.find_by_function_name(name, fuzzy_search=False)
710710
classes = code_finder.find_by_class_name(name, fuzzy_search=False)
711+
variables = code_finder.find_by_variable_name(name)
711712

712713
for f in funcs: f['type'] = 'Function'
713714
for c in classes: c['type'] = 'Class'
715+
for v in variables: v['type'] = 'Variable'
714716

715717
results.extend(funcs)
716718
results.extend(classes)
719+
results.extend(variables)
717720

718721
elif type.lower() == 'function':
719722
results = code_finder.find_by_function_name(name, fuzzy_search=False)
@@ -723,6 +726,10 @@ def find_by_name(
723726
results = code_finder.find_by_class_name(name, fuzzy_search=False)
724727
for r in results: r['type'] = 'Class'
725728

729+
elif type.lower() == 'variable':
730+
results = code_finder.find_by_variable_name(name)
731+
for r in results: r['type'] = 'Variable'
732+
726733
elif type.lower() == 'file':
727734
# Quick query for file
728735
with db_manager.get_driver().session() as session:
@@ -780,7 +787,7 @@ def find_by_pattern(
780787
if not case_sensitive:
781788
query = """
782789
MATCH (n)
783-
WHERE (n:Function OR n:Class OR n:Module) AND toLower(n.name) CONTAINS toLower($pattern)
790+
WHERE (n:Function OR n:Class OR n:Module OR n:Variable) AND toLower(n.name) CONTAINS toLower($pattern)
784791
RETURN
785792
labels(n)[0] as type,
786793
n.name as name,
@@ -793,7 +800,7 @@ def find_by_pattern(
793800
else:
794801
query = """
795802
MATCH (n)
796-
WHERE (n:Function OR n:Class OR n:Module) AND n.name CONTAINS $pattern
803+
WHERE (n:Function OR n:Class OR n:Module OR n:Variable) AND n.name CONTAINS $pattern
797804
RETURN
798805
labels(n)[0] as type,
799806
n.name as name,
@@ -882,6 +889,194 @@ def find_by_type(
882889
finally:
883890
db_manager.close_driver()
884891

892+
@find_app.command("variable")
893+
def find_by_variable(
894+
name: str = typer.Argument(..., help="Variable name to search for")
895+
):
896+
"""
897+
Find variables by name.
898+
899+
Examples:
900+
cgc find variable MAX_RETRIES
901+
cgc find variable config
902+
"""
903+
_load_credentials()
904+
services = _initialize_services()
905+
if not all(services):
906+
return
907+
db_manager, graph_builder, code_finder = services
908+
909+
try:
910+
results = code_finder.find_by_variable_name(name)
911+
912+
if not results:
913+
console.print(f"[yellow]No variables found with name '{name}'[/yellow]")
914+
return
915+
916+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
917+
table.add_column("Name", style="cyan")
918+
table.add_column("File", style="dim", overflow="fold")
919+
table.add_column("Line", style="green", justify="right")
920+
table.add_column("Context", style="yellow")
921+
922+
for res in results:
923+
table.add_row(
924+
res.get('name', ''),
925+
res.get('file_path', ''),
926+
str(res.get('line_number', '')),
927+
res.get('context', '') or 'module'
928+
)
929+
930+
console.print(f"[cyan]Found {len(results)} variable(s) named '{name}':[/cyan]")
931+
console.print(table)
932+
finally:
933+
db_manager.close_driver()
934+
935+
@find_app.command("content")
936+
def find_by_content_search(
937+
query: str = typer.Argument(..., help="Text to search for in source code and docstrings")
938+
):
939+
"""
940+
Search code content (source and docstrings) using full-text index.
941+
942+
Examples:
943+
cgc find content "error 503"
944+
cgc find content "TODO: refactor"
945+
"""
946+
_load_credentials()
947+
services = _initialize_services()
948+
if not all(services):
949+
return
950+
db_manager, graph_builder, code_finder = services
951+
952+
try:
953+
try:
954+
results = code_finder.find_by_content(query)
955+
except Exception as e:
956+
error_msg = str(e).lower()
957+
if 'fulltext' in error_msg or 'db.index.fulltext' in error_msg:
958+
console.print("\n[bold red]❌ Full-text search is not supported on FalkorDB[/bold red]\n")
959+
console.print("[yellow]💡 You have two options:[/yellow]\n")
960+
console.print(" 1. [cyan]Switch to Neo4j:[/cyan]")
961+
console.print(f" [dim]cgc --database neo4j find content \"{query}\"[/dim]\n")
962+
console.print(" 2. [cyan]Use pattern search instead:[/cyan]")
963+
console.print(f" [dim]cgc find pattern \"{query}\"[/dim]")
964+
console.print(" [dim](searches in names only, not source code)[/dim]\n")
965+
return
966+
else:
967+
# Re-raise if it's a different error
968+
raise
969+
970+
if not results:
971+
console.print(f"[yellow]No content matches found for '{query}'[/yellow]")
972+
return
973+
974+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
975+
table.add_column("Name", style="cyan")
976+
table.add_column("Type", style="blue")
977+
table.add_column("File", style="dim", overflow="fold")
978+
table.add_column("Line", style="green", justify="right")
979+
980+
for res in results:
981+
table.add_row(
982+
res.get('name', ''),
983+
res.get('type', 'Unknown'),
984+
res.get('file_path', ''),
985+
str(res.get('line_number', ''))
986+
)
987+
988+
console.print(f"[cyan]Found {len(results)} content match(es) for '{query}':[/cyan]")
989+
console.print(table)
990+
finally:
991+
db_manager.close_driver()
992+
993+
@find_app.command("decorator")
994+
def find_by_decorator_search(
995+
decorator: str = typer.Argument(..., help="Decorator name to search for"),
996+
file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
997+
):
998+
"""
999+
Find functions with a specific decorator.
1000+
1001+
Examples:
1002+
cgc find decorator app.route
1003+
cgc find decorator test --file tests/test_main.py
1004+
"""
1005+
_load_credentials()
1006+
services = _initialize_services()
1007+
if not all(services):
1008+
return
1009+
db_manager, graph_builder, code_finder = services
1010+
1011+
try:
1012+
results = code_finder.find_functions_by_decorator(decorator, file)
1013+
1014+
if not results:
1015+
console.print(f"[yellow]No functions found with decorator '@{decorator}'[/yellow]")
1016+
return
1017+
1018+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1019+
table.add_column("Function", style="cyan")
1020+
table.add_column("File", style="dim", overflow="fold")
1021+
table.add_column("Line", style="green", justify="right")
1022+
table.add_column("Decorators", style="yellow")
1023+
1024+
for res in results:
1025+
decorators_str = ", ".join(res.get('decorators', []))
1026+
table.add_row(
1027+
res.get('function_name', ''),
1028+
res.get('file_path', ''),
1029+
str(res.get('line_number', '')),
1030+
decorators_str
1031+
)
1032+
1033+
console.print(f"[cyan]Found {len(results)} function(s) with decorator '@{decorator}':[/cyan]")
1034+
console.print(table)
1035+
finally:
1036+
db_manager.close_driver()
1037+
1038+
@find_app.command("argument")
1039+
def find_by_argument_search(
1040+
argument: str = typer.Argument(..., help="Argument/parameter name to search for"),
1041+
file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
1042+
):
1043+
"""
1044+
Find functions that take a specific argument/parameter.
1045+
1046+
Examples:
1047+
cgc find argument password
1048+
cgc find argument user_id --file src/auth.py
1049+
"""
1050+
_load_credentials()
1051+
services = _initialize_services()
1052+
if not all(services):
1053+
return
1054+
db_manager, graph_builder, code_finder = services
1055+
1056+
try:
1057+
results = code_finder.find_functions_by_argument(argument, file)
1058+
1059+
if not results:
1060+
console.print(f"[yellow]No functions found with argument '{argument}'[/yellow]")
1061+
return
1062+
1063+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1064+
table.add_column("Function", style="cyan")
1065+
table.add_column("File", style="dim", overflow="fold")
1066+
table.add_column("Line", style="green", justify="right")
1067+
1068+
for res in results:
1069+
table.add_row(
1070+
res.get('function_name', ''),
1071+
res.get('file_path', ''),
1072+
str(res.get('line_number', ''))
1073+
)
1074+
1075+
console.print(f"[cyan]Found {len(results)} function(s) with argument '{argument}':[/cyan]")
1076+
console.print(table)
1077+
finally:
1078+
db_manager.close_driver()
1079+
8851080

8861081
# ============================================================================
8871082
# ANALYZE COMMAND GROUP - Code Analysis & Relationships
@@ -1232,6 +1427,114 @@ def analyze_dead_code(
12321427
finally:
12331428
db_manager.close_driver()
12341429

1430+
@analyze_app.command("overrides")
1431+
def analyze_overrides(
1432+
function_name: str = typer.Argument(..., help="Function/method name to find implementations of")
1433+
):
1434+
"""
1435+
Find all implementations of a function across different classes.
1436+
1437+
Useful for finding polymorphic implementations and method overrides.
1438+
1439+
Example:
1440+
cgc analyze overrides area
1441+
cgc analyze overrides process
1442+
"""
1443+
_load_credentials()
1444+
services = _initialize_services()
1445+
if not all(services):
1446+
return
1447+
db_manager, graph_builder, code_finder = services
1448+
1449+
try:
1450+
results = code_finder.find_function_overrides(function_name)
1451+
1452+
if not results:
1453+
console.print(f"[yellow]No implementations found for function '{function_name}'[/yellow]")
1454+
return
1455+
1456+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1457+
table.add_column("Class", style="cyan")
1458+
table.add_column("Function", style="green")
1459+
table.add_column("File", style="dim", overflow="fold")
1460+
table.add_column("Line", style="yellow", justify="right")
1461+
1462+
for res in results:
1463+
table.add_row(
1464+
res.get('class_name', ''),
1465+
res.get('function_name', ''),
1466+
res.get('class_file_path', ''),
1467+
str(res.get('function_line_number', ''))
1468+
)
1469+
1470+
console.print(f"\n[bold cyan]Found {len(results)} implementation(s) of '{function_name}':[/bold cyan]")
1471+
console.print(table)
1472+
finally:
1473+
db_manager.close_driver()
1474+
1475+
@analyze_app.command("variable")
1476+
def analyze_variable_usage(
1477+
variable_name: str = typer.Argument(..., help="Variable name to analyze")
1478+
):
1479+
"""
1480+
Analyze where a variable is defined and used across the codebase.
1481+
1482+
Shows all instances of the variable and their scope (function, class, module).
1483+
1484+
Example:
1485+
cgc analyze variable MAX_RETRIES
1486+
cgc analyze variable config
1487+
"""
1488+
_load_credentials()
1489+
services = _initialize_services()
1490+
if not all(services):
1491+
return
1492+
db_manager, graph_builder, code_finder = services
1493+
1494+
try:
1495+
# Get variable usage scope
1496+
scope_results = code_finder.find_variable_usage_scope(variable_name)
1497+
instances = scope_results.get('instances', [])
1498+
1499+
if not instances:
1500+
console.print(f"[yellow]No instances found for variable '{variable_name}'[/yellow]")
1501+
return
1502+
1503+
console.print(f"\n[bold cyan]Variable '{variable_name}' Usage Analysis:[/bold cyan]\n")
1504+
1505+
# Group by scope type
1506+
by_scope = {}
1507+
for inst in instances:
1508+
scope_type = inst.get('scope_type', 'unknown')
1509+
if scope_type not in by_scope:
1510+
by_scope[scope_type] = []
1511+
by_scope[scope_type].append(inst)
1512+
1513+
# Display by scope
1514+
for scope_type, items in by_scope.items():
1515+
console.print(f"[bold yellow]{scope_type.upper()} Scope ({len(items)} instance(s)):[/bold yellow]")
1516+
1517+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1518+
table.add_column("Scope Name", style="cyan")
1519+
table.add_column("File", style="dim", overflow="fold")
1520+
table.add_column("Line", style="green", justify="right")
1521+
table.add_column("Value", style="yellow")
1522+
1523+
for item in items:
1524+
table.add_row(
1525+
item.get('scope_name', ''),
1526+
item.get('file_path', ''),
1527+
str(item.get('line_number', '')),
1528+
str(item.get('variable_value', ''))[:50] if item.get('variable_value') else '-'
1529+
)
1530+
1531+
console.print(table)
1532+
console.print()
1533+
1534+
console.print(f"[dim]Total: {len(instances)} instance(s) across {len(by_scope)} scope type(s)[/dim]")
1535+
finally:
1536+
db_manager.close_driver()
1537+
12351538

12361539
# ============================================================================
12371540
# QUERY COMMAND - Raw Cypher Queries

src/codegraphcontext/tools/code_finder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ def find_by_variable_name(self, search_term: str) -> List[Dict]:
6868
with self.driver.session() as session:
6969
result = session.run("""
7070
MATCH (v:Variable)
71-
WHERE v.name CONTAINS $search_term OR v.name =~ $regex_pattern
71+
WHERE v.name CONTAINS $search_term
7272
RETURN v.name as name, v.file_path as file_path, v.line_number as line_number,
7373
v.value as value, v.context as context, v.is_dependency as is_dependency
7474
ORDER BY v.is_dependency ASC, v.name
7575
LIMIT 20
76-
""", search_term=search_term, regex_pattern=f"(?i).*{re.escape(search_term)}.*")
76+
""", search_term=search_term)
7777

7878
return result.data()
7979

0 commit comments

Comments
 (0)