annotate_snippets/renderer/
preprocess.rs1use alloc::borrow::Cow;
2use alloc::vec::Vec;
3use core::cmp::max;
4
5use super::source_map::{AnnotatedLineInfo, SourceMap, SplicedLines, TrimmedPatch};
6use crate::{Annotation, Element, Group, Message, Origin, Padding, Patch, Snippet};
7
8pub(crate) struct Preprocessed<'a> {
9 pub(crate) max_line_num: Option<usize>,
10 pub(crate) report_primary_path: Option<&'a Cow<'a, str>>,
11 pub(crate) groups: Vec<PreprocessedGroup<'a>>,
12}
13
14impl Preprocessed<'_> {
15 pub(crate) fn preprocess<'a>(groups: &'a [Group<'a>]) -> Preprocessed<'a> {
16 preprocess(groups)
17 }
18}
19
20pub(crate) struct PreprocessedGroup<'a> {
21 pub(crate) group: &'a Group<'a>,
22 pub(crate) elements: Vec<PreprocessedElement<'a>>,
23 pub(crate) primary_path: Option<&'a Cow<'a, str>>,
24 pub(crate) max_depth: usize,
25}
26
27pub(crate) enum PreprocessedElement<'a> {
28 Message(&'a Message<'a>),
29 Cause(
30 (
31 &'a Snippet<'a, Annotation<'a>>,
32 SourceMap<'a>,
33 Vec<AnnotatedLineInfo<'a>>,
34 ),
35 ),
36 Suggestion(
37 (
38 &'a Snippet<'a, Patch<'a>>,
39 SourceMap<'a>,
40 SplicedLines<'a>,
41 DisplaySuggestion,
42 ),
43 ),
44 Origin(&'a Origin<'a>),
45 Padding(Padding),
46}
47
48fn preprocess<'a>(groups: &'a [Group<'a>]) -> Preprocessed<'a> {
49 let mut max_line_num = None;
50 let mut report_primary_path = None;
51 let mut out = Vec::with_capacity(groups.len());
52 for group in groups {
53 let mut elements = Vec::with_capacity(group.elements.len());
54 let mut primary_path = None;
55 let mut max_depth = 0;
56 for element in &group.elements {
57 match element {
58 Element::Message(message) => {
59 elements.push(PreprocessedElement::Message(message));
60 }
61 Element::Cause(cause) => {
62 let sm = SourceMap::new(&cause.source, cause.line_start);
63 let (depth, annotated_lines) =
64 sm.annotated_lines(cause.markers.clone(), cause.fold);
65
66 if cause.fold {
67 let end = cause
68 .markers
69 .iter()
70 .map(|a| a.span.end)
71 .max()
72 .unwrap_or(cause.source.len())
73 .min(cause.source.len());
74
75 if cause.line_numbering {
76 max_line_num = Some(max(
77 cause.line_start + newline_count(&cause.source[..end]),
78 max_line_num.unwrap_or(0),
79 ));
80 }
81 } else {
82 if cause.line_numbering {
83 max_line_num = Some(max(
84 cause.line_start + newline_count(&cause.source),
85 max_line_num.unwrap_or(0),
86 ));
87 }
88 }
89
90 if primary_path.is_none() {
91 primary_path = Some(cause.path.as_ref());
92 }
93 max_depth = max(depth, max_depth);
94 elements.push(PreprocessedElement::Cause((cause, sm, annotated_lines)));
95 }
96 Element::Suggestion(suggestion) => {
97 let sm = SourceMap::new(&suggestion.source, suggestion.line_start);
98 if let Some(spliced_lines) =
99 sm.splice_lines(suggestion.markers.clone(), suggestion.fold)
100 {
101 let display_suggestion = DisplaySuggestion::new(
102 &spliced_lines.complete,
103 &spliced_lines.patches,
104 &sm,
105 );
106
107 if suggestion.line_numbering {
108 if suggestion.fold {
109 if let Some(first) = spliced_lines.patches.first() {
110 let (l_start, _) =
111 sm.span_to_locations(first.original_span.clone());
112 let nc = newline_count(&spliced_lines.complete);
113 let sugg_max_line_num = match display_suggestion {
114 DisplaySuggestion::Underline => l_start.line,
115 DisplaySuggestion::Diff => {
116 let file_lines = sm.span_to_lines(first.span.clone());
117 file_lines
118 .last()
119 .map_or(l_start.line + nc, |line| line.line_index)
120 }
121 DisplaySuggestion::None => l_start.line + nc,
122 DisplaySuggestion::Add => l_start.line + nc,
123 };
124 max_line_num =
125 Some(max(sugg_max_line_num, max_line_num.unwrap_or(0)));
126 }
127 } else {
128 max_line_num = Some(max(
129 suggestion.line_start + newline_count(&spliced_lines.complete),
130 max_line_num.unwrap_or(0),
131 ));
132 }
133 }
134
135 elements.push(PreprocessedElement::Suggestion((
136 suggestion,
137 sm,
138 spliced_lines,
139 display_suggestion,
140 )));
141 }
142 }
143 Element::Origin(origin) => {
144 if primary_path.is_none() {
145 primary_path = Some(Some(&origin.path));
146 }
147 elements.push(PreprocessedElement::Origin(origin));
148 }
149 Element::Padding(padding) => {
150 elements.push(PreprocessedElement::Padding(padding.clone()));
151 }
152 }
153 }
154 let group = PreprocessedGroup {
155 group,
156 elements,
157 primary_path: primary_path.unwrap_or_default(),
158 max_depth,
159 };
160 if report_primary_path.is_none() && group.primary_path.is_some() {
161 report_primary_path = group.primary_path;
162 }
163 out.push(group);
164 }
165
166 Preprocessed {
167 max_line_num,
168 report_primary_path,
169 groups: out,
170 }
171}
172
173fn newline_count(body: &str) -> usize {
174 #[cfg(feature = "simd")]
175 {
176 let trailing_newline = body.ends_with('\n');
179 memchr::memchr_iter(b'\n', body.as_bytes()).count() - usize::from(trailing_newline)
180 }
181 #[cfg(not(feature = "simd"))]
182 {
183 body.lines().count().saturating_sub(1)
184 }
185}
186
187#[derive(Clone, Copy, Debug)]
188pub(crate) enum DisplaySuggestion {
189 Underline,
190 Diff,
191 None,
192 Add,
193}
194
195impl DisplaySuggestion {
196 fn new(complete: &str, patches: &[TrimmedPatch<'_>], sm: &SourceMap<'_>) -> Self {
197 let has_deletion = patches
198 .iter()
199 .any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
200 let is_multiline = complete.lines().count() > 1;
201 if has_deletion && !is_multiline {
202 Self::Diff
203 } else if patches.len() == 1
204 && patches.first().is_some_and(|p| {
205 p.replacement.ends_with('\n') && p.replacement.trim() == complete.trim()
206 })
207 {
208 Self::Add
210 } else if (patches.len() != 1 || patches[0].replacement.trim() != complete.trim())
211 && !is_multiline
212 {
213 Self::Underline
214 } else {
215 Self::None
216 }
217 }
218}
219
220#[cfg(test)]
221mod test {
222 use super::newline_count;
223
224 #[test]
225 fn ensure_newline_count_correct() {
226 let source = r#"
227 cargo-features = ["path-bases"]
228
229 [package]
230 name = "foo"
231 version = "0.5.0"
232 authors = ["wycats@example.com"]
233
234 [dependencies]
235 bar = { base = '^^not-valid^^', path = 'bar' }
236 "#;
237 assert_eq!(newline_count(source), 10);
238
239 assert_eq!(newline_count(""), 0);
240
241 assert_eq!(newline_count("one"), 0);
242
243 assert_eq!(newline_count("one\n"), 0);
244
245 assert_eq!(newline_count("one\ntwo"), 1);
246
247 assert_eq!(newline_count("one\ntwo\n"), 1);
248
249 assert_eq!(newline_count("one\n\n"), 1);
250
251 assert_eq!(newline_count("one\r\ntwo\r\n"), 1);
252 }
253}