From c2f0582d1907dbef69e9ad42ba9d4301337fe1e8 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 23 Oct 2021 12:41:52 +0530 Subject: initial implementation of multipass code fixer --- bin/src/fix.rs | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 bin/src/fix.rs (limited to 'bin/src/fix.rs') diff --git a/bin/src/fix.rs b/bin/src/fix.rs new file mode 100644 index 0000000..478dbd9 --- /dev/null +++ b/bin/src/fix.rs @@ -0,0 +1,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, 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::>() + }), + _ => None, + }) + .flatten() + .collect()) +} + +fn reorder(mut reports: Vec) -> Vec { + 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, 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, +} + +#[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 { + 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::>(); + for report in reordered { + report.apply(src.to_mut()); + } + + Ok(FixResult { + src, + fixed + }) +} + +pub fn fix(src: &str) -> Result { + 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"); +} -- cgit v1.2.3