From 3e6c448aca5ff71c4ec5545665ded8ebce727d0e Mon Sep 17 00:00:00 2001 From: yusyus Date: Mon, 2 Feb 2026 21:56:42 +0300 Subject: [PATCH] fix: Add GDScript-specific dependency extraction to eliminate syntax errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/skill_seekers/cli/dependency_analyzer.py | 130 ++++++++++++++++++- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/src/skill_seekers/cli/dependency_analyzer.py b/src/skill_seekers/cli/dependency_analyzer.py index 96f01fb..1365885 100644 --- a/src/skill_seekers/cli/dependency_analyzer.py +++ b/src/skill_seekers/cli/dependency_analyzer.py @@ -3,7 +3,7 @@ Dependency Graph Analyzer (C2.6) 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: - Multi-language import extraction (Python AST, others regex-based) @@ -14,6 +14,8 @@ Features: Supported Languages: - 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) - C/C++: #include directives (regex-based) - C#: using statements (regex, based on MS C# spec) @@ -101,7 +103,8 @@ class DependencyAnalyzer: Args: file_path: Path to source file 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: List of DependencyInfo objects @@ -109,8 +112,8 @@ class DependencyAnalyzer: if language == "Python": deps = self._extract_python_imports(content, file_path) elif language == "GDScript": - # GDScript is Python-like, uses similar import syntax - deps = self._extract_python_imports(content, file_path) + # GDScript uses preload/load, not Python imports + deps = self._extract_gdscript_imports(content, file_path) elif language in ("GodotScene", "GodotResource", "GodotShader"): # Godot resource files use ext_resource references deps = self._extract_godot_resources(content, file_path) @@ -195,6 +198,125 @@ class DependencyAnalyzer: 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]: """ Extract JavaScript/TypeScript import statements.