Skip to main content

normalize_languages/
toml.rs

1//! TOML language support.
2
3use crate::{Language, LanguageSymbols};
4use tree_sitter::Node;
5
6/// TOML language support.
7pub struct Toml;
8
9impl Language for Toml {
10    fn name(&self) -> &'static str {
11        "TOML"
12    }
13    fn extensions(&self) -> &'static [&'static str] {
14        &["toml"]
15    }
16    fn grammar_name(&self) -> &'static str {
17        "toml"
18    }
19
20    fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
21        Some(self)
22    }
23
24    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
25        match node.kind() {
26            "table" | "array_table" | "table_array_element" => {
27                // First bare_key child is the section name
28                let mut cursor = node.walk();
29                for child in node.children(&mut cursor) {
30                    if child.kind() == "bare_key" || child.kind() == "quoted_key" {
31                        return Some(&content[child.byte_range()]);
32                    }
33                }
34                None
35            }
36            "pair" => {
37                // Skip pairs inside inline_table (they appear as noise siblings)
38                if is_inside_inline_table(node) {
39                    return None;
40                }
41                let mut cursor = node.walk();
42                for child in node.children(&mut cursor) {
43                    if child.kind() == "bare_key" || child.kind() == "quoted_key" {
44                        return Some(&content[child.byte_range()]);
45                    }
46                }
47                None
48            }
49            _ => None,
50        }
51    }
52
53    fn build_signature(&self, node: &Node, content: &str) -> String {
54        if let Some(key) = self.node_name(node, content) {
55            match node.kind() {
56                "table" | "array_table" | "table_array_element" => {
57                    let brackets = if node.kind() == "table_array_element" {
58                        ("[[", "]]")
59                    } else {
60                        ("[", "]")
61                    };
62                    return format!("{}{}{}", brackets.0, key, brackets.1);
63                }
64                "pair" => {
65                    // Find value child (after the = sign)
66                    let mut cursor = node.walk();
67                    for child in node.children(&mut cursor) {
68                        let k = child.kind();
69                        if k != "bare_key" && k != "quoted_key" && k != "=" {
70                            let val_text = &content[child.byte_range()];
71                            if val_text.len() > 40 {
72                                return format!("{} = {}…", key, &val_text[..37]);
73                            }
74                            return format!("{} = {}", key, val_text);
75                        }
76                    }
77                    return key.to_string();
78                }
79                _ => {}
80            }
81        }
82        content[node.byte_range()]
83            .lines()
84            .next()
85            .unwrap_or("")
86            .trim()
87            .to_string()
88    }
89}
90
91impl LanguageSymbols for Toml {}
92
93/// Check if a node is inside an inline_table by walking up the parent chain.
94fn is_inside_inline_table(node: &Node) -> bool {
95    let mut current = node.parent();
96    while let Some(n) = current {
97        if n.kind() == "inline_table" {
98            return true;
99        }
100        current = n.parent();
101    }
102    false
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::validate_unused_kinds_audit;
109
110    #[test]
111    fn unused_node_kinds_audit() {
112        // TOML has no "interesting" unused kinds matching our patterns
113        let documented_unused: &[&str] = &[];
114        validate_unused_kinds_audit(&Toml, documented_unused)
115            .expect("TOML unused node kinds audit failed");
116    }
117}