#![cfg_attr(coverage_nightly, coverage(off))]
#![allow(clippy::module_name_repetitions)]
use crate::services::ast::AstStrategy;
use crate::services::context::FileContext;
use crate::services::file_classifier::FileClassifier;
use crate::services::languages::scala::ScalaAstVisitor;
use async_trait::async_trait;
use std::path::Path;
use tokio::fs;
use anyhow::Result;
pub struct ScalaStrategy;
impl ScalaStrategy {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl AstStrategy for ScalaStrategy {
async fn analyze(&self, file_path: &Path, _classifier: &FileClassifier) -> Result<FileContext> {
let content = fs::read_to_string(file_path).await?;
let visitor = ScalaAstVisitor::new(file_path);
match visitor.analyze_scala_source(&content) {
Ok(items) => {
Ok(FileContext {
path: file_path.to_string_lossy().to_string(),
language: "scala".to_string(),
items,
complexity_metrics: None,
})
},
Err(e) => {
tracing::warn!("Failed to parse Scala file {}: {}", file_path.display(), e);
Ok(FileContext {
path: file_path.to_string_lossy().to_string(),
language: "scala".to_string(),
items: vec![],
complexity_metrics: None,
})
}
}
}
fn primary_extension(&self) -> &'static str {
"scala"
}
fn supported_extensions(&self) -> Vec<&'static str> {
vec!["scala", "sc"]
}
fn language_name(&self) -> &'static str {
"Scala"
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use crate::services::context::AstItem;
use std::path::PathBuf;
use std::fs::File;
use std::io::Write;
use tempfile::TempDir;
fn create_temp_scala_file(content: &str) -> (PathBuf, TempDir) {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let file_path = temp_dir.path().join("Test.scala");
let mut file = File::create(&file_path).expect("Failed to create temp file");
file.write_all(content.as_bytes()).expect("Failed to write to temp file");
(file_path, temp_dir)
}
#[tokio::test]
async fn test_scala_strategy_analyze() {
let scala_content = r#"
package com.example
class Test {
def exampleMethod(): String = {
"Hello, Scala!"
}
}
"#;
let (file_path, _temp_dir) = create_temp_scala_file(scala_content);
let strategy = ScalaStrategy::new();
let classifier = FileClassifier::new();
let context = strategy.analyze(&file_path, &classifier).await.unwrap();
assert_eq!(context.language, "scala");
assert!(!context.items.is_empty());
let class_items: Vec<_> = context.items.iter()
.filter(|item| matches!(item, AstItem::Struct { .. }))
.collect();
assert!(!class_items.is_empty());
let method_items: Vec<_> = context.items.iter()
.filter(|item| matches!(item, AstItem::Function { .. }))
.collect();
assert!(!method_items.is_empty());
}
#[tokio::test]
async fn test_scala_strategy_case_class() {
let scala_content = r#"
package com.example.models
case class Person(name: String, age: Int)
object Person {
def apply(name: String): Person = new Person(name, 0)
}
"#;
let (file_path, _temp_dir) = create_temp_scala_file(scala_content);
let strategy = ScalaStrategy::new();
let classifier = FileClassifier::new();
let context = strategy.analyze(&file_path, &classifier).await.unwrap();
assert_eq!(context.language, "scala");
let case_class_items: Vec<_> = context.items.iter()
.filter(|item| {
if let AstItem::Struct { derives, .. } = item {
derives.contains(&"case".to_string())
} else {
false
}
})
.collect();
assert!(!case_class_items.is_empty());
let object_items: Vec<_> = context.items.iter()
.filter(|item| matches!(item, AstItem::Module { .. }))
.collect();
assert!(!object_items.is_empty());
}
#[test]
fn test_scala_strategy_extensions() {
let strategy = ScalaStrategy::new();
assert_eq!(strategy.primary_extension(), "scala");
assert!(strategy.supported_extensions().contains(&"scala"));
assert!(strategy.supported_extensions().contains(&"sc"));
assert_eq!(strategy.language_name(), "Scala");
}
}