Lints and suggestions for the Nix programming language
1use std::{borrow::Cow, convert::TryFrom};
2
3use lib::Report;
4use rnix::{Root, TextSize, WalkEvent};
5
6use crate::{err::SingleFixErr, fix::Source, utils};
7
8pub struct SingleFixResult<'δ> {
9 pub src: Source<'δ>,
10}
11
12fn pos_to_byte(line: usize, col: usize, src: &str) -> Result<TextSize, SingleFixErr> {
13 let mut byte: TextSize = TextSize::of("");
14 for (l, _) in src
15 .split_inclusive('\n')
16 .zip(1..)
17 .take_while(|(_, i)| *i < line)
18 {
19 byte += TextSize::of(l);
20 }
21 byte += TextSize::try_from(col).map_err(|_| SingleFixErr::Conversion(col))?;
22
23 if usize::from(byte) >= src.len() {
24 Err(SingleFixErr::OutOfBounds(line, col))
25 } else {
26 Ok(byte)
27 }
28}
29
30fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> {
31 // we don't really need the source to form a completely parsed tree
32 let parsed = Root::parse(src);
33 let lints = utils::lint_map();
34
35 parsed
36 .syntax()
37 .preorder_with_tokens()
38 .filter_map(|event| match event {
39 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
40 rules
41 .iter()
42 .filter_map(|rule| rule.validate(&child))
43 .find(|report| report.total_suggestion_range().is_some())
44 }),
45 WalkEvent::Leave(_) => None,
46 })
47 .flatten()
48 .find(|report| report.total_diagnostic_range().unwrap().contains(offset))
49 .ok_or(SingleFixErr::NoOp)
50}
51
52pub fn single(line: usize, col: usize, src: &str) -> Result<SingleFixResult<'_>, SingleFixErr> {
53 let mut src = Cow::from(src);
54 let offset = pos_to_byte(line, col, &src)?;
55 let report = find(offset, &src)?;
56
57 report.apply(src.to_mut());
58
59 Ok(SingleFixResult { src })
60}