nu_lint/
violation.rs

1use core::fmt::{self, Display, Formatter};
2use std::borrow::Cow;
3
4use miette::SourceSpan;
5use nu_protocol::Span;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
8pub enum Severity {
9    Info,
10    Warning,
11    Error,
12}
13
14impl Display for Severity {
15    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
16        match self {
17            Self::Error => write!(f, "error"),
18            Self::Warning => write!(f, "warning"),
19            Self::Info => write!(f, "info"),
20        }
21    }
22}
23
24/// A rule violation without severity (created by rules)
25#[derive(Debug, Clone)]
26pub struct RuleViolation {
27    pub rule_id: Cow<'static, str>,
28    pub message: Cow<'static, str>,
29    pub span: Span,
30    pub suggestion: Option<Cow<'static, str>>,
31    pub(crate) fix: Option<Fix>,
32}
33
34impl RuleViolation {
35    /// Create a new rule violation with static strings
36    #[must_use]
37    pub(crate) const fn new_static(
38        rule_id: &'static str,
39        message: &'static str,
40        span: Span,
41    ) -> Self {
42        Self {
43            rule_id: Cow::Borrowed(rule_id),
44            message: Cow::Borrowed(message),
45            span,
46            suggestion: None,
47            fix: None,
48        }
49    }
50
51    /// Create a new rule violation with a dynamic message
52    #[must_use]
53    pub(crate) const fn new_dynamic(rule_id: &'static str, message: String, span: Span) -> Self {
54        Self {
55            rule_id: Cow::Borrowed(rule_id),
56            message: Cow::Owned(message),
57            span,
58            suggestion: None,
59            fix: None,
60        }
61    }
62
63    /// Add a static suggestion to this violation
64    #[must_use]
65    pub(crate) fn with_suggestion_static(mut self, suggestion: &'static str) -> Self {
66        self.suggestion = Some(Cow::Borrowed(suggestion));
67        self
68    }
69
70    /// Add a dynamic suggestion to this violation
71    #[must_use]
72    pub(crate) fn with_suggestion_dynamic(mut self, suggestion: String) -> Self {
73        self.suggestion = Some(Cow::Owned(suggestion));
74        self
75    }
76
77    /// Add a fix to this violation
78    #[must_use]
79    pub(crate) fn with_fix(mut self, fix: Fix) -> Self {
80        self.fix = Some(fix);
81        self
82    }
83
84    /// Convert to a full Violation with severity
85    #[must_use]
86    pub fn into_violation(self, severity: Severity) -> Violation {
87        Violation {
88            rule_id: self.rule_id,
89            severity,
90            message: self.message,
91            span: self.span,
92            suggestion: self.suggestion,
93            fix: self.fix,
94            file: None,
95        }
96    }
97}
98
99/// A complete violation with severity (created by the engine)
100#[derive(Debug, Clone)]
101pub struct Violation {
102    pub rule_id: Cow<'static, str>,
103    pub severity: Severity,
104    pub message: Cow<'static, str>,
105    pub span: Span,
106    pub suggestion: Option<Cow<'static, str>>,
107    pub(crate) fix: Option<Fix>,
108    pub file: Option<Cow<'static, str>>,
109}
110
111impl Violation {
112    /// Create a new violation with static strings (most common case)
113    #[must_use]
114    pub const fn new_static(
115        rule_id: &'static str,
116        severity: Severity,
117        message: &'static str,
118        span: Span,
119    ) -> Self {
120        Self {
121            rule_id: Cow::Borrowed(rule_id),
122            severity,
123            message: Cow::Borrowed(message),
124            span,
125            suggestion: None,
126            fix: None,
127            file: None,
128        }
129    }
130
131    /// Create a new violation with a dynamic message
132    #[must_use]
133    pub const fn new_dynamic(
134        rule_id: &'static str,
135        severity: Severity,
136        message: String,
137        span: Span,
138    ) -> Self {
139        Self {
140            rule_id: Cow::Borrowed(rule_id),
141            severity,
142            message: Cow::Owned(message),
143            span,
144            suggestion: None,
145            fix: None,
146            file: None,
147        }
148    }
149
150    /// Add a static suggestion to this violation
151    #[must_use]
152    pub fn with_suggestion_static(mut self, suggestion: &'static str) -> Self {
153        self.suggestion = Some(Cow::Borrowed(suggestion));
154        self
155    }
156
157    /// Add a dynamic suggestion to this violation
158    #[must_use]
159    pub fn with_suggestion_dynamic(mut self, suggestion: String) -> Self {
160        self.suggestion = Some(Cow::Owned(suggestion));
161        self
162    }
163
164    /// Add a fix to this violation
165    #[must_use]
166    pub(crate) fn to_source_span(&self) -> SourceSpan {
167        SourceSpan::from((self.span.start, self.span.end - self.span.start))
168    }
169}
170
171#[derive(Debug, Clone)]
172pub struct Fix {
173    pub(crate) description: Cow<'static, str>,
174    pub(crate) replacements: Vec<Replacement>,
175}
176
177impl Fix {
178    /// Create a fix with a static description
179    #[must_use]
180    pub const fn new_static(description: &'static str, replacements: Vec<Replacement>) -> Self {
181        Self {
182            description: Cow::Borrowed(description),
183            replacements,
184        }
185    }
186
187    /// Create a fix with a dynamic description
188    #[must_use]
189    pub(crate) const fn new_dynamic(description: String, replacements: Vec<Replacement>) -> Self {
190        Self {
191            description: Cow::Owned(description),
192            replacements,
193        }
194    }
195}
196
197#[derive(Debug, Clone)]
198pub struct Replacement {
199    pub(crate) span: Span,
200    pub(crate) new_text: Cow<'static, str>,
201}
202
203impl Replacement {
204    /// Create a replacement with static text
205    #[must_use]
206    pub const fn new_static(span: Span, new_text: &'static str) -> Self {
207        Self {
208            span,
209            new_text: Cow::Borrowed(new_text),
210        }
211    }
212
213    /// Create a replacement with dynamic text
214    #[must_use]
215    pub const fn new_dynamic(span: Span, new_text: String) -> Self {
216        Self {
217            span,
218            new_text: Cow::Owned(new_text),
219        }
220    }
221}