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
159 changes: 113 additions & 46 deletions src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,19 @@ pub(crate) fn format(
formatter.format_block_comment(token, &mut formatted_query);
}
TokenKind::ReservedTopLevel => {
formatter.format_top_level_reserved_word(token, &mut formatted_query);
formatter.set_top_level_span(token);
let span_info =
formatter.format_top_level_reserved_word(token, &mut formatted_query);
formatter.set_top_level_span(token, span_info);
}
TokenKind::ReservedTopLevelNoIndent => {
formatter.format_top_level_reserved_word_no_indent(token, &mut formatted_query);
formatter.set_top_level_span(token);
let span_info =
formatter.format_top_level_reserved_word_no_indent(token, &mut formatted_query);
formatter.set_top_level_span(token, span_info);
}
TokenKind::ReservedNewlineAfter => {
formatter.format_newline_after_reserved_word(token, &mut formatted_query);
formatter.set_top_level_span(token);
let span_info =
formatter.format_newline_after_reserved_word(token, &mut formatted_query);
formatter.set_top_level_span(token, span_info);
}
TokenKind::ReservedNewline => {
formatter.format_newline_reserved_word(token, &mut formatted_query);
Expand Down Expand Up @@ -167,8 +170,7 @@ impl<'a> Formatter<'a> {
}
}

