Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions queries/gdscript.scm
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,8 @@
(class_definition) @prepend_hardline
(class_definition extends: (extends_statement "extends" @prepend_space))

(source
(class_name_statement
extends: (extends_statement) @append_delimiter @append_hardline (#delimiter! "\n")))
(source
(extends_statement) @append_delimiter @append_hardline (#delimiter! "\n"))
(source (class_name_statement extends: (extends_statement) @append_hardline))
(source (extends_statement) @append_hardline)
(source (class_name_statement (name) @append_hardline))

; CONST DEFINITIONS
Expand Down
40 changes: 29 additions & 11 deletions src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//! rules have too much performance overhead when applied through Topiary.
use std::{collections::VecDeque, io::BufWriter};

use regex::{Regex, RegexBuilder};
use regex::{Regex, RegexBuilder, Replacer};
use topiary_core::{Language, Operation, TopiaryQuery, formatter_tree};
use tree_sitter::{Parser, Point, Query, QueryCursor, StreamingIterator, Tree};

Expand Down Expand Up @@ -126,14 +126,15 @@ impl Formatter {
/// pre-applying rules that could be performance-intensive through topiary.
#[inline(always)]
fn preprocess(&mut self) -> &mut Self {
self.remove_newlines_after_extends_statement()
self
}

/// This function runs over the content after going through topiary. We use it
/// to clean up/balance out the output.
#[inline(always)]
fn postprocess(&mut self) -> &mut Self {
self.fix_dangling_semicolons()
self.add_newlines_after_extends_statement()
.fix_dangling_semicolons()
.fix_dangling_commas()
.remove_trailing_commas_from_preload()
.postprocess_tree_sitter()
Expand All @@ -154,23 +155,40 @@ impl Formatter {
Ok(self.content)
}

/// This function removes additional new line characters after `extends_statement`.
/// This function adds additional new line characters after `extends_statement`.
#[inline(always)]
fn remove_newlines_after_extends_statement(&mut self) -> &mut Self {
fn add_newlines_after_extends_statement(&mut self) -> &mut Self {
// This regex matches substrings which:
// - must NOT contain "#" or "\n" characters between new line and "extends" keyword
// - must end with at least one new line character
// - must start wtih "extends" keyword
// - must contain `extends_name` character sequence that satisfies one of the following conditions:
// - consists out of alphanumeric characters
// - consists out of any characters (except new lines) between double quotes
// - must contain at least one new line character between `extends_name` and optional doc comment
// - may contain multiple doc comment lines that starts with `##` and ends with a new line character
let re = RegexBuilder::new(
r#"(?P<extends_line>^[^#\n]*extends )(?P<extends_name>([a-zA-Z0-9]+|".*?"))\n(\n*)"#,
r#"(?P<extends_line>^extends )(?P<extends_name>([a-zA-Z0-9]+|".*?"))\n+(?P<doc>(?:^##.*\n)*)(?P<EOF>\z)?"#,
)
.multi_line(true)
.build()
.expect("regex should compile");

self.regex_replace_all_outside_strings(re, "$extends_line$extends_name\n");
self.regex_replace_all_outside_strings(re, |caps: &regex::Captures| {
let extends_line = caps.name("extends_line").unwrap().as_str();
let extends_name = caps.name("extends_name").unwrap().as_str();
let doc = caps
.name("doc")
.map(|m| m.as_str())
.unwrap_or_default()
.trim_end(); // remove last new line from doc comment because we add a new line manually
// insert new line only if we are not at the end of file
let blank_new_line = if caps.name("EOF").is_some() { "" } else { "\n" };

format!(
"{}{}\n{}{}",
extends_line, extends_name, doc, blank_new_line
)
});

self
}

Expand Down Expand Up @@ -233,7 +251,7 @@ impl Formatter {
/// outside of strings (simple or multiline).
/// Use this to make post-processing changes needed for formatting but that
/// shouldn't affect strings in the source code.
fn regex_replace_all_outside_strings(&mut self, re: Regex, rep: &str) {
fn regex_replace_all_outside_strings<R: Replacer>(&mut self, re: Regex, mut rep: R) {
let mut iter = re.captures_iter(&self.content).peekable();
if iter.peek().is_none() {
return;
Expand All @@ -260,7 +278,7 @@ impl Formatter {
}

let mut replacement = String::new();
capture.expand(rep, &mut replacement);
rep.replace_append(&capture, &mut replacement);

let new_end_byte = start_byte + replacement.len();

Expand Down
24 changes: 24 additions & 0 deletions tests/expected/class_doc_comment.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class_name Player
extends CharacterBody2D
## A brief description of the class's role and functionality.
##
## The description of script
class_name Player
extends CharacterBody2D
## A brief description of the class's role and functionality.
##
## The description of script
class_name Player
extends CharacterBody2D
## A brief description of the class's role and functionality.
##
## The description of script

var a = 10
class_name Player
extends CharacterBody2D
## A brief description of the class's role and functionality.
##
## The description of script

var a = 10
23 changes: 23 additions & 0 deletions tests/input/class_doc_comment.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class_name Player extends CharacterBody2D

## A brief description of the class's role and functionality.
##
## The description of script
class_name Player extends CharacterBody2D
## A brief description of the class's role and functionality.
##
## The description of script
class_name Player extends CharacterBody2D
## A brief description of the class's role and functionality.
##
## The description of script

var a = 10
class_name Player
extends CharacterBody2D

## A brief description of the class's role and functionality.
##
## The description of script

var a = 10