diff options
Diffstat (limited to 'bin/src/fix')
-rw-r--r-- | bin/src/fix/all.rs | 86 | ||||
-rw-r--r-- | bin/src/fix/single.rs | 59 |
2 files changed, 145 insertions, 0 deletions
diff --git a/bin/src/fix/all.rs b/bin/src/fix/all.rs new file mode 100644 index 0000000..8c0770d --- /dev/null +++ b/bin/src/fix/all.rs | |||
@@ -0,0 +1,86 @@ | |||
1 | use std::borrow::Cow; | ||
2 | |||
3 | use lib::{Report, LINTS}; | ||
4 | use rnix::{parser::ParseError as RnixParseErr, WalkEvent}; | ||
5 | |||
6 | use crate::fix::{Fixed, FixResult}; | ||
7 | |||
8 | fn collect_fixes(source: &str) -> Result<Vec<Report>, RnixParseErr> { | ||
9 | let parsed = rnix::parse(source).as_result()?; | ||
10 | |||
11 | Ok(parsed | ||
12 | .node() | ||
13 | .preorder_with_tokens() | ||
14 | .filter_map(|event| match event { | ||
15 | WalkEvent::Enter(child) => LINTS.get(&child.kind()).map(|rules| { | ||
16 | rules | ||
17 | .iter() | ||
18 | .filter_map(|rule| rule.validate(&child)) | ||
19 | .filter(|report| report.total_suggestion_range().is_some()) | ||
20 | .collect::<Vec<_>>() | ||
21 | }), | ||
22 | _ => None, | ||
23 | }) | ||
24 | .flatten() | ||
25 | .collect()) | ||
26 | } | ||
27 | |||
28 | fn reorder(mut reports: Vec<Report>) -> Vec<Report> { | ||
29 | use std::collections::VecDeque; | ||
30 | |||
31 | reports.sort_by(|a, b| { | ||
32 | let a_range = a.range(); | ||
33 | let b_range = b.range(); | ||
34 | a_range.end().partial_cmp(&b_range.end()).unwrap() | ||
35 | }); | ||
36 | |||
37 | reports | ||
38 | .into_iter() | ||
39 | .fold(VecDeque::new(), |mut deque: VecDeque<Report>, new_elem| { | ||
40 | let front = deque.front(); | ||
41 | let new_range = new_elem.range(); | ||
42 | if let Some(front_range) = front.map(|f| f.range()) { | ||
43 | if new_range.start() > front_range.end() { | ||
44 | deque.push_front(new_elem); | ||
45 | } | ||
46 | } else { | ||
47 | deque.push_front(new_elem); | ||
48 | } | ||
49 | deque | ||
50 | }) | ||
51 | .into() | ||
52 | } | ||
53 | |||
54 | impl<'a> Iterator for FixResult<'a> { | ||
55 | type Item = FixResult<'a>; | ||
56 | fn next(&mut self) -> Option<Self::Item> { | ||
57 | let all_reports = collect_fixes(&self.src).ok()?; | ||
58 | if all_reports.is_empty() { | ||
59 | return None; | ||
60 | } | ||
61 | |||
62 | let reordered = reorder(all_reports); | ||
63 | let fixed = reordered | ||
64 | .iter() | ||
65 | .map(|r| Fixed { | ||
66 | at: r.range(), | ||
67 | code: r.code, | ||
68 | }) | ||
69 | .collect::<Vec<_>>(); | ||
70 | for report in reordered { | ||
71 | report.apply(self.src.to_mut()); | ||
72 | } | ||
73 | |||
74 | Some(FixResult { | ||
75 | src: self.src.clone(), | ||
76 | fixed | ||
77 | }) | ||
78 | } | ||
79 | } | ||
80 | |||
81 | pub fn all(src: &str) -> Option<FixResult> { | ||
82 | let src = Cow::from(src); | ||
83 | let _ = rnix::parse(&src).as_result().ok()?; | ||
84 | let initial = FixResult::empty(src); | ||
85 | initial.into_iter().last() | ||
86 | } | ||
diff --git a/bin/src/fix/single.rs b/bin/src/fix/single.rs new file mode 100644 index 0000000..d430693 --- /dev/null +++ b/bin/src/fix/single.rs | |||
@@ -0,0 +1,59 @@ | |||
1 | use std::{borrow::Cow, convert::TryFrom}; | ||
2 | |||
3 | use lib::{Report, LINTS}; | ||
4 | use rnix::{TextRange, TextSize}; | ||
5 | |||
6 | use crate::err::SingleFixErr; | ||
7 | use crate::fix::Source; | ||
8 | |||
9 | pub struct SingleFixResult<'δ> { | ||
10 | pub src: Source<'δ>, | ||
11 | } | ||
12 | |||
13 | fn pos_to_byte(line: usize, col: usize, src: &str) -> Result<TextSize, SingleFixErr> { | ||
14 | let mut byte: TextSize = TextSize::of(""); | ||
15 | for (_, l) in src.lines().enumerate().take_while(|(i, _)| i <= &line) { | ||
16 | byte += TextSize::of(l); | ||
17 | } | ||
18 | byte += TextSize::try_from(col).map_err(|_| SingleFixErr::Conversion(col))?; | ||
19 | |||
20 | if usize::from(byte) >= src.len() { | ||
21 | Err(SingleFixErr::OutOfBounds(line, col)) | ||
22 | } else { | ||
23 | Ok(byte) | ||
24 | } | ||
25 | } | ||
26 | |||
27 | fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> { | ||
28 | // we don't really need the source to form a completely parsed tree | ||
29 | let parsed = rnix::parse(src); | ||
30 | |||
31 | let elem_at = parsed | ||
32 | .node() | ||
33 | .child_or_token_at_range(TextRange::empty(offset)) | ||
34 | .ok_or(SingleFixErr::NoOp)?; | ||
35 | |||
36 | LINTS | ||
37 | .get(&elem_at.kind()) | ||
38 | .map(|rules| { | ||
39 | rules | ||
40 | .iter() | ||
41 | .filter_map(|rule| rule.validate(&elem_at)) | ||
42 | .filter(|report| report.total_suggestion_range().is_some()) | ||
43 | .next() | ||
44 | }) | ||
45 | .flatten() | ||
46 | .ok_or(SingleFixErr::NoOp) | ||
47 | } | ||
48 | |||
49 | pub fn single(line: usize, col: usize, src: &str) -> Result<SingleFixResult, SingleFixErr> { | ||
50 | let mut src = Cow::from(src); | ||
51 | let offset = pos_to_byte(line, col, &*src)?; | ||
52 | let report = find(offset, &*src)?; | ||
53 | |||
54 | report.apply(src.to_mut()); | ||
55 | |||
56 | Ok(SingleFixResult { | ||
57 | src | ||
58 | }) | ||
59 | } | ||