fn set_top_level_span(&mut self, token: &'a Token<'a>) {
let span_info = self.top_level_tokens_info();
fn set_top_level_span(&mut self, token: &'a Token<'a>, span_info: SpanInfo) {
self.indentation.set_previous_top_level(token, span_info);
}

Expand Down Expand Up @@ -207,57 +209,54 @@ impl<'a> Formatter<'a> {
self.add_new_line(query);
}

// if we are inside an inline block we decide our behaviour as if were inline
fn top_level_behavior(&self, span_info: &SpanInfo) -> (bool, bool) {
let span_len = span_info.full_span;
let block_len = self.inline_block.cur_len();
if block_len > 0 {
let limit = self.options.max_inline_top_level.unwrap_or(0);
(limit < block_len, limit < span_len)
} else {
(
true,
self.options
.max_inline_top_level
.is_none_or(|limit| limit < span_len),
)
}
}

fn format_top_level_reserved_word(&mut self, token: &Token<'_>, query: &mut String) {
fn format_top_level_reserved_word(
&mut self,
token: &Token<'_>,
query: &mut String,
) -> SpanInfo {
let span_info = self.top_level_tokens_info();
let (newline_before, newline_after) = self.top_level_behavior(&span_info);

if newline_before {
if span_info.newline_before {
self.indentation.decrease_top_level();
self.add_new_line(query);
}
query.push_str(&self.equalize_whitespace(&self.format_reserved_word(token.value)));
if newline_after && token.alias != "CREATE" {
self.indentation.increase_top_level(span_info);
if span_info.newline_after && token.alias != "CREATE" {
self.indentation.increase_top_level(span_info.clone());
self.add_new_line(query);
} else {
query.push(' ');
}

span_info
}

fn format_top_level_reserved_word_no_indent(&mut self, token: &Token<'_>, query: &mut String) {
fn format_top_level_reserved_word_no_indent(
&mut self,
token: &Token<'_>,
query: &mut String,
) -> SpanInfo {
let span_info = self.top_level_tokens_info();
let (newline_before, newline_after) = self.top_level_behavior(&span_info);

if newline_before {
if span_info.newline_before {
self.indentation.decrease_top_level();
self.add_new_line(query);
}
query.push_str(&self.equalize_whitespace(&self.format_reserved_word(token.value)));
if newline_after {
if span_info.newline_after {
self.add_new_line(query);
} else {
query.push(' ');
}

span_info
}

fn format_newline_after_reserved_word(&mut self, token: &Token<'_>, query: &mut String) {
fn format_newline_after_reserved_word(
&mut self,
token: &Token<'_>,
query: &mut String,
) -> SpanInfo {
let span_info = self.top_level_tokens_info();

let newline_before = match (
Expand All @@ -278,8 +277,10 @@ impl<'a> Formatter<'a> {

query.push_str(&self.equalize_whitespace(&self.format_reserved_word(token.value)));

self.indentation.increase_top_level(span_info);
self.indentation.increase_top_level(span_info.clone());
self.add_new_line(query);

span_info
}

fn format_newline_reserved_word(&mut self, token: &Token<'_>, query: &mut String) {
Expand Down Expand Up @@ -318,6 +319,19 @@ impl<'a> Formatter<'a> {
TokenKind::LineComment,
];

let inlined = self.inline_block.begin_if_possible(self.tokens, self.index);
let fold_in_top_level = !inlined
&& self.options.max_inline_top_level.is_some()
&& self
.previous_non_whitespace_token(1)
.is_some_and(|t| t.kind == TokenKind::ReservedTopLevel)
&& self
.indentation
.previous_top_level_reserved()
.is_some_and(|(_, span)| {
span.blocks == 1 && span.newline_after && span.arguments == 1
});

// Take out the preceding space unless there was whitespace there in the original query
// or another opening parens or line comment
let previous_token = self.previous_token(1);
Expand Down Expand Up @@ -348,11 +362,14 @@ impl<'a> Formatter<'a> {
_ => Cow::Borrowed(token.value),
};

query.push_str(&value);
if fold_in_top_level {
self.trim_all_spaces_end(query);
query.push(' ');
}

self.inline_block.begin_if_possible(self.tokens, self.index);
self.indentation.increase_block_level(fold_in_top_level);

self.indentation.increase_block_level();
query.push_str(&value);

if !self.inline_block.is_active() {
self.add_new_line(query);
Expand Down Expand Up @@ -388,7 +405,7 @@ impl<'a> Formatter<'a> {

token.value = &value;

self.indentation.decrease_block_level();
let folded = self.indentation.decrease_block_level();

if self.inline_block.is_active() {
self.inline_block.end();
Expand All @@ -400,7 +417,7 @@ impl<'a> Formatter<'a> {
self.format_with_space_after(&token, query);
}
} else {
self.add_new_line(query);
self.add_new_line_inner(query, folded);
self.format_with_spaces(&token, query);
}
}
Expand Down Expand Up @@ -458,7 +475,7 @@ impl<'a> Formatter<'a> {
}
}

fn add_new_line(&self, query: &mut String) {
fn add_new_line_inner(&self, query: &mut String, folded: bool) {
self.trim_spaces_end(query);
if self.options.inline {
query.push(' ');
Expand All @@ -467,7 +484,11 @@ impl<'a> Formatter<'a> {
if !query.ends_with('\n') {
query.push('\n');
}
query.push_str(&self.indentation.get_indent());
query.push_str(&self.indentation.get_indent(folded));
}

fn add_new_line(&self, query: &mut String) {
self.add_new_line_inner(query, false);
}

fn trim_spaces_end(&self, query: &mut String) {
Expand All @@ -484,7 +505,7 @@ impl<'a> Formatter<'a> {
if i == 0 {
combined.push_str(line)
} else if line.starts_with([' ', '\t']) {
let indent = self.indentation.get_indent();
let indent = self.indentation.get_indent(false);
let start_trimmed = line.trim_start_matches([' ', '\t']);
combined.reserve(indent.len() + start_trimmed.len() + 2);
combined.push('\n');
Expand Down Expand Up @@ -553,13 +574,30 @@ impl<'a> Formatter<'a> {
}
}

fn previous_non_whitespace_token(&self, idx: usize) -> Option<&Token<'_>> {
let index = self.index.checked_sub(idx);
if let Some(index) = index {
self.tokens[..=index]
.iter()
.rev()
.find(|t| t.kind != TokenKind::Whitespace)
} else {
None
}
}

fn top_level_tokens_info(&self) -> SpanInfo {
let mut block_level = self.block_level;
let mut full_span = 0;
let mut blocks = 0;
let mut arguments = 0;

for token in self.tokens[self.index..].iter().skip(1) {
match token.kind {
TokenKind::OpenParen => {
if block_level == self.block_level {
blocks += 1;
}
block_level += 1;
}
TokenKind::CloseParen => {
Expand All @@ -579,14 +617,43 @@ impl<'a> Formatter<'a> {
full_span += 1;
continue;
}
TokenKind::Operator if token.value == "," => {
if block_level == self.block_level {
arguments += 1;
}
}

_ => {}
_ => {
if arguments == 0 {
arguments += 1
Comment on lines +627 to +628
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for counting arguments is incorrect. This code increments arguments for every non-comma token when arguments is 0, which means arguments will always be at least 1 even for empty parameter lists. This should only increment when encountering the first actual argument token, not for every token.

Suggested change
if arguments == 0 {
arguments += 1
// Only increment arguments for the first actual argument token,
// and only if it's not whitespace, parentheses, or reserved keywords.
// Only increment arguments for the first actual argument token,
// and only if it's not whitespace, parentheses, or reserved keywords.
if arguments == 0 {
arguments += 1;
TokenKind::Whitespace
| TokenKind::OpenParen
| TokenKind::CloseParen
| TokenKind::ReservedTopLevel
| TokenKind::ReservedTopLevelNoIndent
| TokenKind::ReservedNewlineAfter
| TokenKind::Operator if token.value == "," => {}
_ => arguments += 1,
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if the comment is valid or not, but I know for sure the code suggestion is broken.

}
}
}

full_span += token.value.len();
}

SpanInfo { full_span }
// if we are inside an inline block we decide our behaviour as if were inline
let block_len = self.inline_block.cur_len();
let (newline_before, newline_after) = if block_len > 0 {
let limit = self.options.max_inline_top_level.unwrap_or(0);
(limit < block_len, limit < full_span)
} else {
(
true,
self.options
.max_inline_top_level
.is_none_or(|limit| limit < full_span),
)
};

SpanInfo {
full_span,
blocks,
newline_before,
newline_after,
arguments,
}
}

fn format_no_change(&self, token: &Token<'_>, query: &mut String) {
Expand Down
41 changes: 28 additions & 13 deletions src/indentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ pub(crate) struct Indentation<'a> {

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum IndentType {
TopLevel,
BlockLevel,
Top,
Block,
FoldedBlock,
}

impl<'a> Indentation<'a> {
Expand All @@ -29,41 +30,55 @@ impl<'a> Indentation<'a> {
}
}

pub fn get_indent(&self) -> String {
pub fn get_indent(&self, folded: bool) -> String {
// TODO compute in place?
let level = self
.indent_types
.iter()
.copied()
.filter(|t| *t != IndentType::FoldedBlock)
.count()
- if folded { 1 } else { 0 };
match self.options.indent {
Indent::Spaces(num_spaces) => " "
.repeat(num_spaces as usize)
.repeat(self.indent_types.len()),
Indent::Tabs => "\t".repeat(self.indent_types.len()),
Indent::Spaces(num_spaces) => " ".repeat(num_spaces as usize).repeat(level),
Indent::Tabs => "\t".repeat(level),
}
}

pub fn increase_top_level(&mut self, span: SpanInfo) {
self.indent_types.push(IndentType::TopLevel);
self.indent_types.push(IndentType::Top);
self.top_level_span.push(span);
}

pub fn increase_block_level(&mut self) {
self.indent_types.push(IndentType::BlockLevel);
pub fn increase_block_level(&mut self, folded: bool) {
self.indent_types.push(if folded {
IndentType::FoldedBlock
} else {
IndentType::Block
});
self.previous.push(Default::default());
}

pub fn decrease_top_level(&mut self) {
if self.indent_types.last() == Some(&IndentType::TopLevel) {
if self.indent_types.last() == Some(&IndentType::Top) {
self.indent_types.pop();
self.top_level_span.pop();
self.previous.pop();
}
}

pub fn decrease_block_level(&mut self) {
/// Return true if the block was folded
pub fn decrease_block_level(&mut self) -> bool {
let mut folded = false;
while !self.indent_types.is_empty() {
let kind = self.indent_types.pop();
self.previous.pop();
if kind != Some(IndentType::TopLevel) {
folded = kind == Some(IndentType::FoldedBlock);
if kind != Some(IndentType::Top) {
break;
}
}
folded
}

pub fn reset_indentation(&mut self) {
Expand Down
5 changes: 4 additions & 1 deletion src/inline_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl InlineBlock {
&& (!info.has_reseved_tokens || info.length <= self.reserved_limit)
}

pub fn begin_if_possible(&mut self, tokens: &[Token<'_>], index: usize) {
pub fn begin_if_possible(&mut self, tokens: &[Token<'_>], index: usize) -> bool {
let info = self.build_info(tokens, index);
if self.level == 0 && self.is_inline_block(&info) {
self.level = 1;
Expand All @@ -55,6 +55,9 @@ impl InlineBlock {
}
if self.level > 0 {
self.info.push(info);
true
} else {
false
}
}

Expand Down
Loading
Loading