Skip to content

Commit

Permalink
feat: Add an option to keep arguments inline
Browse files Browse the repository at this point in the history
  • Loading branch information
lu-zero committed Jan 15, 2025
1 parent fa7c6ab commit 77b7b30
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 10 deletions.
51 changes: 43 additions & 8 deletions src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,12 @@ pub(crate) fn format(
TokenKind::ReservedTopLevel => {
formatter.format_top_level_reserved_word(token, &mut formatted_query);
formatter.previous_reserved_word = Some(token);
formatter.previous_top_level_reserved_word = Some(token);
}
TokenKind::ReservedTopLevelNoIndent => {
formatter.format_top_level_reserved_word_no_indent(token, &mut formatted_query);
formatter.previous_reserved_word = Some(token);
formatter.previous_top_level_reserved_word = Some(token);
}
TokenKind::ReservedNewline => {
formatter.format_newline_reserved_word(token, &mut formatted_query);
Expand Down Expand Up @@ -140,27 +142,34 @@ pub(crate) fn format(
struct Formatter<'a> {
index: usize,
previous_reserved_word: Option<&'a Token<'a>>,
previous_top_level_reserved_word: Option<&'a Token<'a>>,
tokens: &'a [Token<'a>],
params: Params<'a>,
options: &'a FormatOptions<'a>,
indentation: Indentation<'a>,
inline_block: InlineBlock,
line_start: usize,
}

impl<'a> Formatter<'a> {
fn new(tokens: &'a [Token<'a>], params: &'a QueryParams, options: &'a FormatOptions) -> Self {
Formatter {
index: 0,
previous_reserved_word: None,
previous_top_level_reserved_word: None,
tokens,
params: Params::new(params),
options,
indentation: Indentation::new(options),
inline_block: InlineBlock::new(options.max_inline_block),
inline_block: InlineBlock::new(
options.max_inline_block,
options.max_inline_arguments.is_none(),
),
line_start: 0,
}
}

fn format_line_comment(&self, token: &Token<'_>, query: &mut String) {
fn format_line_comment(&mut self, token: &Token<'_>, query: &mut String) {
let is_whitespace_followed_by_special_token =
self.next_token(1).map_or(false, |current_token| {
current_token.kind == TokenKind::Whitespace
Expand Down Expand Up @@ -189,7 +198,7 @@ impl<'a> Formatter<'a> {
self.trim_all_spaces_end(query);
query.push_str("::");
}
fn format_block_comment(&self, token: &Token<'_>, query: &mut String) {
fn format_block_comment(&mut self, token: &Token<'_>, query: &mut String) {
self.add_new_line(query);
query.push_str(&self.indent_comment(token.value));
self.add_new_line(query);
Expand All @@ -210,8 +219,17 @@ impl<'a> Formatter<'a> {
self.add_new_line(query);
}

fn format_newline_reserved_word(&self, token: &Token<'_>, query: &mut String) {
self.add_new_line(query);
fn format_newline_reserved_word(&mut self, token: &Token<'_>, query: &mut String) {
if self
.options
.max_inline_arguments
.map_or(true, |limit| limit < self.line_len_next(query))
{
self.add_new_line(query);
} else {
self.trim_spaces_end(query);
query.push(' ');
}
query.push_str(&self.equalize_whitespace(&self.format_reserved_word(token.value)));
query.push(' ');
}
Expand Down Expand Up @@ -303,7 +321,13 @@ impl<'a> Formatter<'a> {

if self.inline_block.is_active() {
self.inline_block.end();
self.format_with_space_after(&token, query);
if token.value.to_lowercase() == "end" {
self.trim_spaces_end(query);
query.push(' ');
self.format_with_spaces(&token, query);
} else {
self.format_with_space_after(&token, query);
}
} else {
self.indentation.decrease_block_level();
self.add_new_line(query);
Expand All @@ -317,7 +341,7 @@ impl<'a> Formatter<'a> {
}

// Commas start a new line (unless within inline parentheses or SQL "LIMIT" clause)
fn format_comma(&self, token: &Token<'_>, query: &mut String) {
fn format_comma(&mut self, token: &Token<'_>, query: &mut String) {
self.trim_spaces_end(query);
query.push_str(token.value);
query.push(' ');
Expand All @@ -332,6 +356,12 @@ impl<'a> Formatter<'a> {
{
return;
}

if matches!((self.previous_top_level_reserved_word, self.options.max_inline_arguments),
(Some(word), Some(limit)) if word.value.to_lowercase() == "select" && limit > self.line_len_next(query))
{
return;
}
self.add_new_line(query);
}

Expand All @@ -355,7 +385,7 @@ impl<'a> Formatter<'a> {
}
}

fn add_new_line(&self, query: &mut String) {
fn add_new_line(&mut self, query: &mut String) {
self.trim_spaces_end(query);
if self.options.inline {
query.push(' ');
Expand All @@ -364,6 +394,7 @@ impl<'a> Formatter<'a> {
if !query.ends_with('\n') {
query.push('\n');
}
self.line_start = query.len();
query.push_str(&self.indentation.get_indent());
}

Expand Down Expand Up @@ -450,6 +481,10 @@ impl<'a> Formatter<'a> {
}
}

fn line_len_next(&self, query: &str) -> usize {
query.len() - self.line_start + self.next_token(1).map_or(0, |t| t.value.len())
}

fn format_no_change(&self, token: &Token<'_>, query: &mut String) {
query.push_str(token.value);
}
Expand Down
12 changes: 10 additions & 2 deletions src/inline_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ use crate::tokenizer::{Token, TokenKind};
pub(crate) struct InlineBlock {
level: usize,
inline_max_length: usize,
newline_on_reserved: bool,
}

impl Default for InlineBlock {
fn default() -> Self {
InlineBlock {
level: 0,
inline_max_length: 50,
newline_on_reserved: true,
}
}
}

impl InlineBlock {
pub fn new(inline_max_length: usize) -> Self {
pub fn new(inline_max_length: usize, newline_on_reserved: bool) -> Self {
InlineBlock {
level: 0,
inline_max_length,
newline_on_reserved,
}
}

Expand Down Expand Up @@ -70,9 +73,14 @@ impl InlineBlock {

fn is_forbidden_token(&self, token: &Token<'_>) -> bool {
token.kind == TokenKind::ReservedTopLevel
|| token.kind == TokenKind::ReservedNewline
|| token.kind == TokenKind::LineComment
|| token.kind == TokenKind::BlockComment
|| token.value == ";"
|| if self.newline_on_reserved {
token.kind == TokenKind::ReservedNewline
} else {
false
}
|| token.value.to_lowercase() == "case"
}
}
91 changes: 91 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ pub struct FormatOptions<'a> {
///
/// Default: 50
pub max_inline_block: usize,
/// Maximum lenght of inline arguments
///
/// If unset keep every argument in a separate line
///
/// Default: None
pub max_inline_arguments: Option<usize>,
}

impl<'a> Default for FormatOptions<'a> {
Expand All @@ -62,6 +68,7 @@ impl<'a> Default for FormatOptions<'a> {
ignore_case_convert: None,
inline: false,
max_inline_block: 50,
max_inline_arguments: None,
}
}
}
Expand Down Expand Up @@ -155,6 +162,65 @@ mod tests {
assert_eq!(format(input, &QueryParams::None, &options), expected);
}

#[test]
fn keep_select_arguments_inline() {
let input = indoc! {
"
SELECT
a,
b,
c,
d,
e,
f,
g,
h
FROM foo;"
};
let options = FormatOptions {
max_inline_arguments: Some(50),
..Default::default()
};
let expected = indoc! {
"
SELECT
a, b, c, d, e, f, g, h
FROM
foo;"
};
assert_eq!(format(input, &QueryParams::None, &options), expected);
}

#[test]
fn split_select_arguments_over_lines() {
let input = indoc! {
"
SELECT
a,
b,
c,
d,
e,
f,
g,
h
FROM foo;"
};
let options = FormatOptions {
max_inline_arguments: Some(20),
..Default::default()
};
let expected = indoc! {
"
SELECT
a, b, c, d, e,
f, g, h
FROM
foo;"
};
assert_eq!(format(input, &QueryParams::None, &options), expected);
}

#[test]
fn it_formats_select_with_complex_where() {
let input = indoc!(
Expand Down Expand Up @@ -183,6 +249,31 @@ mod tests {
assert_eq!(format(input, &QueryParams::None, &options), expected);
}

#[test]
fn it_formats_select_with_complex_where_inline() {
let input = indoc!(
"
SELECT * FROM foo WHERE Column1 = 'testing'
AND ( (Column2 = Column3 OR Column4 >= NOW()) );
"
);
let options = FormatOptions {
max_inline_arguments: Some(100),
..Default::default()
};
let expected = indoc!(
"
SELECT
*
FROM
foo
WHERE
Column1 = 'testing' AND ((Column2 = Column3 OR Column4 >= NOW()));"
);

assert_eq!(format(input, &QueryParams::None, &options), expected);
}

#[test]
fn it_formats_select_with_top_level_reserved_words() {
let input = indoc!(
Expand Down

0 comments on commit 77b7b30

Please sign in to comment.