toml_parse/sort/
mod.rs

1//! Sort the given toml file based on SyntaxElements.
2//!
3//! Using a `Matcher` to specify the tables and values that have items that should be sorted
4//! call `sort_toml_items` then compare the resulting tree using `SyntaxNodeExtTrait::deep_eq`.
5
6use std::cmp::Ordering;
7
8use rowan::{GreenNode, GreenNodeBuilder};
9
10use super::tkn_tree::{SyntaxElement, SyntaxNode, SyntaxNodeExtTrait, TomlKind};
11
12/// Each `Matcher` field when matched to a heading or key token
13/// will be matched with `.contains()`.
14pub struct Matcher<'a> {
15    /// Toml headings with braces `[heading]`.
16    pub heading: &'a [&'a str],
17    /// Toml segmented heading without braces.
18    pub segmented: &'a [&'a str],
19    /// Toml heading with braces `[heading]` and the key
20    /// of the array to sort.
21    pub heading_key: &'a [(&'a str, &'a str)],
22}
23
24fn split_seg_last<S: AsRef<str>>(s: S) -> String {
25    let open_close: &[char] = &['[', ']'];
26    let heading = s.as_ref();
27
28    heading
29        .replace(open_close, "")
30        .split('.')
31        .last()
32        .map(ToString::to_string)
33        .unwrap()
34}
35
36pub fn sort_toml_items(root: &SyntaxNode, matcher: &Matcher<'_>) -> SyntaxNode {
37    let mut builder = GreenNodeBuilder::new();
38    builder.start_node(TomlKind::Root.into());
39
40    for ele in sorted_tables_with_tokens(root, matcher.segmented) {
41        match ele.kind() {
42            TomlKind::Table => {
43                // for [workspace] members = ...
44                // this is heading and members is key.
45                let (head, key): (Vec<_>, Vec<_>) = matcher.heading_key.iter().cloned().unzip();
46                let node = ele.as_node().unwrap();
47                if match_table(node, matcher.heading) {
48                    add_sorted_table(node, &mut builder)
49                } else if match_table(node, &head) {
50                    add_table_sort_items(node, &mut builder, &key)
51                } else {
52                    add_element(ele, &mut builder)
53                }
54            }
55            _ => add_element(ele, &mut builder),
56        }
57    }
58    builder.finish_node();
59    let green: GreenNode = builder.finish();
60    SyntaxNode::new_root(green)
61}
62
63fn match_table(node: &SyntaxNode, headings: &[&str]) -> bool {
64    match node.first_child().map(|n| n.kind()) {
65        Some(TomlKind::Heading) => headings.iter().any(|h| node.token_text().contains(h)),
66        _ => false,
67    }
68}
69
70fn sorted_tables_with_tokens(
71    root: &SyntaxNode,
72    segmented: &[&str],
73) -> impl Iterator<Item = SyntaxElement> {
74    let kids = root.children_with_tokens().collect::<Vec<_>>();
75    let pos = root
76        .children_with_tokens()
77        .enumerate()
78        .filter(|(_, n)| n.as_node().map(|n| n.kind()) == Some(TomlKind::Table))
79        .map(|(i, n)| {
80            (
81                i,
82                n.as_node()
83                    .unwrap()
84                    .children()
85                    .find(|n| n.kind() == TomlKind::Heading)
86                    .map(|n| n.token_text()),
87            )
88        })
89        .collect::<Vec<_>>();
90
91    let mut tables = Vec::default();
92    let mut start = 0;
93    for (idx, key) in pos {
94        let next_is_whitespace = kids
95            .get(idx + 1)
96            .map(|el| el.as_token().map(|t| t.kind()) == Some(TomlKind::Whitespace))
97            == Some(true);
98
99        let idx = if next_is_whitespace { idx + 1 } else { idx };
100
101        tables.push((key, kids[start..=idx].to_vec()));
102        start = idx + 1;
103    }
104
105    if start != kids.len() {
106        tables.push((None, kids[start..].to_vec()))
107    }
108
109    for seg in segmented {
110        #[rustfmt::skip]
111        tables.sort_by(|chunk, other| {
112            let chunk_matches_heading = chunk.0.as_ref()
113                .map(|head| head.contains(&format!("[{}", seg))) == Some(true);
114            let other_matches_heading = other.0.as_ref()
115                .map(|head| head.contains(&format!("[{}", seg))) == Some(true);
116
117            if chunk_matches_heading && other_matches_heading {
118                chunk
119                    .0
120                    .as_ref()
121                    .map(split_seg_last)
122                    .cmp(&other.0.as_ref().map(split_seg_last))
123            } else {
124                Ordering::Equal
125            }
126        });
127    }
128
129    tables.into_iter().map(|p| p.1).flatten()
130}
131
132fn add_sorted_table(node: &SyntaxNode, builder: &mut GreenNodeBuilder) {
133    builder.start_node(node.kind().into());
134
135    if let Some(heading) = node.first_child() {
136        add_node(&heading, builder);
137    } else {
138        unreachable!("table without heading")
139    }
140
141    // skip the table heading we just added
142    let kv = node.children_with_tokens().skip(1).collect::<Vec<_>>();
143    for ele in sort_key_value(&kv) {
144        add_element(ele, builder);
145    }
146
147    builder.finish_node();
148}
149
150fn sort_key_value(kv: &[SyntaxElement]) -> Vec<SyntaxElement> {
151    let pos = kv
152        .iter()
153        .enumerate()
154        .filter(|(_, n)| n.as_node().map(|n| n.kind()) == Some(TomlKind::KeyValue))
155        .map(|(i, n)| {
156            (
157                i,
158                n.as_node()
159                    .unwrap()
160                    .children()
161                    .find(|n| n.kind() == TomlKind::Key)
162                    .map(|n| n.token_text()),
163            )
164        })
165        .collect::<Vec<_>>();
166
167    let mut keys = Vec::default();
168    let mut start = 0_usize;
169    for (found_idx, key) in pos {
170        let idx = kv
171            .iter()
172            .skip(start)
173            .enumerate()
174            .take_while(|(count, n)| {
175                // We group everything between key -> value pairs to *hopefully* maintain
176                // comments
177                n.as_node().map(|n| n.kind()) != Some(TomlKind::KeyValue) || *count == 0
178            })
179            .map(|(idx, _)| idx)
180            .sum::<usize>()
181            + found_idx;
182
183        // At the end of a table the `start` may be greater than `idx`
184        // this defers to the `if` block that adds the rest of the vec to `keys`
185        if start > idx || idx > kv.len() {
186            break;
187        }
188
189        keys.push((key, &kv[start..=idx]));
190        start = idx + 1;
191    }
192
193    // if we did not reach the end of the table add the last whitespace/comments
194    if start < kv.len() {
195        keys.push((None, &kv[start..]))
196    }
197
198    keys.sort_by(|chunk, other| {
199        if chunk.0.is_none() || other.0.is_none() {
200            return Ordering::Equal;
201        }
202        chunk.0.cmp(&other.0)
203    });
204    keys.into_iter().map(|p| p.1).flatten().cloned().collect()
205}
206
207fn match_key(node: &SyntaxElement, keys: &[&str]) -> bool {
208    match node
209        .as_node()
210        .map(|n| n.first_child().map(|n| n.kind()))
211        .flatten()
212    {
213        Some(TomlKind::Key) => keys.iter().any(|h| {
214            node.as_node()
215                .unwrap()
216                .first_child()
217                .unwrap()
218                .token_text()
219                .contains(h)
220                && node
221                    .as_node()
222                    .unwrap()
223                    .children()
224                    .find(|n| n.kind() == TomlKind::Value)
225                    .map(|n| n.first_child().map(|n| n.kind() == TomlKind::Array))
226                    .flatten()
227                    == Some(true)
228        }),
229        _ => false,
230    }
231}
232
233fn add_table_sort_items(node: &SyntaxNode, builder: &mut GreenNodeBuilder, key: &[&str]) {
234    builder.start_node(node.kind().into());
235
236    if let Some(heading) = node.first_child() {
237        add_node(&heading, builder);
238    } else {
239        unreachable!("table without heading")
240    }
241
242    for ele in node.children_with_tokens().skip(1) {
243        if match_key(&ele, key) {
244            // this is a `KeyValue` node
245            builder.start_node(ele.kind().into());
246            for el in ele.as_node().unwrap().children_with_tokens() {
247                match el {
248                    SyntaxElement::Node(n) => match n.kind() {
249                        TomlKind::Value => {
250                            builder.start_node(TomlKind::Value.into());
251                            if n.first_child().map(|n| n.kind()) == Some(TomlKind::Array) {
252                                // the node type like TomlKind::Array
253                                builder.start_node(TomlKind::Array.into());
254                                builder
255                                    .token(TomlKind::OpenBrace.into(), rowan::SmolStr::from("["));
256                                for (end, sorted) in sort_items(n.first_child().unwrap()) {
257                                    add_array_items(sorted, builder, end);
258                                }
259                                builder
260                                    .token(TomlKind::CloseBrace.into(), rowan::SmolStr::from("]"));
261                                builder.finish_node();
262                            }
263                            builder.finish_node();
264                        }
265                        _ => add_node(&n, builder),
266                    },
267                    SyntaxElement::Token(t) => builder.token(t.kind().into(), t.text().clone()),
268                }
269            }
270            builder.finish_node();
271        } else {
272            add_element(ele, builder);
273        }
274    }
275
276    builder.finish_node();
277}
278
279fn sort_items(node: SyntaxNode) -> Vec<(bool, SyntaxElement)> {
280    // node is TomlKind::Array
281    let children = node
282        .children_with_tokens()
283        .filter(|n| {
284            let n = n.as_token().map(|n| n.kind());
285            n != Some(TomlKind::CloseBrace) && n != Some(TomlKind::OpenBrace)
286        })
287        .collect::<Vec<_>>();
288
289    let pos = children
290        .iter()
291        .enumerate()
292        .filter(|(_, n)| n.as_node().map(|n| n.kind()) == Some(TomlKind::ArrayItem))
293        .map(|(i, n)| {
294            (
295                i,
296                n.as_node()
297                    .unwrap()
298                    .children()
299                    .find(|n| n.kind() == TomlKind::Value)
300                    .map(|n| n.token_text()),
301            )
302        })
303        .collect::<Vec<_>>();
304
305    let mut sorted = Vec::default();
306    let mut current = 0;
307    for (idx, key) in pos {
308        let next_is_whitespace = children
309            .get(idx + 1)
310            .map(|el| el.as_token().map(|t| t.kind()) == Some(TomlKind::Whitespace))
311            == Some(true);
312
313        let idx = if next_is_whitespace { idx + 1 } else { idx };
314        sorted.push((key, &children[current..=idx]));
315        current = idx + 1;
316    }
317    if current != children.len() {
318        sorted.push((None, &children[current..]))
319    }
320    sorted.sort_by(|chunk, other| {
321        if chunk.0.is_none() {
322            return Ordering::Equal;
323        }
324        if other.0.is_none() {
325            return Ordering::Equal;
326        }
327        chunk.0.cmp(&other.0)
328    });
329    let end = sorted.len() - 1;
330    sorted
331        .into_iter()
332        .flat_map(|p| p.1)
333        .cloned()
334        .enumerate()
335        .map(|(i, el)| (i == end, el))
336        .collect()
337}
338
339fn add_node(node: &SyntaxNode, builder: &mut GreenNodeBuilder) {
340    builder.start_node(node.kind().into());
341
342    for kid in node.children_with_tokens() {
343        match kid {
344            SyntaxElement::Node(n) => add_node(&n, builder),
345            SyntaxElement::Token(t) => builder.token(t.kind().into(), t.text().clone()),
346        }
347    }
348
349    builder.finish_node();
350}
351
352// TODO This for now alters the tokens, it checks if each element has a comma and space
353// and removes it from the last element if it has comma and space ????
354fn add_array_items(node: SyntaxElement, builder: &mut GreenNodeBuilder, end: bool) {
355    match node {
356        SyntaxElement::Node(node) => {
357            if node.kind() == TomlKind::ArrayItem && !end && !node.token_text().contains("\n ") {
358                match node
359                    .children_with_tokens()
360                    .map(|el| el.kind())
361                    .collect::<Vec<TomlKind>>()
362                    .as_slice()
363                {
364                    [.., TomlKind::Comma, TomlKind::Whitespace] => {
365                        // this is a normal ArrayItem with comma and space
366                        builder.start_node(node.kind().into());
367                        for kid in node.children_with_tokens() {
368                            match kid {
369                                SyntaxElement::Node(n) => add_node(&n, builder),
370                                SyntaxElement::Token(t) => {
371                                    builder.token(t.kind().into(), t.text().clone())
372                                }
373                            }
374                        }
375                        builder.finish_node();
376                    }
377                    [.., _, _] | [_] | [] => {
378                        // these have no comma or space and aren't the last ele so add comma...
379                        builder.start_node(node.kind().into());
380                        for kid in node.children_with_tokens() {
381                            match kid {
382                                SyntaxElement::Node(n) => add_node(&n, builder),
383                                SyntaxElement::Token(t) => {
384                                    builder.token(t.kind().into(), t.text().clone())
385                                }
386                            }
387                        }
388                        builder.token(TomlKind::Comma.into(), rowan::SmolStr::from(","));
389                        builder.token(TomlKind::Whitespace.into(), rowan::SmolStr::from(" "));
390                        builder.finish_node();
391                    }
392                }
393            } else if end && !node.token_text().contains("\n ") {
394                // removes last comma
395                builder.start_node(node.kind().into());
396                for kid in node.children_with_tokens() {
397                    match kid {
398                        SyntaxElement::Node(n) => add_node(&n, builder),
399                        SyntaxElement::Token(t) => {
400                            if t.kind() == TomlKind::Comma {
401                                builder.finish_node();
402                                return;
403                            }
404                            builder.token(t.kind().into(), t.text().clone())
405                        }
406                    }
407                }
408                builder.finish_node();
409            } else {
410                // we dont care what sequence of tokens are here just add em
411                builder.start_node(node.kind().into());
412                for kid in node.children_with_tokens() {
413                    match kid {
414                        SyntaxElement::Node(n) => add_node(&n, builder),
415                        SyntaxElement::Token(t) => builder.token(t.kind().into(), t.text().clone()),
416                    }
417                }
418                builder.finish_node();
419            }
420        }
421        SyntaxElement::Token(t) => builder.token(t.kind().into(), t.text().clone()),
422    }
423}
424
425fn add_element(node: SyntaxElement, builder: &mut GreenNodeBuilder) {
426    match node {
427        SyntaxElement::Node(node) => {
428            builder.start_node(node.kind().into());
429            for kid in node.children_with_tokens() {
430                match kid {
431                    SyntaxElement::Node(n) => add_node(&n, builder),
432                    SyntaxElement::Token(t) => builder.token(t.kind().into(), t.text().clone()),
433                }
434            }
435            builder.finish_node();
436        }
437        SyntaxElement::Token(t) => builder.token(t.kind().into(), t.text().clone()),
438    }
439}