Skip to main content

normalize_languages/
yaml.rs

1//! YAML language support.
2
3use crate::{Language, LanguageSymbols};
4use tree_sitter::Node;
5
6/// YAML language support.
7pub struct Yaml;
8
9impl Language for Yaml {
10    fn name(&self) -> &'static str {
11        "YAML"
12    }
13    fn extensions(&self) -> &'static [&'static str] {
14        &["yaml", "yml"]
15    }
16    fn grammar_name(&self) -> &'static str {
17        "yaml"
18    }
19
20    fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
21        Some(self)
22    }
23
24    fn refine_kind(
25        &self,
26        node: &Node,
27        _content: &str,
28        tag_kind: crate::SymbolKind,
29    ) -> crate::SymbolKind {
30        // Pairs with block_mapping values are containers
31        if node.kind() == "block_mapping_pair"
32            && let Some(value) = node.child_by_field_name("value")
33        {
34            // value is either flow_node (scalar) or block_node > block_mapping
35            if value.kind() == "block_node" {
36                let mut cursor = value.walk();
37                for child in value.children(&mut cursor) {
38                    if child.kind() == "block_mapping" {
39                        return crate::SymbolKind::Module;
40                    }
41                }
42            }
43        }
44        tag_kind
45    }
46
47    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
48        if node.kind() == "block_mapping_pair"
49            && let Some(key) = node.child_by_field_name("key")
50        {
51            return find_scalar_text(key, content);
52        }
53        None
54    }
55
56    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
57        if node.kind() == "block_mapping_pair"
58            && let Some(value) = node.child_by_field_name("value")
59            && value.kind() == "block_node"
60        {
61            let mut cursor = value.walk();
62            for child in value.children(&mut cursor) {
63                if child.kind() == "block_mapping" {
64                    return Some(child);
65                }
66            }
67        }
68        None
69    }
70
71    fn build_signature(&self, node: &Node, content: &str) -> String {
72        if let Some(key) = self.node_name(node, content) {
73            if let Some(value) = node.child_by_field_name("value") {
74                if value.kind() == "block_node" {
75                    return format!("{}:", key);
76                }
77                let val_text = content[value.byte_range()].trim();
78                if val_text.len() > 40 {
79                    return format!("{}: {}…", key, &val_text[..37]);
80                }
81                return format!("{}: {}", key, val_text);
82            }
83            return key.to_string();
84        }
85        content[node.byte_range()]
86            .lines()
87            .next()
88            .unwrap_or("")
89            .trim()
90            .to_string()
91    }
92}
93
94impl LanguageSymbols for Yaml {}
95
96/// Walk into nested scalar nodes to find the text content.
97fn find_scalar_text<'a>(node: Node, content: &'a str) -> Option<&'a str> {
98    let kind = node.kind();
99    if kind == "string_scalar" || kind == "string_content" {
100        return Some(&content[node.byte_range()]);
101    }
102    let mut cursor = node.walk();
103    for child in node.children(&mut cursor) {
104        if let Some(text) = find_scalar_text(child, content) {
105            return Some(text);
106        }
107    }
108    None
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use crate::validate_unused_kinds_audit;
115
116    #[test]
117    fn unused_node_kinds_audit() {
118        #[rustfmt::skip]
119        let documented_unused: &[&str] = &[
120            "block_node", "block_scalar",
121            "block_sequence", "block_sequence_item",
122            // structural node, not extracted as symbols
123            "block_mapping",
124        ];
125        validate_unused_kinds_audit(&Yaml, documented_unused)
126            .expect("YAML unused node kinds audit failed");
127    }
128}