| //! Types and function used to emit pretty diagnostics for `bindgen`. |
| //! |
| //! The entry point of this module is the [`Diagnostic`] type. |
| |
| use std::fmt::Write; |
| use std::io::{self, BufRead, BufReader}; |
| use std::{borrow::Cow, fs::File}; |
| |
| use annotate_snippets::{ |
| display_list::{DisplayList, FormatOptions}, |
| snippet::{Annotation, Slice as ExtSlice, Snippet}, |
| }; |
| |
| use annotate_snippets::snippet::AnnotationType; |
| |
| #[derive(Clone, Copy, Debug)] |
| pub(crate) enum Level { |
| Error, |
| Warn, |
| Info, |
| Note, |
| Help, |
| } |
| |
| impl From<Level> for AnnotationType { |
| fn from(level: Level) -> Self { |
| match level { |
| Level::Error => Self::Error, |
| Level::Warn => Self::Warning, |
| Level::Info => Self::Info, |
| Level::Note => Self::Note, |
| Level::Help => Self::Help, |
| } |
| } |
| } |
| |
| /// A `bindgen` diagnostic. |
| #[derive(Default)] |
| pub(crate) struct Diagnostic<'a> { |
| title: Option<(Cow<'a, str>, Level)>, |
| slices: Vec<Slice<'a>>, |
| footer: Vec<(Cow<'a, str>, Level)>, |
| } |
| |
| impl<'a> Diagnostic<'a> { |
| /// Add a title to the diagnostic and set its type. |
| pub(crate) fn with_title( |
| &mut self, |
| title: impl Into<Cow<'a, str>>, |
| level: Level, |
| ) -> &mut Self { |
| self.title = Some((title.into(), level)); |
| self |
| } |
| |
| /// Add a slice of source code to the diagnostic. |
| pub(crate) fn add_slice(&mut self, slice: Slice<'a>) -> &mut Self { |
| self.slices.push(slice); |
| self |
| } |
| |
| /// Add a footer annotation to the diagnostic. This annotation will have its own type. |
| pub(crate) fn add_annotation( |
| &mut self, |
| msg: impl Into<Cow<'a, str>>, |
| level: Level, |
| ) -> &mut Self { |
| self.footer.push((msg.into(), level)); |
| self |
| } |
| |
| /// Print this diagnostic. |
| /// |
| /// The diagnostic is printed using `cargo:warning` if `bindgen` is being invoked by a build |
| /// script or using `eprintln` otherwise. |
| pub(crate) fn display(&self) { |
| std::thread_local! { |
| static INVOKED_BY_BUILD_SCRIPT: bool = std::env::var_os("CARGO_CFG_TARGET_ARCH").is_some(); |
| } |
| |
| let mut title = None; |
| let mut footer = vec![]; |
| let mut slices = vec![]; |
| if let Some((msg, level)) = &self.title { |
| title = Some(Annotation { |
| id: Some("bindgen"), |
| label: Some(msg.as_ref()), |
| annotation_type: (*level).into(), |
| }) |
| } |
| |
| for (msg, level) in &self.footer { |
| footer.push(Annotation { |
| id: None, |
| label: Some(msg.as_ref()), |
| annotation_type: (*level).into(), |
| }); |
| } |
| |
| // add additional info that this is generated by bindgen |
| // so as to not confuse with rustc warnings |
| footer.push(Annotation { |
| id: None, |
| label: Some("This diagnostic was generated by bindgen."), |
| annotation_type: AnnotationType::Info, |
| }); |
| |
| for slice in &self.slices { |
| if let Some(source) = &slice.source { |
| slices.push(ExtSlice { |
| source: source.as_ref(), |
| line_start: slice.line.unwrap_or_default(), |
| origin: slice.filename.as_deref(), |
| annotations: vec![], |
| fold: false, |
| }) |
| } |
| } |
| |
| let snippet = Snippet { |
| title, |
| footer, |
| slices, |
| opt: FormatOptions { |
| color: true, |
| ..Default::default() |
| }, |
| }; |
| let dl = DisplayList::from(snippet); |
| |
| if INVOKED_BY_BUILD_SCRIPT.with(Clone::clone) { |
| // This is just a hack which hides the `warning:` added by cargo at the beginning of |
| // every line. This should be fine as our diagnostics already have a colorful title. |
| // FIXME (pvdrz): Could it be that this doesn't work in other languages? |
| let hide_warning = "\r \r"; |
| let string = dl.to_string(); |
| for line in string.lines() { |
| println!("cargo:warning={}{}", hide_warning, line); |
| } |
| } else { |
| eprintln!("{}\n", dl); |
| } |
| } |
| } |
| |
| /// A slice of source code. |
| #[derive(Default)] |
| pub(crate) struct Slice<'a> { |
| source: Option<Cow<'a, str>>, |
| filename: Option<String>, |
| line: Option<usize>, |
| } |
| |
| impl<'a> Slice<'a> { |
| /// Set the source code. |
| pub(crate) fn with_source( |
| &mut self, |
| source: impl Into<Cow<'a, str>>, |
| ) -> &mut Self { |
| self.source = Some(source.into()); |
| self |
| } |
| |
| /// Set the file, line and column. |
| pub(crate) fn with_location( |
| &mut self, |
| mut name: String, |
| line: usize, |
| col: usize, |
| ) -> &mut Self { |
| write!(name, ":{}:{}", line, col) |
| .expect("Writing to a string cannot fail"); |
| self.filename = Some(name); |
| self.line = Some(line); |
| self |
| } |
| } |
| |
| pub(crate) fn get_line( |
| filename: &str, |
| line: usize, |
| ) -> io::Result<Option<String>> { |
| let file = BufReader::new(File::open(filename)?); |
| if let Some(line) = file.lines().nth(line.wrapping_sub(1)) { |
| return line.map(Some); |
| } |
| |
| Ok(None) |
| } |