fix: Add GDScript-specific dependency extraction to eliminate syntax errors

PROBLEM:
- 265+ "Syntax error in *.gd" warnings during analysis
- GDScript files were routed to Python AST parser (_extract_python_imports)
- Python AST failed because GDScript syntax differs (extends, signal, @export)

SOLUTION:
- Created dedicated _extract_gdscript_imports() method using regex
- Parses GDScript-specific patterns:
  * const/var = preload("res://path")
  * const/var = load("res://path")
  * extends "res://path/to/base.gd"
  * extends MyBaseClass (with built-in Godot class filtering)
- Converts res:// paths to relative paths
- Routes GDScript files to new extractor instead of Python AST

CHANGES:
- dependency_analyzer.py (line 114-116): Route GDScript to new extractor
- dependency_analyzer.py (line 201-318): Add _extract_gdscript_imports()
- Updated module docstring: 9 → 10 languages + Godot ecosystem
- Updated analyze_file() docstring with GDScript support

IMPACT:
- Eliminates all 265+ syntax error warnings
- Correctly extracts GDScript dependencies (preload/load/extends)
- Completes C3.10 Signal Flow Analysis integration

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
yusyus
2026-02-02 21:56:42 +03:00
parent 1831c1bb47
commit 3e6c448aca

View File

@@ -3,7 +3,7 @@
Dependency Graph Analyzer (C2.6) Dependency Graph Analyzer (C2.6)
Analyzes import/require/include/use statements to build dependency graphs. Analyzes import/require/include/use statements to build dependency graphs.
Supports 9 programming languages with language-specific extraction. Supports 10 programming languages + Godot ecosystem with language-specific extraction.
Features: Features:
- Multi-language import extraction (Python AST, others regex-based) - Multi-language import extraction (Python AST, others regex-based)
@@ -14,6 +14,8 @@ Features:
Supported Languages: Supported Languages:
- Python: import, from...import, relative imports (AST-based) - Python: import, from...import, relative imports (AST-based)
- GDScript: preload(), load(), extends (regex-based, Godot game engine)
- Godot Files: .tscn, .tres, .gdshader ext_resource parsing
- JavaScript/TypeScript: ES6 import, CommonJS require (regex-based) - JavaScript/TypeScript: ES6 import, CommonJS require (regex-based)
- C/C++: #include directives (regex-based) - C/C++: #include directives (regex-based)
- C#: using statements (regex, based on MS C# spec) - C#: using statements (regex, based on MS C# spec)
@@ -101,7 +103,8 @@ class DependencyAnalyzer:
Args: Args:
file_path: Path to source file file_path: Path to source file
content: File content content: File content
language: Programming language (Python, JavaScript, TypeScript, C, C++, C#, Go, Rust, Java, Ruby, PHP) language: Programming language (Python, GDScript, GodotScene, GodotResource, GodotShader,
JavaScript, TypeScript, C, C++, C#, Go, Rust, Java, Ruby, PHP)
Returns: Returns:
List of DependencyInfo objects List of DependencyInfo objects
@@ -109,8 +112,8 @@ class DependencyAnalyzer:
if language == "Python": if language == "Python":
deps = self._extract_python_imports(content, file_path) deps = self._extract_python_imports(content, file_path)
elif language == "GDScript": elif language == "GDScript":
# GDScript is Python-like, uses similar import syntax # GDScript uses preload/load, not Python imports
deps = self._extract_python_imports(content, file_path) deps = self._extract_gdscript_imports(content, file_path)
elif language in ("GodotScene", "GodotResource", "GodotShader"): elif language in ("GodotScene", "GodotResource", "GodotShader"):
# Godot resource files use ext_resource references # Godot resource files use ext_resource references
deps = self._extract_godot_resources(content, file_path) deps = self._extract_godot_resources(content, file_path)
@@ -195,6 +198,125 @@ class DependencyAnalyzer:
return deps return deps
def _extract_gdscript_imports(self, content: str, file_path: str) -> list[DependencyInfo]:
"""
Extract GDScript import/preload/load statements.
Handles:
- const MyClass = preload("res://path/to/file.gd")
- var scene = load("res://path/to/scene.tscn")
- extends "res://path/to/base.gd"
- extends MyBaseClass (implicit dependency)
Note: GDScript uses res:// paths which are converted to relative paths.
"""
deps = []
# Extract preload() calls: const/var NAME = preload("path")
preload_pattern = r'(?:const|var)\s+\w+\s*=\s*preload\("(.+?)"\)'
for match in re.finditer(preload_pattern, content):
resource_path = match.group(1)
line_num = content[: match.start()].count("\n") + 1
# Convert res:// paths to relative
if resource_path.startswith("res://"):
resource_path = resource_path[6:]
deps.append(
DependencyInfo(
source_file=file_path,
imported_module=resource_path,
import_type="preload",
is_relative=True,
line_number=line_num,
)
)
# Extract load() calls: var/const NAME = load("path")
load_pattern = r'(?:const|var)\s+\w+\s*=\s*load\("(.+?)"\)'
for match in re.finditer(load_pattern, content):
resource_path = match.group(1)
line_num = content[: match.start()].count("\n") + 1
if resource_path.startswith("res://"):
resource_path = resource_path[6:]
deps.append(
DependencyInfo(
source_file=file_path,
imported_module=resource_path,
import_type="load",
is_relative=True,
line_number=line_num,
)
)
# Extract extends with string path: extends "res://path/to/base.gd"
extends_path_pattern = r'extends\s+"(.+?)"'
for match in re.finditer(extends_path_pattern, content):
resource_path = match.group(1)
line_num = content[: match.start()].count("\n") + 1
if resource_path.startswith("res://"):
resource_path = resource_path[6:]
deps.append(
DependencyInfo(
source_file=file_path,
imported_module=resource_path,
import_type="extends",
is_relative=True,
line_number=line_num,
)
)
# Extract extends with class name: extends MyBaseClass
# Note: This creates a symbolic dependency that may not resolve to a file
extends_class_pattern = r'extends\s+([A-Z]\w+)'
for match in re.finditer(extends_class_pattern, content):
class_name = match.group(1)
line_num = content[: match.start()].count("\n") + 1
# Skip built-in Godot classes (Node, Resource, etc.)
if class_name not in (
"Node",
"Node2D",
"Node3D",
"Resource",
"RefCounted",
"Object",
"Control",
"Area2D",
"Area3D",
"CharacterBody2D",
"CharacterBody3D",
"RigidBody2D",
"RigidBody3D",
"StaticBody2D",
"StaticBody3D",
"Camera2D",
"Camera3D",
"Sprite2D",
"Sprite3D",
"Label",
"Button",
"Panel",
"Container",
"VBoxContainer",
"HBoxContainer",
):
deps.append(
DependencyInfo(
source_file=file_path,
imported_module=class_name,
import_type="extends",
is_relative=False,
line_number=line_num,
)
)
return deps
def _extract_js_imports(self, content: str, file_path: str) -> list[DependencyInfo]: def _extract_js_imports(self, content: str, file_path: str) -> list[DependencyInfo]:
""" """
Extract JavaScript/TypeScript import statements. Extract JavaScript/TypeScript import statements.