Skip to main content

normalize_languages/
swift.rs

1//! Swift language support.
2
3use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
4use tree_sitter::Node;
5
6/// Swift language support.
7pub struct Swift;
8
9impl Swift {
10    /// Find the first type_identifier in an inheritance_specifier subtree.
11    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        // Swift uses class_declaration for class/struct/enum/actor,
53        // distinguished by the declaration_kind field.
54        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        // Swift doc comments use triple-slash `///` lines or `/** */` blocks.
70        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                    // Skip attributes between doc comment and declaration
105                }
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        // Get the module name
218        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        // Swift: import Module
238        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        // Swift default is internal
271        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            // STRUCTURAL
300            "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            // EXPRESSION
322            "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            // TYPE
329            "array_type", "dictionary_type", "function_type",
330            // covered by tags.scm
331            "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}