aboutsummaryrefslogtreecommitdiff
path: root/bin/src/fix
diff options
context:
space:
mode:
Diffstat (limited to 'bin/src/fix')
-rw-r--r--bin/src/fix/all.rs86
-rw-r--r--bin/src/fix/single.rs59
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 @@
1use std::borrow::Cow;
2
3use lib::{Report, LINTS};
4use rnix::{parser::ParseError as RnixParseErr, WalkEvent};
5
6use crate::fix::{Fixed, FixResult};
7
8fn 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
28fn 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
54impl<'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
81pub 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 @@
1use std::{borrow::Cow, convert::TryFrom};
2
3use lib::{Report, LINTS};
4use rnix::{TextRange, TextSize};
5
6use crate::err::SingleFixErr;
7use crate::fix::Source;
8
9pub struct SingleFixResult<'δ> {
10 pub src: Source<'δ>,
11}
12
13fn 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
27fn 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
49pub 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}