Skip to content

Commit 921d05d

Browse files
committed
Reorder: fix reordering detaching trailing inline comments from
top-level declarations Close #271 Also refactored/renamed some variables to be clearer
1 parent 0a66947 commit 921d05d

5 files changed

Lines changed: 119 additions & 68 deletions

File tree

CHANGELOG.md

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

1919
- Conditions that exceed max-line-length by only a few columns get backslash + attribute-dot wrapping instead of the parenthesized and-wrap used for larger overflows (#270)
20+
- Fixed --reorder-code detaches trailing comments from top-level declarations and re-attaches them above the following declaration (#271)
2021

2122
## Release 0.21.0 (2026-07-16)
2223

src/formatter.rs

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2593,40 +2593,48 @@ fn process_source_reorder(
25932593
previous_is_double_spaced = current_needs_two_blank;
25942594
previous_child_index = Some(item.child_index);
25952595

2596-
// output leading comments/annotations.
2596+
// Output source children attached before the declaration.
25972597
if item.classification != DeclarationKind::Docstring {
2598-
let declaration_start = match node.child(item.child_index as u32) {
2598+
let declaration_start_byte = match node.child(item.child_index as u32) {
25992599
Some(child) => child.start_byte(),
26002600
None => 0,
26012601
};
2602-
let mut leading_index = 0;
2603-
while leading_index < item.leading_indices.len() {
2604-
let leading_child_index = item.leading_indices[leading_index];
2605-
if let Some(child) = node.child(leading_child_index as u32) {
2602+
let mut attached_before_declaration_index = 0;
2603+
while attached_before_declaration_index
2604+
< item.child_indices_attached_before_declaration.len()
2605+
{
2606+
let child_index_attached_before_declaration = item
2607+
.child_indices_attached_before_declaration[attached_before_declaration_index];
2608+
if let Some(child) = node.child(child_index_attached_before_declaration as u32) {
26062609
process_node(input, child, render_elements);
2607-
let next_start = if leading_index + 1 < item.leading_indices.len() {
2608-
let lookahead_index = item.leading_indices[leading_index + 1];
2609-
match node.child(lookahead_index as u32) {
2610+
let next_child_start_byte = if attached_before_declaration_index + 1
2611+
< item.child_indices_attached_before_declaration.len()
2612+
{
2613+
let next_child_index_attached_before_declaration = item
2614+
.child_indices_attached_before_declaration
2615+
[attached_before_declaration_index + 1];
2616+
match node.child(next_child_index_attached_before_declaration as u32) {
26102617
Some(next_child) => next_child.start_byte(),
2611-
None => declaration_start,
2618+
None => declaration_start_byte,
26122619
}
26132620
} else {
2614-
declaration_start
2621+
declaration_start_byte
26152622
};
2616-
if has_newline(source, child.end_byte(), next_start) {
2623+
if has_newline(source, child.end_byte(), next_child_start_byte) {
26172624
render_elements.push(RenderElement::HardLine);
26182625
} else {
26192626
render_elements.push(RenderElement::Space);
26202627
}
26212628
}
2622-
leading_index += 1;
2629+
attached_before_declaration_index += 1;
26232630
}
26242631
}
26252632

26262633
if item.classification == DeclarationKind::Docstring {
26272634
let mut docstring_index = 0;
2628-
while docstring_index < item.leading_indices.len() {
2629-
let docstring_child_index = item.leading_indices[docstring_index];
2635+
while docstring_index < item.child_indices_attached_before_declaration.len() {
2636+
let docstring_child_index =
2637+
item.child_indices_attached_before_declaration[docstring_index];
26302638
if let Some(child) = node.child(docstring_child_index as u32) {
26312639
process_node(input, child, render_elements);
26322640
render_elements.push(RenderElement::HardLine);
@@ -2673,22 +2681,25 @@ fn process_source_reorder(
26732681
}
26742682
}
26752683

2676-
let declaration_end: usize = match node.child(item.child_index as u32) {
2684+
let declaration_end_byte: usize = match node.child(item.child_index as u32) {
26772685
Some(child) => child.end_byte(),
26782686
None => 0,
26792687
};
2680-
let mut trailing_index = 0;
2681-
while trailing_index < item.trailing_indices.len() {
2682-
let trailing_child_index = item.trailing_indices[trailing_index];
2683-
if let Some(child) = node.child(trailing_child_index as u32) {
2684-
if has_newline(source, declaration_end, child.start_byte()) {
2688+
let mut attached_after_declaration_index = 0;
2689+
while attached_after_declaration_index
2690+
< item.child_indices_attached_after_declaration.len()
2691+
{
2692+
let child_index_attached_after_declaration = item
2693+
.child_indices_attached_after_declaration[attached_after_declaration_index];
2694+
if let Some(child) = node.child(child_index_attached_after_declaration as u32) {
2695+
if has_newline(source, declaration_end_byte, child.start_byte()) {
26852696
render_elements.push(RenderElement::HardLine);
26862697
} else {
26872698
render_elements.push(RenderElement::Space);
26882699
}
26892700
process_node(input, child, render_elements);
26902701
}
2691-
trailing_index += 1;
2702+
attached_after_declaration_index += 1;
26922703
}
26932704
item_index += 1;
26942705
}

src/reorder.rs

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ pub struct ReorderItem<'a> {
2424
/// `child_index`. Used for split inline extends: the extends_statement
2525
/// is a child of class_name_statement, not a sibling.
2626
pub sub_child: Option<usize>,
27-
/// Indices of comment/annotation children that precede this declaration
28-
/// in source order (they move with it during reorder).
29-
pub leading_indices: Vec<usize>,
30-
/// Indices of trailing children (e.g. #endregion) glued to this decl.
31-
pub trailing_indices: Vec<usize>,
27+
/// Indices of source children that precede and move with this declaration,
28+
/// such as comments, annotations, and region starts.
29+
pub child_indices_attached_before_declaration: Vec<usize>,
30+
/// Indices of source children that follow and move with this declaration,
31+
/// such as end-of-line comments and region ends.
32+
pub child_indices_attached_after_declaration: Vec<usize>,
3233
/// Whether this declaration or its leading comments had a blank line before
3334
/// it in the source. This preserves blanks lines input by the user in their
3435
/// source code, up to 1. For example, blank lines used to separate groups
@@ -129,7 +130,9 @@ pub fn build_reorder_plan<'a>(parent: Node<'a>, content: &'a str) -> ReorderPlan
129130
let mut items = Vec::with_capacity(child_count);
130131

131132
// Pass 1: classify each child.
132-
let mut is_comment = vec![false; child_count];
133+
// These source children move with a nearby declaration. They include
134+
// comments, annotations, and region markers.
135+
let mut is_child_attached_to_declaration = vec![false; child_count];
133136
let mut is_region_end = vec![false; child_count];
134137

135138
let mut child_index = 0;
@@ -143,9 +146,9 @@ pub fn build_reorder_plan<'a>(parent: Node<'a>, content: &'a str) -> ReorderPlan
143146
|| kind == GDScriptNodeKind::Annotation
144147
|| kind == GDScriptNodeKind::RegionStart
145148
{
146-
is_comment[child_index] = true;
149+
is_child_attached_to_declaration[child_index] = true;
147150
} else if kind == GDScriptNodeKind::RegionEnd {
148-
is_comment[child_index] = true;
151+
is_child_attached_to_declaration[child_index] = true;
149152
is_region_end[child_index] = true;
150153
} else if kind == GDScriptNodeKind::SemiColon {
151154
// skip; handled by builder spacing
@@ -155,8 +158,8 @@ pub fn build_reorder_plan<'a>(parent: Node<'a>, content: &'a str) -> ReorderPlan
155158
items.push(ReorderItem {
156159
child_index,
157160
sub_child: None,
158-
leading_indices: Vec::new(),
159-
trailing_indices: Vec::new(),
161+
child_indices_attached_before_declaration: Vec::new(),
162+
child_indices_attached_after_declaration: Vec::new(),
160163
has_blank_line_before: false,
161164
classification: child_classification.classification,
162165
name: child_classification.name,
@@ -170,8 +173,8 @@ pub fn build_reorder_plan<'a>(parent: Node<'a>, content: &'a str) -> ReorderPlan
170173
items.push(ReorderItem {
171174
child_index,
172175
sub_child: Some(extends_index),
173-
leading_indices: Vec::new(),
174-
trailing_indices: Vec::new(),
176+
child_indices_attached_before_declaration: Vec::new(),
177+
child_indices_attached_after_declaration: Vec::new(),
175178
has_blank_line_before: false,
176179
classification: DeclarationKind::Extends,
177180
name: "",
@@ -222,7 +225,7 @@ pub fn build_reorder_plan<'a>(parent: Node<'a>, content: &'a str) -> ReorderPlan
222225
scan_index += 1;
223226
continue;
224227
};
225-
if !is_comment[scan_index]
228+
if !is_child_attached_to_declaration[scan_index]
226229
|| !get_node_text(child, content).trim_start().starts_with("##")
227230
{
228231
break;
@@ -250,16 +253,16 @@ pub fn build_reorder_plan<'a>(parent: Node<'a>, content: &'a str) -> ReorderPlan
250253
// Mark docstring comments as consumed.
251254
let mut docstring_index = 0;
252255
while docstring_index < docstring_indices.len() {
253-
is_comment[docstring_indices[docstring_index]] = false;
256+
is_child_attached_to_declaration[docstring_indices[docstring_index]] = false;
254257
docstring_index += 1;
255258
}
256259

257260
if !docstring_indices.is_empty() {
258261
items.push(ReorderItem {
259262
child_index: docstring_indices[0],
260263
sub_child: None,
261-
leading_indices: docstring_indices,
262-
trailing_indices: Vec::new(),
264+
child_indices_attached_before_declaration: docstring_indices,
265+
child_indices_attached_after_declaration: Vec::new(),
263266
has_blank_line_before: false,
264267
classification: DeclarationKind::Docstring,
265268
name: "",
@@ -269,8 +272,8 @@ pub fn build_reorder_plan<'a>(parent: Node<'a>, content: &'a str) -> ReorderPlan
269272
});
270273
}
271274

272-
// Pass 2: assign leading/trailing children to each declaration.
273-
let mut previous_declaration_end: Option<usize> = None;
275+
// Pass 2: assign source children before and after each declaration.
276+
let mut previous_declaration_child_index: Option<usize> = None;
274277
let mut declaration_index = 0;
275278
while declaration_index < declaration_count {
276279
let declaration_child_index = items[declaration_index].child_index;
@@ -281,29 +284,36 @@ pub fn build_reorder_plan<'a>(parent: Node<'a>, content: &'a str) -> ReorderPlan
281284
None
282285
};
283286

284-
// Leading: all comments (non-region-end) between previous_child decl and this one.
285-
let start: usize = match previous_declaration_end {
286-
Some(previous_end) => previous_end + 1,
287+
// Attach every eligible source child between the previous declaration
288+
// and this one before the current declaration.
289+
let first_possible_attachment_child_index: usize = match previous_declaration_child_index {
290+
Some(previous_declaration_child_index) => previous_declaration_child_index + 1,
287291
None => 0,
288292
};
289-
let mut leading = Vec::new();
290-
let mut scan_index = start;
291-
while scan_index < declaration_child_index {
292-
if is_comment[scan_index] && !is_region_end[scan_index] {
293-
leading.push(scan_index);
293+
let mut child_indices_attached_before_declaration = Vec::new();
294+
let mut current_child_index = first_possible_attachment_child_index;
295+
while current_child_index < declaration_child_index {
296+
if is_child_attached_to_declaration[current_child_index]
297+
&& !is_region_end[current_child_index]
298+
{
299+
child_indices_attached_before_declaration.push(current_child_index);
294300
}
295-
scan_index += 1;
301+
current_child_index += 1;
296302
}
297-
items[declaration_index].leading_indices = leading;
303+
items[declaration_index].child_indices_attached_before_declaration =
304+
child_indices_attached_before_declaration;
298305

299306
// Determine if there is a blank line before the current declaration
300307
// that we need to preserve after reordering.
301-
if let Some(previous_declaration_child_index) = previous_declaration_end {
308+
if let Some(previous_declaration_child_index) = previous_declaration_child_index {
302309
let previous_declaration = parent.child(previous_declaration_child_index as u32);
303-
let first_item_child_index = if items[declaration_index].leading_indices.is_empty() {
310+
let first_item_child_index = if items[declaration_index]
311+
.child_indices_attached_before_declaration
312+
.is_empty()
313+
{
304314
declaration_child_index
305315
} else {
306-
items[declaration_index].leading_indices[0]
316+
items[declaration_index].child_indices_attached_before_declaration[0]
307317
};
308318
let first_item_child = parent.child(first_item_child_index as u32);
309319
if let (Some(previous_declaration), Some(first_item_child)) =
@@ -317,28 +327,49 @@ pub fn build_reorder_plan<'a>(parent: Node<'a>, content: &'a str) -> ReorderPlan
317327
}
318328
}
319329

320-
// Trailing: region-end between this decl and the next one, or all remaining comments.
321-
let mut trailing = Vec::new();
322-
let mut scan_index = declaration_child_index + 1;
323-
if let Some(next) = next_declaration_child_index {
324-
while scan_index < next {
325-
if is_region_end[scan_index] {
326-
trailing.push(scan_index);
330+
// Attach region ends and an inline comment after the declaration to
331+
// that declaration.
332+
let mut indices_of_children_attached_after_declaration = Vec::new();
333+
let mut next_child_index = declaration_child_index + 1;
334+
if let Some(next_declaration_child_index) = next_declaration_child_index {
335+
let current_declaration = parent
336+
.child(declaration_child_index as u32)
337+
.expect("declaration child index came from this parent");
338+
while next_child_index < next_declaration_child_index {
339+
if is_region_end[next_child_index] {
340+
indices_of_children_attached_after_declaration.push(next_child_index);
341+
} else if is_child_attached_to_declaration[next_child_index] {
342+
if let Some(following_attached_child) = parent.child(next_child_index as u32) {
343+
// This checks mainly for inline comments after the
344+
// declaration. They appear as siblings in the AST even
345+
// if on the same line so that's why we check for a
346+
// newline.
347+
let has_newline_before_following_child = content
348+
[current_declaration.end_byte()..following_attached_child.start_byte()]
349+
.contains('\n');
350+
if !has_newline_before_following_child {
351+
indices_of_children_attached_after_declaration.push(next_child_index);
352+
// Prevent the later declaration from also treating
353+
// this source child as preceding content.
354+
is_child_attached_to_declaration[next_child_index] = false;
355+
}
356+
}
327357
}
328-
scan_index += 1;
358+
next_child_index += 1;
329359
}
330360
} else {
331-
// After last declaration: trailing = all remaining comments.
332-
while scan_index < child_count {
333-
if is_comment[scan_index] {
334-
trailing.push(scan_index);
361+
// After the last declaration, attach all remaining source children.
362+
while next_child_index < child_count {
363+
if is_child_attached_to_declaration[next_child_index] {
364+
indices_of_children_attached_after_declaration.push(next_child_index);
335365
}
336-
scan_index += 1;
366+
next_child_index += 1;
337367
}
338368
}
339-
items[declaration_index].trailing_indices = trailing;
369+
items[declaration_index].child_indices_attached_after_declaration =
370+
indices_of_children_attached_after_declaration;
340371

341-
previous_declaration_end = Some(declaration_child_index);
372+
previous_declaration_child_index = Some(declaration_child_index);
342373
declaration_index += 1;
343374
}
344375

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extends Node
2+
3+
const A := 1 # comment for A
4+
const B := 2
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extends Node
2+
3+
const A := 1 # comment for A
4+
const B := 2

0 commit comments

Comments
 (0)