normalize_languages/
elm.rs1use crate::{Import, Language, LanguageSymbols};
4use tree_sitter::Node;
5
6pub struct Elm;
8
9impl Language for Elm {
10 fn name(&self) -> &'static str {
11 "Elm"
12 }
13 fn extensions(&self) -> &'static [&'static str] {
14 &["elm"]
15 }
16 fn grammar_name(&self) -> &'static str {
17 "elm"
18 }
19
20 fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
21 Some(self)
22 }
23
24 fn node_name<'a>(&self, node: &Node, content: &'a str) -> Option<&'a str> {
25 if node.kind() == "value_declaration" {
27 let mut cursor = node.walk();
28 for child in node.children(&mut cursor) {
29 if child.kind() == "function_declaration_left" {
30 let mut inner = child.walk();
31 for grandchild in child.children(&mut inner) {
32 if grandchild.kind() == "lower_case_identifier" {
33 return Some(&content[grandchild.byte_range()]);
34 }
35 }
36 }
37 }
38 return None;
39 }
40 let mut cursor = node.walk();
42 for child in node.children(&mut cursor) {
43 if child.kind() == "upper_case_identifier" || child.kind() == "lower_case_identifier" {
44 return Some(&content[child.byte_range()]);
45 }
46 }
47 None
48 }
49
50 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
51 if node.kind() != "import_clause" {
52 return Vec::new();
53 }
54
55 let text = &content[node.byte_range()];
56 let line = node.start_position().row + 1;
57
58 if let Some(rest) = text.strip_prefix("import ") {
60 let parts: Vec<&str> = rest.split_whitespace().collect();
61 if let Some(&module) = parts.first() {
62 let alias = parts
63 .iter()
64 .position(|&p| p == "as")
65 .and_then(|i| parts.get(i + 1))
66 .map(|s| s.to_string());
67
68 return vec![Import {
69 module: module.to_string(),
70 names: Vec::new(),
71 alias,
72 is_wildcard: text.contains("exposing (..)"),
73 is_relative: false,
74 line,
75 }];
76 }
77 }
78
79 Vec::new()
80 }
81
82 fn format_import(&self, import: &Import, names: Option<&[&str]>) -> String {
83 let names_to_use: Vec<&str> = names
85 .map(|n| n.to_vec())
86 .unwrap_or_else(|| import.names.iter().map(|s| s.as_str()).collect());
87 if import.is_wildcard {
88 format!("import {} exposing (..)", import.module)
89 } else if names_to_use.is_empty() {
90 format!("import {}", import.module)
91 } else {
92 format!(
93 "import {} exposing ({})",
94 import.module,
95 names_to_use.join(", ")
96 )
97 }
98 }
99
100 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
101 let prev = node.prev_sibling()?;
102
103 if prev.kind() != "block_comment" {
104 return None;
105 }
106
107 let text = &content[prev.byte_range()];
108 let inner = text.strip_prefix("{-|")?;
110 let inner = inner.strip_suffix("-}").unwrap_or(inner).trim().to_string();
111 if inner.is_empty() { None } else { Some(inner) }
112 }
113
114 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
115 let name = symbol.name.as_str();
116 match symbol.kind {
117 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
118 crate::SymbolKind::Module => name == "tests" || name == "test",
119 _ => false,
120 }
121 }
122}
123
124impl LanguageSymbols for Elm {}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::validate_unused_kinds_audit;
130
131 #[test]
132 fn unused_node_kinds_audit() {
133 #[rustfmt::skip]
134 let documented_unused: &[&str] = &[
135 "as_clause", "block_comment", "case", "exposed_operator", "exposed_type",
136 "exposed_union_constructors", "field_accessor_function_expr", "field_type",
137 "function_call_expr", "import", "infix_declaration",
138 "lower_type_name", "module", "nullary_constructor_argument_pattern",
139 "operator", "operator_as_function_expr", "operator_identifier",
140 "record_base_identifier", "record_type", "tuple_type", "type",
141 "type_annotation", "type_expression", "type_ref", "type_variable",
142 "upper_case_qid",
143 "if_else_expr",
145 "import_clause",
146 "anonymous_function_expr",
147 "module_declaration",
148 "case_of_expr",
149 "case_of_branch",
150 ];
151 validate_unused_kinds_audit(&Elm, documented_unused)
152 .expect("Elm unused node kinds audit failed");
153 }
154}