1use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
4use tree_sitter::Node;
5
6pub struct Swift;
8
9impl Swift {
10 fn find_type_identifier(node: &Node, content: &str, out: &mut Vec<String>) {
12 let before = out.len();
13 if node.kind() == "type_identifier" {
14 out.push(content[node.byte_range()].to_string());
15 return;
16 }
17 let mut cursor = node.walk();
18 for child in node.children(&mut cursor) {
19 Self::find_type_identifier(&child, content, out);
20 if out.len() > before {
21 return;
22 }
23 }
24 }
25}
26
27impl Language for Swift {
28 fn name(&self) -> &'static str {
29 "Swift"
30 }
31 fn extensions(&self) -> &'static [&'static str] {
32 &["swift"]
33 }
34 fn grammar_name(&self) -> &'static str {
35 "swift"
36 }
37
38 fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
39 Some(self)
40 }
41
42 fn signature_suffix(&self) -> &'static str {
43 " {}"
44 }
45
46 fn refine_kind(
47 &self,
48 node: &Node,
49 content: &str,
50 tag_kind: crate::SymbolKind,
51 ) -> crate::SymbolKind {
52 if node.kind() == "class_declaration"
55 && let Some(kind_node) = node.child_by_field_name("declaration_kind")
56 {
57 let kind_text = &content[kind_node.byte_range()];
58 return match kind_text {
59 "struct" => crate::SymbolKind::Struct,
60 "enum" => crate::SymbolKind::Enum,
61 "class" | "actor" => crate::SymbolKind::Class,
62 _ => tag_kind,
63 };
64 }
65 tag_kind
66 }
67
68 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
69 let mut doc_lines: Vec<String> = Vec::new();
71 let mut prev = node.prev_sibling();
72
73 while let Some(sibling) = prev {
74 match sibling.kind() {
75 "comment" => {
76 let text = &content[sibling.byte_range()];
77 if text.starts_with("///") {
78 let line = text.strip_prefix("///").unwrap_or("").trim().to_string();
79 doc_lines.push(line);
80 } else {
81 break;
82 }
83 }
84 "multiline_comment" => {
85 let text = &content[sibling.byte_range()];
86 if text.starts_with("/**") {
87 let lines: Vec<&str> = text
88 .strip_prefix("/**")
89 .unwrap_or(text)
90 .strip_suffix("*/")
91 .unwrap_or(text)
92 .lines()
93 .map(|l| l.trim().strip_prefix('*').unwrap_or(l).trim())
94 .filter(|l| !l.is_empty())
95 .collect();
96 if lines.is_empty() {
97 return None;
98 }
99 return Some(lines.join(" "));
100 }
101 break;
102 }
103 "attribute" => {
104 }
106 _ => break,
107 }
108 prev = sibling.prev_sibling();
109 }
110
111 if doc_lines.is_empty() {
112 return None;
113 }
114 doc_lines.reverse();
115 let joined = doc_lines.join(" ");
116 let trimmed = joined.trim().to_string();
117 if trimmed.is_empty() {
118 None
119 } else {
120 Some(trimmed)
121 }
122 }
123
124 fn extract_attributes(&self, node: &Node, content: &str) -> Vec<String> {
125 let mut attrs = Vec::new();
126 if let Some(mods) = node.child_by_field_name("modifiers") {
127 let mut cursor = mods.walk();
128 for child in mods.children(&mut cursor) {
129 if child.kind() == "attribute" {
130 let text = content[child.byte_range()].trim().to_string();
131 if !text.is_empty() {
132 attrs.push(text);
133 }
134 }
135 }
136 }
137 let mut prev = node.prev_sibling();
138 while let Some(sibling) = prev {
139 if sibling.kind() == "attribute" {
140 let text = content[sibling.byte_range()].trim().to_string();
141 if !text.is_empty() {
142 attrs.insert(0, text);
143 }
144 prev = sibling.prev_sibling();
145 } else {
146 break;
147 }
148 }
149 attrs
150 }
151
152 fn extract_implements(&self, node: &Node, content: &str) -> crate::ImplementsInfo {
153 let mut implements = Vec::new();
154 let mut cursor = node.walk();
155 for child in node.children(&mut cursor) {
156 if child.kind() == "inheritance_specifier" {
157 Self::find_type_identifier(&child, content, &mut implements);
158 }
159 }
160 crate::ImplementsInfo {
161 is_interface: false,
162 implements,
163 }
164 }
165
166 fn build_signature(&self, node: &Node, content: &str) -> String {
167 let name = match self.node_name(node, content) {
168 Some(n) => n,
169 None => {
170 return content[node.byte_range()]
171 .lines()
172 .next()
173 .unwrap_or("")
174 .trim()
175 .to_string();
176 }
177 };
178 match node.kind() {
179 "function_declaration" => {
180 let params = node
181 .child_by_field_name("parameters")
182 .map(|p| content[p.byte_range()].to_string())
183 .unwrap_or_else(|| "()".to_string());
184 let return_type = node
185 .child_by_field_name("return_type")
186 .map(|t| format!(" -> {}", content[t.byte_range()].trim()))
187 .unwrap_or_default();
188 format!("func {}{}{}", name, params, return_type)
189 }
190 "class_declaration" => format!("class {}", name),
191 "struct_declaration" => format!("struct {}", name),
192 "protocol_declaration" => format!("protocol {}", name),
193 "enum_declaration" => format!("enum {}", name),
194 "extension_declaration" => format!("extension {}", name),
195 "actor_declaration" => format!("actor {}", name),
196 "typealias_declaration" => {
197 let target = node
198 .child_by_field_name("value")
199 .map(|t| content[t.byte_range()].to_string())
200 .unwrap_or_default();
201 format!("typealias {} = {}", name, target)
202 }
203 _ => {
204 let text = &content[node.byte_range()];
205 text.lines().next().unwrap_or(text).trim().to_string()
206 }
207 }
208 }
209
210 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
211 if node.kind() != "import_declaration" {
212 return Vec::new();
213 }
214
215 let line = node.start_position().row + 1;
216
217 let mut cursor = node.walk();
219 for child in node.children(&mut cursor) {
220 if child.kind() == "identifier" || child.kind() == "simple_identifier" {
221 let module = content[child.byte_range()].to_string();
222 return vec![Import {
223 module,
224 names: Vec::new(),
225 alias: None,
226 is_wildcard: false,
227 is_relative: false,
228 line,
229 }];
230 }
231 }
232
233 Vec::new()
234 }
235
236 fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
237 format!("import {}", import.module)
239 }
240
241 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
242 node.child_by_field_name("body")
243 }
244
245 fn analyze_container_body(
246 &self,
247 body_node: &Node,
248 content: &str,
249 inner_indent: &str,
250 ) -> Option<ContainerBody> {
251 crate::body::analyze_brace_body(body_node, content, inner_indent)
252 }
253
254 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
255 let mut cursor = node.walk();
256 for child in node.children(&mut cursor) {
257 if child.kind() == "modifiers" || child.kind() == "modifier" {
258 let mod_text = &content[child.byte_range()];
259 if mod_text.contains("private") || mod_text.contains("fileprivate") {
260 return Visibility::Private;
261 }
262 if mod_text.contains("internal") {
263 return Visibility::Protected;
264 }
265 if mod_text.contains("public") || mod_text.contains("open") {
266 return Visibility::Public;
267 }
268 }
269 }
270 Visibility::Protected
272 }
273
274 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
275 let name = symbol.name.as_str();
276 match symbol.kind {
277 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
278 crate::SymbolKind::Module => name == "tests" || name == "test",
279 _ => false,
280 }
281 }
282
283 fn test_file_globs(&self) -> &'static [&'static str] {
284 &["**/*Tests.swift", "**/*Test.swift"]
285 }
286}
287
288impl LanguageSymbols for Swift {}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::validate_unused_kinds_audit;
294
295 #[test]
296 fn unused_node_kinds_audit() {
297 #[rustfmt::skip]
298 let documented_unused: &[&str] = &[
299 "as_operator", "associatedtype_declaration", "catch_keyword", "class_body",
301 "computed_modify", "constructor_expression", "constructor_suffix", "custom_operator",
302 "deinit_declaration", "deprecated_operator_declaration_body", "didset_clause",
303 "else", "enum_class_body", "enum_entry", "enum_type_parameters",
304 "existential_type", "external_macro_definition", "function_body", "function_modifier",
305 "getter_specifier", "identifier", "inheritance_modifier", "inheritance_specifier",
306 "interpolated_expression", "key_path_expression", "key_path_string_expression",
307 "lambda_function_type", "lambda_function_type_parameters", "lambda_parameter",
308 "macro_declaration", "macro_definition", "member_modifier", "metatype", "modifiers",
309 "modify_specifier", "mutation_modifier", "opaque_type", "operator_declaration",
310 "optional_type", "ownership_modifier", "parameter_modifier", "parameter_modifiers",
311 "precedence_group_declaration", "property_behavior_modifier", "property_declaration",
312 "property_modifier", "protocol_body", "protocol_composition_type",
313 "protocol_function_declaration", "protocol_property_declaration", "self_expression",
314 "setter_specifier", "simple_identifier", "statement_label", "statements",
315 "super_expression", "switch_entry", "throw_keyword", "throws", "try_operator",
316 "tuple_expression", "tuple_type", "tuple_type_item", "type_annotation",
317 "type_arguments", "type_constraint", "type_constraints", "type_identifier",
318 "type_modifiers", "type_pack_expansion", "type_parameter", "type_parameter_modifiers",
319 "type_parameter_pack", "type_parameters", "user_type", "visibility_modifier",
320 "where_clause", "willset_clause", "willset_didset_block",
321 "additive_expression", "as_expression", "await_expression", "call_expression",
323 "check_expression", "comparison_expression", "conjunction_expression",
324 "directly_assignable_expression", "disjunction_expression", "equality_expression",
325 "infix_expression", "multiplicative_expression", "navigation_expression",
326 "open_end_range_expression", "open_start_range_expression", "postfix_expression",
327 "prefix_expression", "range_expression", "selector_expression", "try_expression",
328 "array_type", "dictionary_type", "function_type",
330 "init_declaration",
332 "repeat_while_statement",
333 "while_statement",
334 "import_declaration",
335 "subscript_declaration",
336 "lambda_literal",
337 "for_statement",
338 "if_statement",
339 "nil_coalescing_expression",
340 "do_statement",
341 "ternary_expression",
342 "catch_block",
343 "control_transfer_statement",
344 "switch_statement",
345 "guard_statement",
346 ];
347
348 validate_unused_kinds_audit(&Swift, documented_unused)
349 .expect("Swift unused node kinds audit failed");
350 }
351}