Skip to content

Commit 31a2dfe

Browse files
authored
More formatting options (#78)
* chore: Upgrade to Winnow 0.6.26 * refactor: Resolve deprecations * refactor: Switch from Parser to ModalParser * chore: Upgrade to Winnow 0.7.0 * refactor: Remove use of ErrMode * fix: Fix the span computation and usage * feat: Extend ALTER TABLE actions support * feat: Always keep track of the indentation level of blocks This way is possible to have inlined blocks that break if the argument limit is hit. * feat!: Consider SELECT ALL/DISTINCT a single token * feat: Give joins their own token kind * fix: Allow inline case-end * fix: Improve inlining of subqueries
1 parent dcde8d1 commit 31a2dfe

File tree

4 files changed

+197
-48
lines changed

4 files changed

+197
-48
lines changed

src/formatter.rs

+53-16
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ pub(crate) fn format(
101101
formatter.format_newline_reserved_word(token, &mut formatted_query);
102102
formatter.previous_reserved_word = Some(token);
103103
}
104+
TokenKind::Join => {
105+
formatter.format_newline_reserved_word(token, &mut formatted_query);
106+
formatter.previous_reserved_word = Some(token);
107+
}
104108
TokenKind::Reserved => {
105109
formatter.format_with_spaces(token, &mut formatted_query);
106110
formatter.previous_reserved_word = Some(token);
@@ -163,7 +167,14 @@ impl<'a> Formatter<'a> {
163167
indentation: Indentation::new(options),
164168
inline_block: InlineBlock::new(
165169
options.max_inline_block,
166-
options.max_inline_arguments.is_none(),
170+
match (options.max_inline_arguments, options.max_inline_top_level) {
171+
(Some(max_inline_args), Some(max_inline_top)) => {
172+
max_inline_args.min(max_inline_top)
173+
}
174+
(Some(max_inline_args), None) => max_inline_args,
175+
(None, Some(max_inline_top)) => max_inline_top,
176+
(None, None) => 0,
177+
},
167178
),
168179
block_level: 0,
169180
}
@@ -204,28 +215,51 @@ impl<'a> Formatter<'a> {
204215
self.add_new_line(query);
205216
}
206217

218+
// if we are inside an inline block we decide our behaviour as if we are an inline argument
219+
fn top_level_behavior(&self) -> (bool, bool) {
220+
let span_len = self.top_level_tokens_span();
221+
let block_len = self.inline_block.cur_len();
222+
if block_len > 0 {
223+
let limit = self.options.max_inline_arguments.unwrap_or(0);
224+
(limit < block_len, limit < span_len)
225+
} else {
226+
(
227+
true,
228+
self.options
229+
.max_inline_top_level
230+
.map_or(true, |limit| limit < span_len),
231+
)
232+
}
233+
}
234+
207235
fn format_top_level_reserved_word(&mut self, token: &Token<'_>, query: &mut String) {
208236
let span_len = self.top_level_tokens_span();
209-
self.indentation.decrease_top_level();
210-
self.add_new_line(query);
211-
self.indentation.increase_top_level(span_len);
237+
let (newline_before, newline_after) = self.top_level_behavior();
238+
239+
if newline_before {
240+
self.indentation.decrease_top_level();
241+
self.add_new_line(query);
242+
}
212243
query.push_str(&self.equalize_whitespace(&self.format_reserved_word(token.value)));
213-
if self
214-
.options
215-
.max_inline_top_level
216-
.map_or(true, |limit| limit < span_len)
217-
{
244+
if newline_after {
245+
self.indentation.increase_top_level(span_len);
218246
self.add_new_line(query);
219247
} else {
220248
query.push(' ');
221249
}
222250
}
223251

224252
fn format_top_level_reserved_word_no_indent(&mut self, token: &Token<'_>, query: &mut String) {
225-
self.indentation.decrease_top_level();
226-
self.add_new_line(query);
253+
let (newline_before, newline_after) = self.top_level_behavior();
254+
255+
if newline_before {
256+
self.indentation.decrease_top_level();
257+
self.add_new_line(query);
258+
}
227259
query.push_str(&self.equalize_whitespace(&self.format_reserved_word(token.value)));
228-
self.add_new_line(query);
260+
if newline_after {
261+
self.add_new_line(query);
262+
}
229263
}
230264

231265
fn format_newline_reserved_word(&mut self, token: &Token<'_>, query: &mut String) {
@@ -297,8 +331,9 @@ impl<'a> Formatter<'a> {
297331

298332
self.inline_block.begin_if_possible(self.tokens, self.index);
299333

334+
self.indentation.increase_block_level();
335+
300336
if !self.inline_block.is_active() {
301-
self.indentation.increase_block_level();
302337
self.add_new_line(query);
303338
}
304339
}
@@ -330,6 +365,8 @@ impl<'a> Formatter<'a> {
330365

331366
token.value = &value;
332367

368+
self.indentation.decrease_block_level();
369+
333370
if self.inline_block.is_active() {
334371
self.inline_block.end();
335372
if token.value.to_lowercase() == "end" {
@@ -340,7 +377,6 @@ impl<'a> Formatter<'a> {
340377
self.format_with_space_after(&token, query);
341378
}
342379
} else {
343-
self.indentation.decrease_block_level();
344380
self.add_new_line(query);
345381
self.format_with_spaces(&token, query);
346382
}
@@ -493,7 +529,6 @@ impl<'a> Formatter<'a> {
493529
}
494530

495531
fn top_level_tokens_span(&self) -> usize {
496-
assert_eq!(self.tokens[self.index].kind, TokenKind::ReservedTopLevel);
497532
let mut block_level = self.block_level;
498533

499534
self.tokens[self.index..]
@@ -508,7 +543,9 @@ impl<'a> Formatter<'a> {
508543
block_level = block_level.saturating_sub(1);
509544
block_level > self.block_level
510545
}
511-
TokenKind::ReservedTopLevel => block_level != self.block_level,
546+
TokenKind::ReservedTopLevel | TokenKind::ReservedTopLevelNoIndent => {
547+
block_level != self.block_level
548+
}
512549
_ => true,
513550
})
514551
.map(|token| token.value.len())

src/inline_block.rs

+67-24
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,129 @@
11
use crate::tokenizer::{Token, TokenKind};
22

3+
pub(crate) struct BlockInfo {
4+
length: usize,
5+
has_forbidden_tokens: bool,
6+
top_level_token_span: usize,
7+
}
8+
39
pub(crate) struct InlineBlock {
410
level: usize,
511
inline_max_length: usize,
6-
newline_on_reserved: bool,
12+
newline_on_reserved_limit: usize,
13+
info: Vec<BlockInfo>,
714
}
815

916
impl Default for InlineBlock {
1017
fn default() -> Self {
1118
InlineBlock {
19+
info: Vec::new(),
1220
level: 0,
1321
inline_max_length: 50,
14-
newline_on_reserved: true,
22+
newline_on_reserved_limit: 0,
1523
}
1624
}
1725
}
1826

1927
impl InlineBlock {
20-
pub fn new(inline_max_length: usize, newline_on_reserved: bool) -> Self {
28+
pub fn new(inline_max_length: usize, newline_on_reserved_limit: usize) -> Self {
2129
InlineBlock {
22-
level: 0,
2330
inline_max_length,
24-
newline_on_reserved,
31+
newline_on_reserved_limit,
32+
..Default::default()
2533
}
2634
}
2735

36+
fn is_inline_block(&self, info: &BlockInfo) -> bool {
37+
!info.has_forbidden_tokens
38+
&& info.length <= self.inline_max_length
39+
&& info.top_level_token_span <= self.newline_on_reserved_limit
40+
}
41+
2842
pub fn begin_if_possible(&mut self, tokens: &[Token<'_>], index: usize) {
29-
if self.level == 0 && self.is_inline_block(tokens, index) {
43+
let info = self.build_info(tokens, index);
44+
if self.level == 0 && self.is_inline_block(&info) {
3045
self.level = 1;
3146
} else if self.level > 0 {
3247
self.level += 1;
3348
} else {
3449
self.level = 0;
3550
}
51+
if self.level > 0 {
52+
self.info.push(info);
53+
}
3654
}
3755

3856
pub fn end(&mut self) {
57+
self.info.pop();
3958
self.level -= 1;
4059
}
4160

4261
pub fn is_active(&self) -> bool {
4362
self.level > 0
4463
}
4564

46-
fn is_inline_block(&self, tokens: &[Token<'_>], index: usize) -> bool {
65+
/// Get the current inline block length
66+
pub fn cur_len(&self) -> usize {
67+
self.info.last().map_or(0, |info| info.length)
68+
}
69+
70+
fn build_info(&self, tokens: &[Token<'_>], index: usize) -> BlockInfo {
4771
let mut length = 0;
4872
let mut level = 0;
73+
let mut top_level_token_span = 0;
74+
let mut start_top_level = -1;
75+
let mut start_span = 0;
76+
let mut has_forbidden_tokens = false;
4977

5078
for token in &tokens[index..] {
5179
length += token.value.len();
5280

53-
// Overran max length
54-
if length > self.inline_max_length {
55-
return false;
56-
}
57-
if token.kind == TokenKind::OpenParen {
58-
level += 1;
59-
} else if token.kind == TokenKind::CloseParen {
60-
level -= 1;
61-
if level == 0 {
62-
return true;
81+
match token.kind {
82+
TokenKind::ReservedTopLevel | TokenKind::ReservedTopLevelNoIndent => {
83+
if start_top_level != -1 {
84+
if start_top_level == level {
85+
top_level_token_span = top_level_token_span.max(length - start_span);
86+
start_top_level = -1;
87+
}
88+
} else {
89+
start_top_level = level;
90+
start_span = length;
91+
}
6392
}
93+
TokenKind::OpenParen => {
94+
level += 1;
95+
}
96+
TokenKind::CloseParen => {
97+
level -= 1;
98+
if level == 0 {
99+
break;
100+
}
101+
}
102+
_ => {}
64103
}
65104

66105
if self.is_forbidden_token(token) {
67-
return false;
106+
has_forbidden_tokens = true;
68107
}
69108
}
70109

71-
false
110+
// broken syntax let's try our best
111+
BlockInfo {
112+
length,
113+
has_forbidden_tokens,
114+
top_level_token_span,
115+
}
72116
}
73117

74118
fn is_forbidden_token(&self, token: &Token<'_>) -> bool {
75-
token.kind == TokenKind::ReservedTopLevel
76-
|| token.kind == TokenKind::LineComment
119+
token.kind == TokenKind::LineComment
77120
|| token.kind == TokenKind::BlockComment
78121
|| token.value == ";"
79-
|| if self.newline_on_reserved {
80-
token.kind == TokenKind::ReservedNewline
122+
|| if self.newline_on_reserved_limit == 0 {
123+
token.kind == TokenKind::ReservedTopLevel
124+
|| token.kind == TokenKind::ReservedNewline
81125
} else {
82126
false
83127
}
84-
|| ["case", "end"].contains(&token.value.to_lowercase().as_str())
85128
}
86129
}

src/lib.rs

+31
Original file line numberDiff line numberDiff line change
@@ -2135,4 +2135,35 @@ from
21352135

21362136
assert_eq!(format(input, &QueryParams::None, &options), expected);
21372137
}
2138+
2139+
#[test]
2140+
fn it_formats_blocks_inline_or_not() {
2141+
let input = " UPDATE t SET o = ($5 + $6 + $7 + $8),a = CASE WHEN $2
2142+
THEN NULL ELSE COALESCE($3, b) END, b = CASE WHEN $4 THEN NULL ELSE
2143+
COALESCE($5, b) END, s = (SELECT true FROM bar WHERE bar.foo = $99),
2144+
c = CASE WHEN $6 THEN NULL ELSE COALESCE($7, c) END,
2145+
d = CASE WHEN $8 THEN NULL ELSE COALESCE($9, d) END,
2146+
e = (SELECT true FROM bar) WHERE id = $1";
2147+
let options = FormatOptions {
2148+
max_inline_arguments: Some(50),
2149+
max_inline_block: 100,
2150+
max_inline_top_level: Some(10),
2151+
..Default::default()
2152+
};
2153+
let expected = indoc!(
2154+
"
2155+
UPDATE t
2156+
SET
2157+
o = ($5 + $6 + $7 + $8),
2158+
a = CASE WHEN $2 THEN NULL ELSE COALESCE($3, b) END,
2159+
b = CASE WHEN $4 THEN NULL ELSE COALESCE($5, b) END,
2160+
s = (SELECT true FROM bar WHERE bar.foo = $99),
2161+
c = CASE WHEN $6 THEN NULL ELSE COALESCE($7, c) END,
2162+
d = CASE WHEN $8 THEN NULL ELSE COALESCE($9, d) END,
2163+
e = (SELECT true FROM bar)
2164+
WHERE id = $1"
2165+
);
2166+
2167+
assert_eq!(format(input, &QueryParams::None, &options), expected);
2168+
}
21382169
}

0 commit comments

Comments
 (0)