1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5use thiserror::Error;
6
7pub type LintResult<T> = Result<T, LintError>;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Fix {
12 pub start_byte: usize,
14 pub end_byte: usize,
16 pub replacement: String,
18 pub description: String,
20 pub safe: bool,
22}
23
24impl Fix {
25 pub fn replace(
27 start: usize,
28 end: usize,
29 replacement: impl Into<String>,
30 description: impl Into<String>,
31 safe: bool,
32 ) -> Self {
33 Self {
34 start_byte: start,
35 end_byte: end,
36 replacement: replacement.into(),
37 description: description.into(),
38 safe,
39 }
40 }
41
42 pub fn insert(
44 position: usize,
45 text: impl Into<String>,
46 description: impl Into<String>,
47 safe: bool,
48 ) -> Self {
49 Self {
50 start_byte: position,
51 end_byte: position,
52 replacement: text.into(),
53 description: description.into(),
54 safe,
55 }
56 }
57
58 pub fn delete(start: usize, end: usize, description: impl Into<String>, safe: bool) -> Self {
60 Self {
61 start_byte: start,
62 end_byte: end,
63 replacement: String::new(),
64 description: description.into(),
65 safe,
66 }
67 }
68
69 pub fn is_insertion(&self) -> bool {
71 self.start_byte == self.end_byte && !self.replacement.is_empty()
72 }
73
74 pub fn is_deletion(&self) -> bool {
76 self.replacement.is_empty() && self.start_byte < self.end_byte
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Diagnostic {
83 pub level: DiagnosticLevel,
84 pub message: String,
85 pub file: PathBuf,
86 pub line: usize,
87 pub column: usize,
88 pub rule: String,
89 pub suggestion: Option<String>,
90 #[serde(default)]
92 pub fixes: Vec<Fix>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub assumption: Option<String>,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
104pub enum DiagnosticLevel {
105 Error,
106 Warning,
107 Info,
108}
109
110impl Diagnostic {
111 pub fn error(file: PathBuf, line: usize, column: usize, rule: &str, message: String) -> Self {
112 Self {
113 level: DiagnosticLevel::Error,
114 message,
115 file,
116 line,
117 column,
118 rule: rule.to_string(),
119 suggestion: None,
120 fixes: Vec::new(),
121 assumption: None,
122 }
123 }
124
125 pub fn warning(file: PathBuf, line: usize, column: usize, rule: &str, message: String) -> Self {
126 Self {
127 level: DiagnosticLevel::Warning,
128 message,
129 file,
130 line,
131 column,
132 rule: rule.to_string(),
133 suggestion: None,
134 fixes: Vec::new(),
135 assumption: None,
136 }
137 }
138
139 pub fn info(file: PathBuf, line: usize, column: usize, rule: &str, message: String) -> Self {
140 Self {
141 level: DiagnosticLevel::Info,
142 message,
143 file,
144 line,
145 column,
146 rule: rule.to_string(),
147 suggestion: None,
148 fixes: Vec::new(),
149 assumption: None,
150 }
151 }
152
153 pub fn with_suggestion(mut self, suggestion: String) -> Self {
154 self.suggestion = Some(suggestion);
155 self
156 }
157
158 pub fn with_assumption(mut self, assumption: impl Into<String>) -> Self {
163 self.assumption = Some(assumption.into());
164 self
165 }
166
167 pub fn with_fix(mut self, fix: Fix) -> Self {
169 self.fixes.push(fix);
170 self
171 }
172
173 pub fn with_fixes(mut self, fixes: impl IntoIterator<Item = Fix>) -> Self {
175 self.fixes.extend(fixes);
176 self
177 }
178
179 pub fn has_fixes(&self) -> bool {
181 !self.fixes.is_empty()
182 }
183
184 pub fn has_safe_fixes(&self) -> bool {
186 self.fixes.iter().any(|f| f.safe)
187 }
188}
189
190#[derive(Error, Debug)]
192pub enum LintError {
193 #[error("Failed to read file: {path}")]
194 FileRead {
195 path: PathBuf,
196 #[source]
197 source: std::io::Error,
198 },
199
200 #[error("Failed to write file: {path}")]
201 FileWrite {
202 path: PathBuf,
203 #[source]
204 source: std::io::Error,
205 },
206
207 #[error("Refusing to read symlink: {path}")]
208 FileSymlink { path: PathBuf },
209
210 #[error("File too large: {path} ({size} bytes, limit {limit} bytes)")]
211 FileTooBig {
212 path: PathBuf,
213 size: u64,
214 limit: u64,
215 },
216
217 #[error("Not a regular file: {path}")]
218 FileNotRegular { path: PathBuf },
219
220 #[error(transparent)]
221 Other(anyhow::Error),
222}