cargo_cargofmt/formatting/
indent.rs

1use std::borrow::Cow;
2
3use crate::toml::TokenKind;
4use crate::toml::TomlToken;
5
6#[tracing::instrument]
7pub fn normalize_indent(
8    tokens: &mut crate::toml::TomlTokens<'_>,
9    hard_tabs: bool,
10    tab_spaces: usize,
11) {
12    let mut depth = 0;
13    let mut indices = crate::toml::TokenIndices::new();
14    let mut buffer = PaddingBuffer::new(hard_tabs, tab_spaces);
15    while let Some(i) = indices.next_index(tokens) {
16        match tokens.tokens[i].kind {
17            TokenKind::StdTableOpen | TokenKind::ArrayTableOpen => {}
18            TokenKind::StdTableClose | TokenKind::ArrayTableClose => {}
19            TokenKind::ArrayOpen | TokenKind::InlineTableOpen => {
20                depth += 1;
21            }
22            TokenKind::ArrayClose | TokenKind::InlineTableClose => {
23                depth -= 1;
24            }
25            TokenKind::SimpleKey => {}
26            TokenKind::KeySep => {}
27            TokenKind::KeyValSep => {}
28            TokenKind::Scalar => {}
29            TokenKind::ValueSep => {}
30            TokenKind::Whitespace => {}
31            TokenKind::Comment => {}
32            TokenKind::Newline => {
33                let next_i = i + 1;
34                if let Some(next) = tokens.tokens.get_mut(next_i) {
35                    match (next.kind, depth) {
36                        (TokenKind::Newline, _) => {}
37                        (TokenKind::Whitespace, 0) => {
38                            *next = TomlToken::EMPTY;
39                        }
40                        (TokenKind::Whitespace, _) => {
41                            let close_count = close_count(tokens, next_i);
42                            let ws = buffer.whitespace(depth - close_count);
43                            let mut token = TomlToken::EMPTY;
44                            token.raw = Cow::Owned(ws.to_owned());
45                            tokens.tokens[next_i] = token;
46                        }
47                        (_, 0) => {}
48                        (_, _) => {
49                            let close_count = close_count(tokens, next_i);
50                            let ws = buffer.whitespace(depth - close_count);
51                            let mut token = TomlToken::EMPTY;
52                            token.raw = Cow::Owned(ws.to_owned());
53                            tokens.tokens.insert(next_i, token);
54                        }
55                    }
56                }
57            }
58            TokenKind::Error => {}
59        }
60    }
61    tokens.trim_empty_whitespace();
62}
63
64struct PaddingBuffer {
65    buffer: String,
66    c: &'static str,
67    count_per_indent: usize,
68}
69
70impl PaddingBuffer {
71    fn new(hard_tabs: bool, tab_spaces: usize) -> Self {
72        let (count_per_indent, c) = if hard_tabs {
73            (1, "\t")
74        } else {
75            (tab_spaces, " ")
76        };
77        Self {
78            buffer: Default::default(),
79            c,
80            count_per_indent,
81        }
82    }
83
84    fn whitespace(&mut self, depth: usize) -> &str {
85        let count = depth * self.count_per_indent;
86
87        self.buffer.truncate(count);
88        if let Some(add) = count.checked_sub(self.buffer.len()) {
89            self.buffer.reserve(add);
90            for _ in 0..add {
91                self.buffer.push_str(self.c);
92            }
93        }
94
95        &self.buffer
96    }
97}
98
99fn close_count(tokens: &crate::toml::TomlTokens<'_>, i: usize) -> usize {
100    let token_line_count = tokens.tokens[i..]
101        .iter()
102        .take_while(|t| {
103            !matches!(
104                t.kind,
105                TokenKind::Newline
106                    | TokenKind::Comment
107                    | TokenKind::ArrayOpen
108                    | TokenKind::InlineTableOpen
109            )
110        })
111        .count();
112    let end = i + token_line_count + 1;
113    tokens.tokens[i..end]
114        .iter()
115        .filter(|t| matches!(t.kind, TokenKind::ArrayClose | TokenKind::InlineTableClose))
116        .count()
117}
118
119#[cfg(test)]
120mod test {
121    use snapbox::assert_data_eq;
122    use snapbox::str;
123    use snapbox::IntoData;
124
125    #[track_caller]
126    fn valid(input: &str, hard_tabs: bool, tab_spaces: usize, expected: impl IntoData) {
127        let mut tokens = crate::toml::TomlTokens::parse(input);
128        super::normalize_indent(&mut tokens, hard_tabs, tab_spaces);
129        let actual = tokens.to_string();
130
131        assert_data_eq!(&actual, expected);
132
133        let (_, errors) = toml::de::DeTable::parse_recoverable(&actual);
134        if !errors.is_empty() {
135            use std::fmt::Write as _;
136            let mut result = String::new();
137            writeln!(&mut result, "---").unwrap();
138            for error in errors {
139                writeln!(&mut result, "{error}").unwrap();
140                writeln!(&mut result, "---").unwrap();
141            }
142            panic!("failed to parse\n---\n{actual}\n{result}");
143        }
144    }
145
146    #[test]
147    fn empty_tabs() {
148        valid("", true, 10, str![]);
149    }
150
151    #[test]
152    fn empty_spaces() {
153        valid("", false, 10, str![]);
154    }
155
156    #[test]
157    fn cleanup_tabs() {
158        valid(
159            "
160  a = 5
161
162  # Hello
163
164  [b]
165  a = 10
166  b = [
167    1,
168    2,
169    3,
170  ]
171  c = [
172    [
173      1,
174      2,
175      3,
176    ]
177  ]
178  d = [[
179      1,
180      2,
181      3,
182  ]]
183
184  [e]
185    f = 10
186
187g = 11
188",
189            true,
190            10,
191            str![[r#"
192
193a = 5
194
195# Hello
196
197[b]
198a = 10
199b = [
200	1,
201	2,
202	3,
203]
204c = [
205	[
206		1,
207		2,
208		3,
209	]
210]
211d = [[
212		1,
213		2,
214		3,
215]]
216
217[e]
218f = 10
219
220g = 11
221
222"#]],
223        );
224    }
225
226    #[test]
227    fn cleanup_spaces() {
228        valid(
229            "
230  a = 5
231
232  # Hello
233
234  [b]
235  a = 10
236  b = [
237    1,
238    2,
239    3,
240  ]
241  c = [
242    [
243      1,
244      2,
245      3,
246    ]
247  ]
248  d = [[
249      1,
250      2,
251      3,
252  ]]
253
254  [e]
255    f = 10
256
257g = 11
258",
259            false,
260            10,
261            str![[r#"
262
263a = 5
264
265# Hello
266
267[b]
268a = 10
269b = [
270          1,
271          2,
272          3,
273]
274c = [
275          [
276                    1,
277                    2,
278                    3,
279          ]
280]
281d = [[
282                    1,
283                    2,
284                    3,
285]]
286
287[e]
288f = 10
289
290g = 11
291
292"#]],
293        );
294    }
295}