diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/codespan-reporting/src/term/views.rs | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/codespan-reporting/src/term/views.rs')
-rw-r--r-- | third_party/rust/codespan-reporting/src/term/views.rs | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/third_party/rust/codespan-reporting/src/term/views.rs b/third_party/rust/codespan-reporting/src/term/views.rs new file mode 100644 index 0000000000..f09d9582a6 --- /dev/null +++ b/third_party/rust/codespan-reporting/src/term/views.rs @@ -0,0 +1,478 @@ +use std::ops::Range; + +use crate::diagnostic::{Diagnostic, LabelStyle}; +use crate::files::{Error, Files, Location}; +use crate::term::renderer::{Locus, MultiLabel, Renderer, SingleLabel}; +use crate::term::Config; + +/// Count the number of decimal digits in `n`. +fn count_digits(mut n: usize) -> usize { + let mut count = 0; + while n != 0 { + count += 1; + n /= 10; // remove last digit + } + count +} + +/// Output a richly formatted diagnostic, with source code previews. +pub struct RichDiagnostic<'diagnostic, 'config, FileId> { + diagnostic: &'diagnostic Diagnostic<FileId>, + config: &'config Config, +} + +impl<'diagnostic, 'config, FileId> RichDiagnostic<'diagnostic, 'config, FileId> +where + FileId: Copy + PartialEq, +{ + pub fn new( + diagnostic: &'diagnostic Diagnostic<FileId>, + config: &'config Config, + ) -> RichDiagnostic<'diagnostic, 'config, FileId> { + RichDiagnostic { diagnostic, config } + } + + pub fn render<'files>( + &self, + files: &'files impl Files<'files, FileId = FileId>, + renderer: &mut Renderer<'_, '_>, + ) -> Result<(), Error> + where + FileId: 'files, + { + use std::collections::BTreeMap; + + struct LabeledFile<'diagnostic, FileId> { + file_id: FileId, + start: usize, + name: String, + location: Location, + num_multi_labels: usize, + lines: BTreeMap<usize, Line<'diagnostic>>, + max_label_style: LabelStyle, + } + + impl<'diagnostic, FileId> LabeledFile<'diagnostic, FileId> { + fn get_or_insert_line( + &mut self, + line_index: usize, + line_range: Range<usize>, + line_number: usize, + ) -> &mut Line<'diagnostic> { + self.lines.entry(line_index).or_insert_with(|| Line { + range: line_range, + number: line_number, + single_labels: vec![], + multi_labels: vec![], + // This has to be false by default so we know if it must be rendered by another condition already. + must_render: false, + }) + } + } + + struct Line<'diagnostic> { + number: usize, + range: std::ops::Range<usize>, + // TODO: How do we reuse these allocations? + single_labels: Vec<SingleLabel<'diagnostic>>, + multi_labels: Vec<(usize, LabelStyle, MultiLabel<'diagnostic>)>, + must_render: bool, + } + + // TODO: Make this data structure external, to allow for allocation reuse + let mut labeled_files = Vec::<LabeledFile<'_, _>>::new(); + // Keep track of the outer padding to use when rendering the + // snippets of source code. + let mut outer_padding = 0; + + // Group labels by file + for label in &self.diagnostic.labels { + let start_line_index = files.line_index(label.file_id, label.range.start)?; + let start_line_number = files.line_number(label.file_id, start_line_index)?; + let start_line_range = files.line_range(label.file_id, start_line_index)?; + let end_line_index = files.line_index(label.file_id, label.range.end)?; + let end_line_number = files.line_number(label.file_id, end_line_index)?; + let end_line_range = files.line_range(label.file_id, end_line_index)?; + + outer_padding = std::cmp::max(outer_padding, count_digits(start_line_number)); + outer_padding = std::cmp::max(outer_padding, count_digits(end_line_number)); + + // NOTE: This could be made more efficient by using an associative + // data structure like a hashmap or B-tree, but we use a vector to + // preserve the order that unique files appear in the list of labels. + let labeled_file = match labeled_files + .iter_mut() + .find(|labeled_file| label.file_id == labeled_file.file_id) + { + Some(labeled_file) => { + // another diagnostic also referenced this file + if labeled_file.max_label_style > label.style + || (labeled_file.max_label_style == label.style + && labeled_file.start > label.range.start) + { + // this label has a higher style or has the same style but starts earlier + labeled_file.start = label.range.start; + labeled_file.location = files.location(label.file_id, label.range.start)?; + labeled_file.max_label_style = label.style; + } + labeled_file + } + None => { + // no other diagnostic referenced this file yet + labeled_files.push(LabeledFile { + file_id: label.file_id, + start: label.range.start, + name: files.name(label.file_id)?.to_string(), + location: files.location(label.file_id, label.range.start)?, + num_multi_labels: 0, + lines: BTreeMap::new(), + max_label_style: label.style, + }); + // this unwrap should never fail because we just pushed an element + labeled_files + .last_mut() + .expect("just pushed an element that disappeared") + } + }; + + if start_line_index == end_line_index { + // Single line + // + // ```text + // 2 │ (+ test "") + // │ ^^ expected `Int` but found `String` + // ``` + let label_start = label.range.start - start_line_range.start; + // Ensure that we print at least one caret, even when we + // have a zero-length source range. + let label_end = + usize::max(label.range.end - start_line_range.start, label_start + 1); + + let line = labeled_file.get_or_insert_line( + start_line_index, + start_line_range, + start_line_number, + ); + + // Ensure that the single line labels are lexicographically + // sorted by the range of source code that they cover. + let index = match line.single_labels.binary_search_by(|(_, range, _)| { + // `Range<usize>` doesn't implement `Ord`, so convert to `(usize, usize)` + // to piggyback off its lexicographic comparison implementation. + (range.start, range.end).cmp(&(label_start, label_end)) + }) { + // If the ranges are the same, order the labels in reverse + // to how they were originally specified in the diagnostic. + // This helps with printing in the renderer. + Ok(index) | Err(index) => index, + }; + + line.single_labels + .insert(index, (label.style, label_start..label_end, &label.message)); + + // If this line is not rendered, the SingleLabel is not visible. + line.must_render = true; + } else { + // Multiple lines + // + // ```text + // 4 │ fizz₁ num = case (mod num 5) (mod num 3) of + // │ ╭─────────────^ + // 5 │ │ 0 0 => "FizzBuzz" + // 6 │ │ 0 _ => "Fizz" + // 7 │ │ _ 0 => "Buzz" + // 8 │ │ _ _ => num + // │ ╰──────────────^ `case` clauses have incompatible types + // ``` + + let label_index = labeled_file.num_multi_labels; + labeled_file.num_multi_labels += 1; + + // First labeled line + let label_start = label.range.start - start_line_range.start; + + let start_line = labeled_file.get_or_insert_line( + start_line_index, + start_line_range.clone(), + start_line_number, + ); + + start_line.multi_labels.push(( + label_index, + label.style, + MultiLabel::Top(label_start), + )); + + // The first line has to be rendered so the start of the label is visible. + start_line.must_render = true; + + // Marked lines + // + // ```text + // 5 │ │ 0 0 => "FizzBuzz" + // 6 │ │ 0 _ => "Fizz" + // 7 │ │ _ 0 => "Buzz" + // ``` + for line_index in (start_line_index + 1)..end_line_index { + let line_range = files.line_range(label.file_id, line_index)?; + let line_number = files.line_number(label.file_id, line_index)?; + + outer_padding = std::cmp::max(outer_padding, count_digits(line_number)); + + let line = labeled_file.get_or_insert_line(line_index, line_range, line_number); + + line.multi_labels + .push((label_index, label.style, MultiLabel::Left)); + + // The line should be rendered to match the configuration of how much context to show. + line.must_render |= + // Is this line part of the context after the start of the label? + line_index - start_line_index <= self.config.start_context_lines + || + // Is this line part of the context before the end of the label? + end_line_index - line_index <= self.config.end_context_lines; + } + + // Last labeled line + // + // ```text + // 8 │ │ _ _ => num + // │ ╰──────────────^ `case` clauses have incompatible types + // ``` + let label_end = label.range.end - end_line_range.start; + + let end_line = labeled_file.get_or_insert_line( + end_line_index, + end_line_range, + end_line_number, + ); + + end_line.multi_labels.push(( + label_index, + label.style, + MultiLabel::Bottom(label_end, &label.message), + )); + + // The last line has to be rendered so the end of the label is visible. + end_line.must_render = true; + } + } + + // Header and message + // + // ```text + // error[E0001]: unexpected type in `+` application + // ``` + renderer.render_header( + None, + self.diagnostic.severity, + self.diagnostic.code.as_deref(), + self.diagnostic.message.as_str(), + )?; + + // Source snippets + // + // ```text + // ┌─ test:2:9 + // │ + // 2 │ (+ test "") + // │ ^^ expected `Int` but found `String` + // │ + // ``` + let mut labeled_files = labeled_files.into_iter().peekable(); + while let Some(labeled_file) = labeled_files.next() { + let source = files.source(labeled_file.file_id)?; + let source = source.as_ref(); + + // Top left border and locus. + // + // ```text + // ┌─ test:2:9 + // ``` + if !labeled_file.lines.is_empty() { + renderer.render_snippet_start( + outer_padding, + &Locus { + name: labeled_file.name, + location: labeled_file.location, + }, + )?; + renderer.render_snippet_empty( + outer_padding, + self.diagnostic.severity, + labeled_file.num_multi_labels, + &[], + )?; + } + + let mut lines = labeled_file + .lines + .iter() + .filter(|(_, line)| line.must_render) + .peekable(); + + while let Some((line_index, line)) = lines.next() { + renderer.render_snippet_source( + outer_padding, + line.number, + &source[line.range.clone()], + self.diagnostic.severity, + &line.single_labels, + labeled_file.num_multi_labels, + &line.multi_labels, + )?; + + // Check to see if we need to render any intermediate stuff + // before rendering the next line. + if let Some((next_line_index, _)) = lines.peek() { + match next_line_index.checked_sub(*line_index) { + // Consecutive lines + Some(1) => {} + // One line between the current line and the next line + Some(2) => { + // Write a source line + let file_id = labeled_file.file_id; + + // This line was not intended to be rendered initially. + // To render the line right, we have to get back the original labels. + let labels = labeled_file + .lines + .get(&(line_index + 1)) + .map_or(&[][..], |line| &line.multi_labels[..]); + + renderer.render_snippet_source( + outer_padding, + files.line_number(file_id, line_index + 1)?, + &source[files.line_range(file_id, line_index + 1)?], + self.diagnostic.severity, + &[], + labeled_file.num_multi_labels, + labels, + )?; + } + // More than one line between the current line and the next line. + Some(_) | None => { + // Source break + // + // ```text + // · + // ``` + renderer.render_snippet_break( + outer_padding, + self.diagnostic.severity, + labeled_file.num_multi_labels, + &line.multi_labels, + )?; + } + } + } + } + + // Check to see if we should render a trailing border after the + // final line of the snippet. + if labeled_files.peek().is_none() && self.diagnostic.notes.is_empty() { + // We don't render a border if we are at the final newline + // without trailing notes, because it would end up looking too + // spaced-out in combination with the final new line. + } else { + // Render the trailing snippet border. + renderer.render_snippet_empty( + outer_padding, + self.diagnostic.severity, + labeled_file.num_multi_labels, + &[], + )?; + } + } + + // Additional notes + // + // ```text + // = expected type `Int` + // found type `String` + // ``` + for note in &self.diagnostic.notes { + renderer.render_snippet_note(outer_padding, note)?; + } + renderer.render_empty() + } +} + +/// Output a short diagnostic, with a line number, severity, and message. +pub struct ShortDiagnostic<'diagnostic, FileId> { + diagnostic: &'diagnostic Diagnostic<FileId>, + show_notes: bool, +} + +impl<'diagnostic, FileId> ShortDiagnostic<'diagnostic, FileId> +where + FileId: Copy + PartialEq, +{ + pub fn new( + diagnostic: &'diagnostic Diagnostic<FileId>, + show_notes: bool, + ) -> ShortDiagnostic<'diagnostic, FileId> { + ShortDiagnostic { + diagnostic, + show_notes, + } + } + + pub fn render<'files>( + &self, + files: &'files impl Files<'files, FileId = FileId>, + renderer: &mut Renderer<'_, '_>, + ) -> Result<(), Error> + where + FileId: 'files, + { + // Located headers + // + // ```text + // test:2:9: error[E0001]: unexpected type in `+` application + // ``` + let mut primary_labels_encountered = 0; + let labels = self.diagnostic.labels.iter(); + for label in labels.filter(|label| label.style == LabelStyle::Primary) { + primary_labels_encountered += 1; + + renderer.render_header( + Some(&Locus { + name: files.name(label.file_id)?.to_string(), + location: files.location(label.file_id, label.range.start)?, + }), + self.diagnostic.severity, + self.diagnostic.code.as_deref(), + self.diagnostic.message.as_str(), + )?; + } + + // Fallback to printing a non-located header if no primary labels were encountered + // + // ```text + // error[E0002]: Bad config found + // ``` + if primary_labels_encountered == 0 { + renderer.render_header( + None, + self.diagnostic.severity, + self.diagnostic.code.as_deref(), + self.diagnostic.message.as_str(), + )?; + } + + if self.show_notes { + // Additional notes + // + // ```text + // = expected type `Int` + // found type `String` + // ``` + for note in &self.diagnostic.notes { + renderer.render_snippet_note(0, note)?; + } + } + + Ok(()) + } +} |