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