nu_lint/violation.rs
1use std::borrow::Cow;
2
3use miette::SourceSpan;
4use nu_protocol::Span;
5
6use crate::config::LintLevel;
7
8/// A lint violation with its diagnostic information
9///
10/// # Display Format
11///
12/// When displayed to the user, violations appear as:
13///
14/// ```text
15/// file.nu:10:5
16/// warn(rule_name)
17///
18/// ⚠ [message] <- Short diagnostic message
19/// ╭─[10:5]
20/// 10 │ code here
21/// · ─┬─
22/// · ╰── [label text] <- Message (or help if short)
23/// ╰────
24///
25/// help: [help text] <- Detailed explanation/rationale
26///
27/// ℹ Available fix: [explanation] <- Fix explanation
28/// - old code
29/// + new code <- From replacements
30/// ```
31///
32/// # Example
33///
34/// ```rust,ignore
35/// Violation::new("prefer_pipeline_input", "Use pipeline input", span)
36/// .with_help("Pipeline input enables better composability and streaming performance")
37/// .with_fix(Fix::with_explanation(
38/// format!("Use $in instead of ${}:\n {}", param, transformed_code),
39/// vec![Replacement::new(def_span, transformed_code)]
40/// ))
41/// ```
42#[derive(Debug, Clone)]
43pub struct Violation {
44 pub rule_id: Cow<'static, str>,
45 pub lint_level: LintLevel,
46
47 /// Short message shown in the warning header
48 /// Should be concise, typically < 80 chars
49 /// Example: "Use pipeline input instead of parameter"
50 pub message: Cow<'static, str>,
51
52 /// Span in source code where the violation occurs
53 pub span: Span,
54
55 /// Optional detailed explanation shown in the "help:" section
56 /// Use this to explain WHY the code should change or provide rationale
57 /// Example: "Pipeline input enables better composability and streaming
58 /// performance"
59 pub help: Option<Cow<'static, str>>,
60
61 /// Optional automated fix that can be applied
62 pub fix: Option<Fix>,
63
64 pub(crate) file: Option<Cow<'static, str>>,
65
66 /// Optional source code content (used for stdin or when file is not
67 /// accessible)
68 pub(crate) source: Option<Cow<'static, str>>,
69}
70
71impl Violation {
72 /// Create a new violation
73 ///
74 /// # Arguments
75 ///
76 /// * `rule_id` - The lint rule identifier (e.g., "`prefer_pipeline_input`")
77 /// * `message` - Short diagnostic message shown in the warning header
78 /// * `span` - Location in source code where the violation occurs
79 #[must_use]
80 pub fn new(rule_id: &'static str, message: impl Into<Cow<'static, str>>, span: Span) -> Self {
81 Self {
82 rule_id: Cow::Borrowed(rule_id),
83 lint_level: LintLevel::Allow, // Placeholder, will be set by engine
84 message: message.into(),
85 span,
86 help: None,
87 fix: None,
88 file: None,
89 source: None,
90 }
91 }
92
93 /// Add detailed help text explaining why this change should be made
94 ///
95 /// This appears in the "help:" section of the diagnostic output.
96 ///
97 /// # Example
98 ///
99 /// ```rust,ignore
100 /// violation.with_help("Pipeline input enables better composability and streaming performance")
101 /// ```
102 #[must_use]
103 pub fn with_help(mut self, help: impl Into<Cow<'static, str>>) -> Self {
104 self.help = Some(help.into());
105 self
106 }
107
108 /// Add an automated fix to this violation
109 ///
110 /// # Example
111 ///
112 /// ```rust,ignore
113 /// violation.with_fix(Fix::with_explanation(
114 /// "Replace with pipeline input version",
115 /// vec![Replacement::new(span, new_code)]
116 /// ))
117 /// ```
118 #[must_use]
119 pub fn with_fix(mut self, fix: Fix) -> Self {
120 self.fix = Some(fix);
121 self
122 }
123
124 /// Set the lint level for this violation (used by the engine)
125 pub(crate) const fn set_lint_level(&mut self, level: LintLevel) {
126 self.lint_level = level;
127 }
128
129 #[must_use]
130 pub(crate) fn to_source_span(&self) -> SourceSpan {
131 SourceSpan::from((self.span.start, self.span.end - self.span.start))
132 }
133}
134
135/// An automated fix that can be applied to resolve a violation
136///
137/// # Display Format
138///
139/// Fixes are displayed as:
140///
141/// ```text
142/// ℹ Available fix: [explanation]
143/// - old code
144/// + new code
145/// ```
146///
147/// # Important
148///
149/// - `explanation`: User-facing text shown in "Available fix:" (can be
150/// multi-line)
151/// - `replacements[].replacement_text`: Actual code written to the file
152///
153/// These should be different! The explanation describes the change,
154/// the `replacement_text` is the actual code.
155///
156/// # Example
157///
158/// ```rust,ignore
159/// Fix::with_explanation(
160/// format!("Use pipeline input ($in) instead of parameter (${}):\n {}", param, full_code),
161/// vec![Replacement::new(span, actual_code_to_write)]
162/// )
163/// ```
164#[derive(Debug, Clone)]
165pub struct Fix {
166 /// User-facing explanation of what this fix does
167 /// Shown in the "ℹ Available fix:" line (can be multi-line)
168 pub explanation: Cow<'static, str>,
169
170 /// The actual code replacements to apply to the file
171 pub replacements: Vec<Replacement>,
172}
173
174impl Fix {
175 /// Create a fix with an explanation and code replacements
176 ///
177 /// # Arguments
178 ///
179 /// * `explanation` - User-facing description (shown in "Available fix:")
180 /// * `replacements` - Actual code changes to apply to the file
181 ///
182 /// # Example
183 ///
184 /// ```rust,ignore
185 /// Fix::with_explanation(
186 /// "Replace with is-not-empty",
187 /// vec![Replacement::new(span, "is-not-empty")]
188 /// )
189 /// ```
190 #[must_use]
191 pub fn with_explanation(
192 explanation: impl Into<Cow<'static, str>>,
193 replacements: Vec<Replacement>,
194 ) -> Self {
195 Self {
196 explanation: explanation.into(),
197 replacements,
198 }
199 }
200}
201
202/// A single code replacement to apply when fixing a violation
203///
204/// # Important
205///
206/// The `replacement_text` field contains the ACTUAL CODE that will be written
207/// to the file at the specified span. This is not shown directly to the user
208/// (except in the before/after diff), but is what gets applied when the fix
209/// runs.
210#[derive(Debug, Clone)]
211pub struct Replacement {
212 /// Span in source code to replace
213 pub span: Span,
214
215 /// New text to insert at this location
216 /// This is the ACTUAL CODE written to the file when the fix is applied
217 pub replacement_text: Cow<'static, str>,
218}
219
220impl Replacement {
221 /// Create a new code replacement
222 ///
223 /// # Arguments
224 ///
225 /// * `span` - Location in source code to replace
226 /// * `replacement_text` - Actual code to write (not a description!)
227 ///
228 /// # Example
229 ///
230 /// ```rust,ignore
231 /// Replacement::new(param_span, "[]") // Replace "[x: int]" with "[]"
232 /// ```
233 #[must_use]
234 pub fn new(span: Span, replacement_text: impl Into<Cow<'static, str>>) -> Self {
235 Self {
236 span,
237 replacement_text: replacement_text.into(),
238 }
239 }
240}