@@ -88,6 +88,17 @@ struct ClassifiedElement<'a> {
8888 reorderable_element : Option < GDScriptTokenKind > ,
8989}
9090
91+ /// We use this little container to remember comments or annotations that appear
92+ /// before a declaration while we scan the syntax tree from top to bottom.
93+ ///
94+ /// The `start_byte` helps us preserve the original order when we later rebuild
95+ /// and attach the snippets to the declaration that follows.
96+ #[ derive( Debug , Clone ) ]
97+ struct PendingAttachment {
98+ start_byte : usize ,
99+ text : String ,
100+ }
101+
91102/// This constant lists built-in virtual methods in the order they should appear.
92103/// The higher the method is in the list, the higher the priority (i.e. _init comes before _ready).
93104const BUILTIN_VIRTUAL_METHODS : & [ & str ] = & [
@@ -191,6 +202,24 @@ fn get_token_kind(token_kind: &GDScriptTokenKind) -> TokenKind {
191202 }
192203}
193204
205+ /// This function combines annotations and comments that were collected while
206+ /// scanning the syntax tree, sorts them by their original position in the
207+ /// source code, and returns them as a list of strings in the original source
208+ /// code order.
209+ fn merge_pending_texts (
210+ pending_annotations : & [ PendingAttachment ] ,
211+ pending_comments : & [ PendingAttachment ] ,
212+ ) -> Vec < String > {
213+ let mut merged: Vec < & PendingAttachment > = pending_annotations. iter ( ) . collect ( ) ;
214+ merged. extend ( pending_comments. iter ( ) ) ;
215+ merged. sort_by_key ( |attachment| attachment. start_byte ) ;
216+
217+ merged
218+ . into_iter ( )
219+ . map ( |attachment| attachment. text . clone ( ) )
220+ . collect ( )
221+ }
222+
194223/// Extracts all top-level elements from the parsed tree.
195224fn extract_tokens_to_reorder (
196225 tree : & Tree ,
@@ -317,11 +346,17 @@ fn extract_tokens_to_reorder(
317346 if class_docstring_comments_rows. contains ( & node. start_position ( ) . row ) {
318347 continue ;
319348 } else {
320- pending_comments. push ( text) ;
349+ pending_comments. push ( PendingAttachment {
350+ start_byte : node. start_byte ( ) ,
351+ text : text. clone ( ) ,
352+ } ) ;
321353 }
322354 }
323355 "region_start" => {
324- pending_comments. push ( text) ;
356+ pending_comments. push ( PendingAttachment {
357+ start_byte : node. start_byte ( ) ,
358+ text : text. clone ( ) ,
359+ } ) ;
325360 }
326361 "region_end" => {
327362 region_end_comment = Some ( text. clone ( ) ) ;
@@ -340,22 +375,36 @@ fn extract_tokens_to_reorder(
340375 } ) ;
341376 }
342377 _ => {
343- pending_annotations. push ( text) ;
378+ pending_annotations. push ( PendingAttachment {
379+ start_byte : node. start_byte ( ) ,
380+ text : text. clone ( ) ,
381+ } ) ;
344382 }
345383 }
346384 } else {
347- pending_annotations. push ( text) ;
385+ pending_annotations. push ( PendingAttachment {
386+ start_byte : node. start_byte ( ) ,
387+ text : text. clone ( ) ,
388+ } ) ;
348389 }
349390 }
350391 "class_name_statement" => {
351392 if let Some ( element) = reorderable_element {
352393 // Don't attach class docstring to class_name, save it for extends
353- let mut non_docstring_comments = Vec :: new ( ) ;
354- for comment in & pending_comments {
355- if !class_docstring_comments. contains ( comment) {
356- non_docstring_comments. push ( comment. clone ( ) ) ;
357- }
358- }
394+ let mut attachments: Vec < & PendingAttachment > =
395+ pending_annotations. iter ( ) . collect ( ) ;
396+ attachments. extend ( pending_comments. iter ( ) ) ;
397+ attachments. sort_by_key ( |attachment| attachment. start_byte ) ;
398+ let non_docstring_comments: Vec < String > = attachments
399+ . into_iter ( )
400+ . filter_map ( |attachment| {
401+ if !class_docstring_comments. contains ( & attachment. text ) {
402+ Some ( attachment. text . clone ( ) )
403+ } else {
404+ None
405+ }
406+ } )
407+ . collect ( ) ;
359408 elements. push ( GDScriptTokensWithComments {
360409 token_kind : element,
361410 attached_comments : non_docstring_comments,
@@ -371,9 +420,11 @@ fn extract_tokens_to_reorder(
371420 "extends_statement" => {
372421 found_extends_declaration = true ;
373422 if let Some ( element) = reorderable_element {
423+ let combined_comments =
424+ merge_pending_texts ( & pending_annotations, & pending_comments) ;
374425 elements. push ( GDScriptTokensWithComments {
375426 token_kind : element,
376- attached_comments : pending_comments . clone ( ) ,
427+ attached_comments : combined_comments ,
377428 trailing_comments : Vec :: new ( ) ,
378429 original_text : text,
379430 start_byte : node. start_byte ( ) ,
@@ -417,8 +468,8 @@ fn extract_tokens_to_reorder(
417468 class_docstring_attached = true ;
418469 }
419470
420- let mut combined_comments = pending_annotations . clone ( ) ;
421- combined_comments . extend ( pending_comments. clone ( ) ) ;
471+ let combined_comments =
472+ merge_pending_texts ( & pending_annotations , & pending_comments) ;
422473
423474 // We store trailing #endregion comments to attach them to
424475 // the most recent function that has a #region comment at
@@ -454,9 +505,11 @@ fn extract_tokens_to_reorder(
454505 // We create unknown element for unhandled nodes to preserve
455506 // them. Given how the module works, if we don't do that the
456507 // nodes will be dropped.
508+ let combined_comments =
509+ merge_pending_texts ( & pending_annotations, & pending_comments) ;
457510 elements. push ( GDScriptTokensWithComments {
458511 token_kind : GDScriptTokenKind :: Unknown ( text. clone ( ) ) ,
459- attached_comments : pending_comments . clone ( ) ,
512+ attached_comments : combined_comments ,
460513 trailing_comments : Vec :: new ( ) ,
461514 original_text : text,
462515 start_byte : node. start_byte ( ) ,
@@ -469,6 +522,58 @@ fn extract_tokens_to_reorder(
469522 }
470523 }
471524
525+ // `region_end_comment` stores a trailing `#endregion` that should follow
526+ // the most recent function with a matching `#region`. If we found no
527+ // matching function, we fall back to attaching it to the last element (or
528+ // create a standalone element at the end). This avoids the formatter
529+ // deleting or losing the directive when we reorder code blocks.
530+ if let Some ( region_end) = region_end_comment. take ( ) {
531+ if let Some ( target_element) = elements. iter_mut ( ) . rev ( ) . find ( |element| {
532+ matches ! ( element. token_kind, GDScriptTokenKind :: Method ( _, _, _) )
533+ && element
534+ . attached_comments
535+ . iter ( )
536+ . any ( |c| c. trim ( ) . starts_with ( "#region" ) )
537+ } ) {
538+ target_element. trailing_comments . push ( region_end) ;
539+ } else if let Some ( last_element) = elements. last_mut ( ) {
540+ last_element. trailing_comments . push ( region_end) ;
541+ } else {
542+ elements. push ( GDScriptTokensWithComments {
543+ token_kind : GDScriptTokenKind :: Unknown ( region_end. clone ( ) ) ,
544+ attached_comments : Vec :: new ( ) ,
545+ trailing_comments : Vec :: new ( ) ,
546+ original_text : region_end,
547+ start_byte : 0 ,
548+ end_byte : 0 ,
549+ } ) ;
550+ }
551+ }
552+
553+ // Comments and annotations that we have not yet attached go at the end of
554+ // the file. They are either trailing comments, stray `#region` markers, or
555+ // unknown statements that appear after the last declaration (like new
556+ // syntax in a dev version of Godot). We merge them in source order with
557+ // `merge_pending_texts` and append them either to the last reordered
558+ // element or to a dedicated "Unknown" token so the text remains in the
559+ // output.
560+ if !pending_annotations. is_empty ( ) || !pending_comments. is_empty ( ) {
561+ let trailing_texts = merge_pending_texts ( & pending_annotations, & pending_comments) ;
562+ if let Some ( last_element) = elements. last_mut ( ) {
563+ last_element. trailing_comments . extend ( trailing_texts) ;
564+ } else if !trailing_texts. is_empty ( ) {
565+ let combined_text = trailing_texts. join ( "\n " ) ;
566+ elements. push ( GDScriptTokensWithComments {
567+ token_kind : GDScriptTokenKind :: Unknown ( combined_text. clone ( ) ) ,
568+ attached_comments : Vec :: new ( ) ,
569+ trailing_comments : Vec :: new ( ) ,
570+ original_text : combined_text,
571+ start_byte : 0 ,
572+ end_byte : 0 ,
573+ } ) ;
574+ }
575+ }
576+
472577 Ok ( elements)
473578}
474579
0 commit comments