# Section 02: check_errors Handler (godotiq_server.gd)

## Overview

This section adds the `_handle_check_errors` handler to `godotiq_server.gd`. This handler responds to `check_errors` WebSocket requests from the Python bridge by scanning GDScript files for compilation and parse errors. It uses `ResourceLoader.load()` with cache bypass plus `can_instantiate()` and `reload()` to detect broken scripts, and optionally merges detailed error data from the Logger buffer (if Section 01 has been implemented and the Logger is available on Godot 4.5+).

**File modified:** `/Users/salvatorefarruggio/Desktop/godotiq/godot-addon/addons/godotiq/godotiq_server.gd`

## Dependencies

- **Section 01 (Logger + Error Buffer):** The handler references `_has_logger`, `_clear_script_errors()`, and `_get_script_errors()` introduced in Section 01. When the Logger is unavailable (Godot < 4.5 or Section 01 not yet implemented), the handler still works using the basic `load() + can_instantiate()` path alone. The code must guard all Logger-related calls behind `_has_logger`.

## Manual Verification Checklist (No Python Tests)

This is GDScript-only code. There are no pytest tests for this section.

```
# Manual: check_errors handler responds to scope="project" with all .gd files
# Manual: check_errors handler responds to scope="scene" with current scene scripts + autoloads
# Manual: check_errors handler responds to scope="res://path.gd" with single file result
# Manual: Filesystem scanner excludes addons/, .godot/, build/, export/
# Manual: Autoload discovery finds autoloads via ProjectSettings.get_property_list()
# Manual: Autoload discovery handles .tscn autoloads (extracts root script)
# Manual: Script with parse error returns error entry with file path
# Manual: Script with compilation error returns error entry with file path
# Manual: Clean script returns no error
# Manual: Errors are deduplicated by file+line+message
```

## Implementation Details

### 1. Dispatch Registration

Add `"check_errors"` to the `_dispatch` match statement in `godotiq_server.gd`, before the default `_:` branch:

```gdscript
# In _dispatch(), add this case before the "_:" default:
"check_errors":
    _handle_check_errors(peer_id, id, params)
```

### 2. _handle_check_errors Handler

**Signature:** `func _handle_check_errors(peer_id: int, id: String, params: Dictionary) -> void`

**Parameters from `params`:**
- `scope` (optional, default `"scene"`): One of `"project"`, `"scene"`, or a `res://` path string

**Algorithm:**

1. Read `scope` from `params.get("scope", "scene")`.
2. If `_has_logger` is true, call `_clear_script_errors()` to reset the Logger buffer.
3. Collect script paths based on scope:
   - `"project"`: Call `_get_script_paths_recursive()` on the root filesystem directory.
   - `"scene"`: Call `_get_scene_script_paths()` using the edited scene root.
   - Anything else: Wrap in a single-element array (treated as specific `res://` path).
4. For each script path, perform the error check (see "Script Check Loop" below).
5. If `_has_logger` is true, read `_get_script_errors()` and merge Logger entries (which have line numbers) with the basic entries (which only have pass/fail).
6. Deduplicate errors by `file + str(line) + message` tuple. All unique tuples are kept.
7. Send response:

```gdscript
send_response(peer_id, id, {
    "errors": errors_array,
    "total": errors_array.size(),
    "scripts_checked": total_scripts_checked,
    "scope": scope_used,
})
```

### 3. Script Check Loop

For each script path:

1. `ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE)` — bypass cache for fresh state.
2. If result is null → append `{file: path, error: "Failed to load script", line: 0}`.
3. If result is a `GDScript`:
   - `can_instantiate()` → if false, parse errors exist → append basic error entry
   - `reload()` → if non-OK return, compilation errors → append basic error entry
   - When Logger available, `reload()` triggers `_log_error` callbacks populating `_script_errors` with detailed entries
4. Every 10 scripts, yield with `await get_tree().process_frame` to prevent UI freeze.

### 4. Filesystem Scanning Helper

**Signature:** `func _get_script_paths_recursive(dir: EditorFileSystemDirectory) -> Array[String]`

Recursively walks `EditorInterface.get_resource_filesystem().get_filesystem()`, collecting all `.gd` files.

**Excluded directories** (non-user code):
- `addons/`
- `.godot/`
- `build/`
- `export/`

Implementation:
- Iterate subdirectories with `dir.get_subdir_count()` / `dir.get_subdir(i)`
- Check `dir.get_subdir(i).get_name()` against excluded names before recursing
- Iterate files with `dir.get_file_count()` / `dir.get_file(i)`
- Check if `dir.get_file_path(i)` ends with `.gd`

### 5. Scene Script Collection Helper

**Signature:** `func _get_scene_script_paths(root: Node) -> Array[String]`

Walks the scene tree from the edited scene root. For each node with a `script` property that is a `GDScript`, adds its `resource_path` to the result.

**Autoload discovery** (no `get_autoload_list()` method exists in Godot):

```gdscript
for prop in ProjectSettings.get_property_list():
    if prop.name.begins_with("autoload/"):
        var value: String = ProjectSettings.get_setting(prop.name)
        var path: String = value.lstrip("*")  # Strip singleton marker
        if path.ends_with(".gd"):
            result.append(path)
        elif path.ends_with(".tscn"):
            # Autoload points to a scene -- extract root script
            var scene = load(path)
            if scene and scene is PackedScene:
                var state = scene.get_state()
                if state.get_node_count() > 0:
                    for i in state.get_node_property_count(0):
                        if state.get_node_property_name(0, i) == "script":
                            var script_path = state.get_node_property_value(0, i)
                            if script_path is GDScript:
                                result.append(script_path.resource_path)
```

Deduplicate the result array before returning.

### 6. Logger Merge Logic

When `_has_logger` is true, after the check loop:
1. Call `_get_script_errors()` for Logger-captured entries (shape: `{file, line, message, type, timestamp}`)
2. For each Logger entry, check if a basic error with the same `file` exists — Logger enriches it with specific line/message
3. Final errors array has shape: `{file: String, line: int, error: String}`

### 7. Response Format

```gdscript
{
    "errors": [
        {"file": "res://scripts/player.gd", "line": 42, "error": "Identifier not found: TONE_MAP_ACES"},
        {"file": "res://scripts/enemy.gd", "line": 0, "error": "Script has parse errors (cannot instantiate)"}
    ],
    "total": 2,
    "scripts_checked": 15,
    "scope": "scene"
}
```

### 8. Edge Cases

- **No scene open with `scope="scene"`**: Check only autoloads. Don't error.
- **Invalid `res://` path**: `load()` returns null → captured as "Failed to load script".
- **Empty project**: `{errors: [], total: 0, scripts_checked: 0}` — valid result.

## Implementation Notes (Post-Review)

- Added `_checking_errors` guard flag to prevent reentrancy (concurrent check_errors requests rejected with CHECK_IN_PROGRESS error)
- Changed `lstrip("*")` to `trim_prefix("*")` for correct singleton marker stripping
- Logger merge now enriches basic errors (replaces line-0 generic errors with detailed Logger entries for same file)
- `reload()` now always called even when `can_instantiate()` is false, so Logger captures parse error details
- Dedup key separator changed from `:` to `|` to avoid collision with `res://` path colons
