ricecoder_research/dependency_analyzer/
swift_parser.rs1use crate::error::ResearchError;
4use crate::models::Dependency;
5use std::path::Path;
6use tracing::debug;
7
8#[derive(Debug)]
10pub struct SwiftParser;
11
12impl SwiftParser {
13 pub fn new() -> Self {
15 SwiftParser
16 }
17
18 pub fn parse(&self, root: &Path) -> Result<Vec<Dependency>, ResearchError> {
20 let package_swift_path = root.join("Package.swift");
21
22 if !package_swift_path.exists() {
23 return Ok(Vec::new());
24 }
25
26 debug!("Parsing Swift dependencies from {:?}", package_swift_path);
27
28 let content = std::fs::read_to_string(&package_swift_path).map_err(|e| {
29 ResearchError::DependencyParsingFailed {
30 language: "Swift".to_string(),
31 path: Some(package_swift_path.clone()),
32 reason: format!("Failed to read Package.swift: {}", e),
33 }
34 })?;
35
36 let mut dependencies = Vec::new();
37
38 let package_pattern = regex::Regex::new(
42 r#"\.package\(url:\s*"([^"]+)"[^)]*(?:from|\.upToNextMajor|\.upToNextMinor):\s*"([^"]+)"[^)]*\)"#
43 ).unwrap();
44
45 for cap in package_pattern.captures_iter(&content) {
46 let url = cap.get(1).map(|m| m.as_str()).unwrap_or("");
47 let version = cap.get(2).map(|m| m.as_str()).unwrap_or("");
48
49 let name = if let Some(last_slash) = url.rfind('/') {
51 let name_with_ext = &url[last_slash + 1..];
52 if let Some(stripped) = name_with_ext.strip_suffix(".git") {
53 stripped
54 } else {
55 name_with_ext
56 }
57 } else {
58 url
59 };
60
61 dependencies.push(Dependency {
62 name: name.to_string(),
63 version: version.to_string(),
64 constraints: Some(version.to_string()),
65 is_dev: false,
66 });
67 }
68
69 Ok(dependencies)
70 }
71
72 pub fn has_manifest(&self, root: &Path) -> bool {
74 root.join("Package.swift").exists()
75 }
76}
77
78impl Default for SwiftParser {
79 fn default() -> Self {
80 Self::new()
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use std::fs;
88 use tempfile::TempDir;
89
90 #[test]
91 fn test_swift_parser_creation() {
92 let parser = SwiftParser::new();
93 assert!(true);
94 }
95
96 #[test]
97 fn test_swift_parser_no_manifest() {
98 let parser = SwiftParser::new();
99 let temp_dir = TempDir::new().unwrap();
100 let result = parser.parse(temp_dir.path()).unwrap();
101 assert!(result.is_empty());
102 }
103
104 #[test]
105 fn test_swift_parser_simple_dependencies() {
106 let parser = SwiftParser::new();
107 let temp_dir = TempDir::new().unwrap();
108
109 let package_swift = r#"
110// swift-tools-version:5.5
111import PackageDescription
112
113let package = Package(
114 name: "MyPackage",
115 dependencies: [
116 .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
117 .package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.0.0"))
118 ]
119)
120"#;
121
122 fs::write(temp_dir.path().join("Package.swift"), package_swift).unwrap();
123
124 let deps = parser.parse(temp_dir.path()).unwrap();
125 assert_eq!(deps.len(), 2);
126
127 let nio = deps.iter().find(|d| d.name.contains("nio")).unwrap();
128 assert_eq!(nio.version, "2.0.0");
129 }
130
131 #[test]
132 fn test_swift_parser_has_manifest() {
133 let parser = SwiftParser::new();
134 let temp_dir = TempDir::new().unwrap();
135
136 assert!(!parser.has_manifest(temp_dir.path()));
137
138 fs::write(temp_dir.path().join("Package.swift"), "").unwrap();
139 assert!(parser.has_manifest(temp_dir.path()));
140 }
141}