Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 133 additions & 100 deletions src/analyzer/mod.rs

Large diffs are not rendered by default.

261 changes: 154 additions & 107 deletions src/analyzer/rust.rs

Large diffs are not rendered by default.

220 changes: 120 additions & 100 deletions src/cache/mod.rs

Large diffs are not rendered by default.

324 changes: 175 additions & 149 deletions src/config/mod.rs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/domain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Domain layer for Rust Guardian
//!
//!
//! CDD Principle: Domain Model - Pure business logic for code quality enforcement
//! - Contains all core entities, value objects, and domain services
//! - Independent of infrastructure concerns like databases, file systems, or external APIs
Expand All @@ -8,4 +8,4 @@
pub mod violations;

// Re-export main domain types for convenience
pub use violations::*;
pub use violations::*;
103 changes: 56 additions & 47 deletions src/domain/violations.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//! Core domain models for code quality violations and validation results
//!
//!
//! CDD Principle: Rich Domain Models - Violations are entities with behavior, not just data
//! - Violations can classify themselves, suggest fixes, and maintain context
//! - ValidationReport acts as an aggregate root managing collections of violations
//! - Domain events can be generated when patterns are detected or when validation completes

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use chrono::{DateTime, Utc};

/// Severity levels for code quality violations
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
Expand All @@ -26,12 +26,12 @@ impl Severity {
pub fn is_blocking(self) -> bool {
matches!(self, Self::Error)
}

/// Convert to string for display
pub fn as_str(self) -> &'static str {
match self {
Self::Info => "info",
Self::Warning => "warning",
Self::Warning => "warning",
Self::Error => "error",
}
}
Expand Down Expand Up @@ -80,39 +80,39 @@ impl Violation {
detected_at: Utc::now(),
}
}

/// Set line and column position
pub fn with_position(mut self, line: u32, column: u32) -> Self {
self.line_number = Some(line);
self.column_number = Some(column);
self
}

/// Add source code context
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}

/// Add a suggested fix
pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
self.suggested_fix = Some(suggestion.into());
self
}

/// Whether this violation is blocking (prevents commits/builds)
pub fn is_blocking(&self) -> bool {
self.severity.is_blocking()
}

