1pub mod json;
2pub mod rdjson;
3use serde::{Deserialize, Serialize};
4use serde_repr::*;
5
6use crate::config::toggle;
7
8#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Default, Clone, Copy)]
9#[repr(u8)]
10pub enum Severity {
11 #[default]
12 Pass = 0,
13 Error = 1,
14 Warning = 2,
15}
16
17impl Severity {
18 pub fn is_error(&self) -> bool {
19 self == &Severity::Error
20 }
21
22 pub fn is_warning(&self) -> bool {
23 self == &Severity::Warning
24 }
25
26 pub fn is_pass(&self) -> bool {
27 self == &Severity::Pass
28 }
29}
30
31#[derive(Serialize, Deserialize, Clone)]
32pub struct LineResult {
33 #[serde(rename(serialize = "l"))]
34 pub line: usize,
35 #[serde(rename(serialize = "c"))]
36 pub col: usize,
37 pub new: String,
38 pub old: String,
39 pub severity: Severity,
40}
41
42pub trait Results {
43 fn push(&mut self, line_result: LineResult);
44 fn ignore(&mut self, str: &str);
45 fn error(&mut self, err: &str);
46 #[allow(unused)]
47 fn to_string(&self) -> String;
48 fn is_lint(&self) -> bool;
49 fn get_toggle(&self) -> &toggle::Toggle;
50 fn toggle_mut(&mut self) -> &mut toggle::Toggle;
51
52 fn move_cursor(&mut self, part: &str) -> (usize, usize);
54
55 fn toggle(&mut self, new_toggle: &toggle::Toggle) {
58 if new_toggle.is_none() {
59 return;
60 }
61
62 *self.toggle_mut() = new_toggle.clone();
63 }
64
65 fn toggle_merge_for_codeblock(&mut self) {
66 self.toggle_mut()
67 .merge(toggle::Toggle::disable(vec!["halfwidth-punctuation"]));
68 }
69
70 fn is_enabled(&self) -> bool {
72 match self.get_toggle().match_rule("") {
73 Some(enable) => enable,
74 _ => true,
75 }
76 }
77}
78
79#[derive(Serialize, Deserialize)]
80pub struct FormatResult {
81 pub out: String,
82 pub error: String,
83 #[serde(skip)]
84 pub raw: String,
85 #[serde(skip)]
86 pub enable: bool,
87 #[serde(skip)]
88 pub toggle: toggle::Toggle,
89}
90
91#[derive(Serialize, Deserialize, Clone)]
92pub struct LintResult {
93 #[serde(skip)]
94 pub raw: String,
95 pub filepath: String,
96 pub lines: Vec<LineResult>,
97 pub error: String,
98 #[serde(skip)]
99 pub enable: bool,
100 #[serde(skip)]
101 pub toggle: toggle::Toggle,
102 #[serde(skip)]
104 line: usize,
105 #[serde(skip)]
107 col: usize,
108}
109
110impl FormatResult {
111 pub fn new(raw: &str) -> Self {
112 FormatResult {
113 raw: String::from(raw),
114 out: String::from(""),
115 error: String::from(""),
116 enable: true,
117 toggle: toggle::Toggle::default(),
118 }
119 }
120
121 #[allow(dead_code)]
122 pub fn has_error(&self) -> bool {
123 !self.error.is_empty()
124 }
125}
126
127impl Results for FormatResult {
128 fn push(&mut self, line_result: LineResult) {
129 self.out.push_str(line_result.new.as_str());
130 }
131
132 fn ignore(&mut self, part: &str) {
133 self.out.push_str(part);
134 self.move_cursor(part);
135 }
136
137 fn error(&mut self, err: &str) {
138 self.out = String::from(&self.raw);
140 self.error = String::from(err);
141 }
142
143 fn to_string(&self) -> String {
144 self.out.to_string()
145 }
146
147 fn is_lint(&self) -> bool {
148 false
149 }
150
151 fn toggle_mut(&mut self) -> &mut toggle::Toggle {
152 &mut self.toggle
153 }
154
155 fn get_toggle(&self) -> &toggle::Toggle {
156 &self.toggle
157 }
158
159 fn move_cursor(&mut self, _part: &str) -> (usize, usize) {
160 (0, 0)
161 }
162}
163
164impl LintResult {
165 pub fn new(raw: &str) -> Self {
166 LintResult {
167 line: 1,
168 col: 1,
169 filepath: String::from(""),
170 raw: String::from(raw),
171 lines: Vec::new(),
172 error: String::from(""),
173 enable: true,
174 toggle: toggle::Toggle::default(),
175 }
176 }
177
178 #[allow(dead_code)]
179 pub fn to_json(&self) -> String {
180 match serde_json::to_string(self) {
181 Ok(json) => json,
182 _ => String::from("{}"),
183 }
184 }
185
186 #[allow(dead_code)]
187 pub fn to_json_pretty(&self) -> String {
188 match serde_json::to_string_pretty(self) {
189 Ok(json) => json,
190 _ => String::from("{}"),
191 }
192 }
193
194 #[allow(dead_code)]
195 pub fn to_diff(&self, no_diff_bg_color: bool) -> String {
196 let mut out = String::new();
197 let filepath = self.filepath.replace("./", "");
198
199 for line in self.lines.iter() {
200 out.push_str(&format!("{}:{}:{}\n", filepath, line.line, line.col));
201
202 let out_str = crate::diff::diff_line_result(line, no_diff_bg_color);
203 out.push_str(&out_str);
204 }
205
206 out
207 }
208
209 #[allow(dead_code)]
210 pub fn has_error(&self) -> bool {
211 !self.error.is_empty()
212 }
213
214 pub fn errors_count(&self) -> usize {
216 self.lines.iter().filter(|l| l.severity.is_error()).count()
217 }
218
219 pub fn warnings_count(&self) -> usize {
221 self.lines
222 .iter()
223 .filter(|l| l.severity.is_warning())
224 .count()
225 }
226}
227
228impl Results for LintResult {
229 fn push(&mut self, line_result: LineResult) {
230 self.lines.push(line_result);
231 }
232
233 fn ignore(&mut self, part: &str) {
234 self.move_cursor(part);
236 }
237
238 fn error(&mut self, err: &str) {
239 self.error = String::from(err);
240 }
241
242 fn to_string(&self) -> String {
243 String::from("")
244 }
245
246 fn is_lint(&self) -> bool {
247 true
248 }
249
250 fn get_toggle(&self) -> &toggle::Toggle {
251 &self.toggle
252 }
253
254 fn toggle_mut(&mut self) -> &mut toggle::Toggle {
255 &mut self.toggle
256 }
257
258 fn move_cursor(&mut self, part: &str) -> (usize, usize) {
260 let (l, c, has_new_line) = line_col(part);
261
262 let prev_line = self.line;
263 let prev_col = self.col;
264
265 self.line += l;
266 if has_new_line {
267 self.col = c;
268 } else {
269 self.col += c;
270 }
271 (prev_line, prev_col)
272 }
273}
274
275fn line_col(part: &str) -> (usize, usize, bool) {
280 let mut chars = part.chars().peekable();
281
282 let mut line_col = (0, 0);
283 let mut has_new_line = false;
284
285 loop {
286 match chars.next() {
287 Some('\r') => {
288 if let Some(&'\n') = chars.peek() {
289 chars.next();
290
291 line_col = (line_col.0 + 1, 1);
292 has_new_line = true;
293 } else {
294 line_col = (line_col.0, line_col.1 + 1);
295 }
296 }
297 Some('\n') => {
298 line_col = (line_col.0 + 1, 1);
299 has_new_line = true;
300 }
301 Some(_c) => {
302 line_col = (line_col.0, line_col.1 + 1);
303 }
304 None => {
305 break;
306 }
307 }
308 }
309
310 (line_col.0, line_col.1, has_new_line)
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn test_severity() {
319 assert_eq!(serde_json::to_string(&Severity::Error).unwrap(), "1");
320 assert_eq!(serde_json::to_string(&Severity::Warning).unwrap(), "2");
321
322 assert!(Severity::Error.is_error());
323 assert!(!Severity::Error.is_warning());
324 assert!(Severity::Warning.is_warning());
325 assert!(!Severity::Warning.is_error());
326 }
327
328 #[test]
329 fn test_move_cursor() {
330 let mut out = LintResult::new("");
331 assert_eq!((out.line, out.col), (1, 1));
332
333 assert_eq!(out.move_cursor(""), (1, 1));
334 assert_eq!((out.line, out.col), (1, 1));
335
336 let raw = r#"Foo
337Hello world
338This is "#;
339 assert_eq!(out.move_cursor(raw), (1, 1));
340 assert_eq!((out.line, out.col), (3, 9));
341
342 let raw = "Hello\nworld\r\nHello world\nHello world";
343 assert_eq!(out.move_cursor(raw), (3, 9));
344 assert_eq!((out.line, out.col), (6, 12));
345
346 let raw = "Hello";
347 assert_eq!(out.move_cursor(raw), (6, 12));
348 assert_eq!((out.line, out.col), (6, 17));
349
350 let raw = "\nHello\n\naaa\n";
351 assert_eq!(out.move_cursor(raw), (6, 17));
352 assert_eq!((out.line, out.col), (10, 1));
353 }
354}