#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
const SIMPLE_SWIFT_SOURCE: &str = r#"
import Foundation
print("Hello, World!")
"#;
const SWIFT_SOURCE_WITH_FUNCTIONS: &str = r#"
import Foundation
// Simple function
func printHello() {
print("Hello World!")
}
// Function with parameters
func calculateScore(value: Int, isBonus: Bool) -> Int {
if value < 0 {
return 0
}
if isBonus {
if value > 100 {
return value * 2
} else {
return value + 50
}
}
return value
}
// Function with loop
func sumArray(arr: [Int]) -> Int {
var sum = 0
for num in arr {
if num > 0 {
sum += num
}
}
return sum
}
"#;
const SWIFT_SOURCE_WITH_CLASSES: &str = r#"
import Foundation
class Calculator {
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiply(_ a: Int, _ b: Int) -> Int {
return a * b
}
private func complexOperation(_ x: Int) -> Int {
if x < 0 {
return -1
} else if x == 0 {
return 0
}
return x * x
}
}
"#;
#[test]
fn test_simple_swift_source_analysis() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("simple.swift"));
let items = analyzer
.analyze_swift_source(SIMPLE_SWIFT_SOURCE)
.expect("Should parse simple Swift source");
assert!(
items.is_empty() || !items.is_empty(),
"Should handle simple Swift source"
);
}
#[test]
fn test_swift_functions_analysis() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("functions.swift"));
let items = analyzer
.analyze_swift_source(SWIFT_SOURCE_WITH_FUNCTIONS)
.expect("Should parse Swift source with functions");
let function_items: Vec<_> = items
.iter()
.filter(|item| matches!(item, AstItem::Function { .. }))
.collect();
assert!(
function_items.len() >= 3,
"Should extract printHello, calculateScore, and sumArray functions"
);
let function_names: Vec<_> = function_items
.iter()
.filter_map(|item| match item {
AstItem::Function { name, .. } => Some(name.as_str()),
_ => None,
})
.collect();
assert!(function_names
.iter()
.any(|&name| name.contains("printHello")));
assert!(function_names
.iter()
.any(|&name| name.contains("calculateScore")));
assert!(function_names.iter().any(|&name| name.contains("sumArray")));
}
#[test]
fn test_swift_class_analysis() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("classes.swift"));
let items = analyzer
.analyze_swift_source(SWIFT_SOURCE_WITH_CLASSES)
.expect("Should parse Swift source with classes");
let class_items: Vec<_> = items
.iter()
.filter(|item| matches!(item, AstItem::Struct { .. }))
.collect();
assert!(!class_items.is_empty(), "Should extract Calculator class");
let method_items: Vec<_> = items
.iter()
.filter(|item| matches!(item, AstItem::Function { .. }))
.collect();
assert!(
method_items.len() >= 3,
"Should extract add, multiply, and complexOperation methods"
);
}
#[test]
fn test_swift_complexity_analysis() {
let mut analyzer = SwiftComplexityAnalyzer::new();
let (cyclomatic, cognitive) = analyzer
.analyze_complexity(SWIFT_SOURCE_WITH_FUNCTIONS)
.expect("Should analyze Swift complexity");
assert!(
cyclomatic >= 3,
"Source with conditionals should have cyclomatic complexity >= 3"
);
assert!(
cognitive >= 3,
"Source with conditionals should have cognitive complexity >= 3"
);
}
#[test]
fn test_empty_swift_source() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("empty.swift"));
let items = analyzer
.analyze_swift_source("")
.expect("Should handle empty source");
assert!(items.is_empty(), "Empty source should produce no AST items");
}
#[test]
fn test_swift_analyzer_new() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("test.swift"));
assert_eq!(analyzer.source_name, "test");
assert_eq!(analyzer.function_count, 0);
assert_eq!(analyzer.class_count, 0);
assert_eq!(analyzer.method_count, 0);
}
#[test]
fn test_swift_analyzer_source_name_extraction() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("/path/to/ViewController.swift"));
assert_eq!(analyzer.source_name, "ViewController");
}
#[test]
fn test_swift_analyzer_qualified_name() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("api.swift"));
let qualified = analyzer.get_qualified_name("handleRequest");
assert_eq!(qualified, "api::handleRequest");
}
#[test]
fn test_swift_analyzer_extract_function_name() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("test.swift"));
assert_eq!(
analyzer.extract_function_name("func myFunc() {"),
Some("myFunc".to_string())
);
assert_eq!(
analyzer.extract_function_name("func compute(_ a: Int, _ b: Int) -> Int {"),
Some("compute".to_string())
);
assert_eq!(analyzer.extract_function_name("not a function"), None);
}
#[test]
fn test_swift_analyzer_extract_class_name() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("test.swift"));
assert_eq!(
analyzer.extract_class_name("class MyClass {"),
Some("MyClass".to_string())
);
assert_eq!(
analyzer.extract_class_name("class BaseController: UIViewController {"),
Some("BaseController".to_string())
);
assert_eq!(
analyzer.extract_class_name("struct Point {"),
Some("Point".to_string())
);
assert_eq!(analyzer.extract_class_name("not a class"), None);
}
#[test]
fn test_swift_analyzer_extract_visibility() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("test.swift"));
assert_eq!(
analyzer.extract_visibility("private func helper()"),
"private"
);
assert_eq!(analyzer.extract_visibility("public func api()"), "public");
assert_eq!(
analyzer.extract_visibility("internal func internal_fn()"),
"internal"
);
assert_eq!(analyzer.extract_visibility("func default_fn()"), "internal");
}
#[test]
fn test_swift_complexity_analyzer_new() {
let analyzer = SwiftComplexityAnalyzer::new();
assert_eq!(analyzer.cyclomatic_complexity, 0);
assert_eq!(analyzer.cognitive_complexity, 0);
}
#[test]
fn test_swift_complexity_analyzer_default() {
let analyzer = SwiftComplexityAnalyzer::default();
assert_eq!(analyzer.cyclomatic_complexity, 0);
assert_eq!(analyzer.cognitive_complexity, 0);
}
#[test]
fn test_swift_complexity_simple_code() {
let mut analyzer = SwiftComplexityAnalyzer::new();
let code = r#"
func hello() {
print("Hello")
}
"#;
let (cyclomatic, cognitive) = analyzer.analyze_complexity(code).unwrap();
assert_eq!(cyclomatic, 1);
assert_eq!(cognitive, 1);
}
#[test]
fn test_swift_complexity_with_if() {
let mut analyzer = SwiftComplexityAnalyzer::new();
let code = r#"
if x > 0 {
print("positive")
}
"#;
let (cyclomatic, cognitive) = analyzer.analyze_complexity(code).unwrap();
assert!(cyclomatic >= 2);
assert!(cognitive >= 2);
}
#[test]
fn test_swift_complexity_with_loops() {
let mut analyzer = SwiftComplexityAnalyzer::new();
let code = r#"
for i in 0..<10 {
while j < i {
for item in arr {
print(item)
}
}
}
"#;
let (cyclomatic, cognitive) = analyzer.analyze_complexity(code).unwrap();
assert!(cyclomatic >= 4);
assert!(cognitive >= 4);
}
#[test]
fn test_swift_complexity_with_switch() {
let mut analyzer = SwiftComplexityAnalyzer::new();
let code = r#"
switch value {
case 1:
print("one")
case 2:
print("two")
case 3:
print("three")
}
"#;
let (cyclomatic, cognitive) = analyzer.analyze_complexity(code).unwrap();
assert!(cyclomatic >= 4);
assert!(cognitive >= 4);
}
#[test]
fn test_swift_complexity_with_guard() {
let mut analyzer = SwiftComplexityAnalyzer::new();
let code = r#"
guard let value = optional else {
return
}
"#;
let (cyclomatic, cognitive) = analyzer.analyze_complexity(code).unwrap();
assert!(cyclomatic >= 2);
assert!(cognitive >= 2);
}
#[test]
fn test_swift_complexity_with_ternary() {
let mut analyzer = SwiftComplexityAnalyzer::new();
let code = r#"
let result = x > 0 ? "positive" : "non-positive"
let value = a ? b : c
"#;
let (cyclomatic, cognitive) = analyzer.analyze_complexity(code).unwrap();
assert!(cyclomatic >= 3);
assert!(cognitive >= 3);
}
#[test]
fn test_swift_complexity_with_else_if() {
let mut analyzer = SwiftComplexityAnalyzer::new();
let code = r#"
if x > 10 {
print("big")
} else if x > 5 {
print("medium")
} else if x > 0 {
print("small")
} else {
print("zero or negative")
}
"#;
let (cyclomatic, cognitive) = analyzer.analyze_complexity(code).unwrap();
assert!(cyclomatic >= 4);
assert!(cognitive >= 4);
}
#[test]
fn test_swift_whitespace_handling() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("test.swift"));
let items = analyzer
.analyze_swift_source(" \n\n \t \n ")
.expect("Should handle whitespace-only");
assert!(items.is_empty());
}
#[test]
fn test_swift_struct_parsing() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("test.swift"));
let code = r#"
struct Point {
var x: Double
var y: Double
func distance() -> Double {
return sqrt(x*x + y*y)
}
}
"#;
let items = analyzer.analyze_swift_source(code).expect("Should parse");
let structs: Vec<_> = items
.iter()
.filter(|i| matches!(i, AstItem::Struct { .. }))
.collect();
assert!(!structs.is_empty());
}
#[test]
fn test_swift_multiple_classes() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("models.swift"));
let code = r#"
class User {
func getName() -> String { return name }
}
class Order {
func getTotal() -> Double { return total }
}
struct Product {
func getPrice() -> Double { return price }
}
"#;
let items = analyzer.analyze_swift_source(code).expect("Should parse");
let types: Vec<_> = items
.iter()
.filter(|i| matches!(i, AstItem::Struct { .. }))
.collect();
assert_eq!(types.len(), 3);
}
#[test]
fn test_swift_async_function() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("api.swift"));
let code = r#"
func fetchData() async throws -> Data {
return try await URLSession.shared.data(from: url)
}
"#;
let items = analyzer.analyze_swift_source(code).expect("Should parse");
let functions: Vec<_> = items
.iter()
.filter_map(|i| {
if let AstItem::Function { is_async, .. } = i {
Some(*is_async)
} else {
None
}
})
.collect();
assert!(
functions.iter().any(|&is_async| is_async),
"Should detect async function"
);
}
#[test]
fn test_swift_private_function() {
let analyzer = SwiftSourceAnalyzer::new(Path::new("helper.swift"));
let code = r#"
func helperFunction() {
print("helper")
}
"#;
let items = analyzer.analyze_swift_source(code).expect("Should parse");
let functions: Vec<_> = items
.iter()
.filter(|i| matches!(i, AstItem::Function { .. }))
.collect();
assert!(!functions.is_empty(), "Should detect function");
}
}