1use crate::external_packages::ResolvedPackage;
4use crate::{Export, Import, Language, Symbol, SymbolKind, Visibility, VisibilityMechanism};
5use std::path::{Path, PathBuf};
6use tree_sitter::Node;
7
8pub struct Lean;
10
11impl Language for Lean {
12 fn name(&self) -> &'static str {
13 "Lean"
14 }
15 fn extensions(&self) -> &'static [&'static str] {
16 &["lean"]
17 }
18 fn grammar_name(&self) -> &'static str {
19 "lean"
20 }
21
22 fn has_symbols(&self) -> bool {
23 true
24 }
25
26 fn container_kinds(&self) -> &'static [&'static str] {
27 &["structure", "inductive", "class", "namespace"]
28 }
29
30 fn function_kinds(&self) -> &'static [&'static str] {
31 &["def", "theorem", "constant", "axiom", "example"]
32 }
33
34 fn type_kinds(&self) -> &'static [&'static str] {
35 &["abbrev"]
36 }
37
38 fn import_kinds(&self) -> &'static [&'static str] {
39 &["import"]
40 }
41
42 fn public_symbol_kinds(&self) -> &'static [&'static str] {
43 &["def", "theorem", "structure", "inductive", "instance"]
44 }
45
46 fn visibility_mechanism(&self) -> VisibilityMechanism {
47 VisibilityMechanism::AccessModifier
48 }
49
50 fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
51 match node.kind() {
52 "def" | "theorem" | "constant" | "axiom" | "example" => {
53 if let Some(name) = self.node_name(node, content) {
54 return vec![Export {
55 name: name.to_string(),
56 kind: SymbolKind::Function,
57 line: node.start_position().row + 1,
58 }];
59 }
60 }
61 "structure" | "inductive" | "class" => {
62 if let Some(name) = self.node_name(node, content) {
63 return vec![Export {
64 name: name.to_string(),
65 kind: SymbolKind::Type,
66 line: node.start_position().row + 1,
67 }];
68 }
69 }
70 _ => {}
71 }
72 Vec::new()
73 }
74
75 fn scope_creating_kinds(&self) -> &'static [&'static str] {
76 &["namespace", "section", "def", "theorem", "where_decl"]
77 }
78
79 fn control_flow_kinds(&self) -> &'static [&'static str] {
80 &["if_then_else", "match"]
81 }
82
83 fn complexity_nodes(&self) -> &'static [&'static str] {
84 &["if_then_else", "match", "match_alt"]
85 }
86
87 fn nesting_nodes(&self) -> &'static [&'static str] {
88 &["if_then_else", "match", "do", "namespace", "section"]
89 }
90
91 fn signature_suffix(&self) -> &'static str {
92 ""
93 }
94
95 fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
96 match node.kind() {
97 "def" | "theorem" | "constant" | "axiom" | "example" => {
98 let name = self.node_name(node, content)?;
99 let text = &content[node.byte_range()];
100 let first_line = text.lines().next().unwrap_or(text);
101
102 Some(Symbol {
103 name: name.to_string(),
104 kind: SymbolKind::Function,
105 signature: first_line.trim().to_string(),
106 docstring: None,
107 attributes: Vec::new(),
108 start_line: node.start_position().row + 1,
109 end_line: node.end_position().row + 1,
110 visibility: self.get_visibility(node, content),
111 children: Vec::new(),
112 is_interface_impl: false,
113 implements: Vec::new(),
114 })
115 }
116 _ => None,
117 }
118 }
119
120 fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
121 match node.kind() {
122 "structure" | "inductive" | "class" | "namespace" => {
123 let name = self.node_name(node, content)?;
124 let text = &content[node.byte_range()];
125 let first_line = text.lines().next().unwrap_or(text);
126
127 Some(Symbol {
128 name: name.to_string(),
129 kind: SymbolKind::Type,
130 signature: first_line.trim().to_string(),
131 docstring: None,
132 attributes: Vec::new(),
133 start_line: node.start_position().row + 1,
134 end_line: node.end_position().row + 1,
135 visibility: self.get_visibility(node, content),
136 children: Vec::new(),
137 is_interface_impl: false,
138 implements: Vec::new(),
139 })
140 }
141 _ => None,
142 }
143 }
144
145 fn extract_type(&self, node: &Node, content: &str) -> Option<Symbol> {
146 match node.kind() {
147 "abbrev" => {
148 let name = self.node_name(node, content)?;
149 let text = &content[node.byte_range()];
150 let first_line = text.lines().next().unwrap_or(text);
151
152 Some(Symbol {
153 name: name.to_string(),
154 kind: SymbolKind::Type,
155 signature: first_line.trim().to_string(),
156 docstring: None,
157 attributes: Vec::new(),
158 start_line: node.start_position().row + 1,
159 end_line: node.end_position().row + 1,
160 visibility: self.get_visibility(node, content),
161 children: Vec::new(),
162 is_interface_impl: false,
163 implements: Vec::new(),
164 })
165 }
166 _ => None,
167 }
168 }
169
170 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
171 None
172 }
173
174 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
175 Vec::new()
176 }
177
178 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
179 if node.kind() != "import" {
180 return Vec::new();
181 }
182
183 let text = &content[node.byte_range()];
184 vec![Import {
185 module: text.trim().to_string(),
186 names: Vec::new(),
187 alias: None,
188 is_wildcard: false,
189 is_relative: false,
190 line: node.start_position().row + 1,
191 }]
192 }
193
194 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
195 let names_to_use: Vec<&str> = names
197 .map(|n| n.to_vec())
198 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
199 if names_to_use.is_empty() {
200 format!("import {}", import.module)
201 } else {
202 format!("open {} ({})", import.module, names_to_use.join(", "))
203 }
204 }
205
206 fn is_public(&self, node: &Node, content: &str) -> bool {
207 let text = &content[node.byte_range()];
208 !text.contains("private") && !text.contains("protected")
209 }
210
211 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
212 let text = &content[node.byte_range()];
213 if text.contains("private") {
214 Visibility::Private
215 } else if text.contains("protected") {
216 Visibility::Protected
217 } else {
218 Visibility::Public
219 }
220 }
221
222 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
223 let name = symbol.name.as_str();
224 match symbol.kind {
225 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
226 crate::SymbolKind::Module => name == "tests" || name == "test",
227 _ => false,
228 }
229 }
230
231 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
232 None
233 }
234
235 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
236 node.child_by_field_name("body")
237 }
238
239 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
240 false
241 }
242
243 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
244 node.child_by_field_name("name")
245 .map(|n| &content[n.byte_range()])
246 }
247
248 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
249 let ext = path.extension()?.to_str()?;
250 if ext != "lean" {
251 return None;
252 }
253 let stem = path.file_stem()?.to_str()?;
254 Some(stem.to_string())
255 }
256
257 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
258 vec![format!("{}.lean", module)]
259 }
260
261 fn lang_key(&self) -> &'static str {
262 "lean"
263 }
264
265 fn is_stdlib_import(&self, import_name: &str, _project_root: &Path) -> bool {
266 import_name.starts_with("Lean.")
267 || import_name.starts_with("Init.")
268 || import_name.starts_with("Std.")
269 }
270
271 fn find_stdlib(&self, _project_root: &Path) -> Option<PathBuf> {
272 None
273 }
274 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
275 None
276 }
277 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
278 None
279 }
280 fn get_version(&self, _: &Path) -> Option<String> {
281 None
282 }
283 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
284 None
285 }
286 fn indexable_extensions(&self) -> &'static [&'static str] {
287 &["lean"]
288 }
289 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
290 Vec::new()
291 }
292
293 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
294 use crate::traits::{has_extension, skip_dotfiles};
295 if skip_dotfiles(name) {
296 return true;
297 }
298 !is_dir && !has_extension(name, self.indexable_extensions())
299 }
300
301 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
302 Vec::new()
303 }
304
305 fn package_module_name(&self, entry_name: &str) -> String {
306 entry_name
307 .strip_suffix(".lean")
308 .unwrap_or(entry_name)
309 .to_string()
310 }
311
312 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
313 if path.is_file() {
314 Some(path.to_path_buf())
315 } else {
316 None
317 }
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324 use crate::validate_unused_kinds_audit;
325
326 #[test]
327 fn unused_node_kinds_audit() {
328 #[rustfmt::skip]
329 let documented_unused: &[&str] = &[
330 "for_in", "binary_expression", "unary_expression",
332 "type_ascription", "forall", "subtype", "structure_instance",
333 "anonymous_constructor", "lift_method",
334 "constructor", "declaration", "identifier",
336 "module", "export",
338 "do_return",
340 "class_inductive",
342 ];
343 validate_unused_kinds_audit(&Lean, documented_unused)
344 .expect("Lean unused node kinds audit failed");
345 }
346}