//! Findings, the run report, and the emitter checks report through. //! //! The emitter is deliberately thin: checks name a tag and give a message; //! metadata, output letter and explanations come from the registry, so //! findings from the native engine and findings parsed from the wrapped //! lintian share one shape and one rendering path. use crate::lint::check; use crate::lint::tag::Tag; /// Where a finding comes from. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Origin { /// Emitted by pkh's native Rust checks. Native, /// Parsed from the wrapped lintian's output. Lintian, } /// One lint finding, whatever produced it. #[derive(Debug, Clone)] pub struct Finding { /// Output letter (`E`, `W`, `I`, `P`, `X`, `O`, `C`): severity for /// native findings, verbatim from the output line in wrapper mode. pub letter: char, /// Stable tag name (`missing-debian-copyright-file`). pub tag_name: String, /// Free-form details after the tag name; empty when the tag stands alone. pub message: String, /// Package the finding belongs to (`hello`). pub package: String, /// Processable type lintian displays after the package name (`source`, /// `changes`, ...); None for binary findings, which lintian prints /// without a type. pub processable_type: Option, /// Explanation lines shown by `--info`: the `N:` blocks lintian attaches /// to the finding in wrapper mode, or the native tag's description and /// references. pub explanation: Vec, /// Native or parsed-from-lintian. pub origin: Origin, } impl Finding { /// Lowercase severity/classification name for this finding's letter, as /// used in `--fail-on` values and JSON output. pub fn severity_name(&self) -> &'static str { match self.letter { 'E' => "error", 'W' => "warning", 'I' => "info", 'P' => "pedantic", 'X' => "experimental", 'O' => "overridden", _ => "classification", } } } /// The full result of one lint run: every finding, from every source. pub struct LintReport { /// All findings in emission order: the wrapper's first, then the native /// ones (deduplicated by tag name against the wrapper's). pub findings: Vec, /// Display name of the linted source package. pub source_name: String, /// The wrapper was skipped because lintian is not installed; the /// renderer prints a notice and the run is native-only. pub wrapper_unavailable: bool, /// Run-level notes (`N:` lines): how the linted artifact was obtained, /// and similar context that is not a finding. pub notes: Vec, } /// Sink checks report findings through. Resolves tag metadata from the /// registry so checks only ever name the tag they mean. pub struct Emitter<'a> { package: String, findings: &'a mut Vec, } impl<'a> Emitter<'a> { /// Emitter for findings of `package` (e.g. the source package name), /// appending into `findings`. pub fn new(package: String, findings: &'a mut Vec) -> Emitter<'a> { Emitter { package, findings } } /// Emit `tag_name` with `message` as its detail line. Native findings /// always report on the source package; an unknown tag name (a registry /// bug) still produces a finding rather than panicking — the registry /// test makes that path unreachable in practice. pub fn tag(&mut self, tag_name: &str, message: impl Into) { let (letter, explanation) = match check::find_tag(tag_name) { Some(tag) => (tag.letter(), explanation_of(tag)), None => ('E', Vec::new()), }; self.findings.push(Finding { letter, tag_name: tag_name.to_string(), message: message.into(), package: self.package.clone(), processable_type: Some("source".to_string()), explanation, origin: Origin::Native, }); } } /// The explanation `--info` shows for a native tag: its description plus any /// references, prefixed like lintian's "Please refer to" pointers. fn explanation_of(tag: &Tag) -> Vec { let mut lines = vec![tag.description.to_string()]; for reference in tag.references { lines.push(format!("Please refer to {}", reference)); } lines }