1use crate::traits::{ImportSpec, ModuleId, ModuleResolver, Resolution, ResolverConfig};
4use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
5use std::path::{Path, PathBuf};
6use tree_sitter::Node;
7
8pub struct Swift;
10
11impl Swift {
12 fn find_type_identifier(node: &Node, content: &str, out: &mut Vec<String>) {
14 let before = out.len();
15 if node.kind() == "type_identifier" {
16 out.push(content[node.byte_range()].to_string());
17 return;
18 }
19 let mut cursor = node.walk();
20 for child in node.children(&mut cursor) {
21 Self::find_type_identifier(&child, content, out);
22 if out.len() > before {
23 return;
24 }
25 }
26 }
27}
28
29impl Language for Swift {
30 fn name(&self) -> &'static str {
31 "Swift"
32 }
33 fn extensions(&self) -> &'static [&'static str] {
34 &["swift"]
35 }
36 fn grammar_name(&self) -> &'static str {
37 "swift"
38 }
39
40 fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
41 Some(self)
42 }
43
44 fn signature_suffix(&self) -> &'static str {
45 " {}"
46 }
47
48 fn refine_kind(
49 &self,
50 node: &Node,
51 content: &str,
52 tag_kind: crate::SymbolKind,
53 ) -> crate::SymbolKind {
54 if node.kind() == "class_declaration"
57 && let Some(kind_node) = node.child_by_field_name("declaration_kind")
58 {
59 let kind_text = &content[kind_node.byte_range()];
60 return match kind_text {
61 "struct" => crate::SymbolKind::Struct,
62 "enum" => crate::SymbolKind::Enum,
63 "class" | "actor" => crate::SymbolKind::Class,
64 _ => tag_kind,
65 };
66 }
67 tag_kind
68 }
69
70 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
71 let mut doc_lines: Vec<String> = Vec::new();
73 let mut prev = node.prev_sibling();
74
75 while let Some(sibling) = prev {
76 match sibling.kind() {
77 "comment" => {
78 let text = &content[sibling.byte_range()];
79 if text.starts_with("///") {
80 let line = text.strip_prefix("///").unwrap_or("").trim().to_string();
81 doc_lines.push(line);
82 } else {
83 break;
84 }
85 }
86 "multiline_comment" => {
87 let text = &content[sibling.byte_range()];
88 if text.starts_with("/**") {
89 let lines: Vec<&str> = text
90 .strip_prefix("/**")
91 .unwrap_or(text)
92 .strip_suffix("*/")
93 .unwrap_or(text)
94 .lines()
95 .map(|l| l.trim().strip_prefix('*').unwrap_or(l).trim())
96 .filter(|l| !l.is_empty())
97 .collect();
98 if lines.is_empty() {
99 return None;
100 }
101 return Some(lines.join(" "));
102 }
103 break;
104 }
105 "attribute" => {
106 }
108 _ => break,
109 }
110 prev = sibling.prev_sibling();
111 }
112
113 if doc_lines.is_empty() {
114 return None;
115 }
116 doc_lines.reverse();
117 let joined = doc_lines.join(" ");
118 let trimmed = joined.trim().to_string();
119 if trimmed.is_empty() {
120 None
121 } else {
122 Some(trimmed)
123 }
124 }
125
126 fn extract_attributes(&self, node: &Node, content: &str) -> Vec<String> {
127 let mut attrs = Vec::new();
128 if let Some(mods) = node.child_by_field_name("modifiers") {
129 let mut cursor = mods.walk();
130 for child in mods.children(&mut cursor) {
131 if child.kind() == "attribute" {
132 let text = content[child.byte_range()].trim().to_string();
133 if !text.is_empty() {
134 attrs.push(text);
135 }
136 }
137 }
138 }
139 let mut prev = node.prev_sibling();
140 while let Some(sibling) = prev {
141 if sibling.kind() == "attribute" {
142 let text = content[sibling.byte_range()].trim().to_string();
143 if !text.is_empty() {
144 attrs.insert(0, text);
145 }
146 prev = sibling.prev_sibling();
147 } else {
148 break;
149 }
150 }
151 attrs
152 }
153
154 fn extract_implements(&self, node: &Node, content: &str) -> crate::ImplementsInfo {
155 let mut implements = Vec::new();
156 let mut cursor = node.walk();
157 for child in node.children(&mut cursor) {
158 if child.kind() == "inheritance_specifier" {
159 Self::find_type_identifier(&child, content, &mut implements);
160 }
161 }
162 crate::ImplementsInfo {
163 is_interface: false,
164 implements,
165 }
166 }
167
168 fn build_signature(&self, node: &Node, content: &str) -> String {
169 let name = match self.node_name(node, content) {
170 Some(n) => n,
171 None => {
172 return content[node.byte_range()]
173 .lines()
174 .next()
175 .unwrap_or("")
176 .trim()
177 .to_string();
178 }
179 };
180 match node.kind() {
181 "function_declaration" => {
182 let params = node
183 .child_by_field_name("parameters")
184 .map(|p| content[p.byte_range()].to_string())
185 .unwrap_or_else(|| "()".to_string());
186 let return_type = node
187 .child_by_field_name("return_type")
188 .map(|t| format!(" -> {}", content[t.byte_range()].trim()))
189 .unwrap_or_default();
190 format!("func {}{}{}", name, params, return_type)
191 }
192 "class_declaration" => format!("class {}", name),
193 "struct_declaration" => format!("struct {}", name),
194 "protocol_declaration" => format!("protocol {}", name),
195 "enum_declaration" => format!("enum {}", name),
196 "extension_declaration" => format!("extension {}", name),
197 "actor_declaration" => format!("actor {}", name),
198 "typealias_declaration" => {
199 let target = node
200 .child_by_field_name("value")
201 .map(|t| content[t.byte_range()].to_string())
202 .unwrap_or_default();
203 format!("typealias {} = {}", name, target)
204 }
205 _ => {
206 let text = &content[node.byte_range()];
207 text.lines().next().unwrap_or(text).trim().to_string()
208 }
209 }
210 }
211
212 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
213 if node.kind() != "import_declaration" {
214 return Vec::new();
215 }
216
217 let line = node.start_position().row + 1;
218
219 let mut cursor = node.walk();
221 for child in node.children(&mut cursor) {
222 if child.kind() == "identifier" || child.kind() == "simple_identifier" {
223 let module = content[child.byte_range()].to_string();
224 return vec![Import {
225 module,
226 names: Vec::new(),
227 alias: None,
228 is_wildcard: false,
229 is_relative: false,
230 line,
231 }];
232 }
233 }
234
235 Vec::new()
236 }
237
238 fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
239 format!("import {}", import.module)
241 }
242
243 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
244 node.child_by_field_name("body")
245 }
246
247 fn analyze_container_body(
248 &self,
249 body_node: &Node,
250 content: &str,
251 inner_indent: &str,
252 ) -> Option<ContainerBody> {
253 crate::body::analyze_brace_body(body_node, content, inner_indent)
254 }
255
256 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
257 let mut cursor = node.walk();
258 for child in node.children(&mut cursor) {
259 if child.kind() == "modifiers" || child.kind() == "modifier" {
260 let mod_text = &content[child.byte_range()];
261 if mod_text.contains("private") || mod_text.contains("fileprivate") {
262 return Visibility::Private;
263 }
264 if mod_text.contains("internal") {
265 return Visibility::Protected;
266 }
267 if mod_text.contains("public") || mod_text.contains("open") {
268 return Visibility::Public;
269 }
270 }
271 }
272 Visibility::Protected
274 }
275
276 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
277 let name = symbol.name.as_str();
278 match symbol.kind {
279 crate::SymbolKind::Function | crate::SymbolKind::Method => name.starts_with("test_"),
280 crate::SymbolKind::Module => name == "tests" || name == "test",
281 _ => false,
282 }
283 }
284
285 fn test_file_globs(&self) -> &'static [&'static str] {
286 &["**/*Tests.swift", "**/*Test.swift"]
287 }
288
289 fn module_resolver(&self) -> Option<&dyn ModuleResolver> {
290 static RESOLVER: SwiftModuleResolver = SwiftModuleResolver;
291 Some(&RESOLVER)
292 }
293}
294
295impl LanguageSymbols for Swift {}
296
297pub struct SwiftModuleResolver;
306
307impl ModuleResolver for SwiftModuleResolver {
308 fn workspace_config(&self, root: &Path) -> ResolverConfig {
309 let mut path_mappings: Vec<(String, PathBuf)> = Vec::new();
310
311 let package_swift = root.join("Package.swift");
312 if let Ok(content) = std::fs::read_to_string(&package_swift) {
313 let mut search_start = 0;
315 while let Some(pos) = content[search_start..].find(".target(name:") {
316 let abs_pos = search_start + pos;
317 let after = &content[abs_pos + 13..]; if let Some(q_start) = after.find('"') {
320 let rest = &after[q_start + 1..];
321 if let Some(q_end) = rest.find('"') {
322 let target_name = &rest[..q_end];
323 let target_dir = root.join("Sources").join(target_name);
324 path_mappings.push((target_name.to_string(), target_dir));
325 }
326 }
327 search_start = abs_pos + 13;
328 }
329 }
330
331 if path_mappings.is_empty() {
333 let sources_dir = root.join("Sources");
334 if let Ok(entries) = std::fs::read_dir(&sources_dir) {
335 for entry in entries.flatten() {
336 if entry.path().is_dir()
337 && let Some(name) = entry.file_name().to_str()
338 {
339 path_mappings.push((name.to_string(), entry.path()));
340 }
341 }
342 }
343 }
344
345 ResolverConfig {
346 workspace_root: root.to_path_buf(),
347 path_mappings,
348 search_roots: vec![root.join("Sources")],
349 }
350 }
351
352 fn module_of_file(&self, _root: &Path, file: &Path, cfg: &ResolverConfig) -> Vec<ModuleId> {
353 let ext = file.extension().and_then(|e| e.to_str()).unwrap_or("");
354 if ext != "swift" {
355 return Vec::new();
356 }
357 for (target_name, target_dir) in &cfg.path_mappings {
358 if file.starts_with(target_dir) {
359 return vec![ModuleId {
360 canonical_path: target_name.clone(),
361 }];
362 }
363 }
364 Vec::new()
365 }
366
367 fn resolve(&self, from_file: &Path, spec: &ImportSpec, cfg: &ResolverConfig) -> Resolution {
368 let ext = from_file.extension().and_then(|e| e.to_str()).unwrap_or("");
369 if ext != "swift" {
370 return Resolution::NotApplicable;
371 }
372 let raw = &spec.raw;
373 for (target_name, target_dir) in &cfg.path_mappings {
375 if target_name == raw {
376 return Resolution::Resolved(target_dir.clone(), String::new());
377 }
378 }
379 Resolution::NotFound
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386 use crate::validate_unused_kinds_audit;
387
388 #[test]
389 fn unused_node_kinds_audit() {
390 #[rustfmt::skip]
391 let documented_unused: &[&str] = &[
392 "as_operator", "associatedtype_declaration", "catch_keyword", "class_body",
394 "computed_modify", "constructor_expression", "constructor_suffix", "custom_operator",
395 "deinit_declaration", "deprecated_operator_declaration_body", "didset_clause",
396 "else", "enum_class_body", "enum_entry", "enum_type_parameters",
397 "existential_type", "external_macro_definition", "function_body", "function_modifier",
398 "getter_specifier", "identifier", "inheritance_modifier", "inheritance_specifier",
399 "interpolated_expression", "key_path_expression", "key_path_string_expression",
400 "lambda_function_type", "lambda_function_type_parameters", "lambda_parameter",
401 "macro_declaration", "macro_definition", "member_modifier", "metatype", "modifiers",
402 "modify_specifier", "mutation_modifier", "opaque_type", "operator_declaration",
403 "optional_type", "ownership_modifier", "parameter_modifier", "parameter_modifiers",
404 "precedence_group_declaration", "property_behavior_modifier", "property_declaration",
405 "property_modifier", "protocol_body", "protocol_composition_type",
406 "protocol_function_declaration", "protocol_property_declaration", "self_expression",
407 "setter_specifier", "simple_identifier", "statement_label", "statements",
408 "super_expression", "switch_entry", "throw_keyword", "throws", "try_operator",
409 "tuple_expression", "tuple_type", "tuple_type_item", "type_annotation",
410 "type_arguments", "type_constraint", "type_constraints", "type_identifier",
411 "type_modifiers", "type_pack_expansion", "type_parameter", "type_parameter_modifiers",
412 "type_parameter_pack", "type_parameters", "user_type", "visibility_modifier",
413 "where_clause", "willset_clause", "willset_didset_block",
414 "additive_expression", "as_expression", "await_expression", "call_expression",
416 "check_expression", "comparison_expression", "conjunction_expression",
417 "directly_assignable_expression", "disjunction_expression", "equality_expression",
418 "infix_expression", "multiplicative_expression", "navigation_expression",
419 "open_end_range_expression", "open_start_range_expression", "postfix_expression",
420 "prefix_expression", "range_expression", "selector_expression", "try_expression",
421 "array_type", "dictionary_type", "function_type",
423 "init_declaration",
425 "repeat_while_statement",
426 "while_statement",
427 "import_declaration",
428 "subscript_declaration",
429 "lambda_literal",
430 "for_statement",
431 "if_statement",
432 "nil_coalescing_expression",
433 "do_statement",
434 "ternary_expression",
435 "catch_block",
436 "control_transfer_statement",
437 "switch_statement",
438 "guard_statement",
439 ];
440
441 validate_unused_kinds_audit(&Swift, documented_unused)
442 .expect("Swift unused node kinds audit failed");
443 }
444}