Skip to main content

normalize_languages/
elisp.rs

1//! Emacs Lisp language support.
2
3use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
4use tree_sitter::Node;
5
6/// Emacs Lisp language support.
7pub struct Elisp;
8
9impl Language for Elisp {
10    fn name(&self) -> &'static str {
11        "Emacs Lisp"
12    }
13    fn extensions(&self) -> &'static [&'static str] {
14        &["el", "elc"]
15    }
16    fn grammar_name(&self) -> &'static str {
17        "elisp"
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() != "list" {
26            return Vec::new();
27        }
28
29        let text = &content[node.byte_range()];
30        let line = node.start_position().row + 1;
31
32        if let Some(rest) = text.strip_prefix("(require ") {
33            let module = rest
34                .split(|c: char| c.is_whitespace() || c == ')')
35                .next()
36                .map(|s| s.trim_matches('\''))
37                .unwrap_or("")
38                .to_string();
39
40            if !module.is_empty() {
41                return vec![Import {
42                    module,
43                    names: Vec::new(),
44                    alias: None,
45                    is_wildcard: false,
46                    is_relative: false,
47                    line,
48                }];
49            }
50        }
51
52        Vec::new()
53    }
54
55    fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
56        // Emacs Lisp: (require 'package)
57        format!("(require '{})", import.module)
58    }
59
60    fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
61        let text = &content[node.byte_range()];
62        if text.contains("--") {
63            Visibility::Private
64        } else {
65            Visibility::Public
66        }
67    }
68
69    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
70        let name = symbol.name.as_str();
71        match symbol.kind {
72            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
73            crate::SymbolKind::Module => name == "tests" || name == "test",
74            _ => false,
75        }
76    }
77
78    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
79        // list is itself "( ... )" — use node directly for paren analysis
80        Some(*node)
81    }
82    fn analyze_container_body(
83        &self,
84        body_node: &Node,
85        content: &str,
86        inner_indent: &str,
87    ) -> Option<ContainerBody> {
88        crate::body::analyze_paren_body(body_node, content, inner_indent)
89    }
90
91    fn node_name<'a>(&self, _node: &Node, _content: &'a str) -> Option<&'a str> {
92        None
93    }
94}
95
96impl LanguageSymbols for Elisp {}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::validate_unused_kinds_audit;
102
103    #[test]
104    fn unused_node_kinds_audit() {
105        #[rustfmt::skip]
106        let documented_unused: &[&str] = &[];
107        validate_unused_kinds_audit(&Elisp, documented_unused)
108            .expect("Emacs Lisp unused node kinds audit failed");
109    }
110}