Skip to main content

pedant_core/
violation.rs

1use std::fmt;
2use std::sync::Arc;
3
4pub use crate::checks::{ViolationType, lookup_rationale};
5
6/// Structured explanation of why a check exists, shown by `--explain`.
7#[derive(Debug, Clone, Copy, serde::Serialize)]
8pub struct CheckRationale {
9    /// The code smell or risk this check detects.
10    pub problem: &'static str,
11    /// Concrete refactoring steps to eliminate the violation.
12    pub fix: &'static str,
13    /// Situations where suppression is justified.
14    pub exception: &'static str,
15    /// `true` when the pattern is disproportionately common in LLM output.
16    pub llm_specific: bool,
17}
18
19impl fmt::Display for CheckRationale {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        writeln!(f, "Problem:      {}", self.problem)?;
22        writeln!(f, "Fix:          {}", self.fix)?;
23        writeln!(f, "Exception:    {}", self.exception)?;
24        write!(f, "LLM-specific: {}", self.llm_specific)
25    }
26}
27
28impl ViolationType {
29    /// The glob pattern that triggered this violation, for pattern-based checks only.
30    pub fn pattern(&self) -> Option<&str> {
31        match self {
32            Self::ForbiddenAttribute { pattern }
33            | Self::ForbiddenType { pattern }
34            | Self::ForbiddenCall { pattern }
35            | Self::ForbiddenMacro { pattern } => Some(pattern),
36            _ => None,
37        }
38    }
39}
40
41impl fmt::Display for ViolationType {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        write!(f, "{}", self.code())
44    }
45}
46
47/// A located style violation with diagnostic message.
48#[derive(Debug, Clone)]
49pub struct Violation {
50    /// Which check produced this violation.
51    pub violation_type: ViolationType,
52    /// Absolute path of the offending file.
53    pub file_path: Arc<str>,
54    /// 1-based line number.
55    pub line: usize,
56    /// 1-based column number.
57    pub column: usize,
58    /// Diagnostic message describing the specific issue.
59    pub message: Box<str>,
60}
61
62impl Violation {
63    /// Construct a violation at a specific file location.
64    pub fn new(
65        violation_type: ViolationType,
66        file_path: Arc<str>,
67        line: usize,
68        column: usize,
69        message: impl Into<Box<str>>,
70    ) -> Self {
71        Self {
72            violation_type,
73            file_path,
74            line,
75            column,
76            message: message.into(),
77        }
78    }
79
80    /// Delegates to the violation type's check rationale.
81    pub fn rationale(&self) -> CheckRationale {
82        self.violation_type.rationale()
83    }
84}
85
86impl fmt::Display for Violation {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(
89            f,
90            "{}:{}:{}: {}: {}",
91            self.file_path, self.line, self.column, self.violation_type, self.message
92        )
93    }
94}