normalize_languages/
clojure.rs1use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
4use tree_sitter::Node;
5
6pub struct Clojure;
8
9impl Language for Clojure {
10 fn name(&self) -> &'static str {
11 "Clojure"
12 }
13 fn extensions(&self) -> &'static [&'static str] {
14 &["clj", "cljs", "cljc", "edn"]
15 }
16 fn grammar_name(&self) -> &'static str {
17 "clojure"
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_lit" {
26 return Vec::new();
27 }
28
29 let (form, _) = match self.extract_def_form(node, content) {
30 Some(info) => info,
31 None => return Vec::new(),
32 };
33
34 if form != "require" && form != "use" && form != "import" {
35 return Vec::new();
36 }
37
38 vec![Import {
40 module: form,
41 names: Vec::new(),
42 alias: None,
43 is_wildcard: false,
44 is_relative: false,
45 line: node.start_position().row + 1,
46 }]
47 }
48
49 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
50 let names_to_use: Vec<&str> = names
52 .map(|n| n.to_vec())
53 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
54 if names_to_use.is_empty() {
55 format!("(require '[{}])", import.module)
56 } else {
57 format!(
58 "(require '[{} :refer [{}]])",
59 import.module,
60 names_to_use.join(" ")
61 )
62 }
63 }
64
65 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
66 if let Some((form, _)) = self.extract_def_form(node, content) {
67 if form.ends_with('-') {
68 Visibility::Private
69 } else {
70 Visibility::Public
71 }
72 } else {
73 Visibility::Public
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 test_file_globs(&self) -> &'static [&'static str] {
87 &["**/*_test.clj", "**/*_test.cljs", "**/*_test.cljc"]
88 }
89
90 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
91 Some(*node)
93 }
94 fn analyze_container_body(
95 &self,
96 body_node: &Node,
97 content: &str,
98 inner_indent: &str,
99 ) -> Option<ContainerBody> {
100 crate::body::analyze_paren_body(body_node, content, inner_indent)
101 }
102
103 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
104 if node.kind() != "list_lit" {
107 return node
108 .child_by_field_name("name")
109 .map(|n| &content[n.byte_range()]);
110 }
111 let mut cursor = node.walk();
112 let mut seen_form = false;
113 for child in node.children(&mut cursor) {
114 if child.kind() == "sym_lit" {
115 if !seen_form {
116 seen_form = true;
117 } else {
118 return Some(&content[child.byte_range()]);
119 }
120 }
121 }
122 None
123 }
124}
125
126impl LanguageSymbols for Clojure {}
127
128impl Clojure {
129 fn extract_def_form(&self, node: &Node, content: &str) -> Option<(String, String)> {
131 let mut cursor = node.walk();
132 let mut form = None;
133 let mut name = None;
134
135 for child in node.children(&mut cursor) {
136 match child.kind() {
137 "sym_lit" if form.is_none() => {
138 form = Some(content[child.byte_range()].to_string());
139 }
140 "sym_lit" if form.is_some() && name.is_none() => {
141 name = Some(content[child.byte_range()].to_string());
142 break;
143 }
144 _ => {}
145 }
146 }
147
148 Some((form?, name?))
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::validate_unused_kinds_audit;
156
157 #[test]
158 fn unused_node_kinds_audit() {
159 #[rustfmt::skip]
160 let documented_unused: &[&str] = &[];
161 validate_unused_kinds_audit(&Clojure, documented_unused)
162 .expect("Clojure unused node kinds audit failed");
163 }
164}