aboutsummaryrefslogtreecommitdiff
path: root/bin/src/fix.rs
blob: 478dbd9a816078fd9f027e3bd8edf7ba9fe6227c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use std::borrow::Cow;

use lib::{Report, LINTS};
use rnix::{parser::ParseError as RnixParseErr, TextRange, WalkEvent};

type Source<'a> = Cow<'a, str>;

fn collect_fixes(source: &str) -> Result<Vec<Report>, RnixParseErr> {
    let parsed = rnix::parse(source).as_result()?;

    Ok(parsed
        .node()
        .preorder_with_tokens()
        .filter_map(|event| match event {
            WalkEvent::Enter(child) => LINTS.get(&child.kind()).map(|rules| {
                rules
                    .iter()
                    .filter_map(|rule| rule.validate(&child))
                    .filter(|report| report.total_suggestion_range().is_some())
                    .collect::<Vec<_>>()
            }),
            _ => None,
        })
        .flatten()
        .collect())
}

fn reorder(mut reports: Vec<Report>) -> Vec<Report> {
    use std::collections::VecDeque;

    reports.sort_by(|a, b| {
        let a_range = a.range();
        let b_range = b.range();
        a_range.end().partial_cmp(&b_range.end()).unwrap()
    });

    reports
        .into_iter()
        .fold(VecDeque::new(), |mut deque: VecDeque<Report>, new_elem| {
            let front = deque.front();
            let new_range = new_elem.range();
            if let Some(front_range) = front.map(|f| f.range()) {
                if new_range.start() > front_range.end() {
                    deque.push_front(new_elem);
                }
            } else {
                deque.push_front(new_elem);
            }
            deque
        })
        .into()
}

#[derive(Debug)]
pub struct FixResult<'a> {
    pub src: Source<'a>,
    pub fixed: Vec<Fixed>,
}

#[derive(Debug, Clone)]
pub struct Fixed {
    pub at: TextRange,
    pub code: u32,
}

impl<'a> FixResult<'a> {
    fn empty(src: Source<'a>) -> Self {
        Self { src, fixed: vec![] }
    }
}

fn next(mut src: Source) -> Result<FixResult, RnixParseErr> {
    let all_reports = collect_fixes(&src)?;

    if all_reports.is_empty() {
        return Ok(FixResult::empty(src));
    }

    let reordered = reorder(all_reports);

    let fixed = reordered
        .iter()
        .map(|r| Fixed {
            at: r.range(),
            code: r.code,
        })
        .collect::<Vec<_>>();
    for report in reordered {
        report.apply(src.to_mut());
    }

    Ok(FixResult {
        src,
        fixed
    })
}

pub fn fix(src: &str) -> Result<FixResult, RnixParseErr> {
    let src = Cow::from(src);
    let _ = rnix::parse(&src).as_result()?;
    let mut initial = FixResult::empty(src);

    while let Ok(next_result) = next(initial.src)  {
        if next_result.fixed.is_empty() {
            return Ok(next_result);
        } else {
            initial = FixResult::empty(next_result.src);
        }
    }

    unreachable!("a fix caused a syntax error, please report a bug");
}