#![cfg_attr(coverage_nightly, coverage(off))]
use anyhow::Result;
use async_trait::async_trait;
use std::path::Path;
use tokio::fs;
use crate::services::context::FileContext;
use crate::services::file_classifier::FileClassifier;
use crate::services::languages::kotlin::KotlinAstVisitor;
use super::super::AstStrategy;
pub struct KotlinStrategy;
impl KotlinStrategy {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl AstStrategy for KotlinStrategy {
async fn analyze(&self, file_path: &Path, _classifier: &FileClassifier) -> Result<FileContext> {
let content = fs::read_to_string(file_path).await?;
let visitor = KotlinAstVisitor::new(file_path);
match visitor.analyze_kotlin_source(&content) {
Ok(items) => {
Ok(FileContext {
path: file_path.to_string_lossy().to_string(),
language: "kotlin".to_string(),
items,
complexity_metrics: None, })
},
Err(e) => {
tracing::warn!("Failed to parse Kotlin file {}: {}", file_path.display(), e);
Ok(FileContext {
path: file_path.to_string_lossy().to_string(),
language: "kotlin".to_string(),
items: vec![],
complexity_metrics: None,
})
}
}
}
fn primary_extension(&self) -> &'static str {
"kt"
}
fn supported_extensions(&self) -> Vec<&'static str> {
vec!["kt", "kts"]
}
fn language_name(&self) -> &'static str {
"Kotlin"
}
}
impl Default for KotlinStrategy {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_kotlin_strategy_supports_kt_extension() {
let strategy = KotlinStrategy::new();
assert!(strategy.can_handle("kt"));
assert!(strategy.can_handle("kts"));
assert!(!strategy.can_handle("java"));
}
#[test]
fn test_kotlin_strategy_properties() {
let strategy = KotlinStrategy::new();
assert_eq!(strategy.primary_extension(), "kt");
assert_eq!(strategy.language_name(), "Kotlin");
assert_eq!(strategy.supported_extensions(), vec!["kt", "kts"]);
}
#[tokio::test]
async fn test_kotlin_strategy_analyze_empty_file() {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(b"").unwrap();
temp_file.flush().unwrap();
let kotlin_file_path = temp_file.path().with_extension("kt");
std::fs::copy(temp_file.path(), &kotlin_file_path).unwrap();
let strategy = KotlinStrategy::new();
let result = strategy
.analyze(&kotlin_file_path, &FileClassifier::default())
.await
.unwrap();
assert_eq!(result.language, "kotlin");
assert!(result.items.is_empty());
std::fs::remove_file(&kotlin_file_path).unwrap();
}
#[tokio::test]
async fn test_kotlin_strategy_analyze_simple_file() {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(b"fun main() { println(\"Hello, Kotlin!\") }").unwrap();
temp_file.flush().unwrap();
let kotlin_file_path = temp_file.path().with_extension("kt");
std::fs::copy(temp_file.path(), &kotlin_file_path).unwrap();
let strategy = KotlinStrategy::new();
let result = strategy
.analyze(&kotlin_file_path, &FileClassifier::default())
.await
.unwrap();
assert_eq!(result.language, "kotlin");
let functions: Vec<_> = result
.items
.iter()
.filter(|item| matches!(item, crate::services::context::AstItem::Function { .. }))
.collect();
assert!(!functions.is_empty());
std::fs::remove_file(&kotlin_file_path).unwrap();
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}