/// Format violation for display
pub fn format_display(&self) -> String {
let location = match (self.line_number, self.column_number) {
(Some(line), Some(col)) => format!(":{}:{}", line, col),
(Some(line), None) => format!(":{}", line),
_ => String::new(),
};

format!(
"{}{} [{}] {}",
self.file_path.display(),
Expand Down Expand Up @@ -149,12 +149,12 @@ impl ViolationCounts {
pub fn total(&self) -> usize {
self.error + self.warning + self.info
}

/// Whether there are any blocking violations
pub fn has_blocking(&self) -> bool {
self.error > 0
}

/// Add a violation to the counts
pub fn add(&mut self, severity: Severity) {
match severity {
Expand Down Expand Up @@ -188,55 +188,58 @@ impl ValidationReport {
config_fingerprint: None,
}
}

/// Add a violation to the report
pub fn add_violation(&mut self, violation: Violation) {
self.summary.violations_by_severity.add(violation.severity);
self.violations.push(violation);
}

/// Whether the report contains any violations
pub fn has_violations(&self) -> bool {
!self.violations.is_empty()
}

/// Whether the report contains blocking violations (errors)
pub fn has_errors(&self) -> bool {
self.summary.violations_by_severity.has_blocking()
}

/// Get violations of a specific severity
pub fn violations_by_severity(&self, severity: Severity) -> impl Iterator<Item = &Violation> {
self.violations.iter().filter(move |v| v.severity == severity)
self.violations
.iter()
.filter(move |v| v.severity == severity)
}

/// Set the number of files analyzed
pub fn set_files_analyzed(&mut self, count: usize) {
self.summary.total_files = count;
}

/// Set the execution time
pub fn set_execution_time(&mut self, duration_ms: u64) {
self.summary.execution_time_ms = duration_ms;
}

/// Set the configuration fingerprint
pub fn set_config_fingerprint(&mut self, fingerprint: impl Into<String>) {
self.config_fingerprint = Some(fingerprint.into());
}

/// Merge another report into this one
pub fn merge(&mut self, other: ValidationReport) {
for violation in other.violations {
self.add_violation(violation);
}
self.summary.total_files += other.summary.total_files;
}

/// Sort violations by file path and line number for consistent output
pub fn sort_violations(&mut self) {
self.violations.sort_by(|a, b| {
a.file_path.cmp(&b.file_path)
a.file_path
.cmp(&b.file_path)
.then_with(|| a.line_number.unwrap_or(0).cmp(&b.line_number.unwrap_or(0)))
.then_with(|| a.severity.cmp(&b.severity))
});
Expand All @@ -255,22 +258,22 @@ pub enum GuardianError {
/// Configuration file could not be loaded or parsed
#[error("Configuration error: {message}")]
Configuration { message: String },

/// File could not be read or accessed
#[error("IO error: {source}")]
Io {
#[from]
source: std::io::Error,
},

/// Pattern compilation failed
#[error("Pattern error: {message}")]
Pattern { message: String },

/// Analysis failed for a specific file
#[error("Analysis error in {file}: {message}")]
Analysis { file: String, message: String },

/// Cache operation failed
#[error("Cache error: {message}")]
Cache { message: String },
Expand All @@ -279,25 +282,31 @@ pub enum GuardianError {
impl GuardianError {
/// Create a configuration error
pub fn config(message: impl Into<String>) -> Self {
Self::Configuration { message: message.into() }
Self::Configuration {
message: message.into(),
}
}

/// Create a pattern error
pub fn pattern(message: impl Into<String>) -> Self {
Self::Pattern { message: message.into() }
Self::Pattern {
message: message.into(),
}
}

/// Create an analysis error
pub fn analysis(file: impl Into<String>, message: impl Into<String>) -> Self {
Self::Analysis {
file: file.into(),
message: message.into()
Self::Analysis {
file: file.into(),
message: message.into(),
}
}

/// Create a cache error
pub fn cache(message: impl Into<String>) -> Self {
Self::Cache { message: message.into() }
Self::Cache {
message: message.into(),
}
}
}

Expand All @@ -308,7 +317,7 @@ pub type GuardianResult<T> = Result<T, GuardianError>;
mod tests {
use super::*;
use std::path::Path;

#[test]
fn test_violation_creation() {
let violation = Violation::new(
Expand All @@ -317,14 +326,14 @@ mod tests {
PathBuf::from("src/lib.rs"),
"Test message",
);

assert_eq!(violation.rule_id, "test_rule");
assert_eq!(violation.severity, Severity::Error);
assert_eq!(violation.file_path, Path::new("src/lib.rs"));
assert_eq!(violation.message, "Test message");
assert!(violation.is_blocking());
}

#[test]
fn test_violation_with_position() {
let violation = Violation::new(
Expand All @@ -335,43 +344,43 @@ mod tests {
)
.with_position(42, 15)
.with_context("let x = todo!();");

assert_eq!(violation.line_number, Some(42));
assert_eq!(violation.column_number, Some(15));
assert_eq!(violation.context, Some("let x = todo!();".to_string()));
assert!(!violation.is_blocking());
}

#[test]
fn test_validation_report() {
let mut report = ValidationReport::new();

report.add_violation(Violation::new(
"rule1",
Severity::Error,
PathBuf::from("src/main.rs"),
"Error message",
));

report.add_violation(Violation::new(
"rule2",
"rule2",
Severity::Warning,
PathBuf::from("src/lib.rs"),
"Warning message",
));

assert!(report.has_violations());
assert!(report.has_errors());
assert_eq!(report.summary.violations_by_severity.total(), 2);
assert_eq!(report.summary.violations_by_severity.error, 1);
assert_eq!(report.summary.violations_by_severity.warning, 1);
}

#[test]
fn test_severity_ordering() {
assert!(Severity::Error > Severity::Warning);
assert!(Severity::Warning > Severity::Info);
assert!(Severity::Error.is_blocking());
assert!(!Severity::Warning.is_blocking());
}
}
}
Loading