feat: Add Signal Flow Analysis (C3.10) and Test Framework Detection
Comprehensive Godot signal analysis and test framework support: ## Signal Flow Analysis (C3.10) Enhanced GDScript analyzer to extract: - Signal declarations with documentation comments - Signal connections (.connect() calls) - Signal emissions (.emit() calls) - Signal flow chains (source → signal → handler) Created SignalFlowAnalyzer class: - Analyzes 208 signals, 634 connections, 298 emissions (Cosmic Ideler) - Detects event patterns: - EventBus Pattern (centralized event system) - Observer Pattern (multi-connected signals) - Event Chains (cascading signal emissions) - Generates: - signal_flow.json (full analysis data) - signal_flow.mmd (Mermaid diagram) - signal_reference.md (human-readable docs) Statistics: - Signal density calculation (signals per file) - Most connected signals ranking - Most emitted signals ranking ## Test Framework Detection Added support for 3 Godot test frameworks: - **GUT** (Godot Unit Test) - extends GutTest, test_* functions - **gdUnit4** - @suite and @test annotations - **WAT** (WizAds Test) - extends WAT.Test Detection results (Cosmic Ideler): - 20 GUT test files - 396 test cases detected ## Integration Updated codebase_scraper.py: - Signal flow analysis runs automatically for Godot projects - Test framework detection integrated into code analysis - SKILL.md shows signal statistics and test framework info - New section: 📡 Signal Flow Analysis (C3.10) ## Results (Tested on Cosmic Ideler) - 443/452 files analyzed (98%) - 208 signals documented - 634 signal connections mapped - 298 signal emissions tracked - 3 event patterns detected (EventBus, Observer, Event Chains) - 20 GUT test files found with 396 test cases Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1669,15 +1669,47 @@ class CodeAnalyzer:
|
||||
"line_number": content[: match.start()].count("\n") + 1
|
||||
})
|
||||
|
||||
# Extract signals
|
||||
# Extract signals with documentation
|
||||
signal_connections = []
|
||||
signal_emissions = []
|
||||
|
||||
for match in re.finditer(r'signal\s+(\w+)(?:\(([^)]*)\))?', content):
|
||||
signal_name, params = match.groups()
|
||||
line_number = content[: match.start()].count("\n") + 1
|
||||
|
||||
# Extract documentation comment above signal (## or #)
|
||||
doc_comment = None
|
||||
lines = content[:match.start()].split('\n')
|
||||
if len(lines) >= 2:
|
||||
prev_line = lines[-1].strip()
|
||||
if prev_line.startswith('##') or prev_line.startswith('#'):
|
||||
doc_comment = prev_line.lstrip('#').strip()
|
||||
|
||||
signals.append({
|
||||
"name": signal_name,
|
||||
"parameters": params if params else "",
|
||||
"line_number": line_number,
|
||||
"documentation": doc_comment
|
||||
})
|
||||
|
||||
# Extract signal connections (.connect() calls)
|
||||
for match in re.finditer(r'(\w+(?:\.\w+)*)\.connect\(([^)]+)\)', content):
|
||||
signal_path, handler = match.groups()
|
||||
signal_connections.append({
|
||||
"signal": signal_path,
|
||||
"handler": handler.strip(),
|
||||
"line_number": content[: match.start()].count("\n") + 1
|
||||
})
|
||||
|
||||
|
||||
# Extract signal emissions (.emit() calls)
|
||||
for match in re.finditer(r'(\w+(?:\.\w+)*)\.emit\(([^)]*)\)', content):
|
||||
signal_path, args = match.groups()
|
||||
signal_emissions.append({
|
||||
"signal": signal_path,
|
||||
"arguments": args.strip() if args else "",
|
||||
"line_number": content[: match.start()].count("\n") + 1
|
||||
})
|
||||
|
||||
# Extract @export variables
|
||||
for match in re.finditer(r'@export(?:\(([^)]+)\))?\s+var\s+(\w+)(?:\s*:\s*(\w+))?(?:\s*=\s*(.+?))?(?:\n|$)', content):
|
||||
hint, var_name, var_type, default = match.groups()
|
||||
@@ -1688,15 +1720,61 @@ class CodeAnalyzer:
|
||||
"export_hint": hint,
|
||||
"line_number": content[: match.start()].count("\n") + 1
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
# Detect test framework
|
||||
test_framework = None
|
||||
test_functions = []
|
||||
|
||||
# GUT (Godot Unit Test) - extends "res://addons/gut/test.gd" or extends GutTest
|
||||
if re.search(r'extends\s+["\']?res://addons/gut/test\.gd["\']?', content) or \
|
||||
re.search(r'extends\s+GutTest', content):
|
||||
test_framework = "GUT"
|
||||
|
||||
# Extract test functions (test_* functions)
|
||||
for func in functions:
|
||||
if func["name"].startswith("test_"):
|
||||
test_functions.append(func)
|
||||
|
||||
# gdUnit4 - @suite class annotation
|
||||
elif re.search(r'@suite', content):
|
||||
test_framework = "gdUnit4"
|
||||
|
||||
# Extract test functions (@test annotated or test_* prefix)
|
||||
for i, func in enumerate(functions):
|
||||
# Check for @test annotation above function
|
||||
func_line = func["line_number"]
|
||||
lines = content.split('\n')
|
||||
if func_line > 1:
|
||||
prev_line = lines[func_line - 2].strip()
|
||||
if prev_line.startswith('@test'):
|
||||
test_functions.append(func)
|
||||
elif func["name"].startswith("test_"):
|
||||
test_functions.append(func)
|
||||
|
||||
# WAT (WizAds Test) - less common
|
||||
elif re.search(r'extends\s+WAT\.Test', content):
|
||||
test_framework = "WAT"
|
||||
for func in functions:
|
||||
if func["name"].startswith("test_"):
|
||||
test_functions.append(func)
|
||||
|
||||
result = {
|
||||
"file": file_path,
|
||||
"classes": classes,
|
||||
"functions": functions,
|
||||
"signals": signals,
|
||||
"exports": exports
|
||||
"exports": exports,
|
||||
"signal_connections": signal_connections,
|
||||
"signal_emissions": signal_emissions,
|
||||
}
|
||||
|
||||
# Add test framework info if detected
|
||||
if test_framework:
|
||||
result["test_framework"] = test_framework
|
||||
result["test_functions"] = test_functions
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test the analyzer
|
||||
|
||||
Reference in New Issue
Block a user