cargo_cargofmt/formatting/
blank_lines.rs1use crate::toml::TokenKind;
2use crate::toml::TomlToken;
3
4#[tracing::instrument]
8pub fn constrain_blank_lines(tokens: &mut crate::toml::TomlTokens<'_>, min: usize, max: usize) {
9 let mut depth = 0;
10 let mut indices = crate::toml::TokenIndices::new();
11 while let Some(mut i) = indices.next_index(tokens) {
12 match tokens.tokens[i].kind {
13 TokenKind::StdTableOpen | TokenKind::ArrayTableOpen => {}
14 TokenKind::ArrayOpen | TokenKind::InlineTableOpen => {
15 depth += 1;
16 }
17 TokenKind::StdTableClose | TokenKind::ArrayTableClose => {}
18 TokenKind::ArrayClose | TokenKind::InlineTableClose => {
19 depth -= 1;
20 }
21 TokenKind::SimpleKey => {}
22 TokenKind::KeySep => {}
23 TokenKind::KeyValSep => {}
24 TokenKind::Scalar => {}
25 TokenKind::ValueSep => {}
26 TokenKind::Whitespace => {}
27 TokenKind::Comment => {}
28 TokenKind::Newline if i == 0 => {
29 tokens.tokens.remove(0);
30 indices.set_next_index(0);
31 }
32 TokenKind::Newline => {
33 let blank_i = i + 1;
34 if blank_i < tokens.len() {
35 let actual_newline_count = tokens.tokens[blank_i..]
36 .iter()
37 .take_while(|t| t.kind == TokenKind::Newline)
38 .count();
39 let is_trailing_newline = i + 1 == tokens.tokens.len();
40 let constrained_newline_count =
41 if is_trailing_newline || depth != 0 || after_table_header(tokens, i) {
42 0
43 } else {
44 actual_newline_count.clamp(min, max)
45 };
46 if let Some(remove_count) =
47 actual_newline_count.checked_sub(constrained_newline_count)
48 {
49 tokens.tokens.splice(blank_i..blank_i + remove_count, []);
50 } else if let Some(add_count) =
51 constrained_newline_count.checked_sub(actual_newline_count)
52 {
53 tokens
54 .tokens
55 .splice(blank_i..blank_i, (0..add_count).map(|_| TomlToken::NL));
56 }
57 i = blank_i + constrained_newline_count - 1;
58 indices.set_next_index(i + 1);
59 }
60 }
61 TokenKind::Error => {}
62 }
63 }
64}
65
66fn after_table_header(tokens: &crate::toml::TomlTokens<'_>, i: usize) -> bool {
67 let Some(prev_nl_i) = (0..i)
69 .rev()
70 .find(|i| tokens.tokens[*i].kind == TokenKind::Newline)
71 else {
72 return false;
73 };
74
75 let after_header = tokens.tokens[prev_nl_i..i]
76 .iter()
77 .any(|t| matches!(t.kind, TokenKind::StdTableOpen | TokenKind::ArrayTableOpen));
78 if !after_header {
79 return false;
80 }
81
82 for token in &tokens.tokens[i..] {
83 match token.kind {
84 TokenKind::StdTableOpen | TokenKind::ArrayTableOpen => {
85 return false;
87 }
88 TokenKind::SimpleKey => {
89 break;
90 }
91 _ => {}
92 }
93 }
94
95 true
96}
97
98#[cfg(test)]
99mod test {
100 use snapbox::assert_data_eq;
101 use snapbox::str;
102 use snapbox::IntoData;
103
104 #[track_caller]
105 fn valid(input: &str, min: usize, max: usize, expected: impl IntoData) {
106 let mut tokens = crate::toml::TomlTokens::parse(input);
107 super::constrain_blank_lines(&mut tokens, min, max);
108 let actual = tokens.to_string();
109
110 assert_data_eq!(&actual, expected);
111
112 let (_, errors) = toml::de::DeTable::parse_recoverable(&actual);
113 if !errors.is_empty() {
114 use std::fmt::Write as _;
115 let mut result = String::new();
116 writeln!(&mut result, "---").unwrap();
117 for error in errors {
118 writeln!(&mut result, "{error}").unwrap();
119 writeln!(&mut result, "---").unwrap();
120 }
121 panic!("failed to parse\n---\n{actual}\n{result}");
122 }
123 }
124
125 #[test]
126 fn empty_no_blank() {
127 valid("", 0, 0, str![]);
128 }
129
130 #[test]
131 fn empty_many_blanks() {
132 valid("", 3, 10, str![]);
133 }
134
135 #[test]
136 fn single_key_value_no_blank() {
137 valid("a = 5", 0, 0, str!["a = 5"]);
138 }
139
140 #[test]
141 fn single_key_value_many_blanks() {
142 valid("a = 5", 3, 10, str!["a = 5"]);
143 }
144
145 #[test]
146 fn remove_blank_lines() {
147 valid(
148 "
149
150a = 5
151
152
153b = 6
154
155
156# comment
157
158
159# comment
160
161
162c = 7
163
164
165[d]
166
167
168e = 10
169
170
171f = [
172
173
174 1,
175
176
177 2,
178
179]
180
181
182g = { a = 1, b = 2 }
183
184
185",
186 0,
187 0,
188 str![[r#"
189a = 5
190b = 6
191# comment
192# comment
193c = 7
194[d]
195e = 10
196f = [
197 1,
198 2,
199]
200g = { a = 1, b = 2 }
201
202"#]],
203 );
204 }
205
206 #[test]
207 fn compact_blank_lines() {
208 valid(
209 "
210
211a = 5
212
213
214b = 6
215
216
217# comment
218
219
220# comment
221
222
223c = 7
224
225
226[d]
227
228
229e = 10
230
231
232f = [
233
234
235 1,
236
237
238 2,
239
240
241]
242
243
244g = { a = 1, b = 2 }
245
246
247",
248 1,
249 1,
250 str![[r#"
251a = 5
252
253b = 6
254
255# comment
256
257# comment
258
259c = 7
260
261[d]
262e = 10
263
264f = [
265 1,
266 2,
267]
268
269g = { a = 1, b = 2 }
270
271
272"#]],
273 );
274 }
275
276 #[test]
277 fn add_blank_lines() {
278 valid(
279 "a = 5
280b = 6
281# comment
282# comment
283c = 7
284[d]
285e = 10
286f = [
287 1,
288 2,
289]
290g = { a = 1, b = 2 }",
291 2,
292 2,
293 str![[r#"
294a = 5
295
296
297b = 6
298
299
300# comment
301
302
303# comment
304
305
306c = 7
307
308
309[d]
310e = 10
311
312
313f = [
314 1,
315 2,
316]
317
318
319g = { a = 1, b = 2 }
320"#]],
321 );
322 }
323
324 #[test]
325 fn expand_blank_lines() {
326 valid(
327 "
328a = 5
329
330b = 6
331
332# comment
333
334# comment
335
336c = 7
337
338[d]
339
340e = 10
341
342f = [
343
344 1,
345
346 2,
347
348]
349
350g = { a = 1, b = 2 }
351",
352 3,
353 3,
354 str![[r#"
355a = 5
356
357
358
359b = 6
360
361
362
363# comment
364
365
366
367# comment
368
369
370
371c = 7
372
373
374
375[d]
376e = 10
377
378
379
380f = [
381 1,
382 2,
383]
384
385
386
387g = { a = 1, b = 2 }
388
389"#]],
390 );
391 }
392
393 #[test]
394 fn blank_line_between_array_close_and_table_open() {
395 valid(
396 r#"
397key = [
398]
399
400[b]
401"#,
402 0,
403 1,
404 str![[r#"
405key = [
406]
407
408[b]
409
410"#]],
411 );
412 }
413}