Problem
When formatting a file that contains invalid GDScript syntax, the formatter can silently delete entire function definitions instead of leaving the file untouched.
Reproducer
func jump(force: float) -> void:
self.velocity.y = force
is_shooting = false
func shoot() -> void:
var pos: Vector2 = muzzle_right.global_position
var direction: Vector2 = Vector2.RIGHT
if is_facing_left:
pos = muzzle_left.global_position
direction = Vector2.LEFT
var lazer: Projectile = Instancer.instance_scene_to_level(Instancer.laster_scene, pos) as Projectile
if lazer != null:
lazer.launch(direction, lazer_speed)
is_shooting = false
var yozora: bool.
Run with --reorder-code --use-spaces. After formatting, the entire shoot function disappears from the output.
Root cause
The bug is a three-step failure chain:
1. Tree-sitter error recovery is too aggressive.
The last line var yozora: bool. is invalid — the . after the type is unexpected. When tree-sitter tries to recover, it backtracks far enough to wrap the entire shoot() function in an ERROR node, not just the bad line:
(source
(function_definition ...) ← jump() parses correctly
(ERROR
(name) ← "shoot"
(parameters)
return_type: (type ...)
(variable_statement ...)
(if_statement ...)
... ← entire body of shoot()
)
)
2. The tree-sitter query silently skips ERROR nodes.
The reorder module collects top-level tokens using this query (reorder.rs):
In tree-sitter, the _ wildcard matches any named node but explicitly excludes ERROR nodes. So jump() is captured, and the ERROR node containing shoot() is invisibly dropped.
3. The reorder pass outputs only what it collected.
build_reordered_code rebuilds the file from the token list. Because shoot() was never added to that list, it simply isn't written to the output.
Expected behavior
If the file cannot be fully parsed (i.e. tree.root_node().has_error() is true), the formatter should leave the file completely untouched — or at minimum skip the reorder step — rather than silently losing declarations.
Problem
When formatting a file that contains invalid GDScript syntax, the formatter can silently delete entire function definitions instead of leaving the file untouched.
Reproducer
Run with
--reorder-code --use-spaces. After formatting, the entireshootfunction disappears from the output.Root cause
The bug is a three-step failure chain:
1. Tree-sitter error recovery is too aggressive.
The last line
var yozora: bool.is invalid — the.after the type is unexpected. When tree-sitter tries to recover, it backtracks far enough to wrap the entireshoot()function in anERRORnode, not just the bad line:2. The tree-sitter query silently skips
ERRORnodes.The reorder module collects top-level tokens using this query (
reorder.rs):In tree-sitter, the
_wildcard matches any named node but explicitly excludesERRORnodes. Sojump()is captured, and theERRORnode containingshoot()is invisibly dropped.3. The reorder pass outputs only what it collected.
build_reordered_coderebuilds the file from the token list. Becauseshoot()was never added to that list, it simply isn't written to the output.Expected behavior
If the file cannot be fully parsed (i.e.
tree.root_node().has_error()is true), the formatter should leave the file completely untouched — or at minimum skip the reorder step — rather than silently losing declarations.