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