nu_lint/
violation.rs

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