autocorrect/result/
mod.rs

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    /// Move and save current line,col return the previus line number
53    fn move_cursor(&mut self, part: &str) -> (usize, usize);
54
55    /// Toggle AutoCorrrect template enable or disable
56    /// If new toggle is None, ignore
57    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    /// Is AutoCorrrect current is enable
71    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    // For store line number in loop
103    #[serde(skip)]
104    line: usize,
105    // For store col number in loop
106    #[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        // Revert out to raw when has error, make sure return raw value.
139        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    /// Return number of errors
215    pub fn errors_count(&self) -> usize {
216        self.lines.iter().filter(|l| l.severity.is_error()).count()
217    }
218
219    /// Return number of warnings
220    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        // do nothing
235        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    /// Move the (line, col) with string part
259    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
275/// Calculate line and col number of a string part
276/// Fork from Pest for just count the part.
277///
278/// https://github.com/pest-parser/pest/blob/85b18aae23cc7b266c0b5252f9f74b7ab0000795/pest/src/position.rs#L135
279fn 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}