Skip to content

Commit 69e65fa

Browse files
committed
Fix long function declaration with return type hint just above max line
length not wrapping Close #300
1 parent 9982a81 commit 69e65fa

4 files changed

Lines changed: 95 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This file documents the changes made to the formatter with each release.
1111
### Fixed
1212

1313
- Fixed edge case when a lambda is at the end of an argument list and ends with a match statement, causing the formatter to insert a trailing comma (#301)
14+
- Fixed a case of a long function definition that would not wrap on multiple lines despite being a little over 100 characters (#300)
1415

1516
## Release 0.23.0 (2026-07-24)
1617

src/formatter.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ fn process_node(
341341
process_body(input, node, render_elements)
342342
}
343343
GDScriptNodeKind::Lambda => process_lambda(input, node, render_elements),
344+
GDScriptNodeKind::Function => process_function(input, node, render_elements),
344345
GDScriptNodeKind::SetGet => process_setget(input, node, render_elements),
345346
GDScriptNodeKind::ParenthesizedExpression => {
346347
process_parenthesized_expression(input, node, render_elements)
@@ -352,6 +353,70 @@ fn process_node(
352353
}
353354
}
354355

356+
/// Groups a function header (the declaration line/first line) separately from
357+
/// its body so the complete header, including the return type, is treated as
358+
/// one unit when deciding whether parameters need to be wrapped onto multiple
359+
/// lines.
360+
fn process_function(
361+
input: &ParseInput,
362+
node: tree_sitter::Node,
363+
render_elements: &mut Vec<RenderElement>,
364+
) {
365+
let mut has_disabled_region = false;
366+
let mut region_index = 0;
367+
while region_index < input.disabled_regions.len() {
368+
let region = input.disabled_regions[region_index];
369+
if region.start < node.end_byte() && region.end > node.start_byte() {
370+
has_disabled_region = true;
371+
break;
372+
}
373+
region_index += 1;
374+
}
375+
// Disabled regions (fmt: off / fmt: on comment pairs) can overlap a
376+
// function anywhere, so they can be anywhere in the middle of the function
377+
// AST. If we encounter one we fall back to the generic formatter, which
378+
// handles disabled regions correctly.
379+
if has_disabled_region {
380+
process_children_with_spacing(input, node, render_elements);
381+
return;
382+
}
383+
384+
let group_index = begin_group(render_elements);
385+
let mut previous: Option<tree_sitter::Node> = None;
386+
let mut child_index = 0;
387+
while child_index < node.child_count() {
388+
if let Some(child) = node.child(child_index as u32) {
389+
let child_kind = GDScriptNodeKind::get_kind_from_ast_node(child);
390+
if child_kind == GDScriptNodeKind::Body {
391+
finish_group(render_elements, group_index);
392+
if let Some(previous_child) = previous {
393+
process_separator_between_sibling_nodes(
394+
GDScriptNodeKind::Function,
395+
&previous_child,
396+
&child,
397+
render_elements,
398+
);
399+
}
400+
process_node(input, child, render_elements);
401+
return;
402+
}
403+
404+
if let Some(previous_child) = previous {
405+
process_separator_between_sibling_nodes(
406+
GDScriptNodeKind::Function,
407+
&previous_child,
408+
&child,
409+
render_elements,
410+
);
411+
}
412+
process_node(input, child, render_elements);
413+
previous = Some(child);
414+
}
415+
child_index += 1;
416+
}
417+
finish_group(render_elements, group_index);
418+
}
419+
355420
/// Returns the string with the preferred string delimiters if the user used the
356421
/// option to prefer a specific quote style (' or "). Returns `None` when the
357422
/// original string already uses the preferred quote style to avoid unnecessary
@@ -1513,7 +1578,15 @@ fn process_container(
15131578
return;
15141579
}
15151580

1516-
let group_index = begin_group(render_elements);
1581+
let is_function_parameters = node_kind == GDScriptNodeKind::Parameters
1582+
&& node.parent().is_some_and(|parent| {
1583+
GDScriptNodeKind::get_kind_from_ast_node(parent) == GDScriptNodeKind::Function
1584+
});
1585+
let group_index = if is_function_parameters {
1586+
None
1587+
} else {
1588+
Some(begin_group(render_elements))
1589+
};
15171590

15181591
if let Some(open) = node.child(0) {
15191592
process_node(input, open, render_elements);
@@ -1756,7 +1829,9 @@ fn process_container(
17561829
process_node(input, close, render_elements);
17571830
}
17581831

1759-
finish_group(render_elements, group_index);
1832+
if let Some(group_index) = group_index {
1833+
finish_group(render_elements, group_index);
1834+
}
17601835
}
17611836

17621837
/// Formats ParenthesizedExpression nodes with a Group. Falls back to

tests/expected/lambda.gd

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,13 @@ func issue_301() -> void:
7070
_:
7171
return true
7272
)
73+
74+
75+
# Verifies that this function wraps. In a previous release, the return type
76+
# would not be accounted for in the width calculation, so the parameter list
77+
# would stay on a single line.
78+
func issue_300(
79+
open_xr_analog_threshold_modifier_long: OpenXRAnalogThresholdModifier,
80+
other_type: Test,
81+
) -> void:
82+
pass

tests/input/lambda.gd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,10 @@ func issue_301() -> void:
6363
_:
6464
return true
6565
)
66+
67+
68+
# Verifies that this function wraps. In a previous release, the return type
69+
# would not be accounted for in the width calculation, so the parameter list
70+
# would stay on a single line.
71+
func issue_300(open_xr_analog_threshold_modifier_long: OpenXRAnalogThresholdModifier, other_type: Test) -> void:
72+
pass

0 commit comments

Comments
 (0)