Skip to main content

normalize_languages/
zig.rs

1//! Zig language support.
2
3use crate::{Import, Language, LanguageSymbols, Visibility};
4use tree_sitter::Node;
5
6/// Zig language support.
7pub struct Zig;
8
9impl Language for Zig {
10    fn name(&self) -> &'static str {
11        "Zig"
12    }
13    fn extensions(&self) -> &'static [&'static str] {
14        &["zig"]
15    }
16    fn grammar_name(&self) -> &'static str {
17        "zig"
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        // Look for @import("module")
26        if node.kind() != "builtin_call_expression" {
27            return Vec::new();
28        }
29
30        let text = &content[node.byte_range()];
31        if !text.starts_with("@import") {
32            return Vec::new();
33        }
34
35        // Extract the string argument
36        let mut cursor = node.walk();
37        for child in node.children(&mut cursor) {
38            if child.kind() == "string_literal" {
39                let module = content[child.byte_range()].trim_matches('"').to_string();
40                let is_relative = module.starts_with('.');
41                return vec![Import {
42                    module,
43                    names: Vec::new(),
44                    alias: None,
45                    is_wildcard: false,
46                    is_relative,
47                    line: node.start_position().row + 1,
48                }];
49            }
50        }
51
52        Vec::new()
53    }
54
55    fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
56        // Zig: @import("module")
57        format!("@import(\"{}\")", import.module)
58    }
59
60    fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
61        // Check for pub keyword before the declaration
62        if let Some(prev) = node.prev_sibling() {
63            let text = &content[prev.byte_range()];
64            if text == "pub" {
65                return Visibility::Public;
66            }
67        }
68        // Also check if node starts with pub
69        let text = &content[node.byte_range()];
70        if text.starts_with("pub ") {
71            Visibility::Public
72        } else {
73            Visibility::Private
74        }
75    }
76
77    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
78        let name = symbol.name.as_str();
79        match symbol.kind {
80            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
81            crate::SymbolKind::Module => name == "tests" || name == "test",
82            _ => false,
83        }
84    }
85
86    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
87        // FnProto uses field "function" for the name identifier.
88        // VarDecl uses field "variable_type_function" for the name identifier.
89        let name_node = node
90            .child_by_field_name("function")
91            .or_else(|| node.child_by_field_name("variable_type_function"))?;
92        Some(&content[name_node.byte_range()])
93    }
94
95    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
96        node.child_by_field_name("body")
97    }
98}
99
100impl LanguageSymbols for Zig {}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::validate_unused_kinds_audit;
106
107    #[test]
108    fn unused_node_kinds_audit() {
109        #[rustfmt::skip]
110        let documented_unused: &[&str] = &[
111            // Zig grammar uses PascalCase node kinds
112            "ArrayTypeStart", "BUILTINIDENTIFIER", "BitShiftOp", "BlockExpr",
113            "BlockExprStatement", "BlockLabel", "BuildinTypeExpr", "ContainerDeclType",
114            "ForArgumentsList", "ForExpr", "ForItem", "ForPrefix", "ForTypeExpr",
115            "FormatSequence", "IDENTIFIER", "IfExpr", "IfPrefix", "IfTypeExpr",
116            "LabeledStatement", "LabeledTypeExpr", "LoopExpr", "LoopStatement",
117            "LoopTypeExpr", "ParamType", "PrefixTypeOp", "PtrTypeStart",
118            "SliceTypeStart", "Statement", "SwitchCase", "WhileContinueExpr",
119            "WhileExpr", "WhilePrefix", "WhileTypeExpr",
120            // control flow — not extracted as symbols
121            "ForStatement",
122            "WhileStatement",
123            "Block",
124            "IfStatement",
125        ];
126        validate_unused_kinds_audit(&Zig, documented_unused)
127            .expect("Zig unused node kinds audit failed");
128    }
129}