cargo_cargofmt/formatting/
trailing_comma.rs

1use crate::config::lists::SeparatorTactic;
2use crate::toml::TokenKind;
3use crate::toml::TomlToken;
4
5/// Assumptions:
6/// - blocks are reflowed
7#[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}