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 TlaPlus;
10
11impl Language for TlaPlus {
12 fn name(&self) -> &'static str {
13 "TLA+"
14 }
15 fn extensions(&self) -> &'static [&'static str] {
16 &["tla"]
17 }
18 fn grammar_name(&self) -> &'static str {
19 "tlaplus"
20 }
21
22 fn has_symbols(&self) -> bool {
23 true
24 }
25
26 fn container_kinds(&self) -> &'static [&'static str] {
27 &["module"]
28 }
29
30 fn function_kinds(&self) -> &'static [&'static str] {
31 &["operator_definition"]
32 }
33
34 fn type_kinds(&self) -> &'static [&'static str] {
35 &[]
36 }
37
38 fn import_kinds(&self) -> &'static [&'static str] {
39 &["extends"]
40 }
41
42 fn public_symbol_kinds(&self) -> &'static [&'static str] {
43 &["module", "operator_definition"]
44 }
45
46 fn visibility_mechanism(&self) -> VisibilityMechanism {
47 VisibilityMechanism::AllPublic
48 }
49
50 fn extract_public_symbols(&self, node: &Node, content: &str) -> Vec<Export> {
51 let kind = match node.kind() {
52 "module" => SymbolKind::Module,
53 "operator_definition" => SymbolKind::Function,
54 _ => return Vec::new(),
55 };
56
57 if let Some(name) = self.node_name(node, content) {
58 return vec![Export {
59 name: name.to_string(),
60 kind,
61 line: node.start_position().row + 1,
62 }];
63 }
64 Vec::new()
65 }
66
67 fn scope_creating_kinds(&self) -> &'static [&'static str] {
68 &["module", "operator_definition"]
69 }
70
71 fn control_flow_kinds(&self) -> &'static [&'static str] {
72 &["if_then_else"]
73 }
74
75 fn complexity_nodes(&self) -> &'static [&'static str] {
76 &["if_then_else", "case"]
77 }
78
79 fn nesting_nodes(&self) -> &'static [&'static str] {
80 &["module"]
81 }
82
83 fn signature_suffix(&self) -> &'static str {
84 ""
85 }
86
87 fn extract_function(&self, node: &Node, content: &str, _in_container: bool) -> Option<Symbol> {
88 if node.kind() != "operator_definition" {
89 return None;
90 }
91
92 let name = self.node_name(node, content)?;
93 let text = &content[node.byte_range()];
94 let first_line = text.lines().next().unwrap_or(text);
95
96 Some(Symbol {
97 name: name.to_string(),
98 kind: SymbolKind::Function,
99 signature: first_line.trim().to_string(),
100 docstring: None,
101 attributes: Vec::new(),
102 start_line: node.start_position().row + 1,
103 end_line: node.end_position().row + 1,
104 visibility: Visibility::Public,
105 children: Vec::new(),
106 is_interface_impl: false,
107 implements: Vec::new(),
108 })
109 }
110
111 fn extract_container(&self, node: &Node, content: &str) -> Option<Symbol> {
112 if node.kind() != "module" {
113 return None;
114 }
115
116 let name = self.node_name(node, content)?;
117 let text = &content[node.byte_range()];
118 let first_line = text.lines().next().unwrap_or(text);
119
120 Some(Symbol {
121 name: name.to_string(),
122 kind: SymbolKind::Module,
123 signature: first_line.trim().to_string(),
124 docstring: None,
125 attributes: Vec::new(),
126 start_line: node.start_position().row + 1,
127 end_line: node.end_position().row + 1,
128 visibility: Visibility::Public,
129 children: Vec::new(),
130 is_interface_impl: false,
131 implements: Vec::new(),
132 })
133 }
134
135 fn extract_type(&self, _node: &Node, _content: &str) -> Option<Symbol> {
136 None
137 }
138 fn extract_docstring(&self, _node: &Node, _content: &str) -> Option<String> {
139 None
140 }
141
142 fn extract_attributes(&self, _node: &Node, _content: &str) -> Vec<String> {
143 Vec::new()
144 }
145
146 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
147 if node.kind() != "extends" {
148 return Vec::new();
149 }
150
151 let text = &content[node.byte_range()];
152 vec![Import {
153 module: text.trim().to_string(),
154 names: Vec::new(),
155 alias: None,
156 is_wildcard: false,
157 is_relative: false,
158 line: node.start_position().row + 1,
159 }]
160 }
161
162 fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
163 format!("EXTENDS {}", import.module)
165 }
166
167 fn is_public(&self, _node: &Node, _content: &str) -> bool {
168 true
169 }
170 fn get_visibility(&self, _node: &Node, _content: &str) -> Visibility {
171 Visibility::Public
172 }
173
174 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
175 let name = symbol.name.as_str();
176 match symbol.kind {
177 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
178 crate::SymbolKind::Module => name == "tests" || name == "test",
179 _ => false,
180 }
181 }
182
183 fn embedded_content(&self, _node: &Node, _content: &str) -> Option<crate::EmbeddedBlock> {
184 None
185 }
186
187 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
188 node.child_by_field_name("body")
189 }
190
191 fn body_has_docstring(&self, _body: &Node, _content: &str) -> bool {
192 false
193 }
194
195 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
196 node.child_by_field_name("name")
197 .map(|n| &content[n.byte_range()])
198 }
199
200 fn file_path_to_module_name(&self, path: &Path) -> Option<String> {
201 let ext = path.extension()?.to_str()?;
202 if ext != "tla" {
203 return None;
204 }
205 let stem = path.file_stem()?.to_str()?;
206 Some(stem.to_string())
207 }
208
209 fn module_name_to_paths(&self, module: &str) -> Vec<String> {
210 vec![format!("{}.tla", module)]
211 }
212
213 fn lang_key(&self) -> &'static str {
214 "tlaplus"
215 }
216
217 fn is_stdlib_import(&self, _: &str, _: &Path) -> bool {
218 false
219 }
220 fn find_stdlib(&self, _: &Path) -> Option<PathBuf> {
221 None
222 }
223 fn resolve_local_import(&self, _: &str, _: &Path, _: &Path) -> Option<PathBuf> {
224 None
225 }
226 fn resolve_external_import(&self, _: &str, _: &Path) -> Option<ResolvedPackage> {
227 None
228 }
229 fn get_version(&self, _: &Path) -> Option<String> {
230 None
231 }
232 fn find_package_cache(&self, _: &Path) -> Option<PathBuf> {
233 None
234 }
235 fn indexable_extensions(&self) -> &'static [&'static str] {
236 &["tla"]
237 }
238 fn package_sources(&self, _: &Path) -> Vec<crate::PackageSource> {
239 Vec::new()
240 }
241
242 fn should_skip_package_entry(&self, name: &str, is_dir: bool) -> bool {
243 use crate::traits::{has_extension, skip_dotfiles};
244 if skip_dotfiles(name) {
245 return true;
246 }
247 !is_dir && !has_extension(name, self.indexable_extensions())
248 }
249
250 fn discover_packages(&self, _: &crate::PackageSource) -> Vec<(String, PathBuf)> {
251 Vec::new()
252 }
253
254 fn package_module_name(&self, entry_name: &str) -> String {
255 entry_name
256 .strip_suffix(".tla")
257 .unwrap_or(entry_name)
258 .to_string()
259 }
260
261 fn find_package_entry(&self, path: &Path) -> Option<PathBuf> {
262 if path.is_file() {
263 Some(path.to_path_buf())
264 } else {
265 None
266 }
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use crate::validate_unused_kinds_audit;
274
275 #[test]
276 fn unused_node_kinds_audit() {
277 #[rustfmt::skip]
278 let documented_unused: &[&str] = &[
279 "constant_declaration", "variable_declaration", "operator_declaration",
281 "local_definition", "function_definition", "module_definition",
282 "recursive_declaration", "operator_args",
283 "definition_proof_step", "case_proof_step", "use_body", "use_body_def",
285 "use_body_expr", "statement_level",
286 "forall", "quantifier_bound", "tuple_of_identifiers",
288 "unbounded_quantification", "bounded_quantification", "temporal_forall",
289 "case_arm", "case_arrow", "case_box",
291 "function_literal", "function_evaluation", "set_of_functions",
293 "except", "except_update", "except_update_specifier",
295 "except_update_fn_appl", "except_update_record_field",
296 "pcal_algorithm_body", "pcal_definitions", "pcal_if", "pcal_end_if",
298 "pcal_while", "pcal_end_while", "pcal_with", "pcal_end_with",
299 "pcal_await", "pcal_return",
300 "identifier", "identifier_ref", "module_ref",
302 "block_comment", "block_comment_text",
304 "lambda", "iff", "format", "subexpression",
306 ];
307 validate_unused_kinds_audit(&TlaPlus, documented_unused)
308 .expect("TLA+ unused node kinds audit failed");
309 }
310}