Skip to main content

normalize_languages/
cmake.rs

1//! CMake language support.
2
3use crate::{ContainerBody, Import, Language, LanguageSymbols};
4use tree_sitter::Node;
5
6/// CMake language support.
7pub struct CMake;
8
9impl Language for CMake {
10    fn name(&self) -> &'static str {
11        "CMake"
12    }
13    fn extensions(&self) -> &'static [&'static str] {
14        &["cmake"]
15    }
16    fn grammar_name(&self) -> &'static str {
17        "cmake"
18    }
19
20    fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
21        Some(self)
22    }
23
24    fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
25        if node.kind() != "normal_command" {
26            return Vec::new();
27        }
28
29        let text = &content[node.byte_range()];
30        let line = node.start_position().row + 1;
31
32        // include(file), find_package(pkg)
33        if text.starts_with("include(") || text.starts_with("find_package(") {
34            let inner = text
35                .split('(')
36                .nth(1)
37                .and_then(|s| s.split(')').next())
38                .map(|s| s.trim().to_string());
39
40            if let Some(module) = inner {
41                return vec![Import {
42                    module,
43                    names: Vec::new(),
44                    alias: None,
45                    is_wildcard: false,
46                    is_relative: text.starts_with("include("),
47                    line,
48                }];
49            }
50        }
51
52        Vec::new()
53    }
54
55    fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
56        // CMake: include(file) or find_package(pkg)
57        format!("include({})", import.module)
58    }
59
60    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
61        // CMake function_def/macro_def: body is an unnamed child of kind "body"
62        let mut c = node.walk();
63        node.children(&mut c).find(|&child| child.kind() == "body")
64    }
65
66    fn analyze_container_body(
67        &self,
68        body_node: &Node,
69        content: &str,
70        inner_indent: &str,
71    ) -> Option<ContainerBody> {
72        // body node: "\n  message(...)\n  set(...)" — raw statements after opening newline
73        crate::body::analyze_end_body(body_node, content, inner_indent)
74    }
75
76    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
77        // function(name args...) - name is first argument
78        let mut cursor = node.walk();
79        for child in node.children(&mut cursor) {
80            if child.kind() == "argument" {
81                return Some(&content[child.byte_range()]);
82            }
83        }
84        None
85    }
86}
87
88impl LanguageSymbols for CMake {}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::validate_unused_kinds_audit;
94
95    #[test]
96    fn unused_node_kinds_audit() {
97        #[rustfmt::skip]
98        let documented_unused: &[&str] = &[
99            "block", "block_command", "block_def", "body", "else", "else_command",
100            "elseif", "endblock", "endblock_command", "endforeach", "endforeach_command",
101            "endfunction", "endfunction_command", "endif", "endif_command", "endwhile",
102            "endwhile_command", "foreach", "foreach_command", "function",
103            "identifier", "if", "if_command", "while",
104            "while_command",
105            // control flow — not extracted as symbols
106            "if_condition",
107            "foreach_loop",
108            "while_loop",
109            "elseif_command",
110        ];
111        validate_unused_kinds_audit(&CMake, documented_unused)
112            .expect("CMake unused node kinds audit failed");
113    }
114}