mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			189 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
//! 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)
 | 
						|
}
 |