normalize_languages/
gleam.rs1use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
4use tree_sitter::Node;
5
6pub struct Gleam;
8
9impl Language for Gleam {
10 fn name(&self) -> &'static str {
11 "Gleam"
12 }
13 fn extensions(&self) -> &'static [&'static str] {
14 &["gleam"]
15 }
16 fn grammar_name(&self) -> &'static str {
17 "gleam"
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() != "import" {
26 return Vec::new();
27 }
28
29 let text = &content[node.byte_range()];
30 let line = node.start_position().row + 1;
31
32 if let Some(rest) = text.strip_prefix("import ") {
34 let module = rest.split_whitespace().next().unwrap_or("").to_string();
35
36 if !module.is_empty() {
37 return vec![Import {
38 module,
39 names: Vec::new(),
40 alias: None,
41 is_wildcard: false,
42 is_relative: false,
43 line,
44 }];
45 }
46 }
47
48 Vec::new()
49 }
50
51 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
52 let names_to_use: Vec<&str> = names
54 .map(|n| n.to_vec())
55 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
56 if names_to_use.is_empty() {
57 format!("import {}", import.module)
58 } else {
59 format!("import {}.{{{}}}", import.module, names_to_use.join(", "))
60 }
61 }
62
63 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
64 let mut doc_lines: Vec<String> = Vec::new();
65 let mut prev = node.prev_sibling();
66
67 while let Some(sibling) = prev {
68 let kind = sibling.kind();
69 if kind == "comment" || kind == "statement_comment" {
70 let text = &content[sibling.byte_range()];
71 if let Some(line) = text.strip_prefix("///") {
73 let line = line.strip_prefix(' ').unwrap_or(line);
74 doc_lines.push(line.to_string());
75 } else {
76 break;
77 }
78 } else {
79 break;
80 }
81 prev = sibling.prev_sibling();
82 }
83
84 if doc_lines.is_empty() {
85 return None;
86 }
87
88 doc_lines.reverse();
89 let joined = doc_lines.join("\n").trim().to_string();
90 if joined.is_empty() {
91 None
92 } else {
93 Some(joined)
94 }
95 }
96
97 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
98 if content[node.byte_range()].starts_with("pub ") {
99 Visibility::Public
100 } else {
101 Visibility::Private
102 }
103 }
104
105 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
106 let name = symbol.name.as_str();
107 match symbol.kind {
108 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
109 crate::SymbolKind::Module => name == "tests" || name == "test",
110 _ => false,
111 }
112 }
113
114 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
115 node.child_by_field_name("body")
116 }
117
118 fn analyze_container_body(
119 &self,
120 body_node: &Node,
121 content: &str,
122 inner_indent: &str,
123 ) -> Option<ContainerBody> {
124 crate::body::analyze_brace_body(body_node, content, inner_indent)
125 }
126}
127
128impl LanguageSymbols for Gleam {}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::validate_unused_kinds_audit;
134
135 #[test]
136 fn unused_node_kinds_audit() {
137 #[rustfmt::skip]
138 let documented_unused: &[&str] = &[
139 "data_constructor", "data_constructor_argument", "data_constructor_arguments",
141 "data_constructors", "external_type", "function_parameter", "function_parameter_types",
142 "function_parameters", "function_type", "opacity_modifier", "remote_type_identifier",
143 "tuple_type", "type", "type_argument", "type_arguments", "type_hole", "type_identifier",
144 "type_name", "type_parameter", "type_parameters", "type_var", "visibility_modifier",
145 "case_clause_guard", "case_clause_pattern", "case_clause_patterns", "case_clauses",
147 "case_subjects",
148 "binary_expression", "constructor_name", "external_function", "external_function_body",
150 "function_call", "remote_constructor_name",
151 "unqualified_import", "unqualified_imports",
153 "identifier", "module", "module_comment", "statement_comment",
155 "block",
157 "import",
158 "anonymous_function",
159 "case_clause",
160 "case",
161 ];
162 validate_unused_kinds_audit(&Gleam, documented_unused)
163 .expect("Gleam unused node kinds audit failed");
164 }
165}