normalize_languages/
lua.rs1use crate::docstring::extract_preceding_prefix_comments;
4use crate::{Import, Language, LanguageSymbols, Visibility};
5use tree_sitter::Node;
6
7pub struct Lua;
9
10impl Language for Lua {
11 fn name(&self) -> &'static str {
12 "Lua"
13 }
14 fn extensions(&self) -> &'static [&'static str] {
15 &["lua"]
16 }
17 fn grammar_name(&self) -> &'static str {
18 "lua"
19 }
20
21 fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
22 Some(self)
23 }
24
25 fn signature_suffix(&self) -> &'static str {
26 " end"
27 }
28
29 fn build_signature(&self, node: &Node, content: &str) -> String {
30 let name = match self.node_name(node, content) {
31 Some(n) => n,
32 None => {
33 let text = &content[node.byte_range()];
34 return text.lines().next().unwrap_or(text).trim().to_string();
35 }
36 };
37 let params = node
38 .child_by_field_name("parameters")
39 .map(|p| content[p.byte_range()].to_string())
40 .unwrap_or_else(|| "()".to_string());
41 let text = &content[node.byte_range()];
42 let is_local = text.trim_start().starts_with("local ");
43 let keyword = if is_local {
44 "local function"
45 } else {
46 "function"
47 };
48 format!("{} {}{}", keyword, name, params)
49 }
50
51 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
52 if node.kind() != "function_call" {
54 return Vec::new();
55 }
56
57 let func_name = node
58 .child_by_field_name("name")
59 .map(|n| &content[n.byte_range()]);
60
61 if func_name != Some("require") {
62 return Vec::new();
63 }
64
65 if let Some(args) = node.child_by_field_name("arguments") {
66 let mut cursor = args.walk();
67 for child in args.children(&mut cursor) {
68 if child.kind() == "string" {
69 let module = content[child.byte_range()]
70 .trim_matches(|c| c == '"' || c == '\'' || c == '[' || c == ']')
71 .to_string();
72 return vec![Import {
73 module,
74 names: Vec::new(),
75 alias: None,
76 is_wildcard: false,
77 is_relative: false,
78 line: node.start_position().row + 1,
79 }];
80 }
81 }
82 }
83
84 Vec::new()
85 }
86
87 fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
88 format!("require(\"{}\")", import.module)
90 }
91
92 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
93 let text = &content[node.byte_range()];
94 if text.trim_start().starts_with("local ") {
95 Visibility::Private
96 } else {
97 Visibility::Public
98 }
99 }
100
101 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
102 let name = symbol.name.as_str();
103 match symbol.kind {
104 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
105 crate::SymbolKind::Module => name == "tests" || name == "test",
106 _ => false,
107 }
108 }
109
110 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
111 extract_preceding_prefix_comments(node, content, "---")
113 }
114
115 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
116 node.child_by_field_name("body")
117 }
118}
119
120impl LanguageSymbols for Lua {}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::validate_unused_kinds_audit;
126
127 #[test]
128 fn unused_node_kinds_audit() {
129 #[rustfmt::skip]
130 let documented_unused: &[&str] = &[ "binary_expression", "block",
131 "bracket_index_expression", "else_statement",
132 "empty_statement", "for_generic_clause",
133 "for_numeric_clause", "identifier", "label_statement", "parenthesized_expression", "table_constructor",
134 "unary_expression", "vararg_expression", "variable_declaration",
135 "return_statement",
137 "while_statement",
138 "elseif_statement",
139 "for_statement",
140 "goto_statement",
141 "do_statement",
142 "if_statement",
143 "break_statement",
144 "repeat_statement",
145 "function_call",
146 ];
147 validate_unused_kinds_audit(&Lua, documented_unused)
148 .expect("Lua unused node kinds audit failed");
149 }
150}