Skip to main content

normalize_languages/
zsh.rs

1//! Zsh language support.
2
3use crate::{Import, Language, LanguageSymbols};
4use tree_sitter::Node;
5
6/// Zsh language support.
7pub struct Zsh;
8
9impl Language for Zsh {
10    fn name(&self) -> &'static str {
11        "Zsh"
12    }
13    fn extensions(&self) -> &'static [&'static str] {
14        &["zsh", "zshrc", "zshenv", "zprofile"]
15    }
16    fn grammar_name(&self) -> &'static str {
17        "zsh"
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() != "command" {
26            return Vec::new();
27        }
28
29        let text = &content[node.byte_range()];
30        let line = node.start_position().row + 1;
31
32        // source file or . file
33        let module = text
34            .strip_prefix("source ")
35            .or_else(|| text.strip_prefix(". "))
36            .map(|rest| rest.trim().to_string());
37
38        if let Some(module) = module {
39            return vec![Import {
40                module,
41                names: Vec::new(),
42                alias: None,
43                is_wildcard: false,
44                is_relative: true,
45                line,
46            }];
47        }
48
49        Vec::new()
50    }
51
52    fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
53        // Zsh: source file or . file
54        format!("source {}", import.module)
55    }
56
57    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
58        let name = symbol.name.as_str();
59        match symbol.kind {
60            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
61            crate::SymbolKind::Module => name == "tests" || name == "test",
62            _ => false,
63        }
64    }
65
66    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
67        node.child_by_field_name("body")
68    }
69}
70
71impl LanguageSymbols for Zsh {}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::validate_unused_kinds_audit;
77
78    #[test]
79    fn unused_node_kinds_audit() {
80        #[rustfmt::skip]
81        let documented_unused: &[&str] = &[
82            "else_clause",
83            // control flow — not extracted as symbols
84            "case_item",
85            "if_statement",
86            "elif_clause",
87            "while_statement",
88            "for_statement",
89            "case_statement",
90        ];
91        validate_unused_kinds_audit(&Zsh, documented_unused)
92            .expect("Zsh unused node kinds audit failed");
93    }
94}