Skip to main content

normalize_languages/
scheme.rs

1//! Scheme language support.
2
3use crate::{ContainerBody, Import, Language, LanguageSymbols};
4use tree_sitter::Node;
5
6/// Scheme language support.
7pub struct Scheme;
8
9impl Language for Scheme {
10    fn name(&self) -> &'static str {
11        "Scheme"
12    }
13    fn extensions(&self) -> &'static [&'static str] {
14        &["scm", "ss", "rkt"]
15    }
16    fn grammar_name(&self) -> &'static str {
17        "scheme"
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        for prefix in &["(import ", "(require "] {
33            if text.starts_with(prefix) {
34                return vec![Import {
35                    module: "import".to_string(),
36                    names: Vec::new(),
37                    alias: None,
38                    is_wildcard: false,
39                    is_relative: false,
40                    line,
41                }];
42            }
43        }
44
45        Vec::new()
46    }
47
48    fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
49        // Scheme: (import (library)) or (import (only (library) a b c))
50        let names_to_use: Vec<&str> = names
51            .map(|n| n.to_vec())
52            .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
53        if names_to_use.is_empty() {
54            format!("(import ({}))", import.module)
55        } else {
56            format!(
57                "(import (only ({}) {}))",
58                import.module,
59                names_to_use.join(" ")
60            )
61        }
62    }
63
64    fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
65        let name = symbol.name.as_str();
66        match symbol.kind {
67            crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
68            crate::SymbolKind::Module => name == "tests" || name == "test",
69            _ => false,
70        }
71    }
72
73    fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
74        // list is itself "( ... )" — use node directly for paren analysis
75        Some(*node)
76    }
77    fn analyze_container_body(
78        &self,
79        body_node: &Node,
80        content: &str,
81        inner_indent: &str,
82    ) -> Option<ContainerBody> {
83        crate::body::analyze_paren_body(body_node, content, inner_indent)
84    }
85
86    fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
87        // The @definition.* captures a list for (define ...) forms.
88        // Two cases:
89        // 1. (define (name args) body) — second named-child is a list; its first
90        //    symbol child is the function name.
91        // 2. (define name ...) — second named-child is a symbol (the name directly).
92        if node.kind() != "list" {
93            return node
94                .child_by_field_name("name")
95                .map(|n| &content[n.byte_range()]);
96        }
97        let mut cursor = node.walk();
98        let mut seen_define = false;
99        for child in node.children(&mut cursor) {
100            match child.kind() {
101                "symbol" if !seen_define => {
102                    // First symbol is the form keyword (define, define-syntax, etc.)
103                    seen_define = true;
104                }
105                "symbol" if seen_define => {
106                    // Second symbol: (define name ...)
107                    return Some(&content[child.byte_range()]);
108                }
109                "list" if seen_define => {
110                    // Second child is a nested list: (define (name args) ...) form
111                    // The name is the first symbol inside this nested list.
112                    let mut inner_cursor = child.walk();
113                    for inner in child.children(&mut inner_cursor) {
114                        if inner.kind() == "symbol" {
115                            return Some(&content[inner.byte_range()]);
116                        }
117                    }
118                    return None;
119                }
120                _ => {}
121            }
122        }
123        None
124    }
125}
126
127impl LanguageSymbols for Scheme {}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::validate_unused_kinds_audit;
133
134    #[test]
135    fn unused_node_kinds_audit() {
136        #[rustfmt::skip]
137        let documented_unused: &[&str] = &[
138            "block_comment",
139        ];
140        validate_unused_kinds_audit(&Scheme, documented_unused)
141            .expect("Scheme unused node kinds audit failed");
142    }
143}