cargo-deny 0.14.20

Cargo plugin to help you manage large dependency graphs
Documentation
pub mod general;
mod grapher;
mod sink;

pub use grapher::{cs_diag_to_json, diag_to_json, write_graph_as_text, InclusionGrapher};
pub use sink::{DiagnosticOverrides, ErrorSink};

use std::{collections::HashMap, ops::Range};

use crate::{Kid, Krates, Span};
pub use codespan_reporting::diagnostic::Severity;

pub use codespan::FileId;

pub type Diagnostic = codespan_reporting::diagnostic::Diagnostic<FileId>;
pub type Label = codespan_reporting::diagnostic::Label<FileId>;
pub type Files = codespan::Files<String>;
// Map of crate id => (path to cargo.toml, synthetic cargo.toml content, map(cratename => crate span))
pub type RawCargoSpans = HashMap<Kid, (krates::Utf8PathBuf, String, HashMap<String, Range<usize>>)>;
// Same as RawCargoSpans but path to cargo.toml and cargo.toml content replaced with FileId
pub type CargoSpans = HashMap<Kid, (FileId, HashMap<String, Range<usize>>)>;
/// Channel type used to send diagnostics from checks
pub type PackChannel = crossbeam::channel::Sender<Pack>;

impl From<crate::LintLevel> for Severity {
    fn from(ll: crate::LintLevel) -> Self {
        match ll {
            crate::LintLevel::Warn => Severity::Warning,
            crate::LintLevel::Deny => Severity::Error,
            crate::LintLevel::Allow => Severity::Note,
        }
    }
}

pub struct GraphNode {
    pub kid: Kid,
    pub feature: Option<String>,
}

pub struct Diag {
    pub diag: Diagnostic,
    pub graph_nodes: smallvec::SmallVec<[GraphNode; 2]>,
    pub extra: Option<(&'static str, serde_json::Value)>,
    pub with_features: bool,
}

impl Diag {
    pub(crate) fn new(diag: Diagnostic) -> Self {
        Self {
            diag,
            graph_nodes: smallvec::SmallVec::new(),
            extra: None,
            with_features: false,
        }
    }
}

impl From<Diagnostic> for Diag {
    fn from(d: Diagnostic) -> Self {
        Diag::new(d)
    }
}

pub enum Check {
    Advisories,
    Bans,
    Licenses,
    Sources,
}

pub struct Pack {
    pub check: Check,
    pub(crate) diags: Vec<Diag>,
    kid: Option<Kid>,
}

impl Pack {
    #[inline]
    pub(crate) fn new(check: Check) -> Self {
        Self {
            check,
            diags: Vec::new(),
            kid: None,
        }
    }

    #[inline]
    pub(crate) fn with_kid(check: Check, kid: Kid) -> Self {
        Self {
            check,
            diags: Vec::new(),
            kid: Some(kid),
        }
    }

    #[inline]
    pub(crate) fn push(&mut self, diag: impl Into<Diag>) -> &mut Diag {
        let mut diag = diag.into();
        if diag.graph_nodes.is_empty() {
            if let Some(kid) = self.kid.clone() {
                diag.graph_nodes.push(GraphNode { kid, feature: None });
            }
        }

        self.diags.push(diag);
        self.diags.last_mut().unwrap()
    }

    #[inline]
    pub(crate) fn len(&self) -> usize {
        self.diags.len()
    }

    #[inline]
    pub(crate) fn is_empty(&self) -> bool {
        self.diags.is_empty()
    }

    #[inline]
    pub fn iter(&self) -> impl Iterator<Item = &Diag> {
        self.diags.iter()
    }
}

impl IntoIterator for Pack {
    type Item = Diag;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.diags.into_iter()
    }
}

impl<T> From<(Check, T)> for Pack
where
    T: Into<Diag>,
{
    fn from((check, t): (Check, T)) -> Self {
        Self {
            check,
            diags: vec![t.into()],
            kid: None,
        }
    }
}

pub struct KrateSpan {
    pub total: Span,
    pub source: usize,
}

pub struct KrateSpans {
    spans: Vec<KrateSpan>,
    pub file_id: FileId,
}

impl std::ops::Index<usize> for KrateSpans {
    type Output = KrateSpan;

    #[inline]
    fn index(&self, i: usize) -> &Self::Output {
        &self.spans[i]
    }
}

impl KrateSpans {
    pub fn with_spans(spans: Vec<KrateSpan>, id: FileId) -> Self {
        Self { spans, file_id: id }
    }

    pub fn synthesize(krates: &Krates) -> (Vec<KrateSpan>, String, RawCargoSpans) {
        use std::fmt::Write;

        let mut sl = String::with_capacity(4 * 1024);
        let mut spans = Vec::with_capacity(krates.len());
        let mut cargo_spans = RawCargoSpans::new();

        let mut krates: Vec<_> = krates.krates().collect();
        // [Krates::krates] guarantees the krates to be ordered by name but we
        // want the outputs of diagnostics to also be stable in regards to
        // their version, so we do an additional sort for that here.
        krates.sort_unstable_by_key(|a| (&a.name, &a.version));
        for krate in krates {
            let span_start = sl.len();
            let source = if krate.source.is_some() {
                krate.id.source()
            } else {
                krate.manifest_path.parent().unwrap().as_str()
            };

            writeln!(sl, "{} {} {source}", krate.name, krate.version)
                .expect("unable to synthesize lockfile");

            let total = span_start..sl.len() - 1;
            let source = total.end - source.len();
            spans.push(KrateSpan {
                total: total.into(),
                source,
            });

            let mut sl2 = String::with_capacity(4 * 1024);
            let mut deps_map = HashMap::new();

            for dep in &krate.deps {
                let span_start = sl2.len();
                writeln!(sl2, "{} = '{}'", dep.name, dep.req)
                    .expect("unable to synthesize Cargo.toml");
                let span_end = sl2.len() - 1;
                deps_map.insert(dep.name.clone(), span_start..span_end);
            }

            cargo_spans.insert(
                krate.id.clone(),
                (krate.manifest_path.clone(), sl2, deps_map),
            );
        }

        (spans, sl, cargo_spans)
    }

    #[inline]
    pub fn label_for_index(&self, krate_index: usize, msg: impl Into<String>) -> Label {
        Label::secondary(self.file_id, self.spans[krate_index].total).with_message(msg)
    }

    #[inline]
    pub fn get_coord(&self, krate_index: usize) -> KrateCoord {
        KrateCoord {
            file: self.file_id,
            span: self.spans[krate_index].total,
        }
    }
}

pub type KrateCoord = Coord;
pub type CfgCoord = Coord;

#[derive(Clone)]
pub struct Coord {
    pub file: FileId,
    pub span: Span,
}

impl Coord {
    pub(crate) fn into_label(self) -> Label {
        self.into()
    }
}

impl From<Coord> for Label {
    fn from(c: Coord) -> Self {
        Label::primary(c.file, c.span)
    }
}

struct NodePrint {
    node: krates::NodeId,
    edge: Option<krates::EdgeId>,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DiagnosticCode {
    Advisory(crate::advisories::Code),
    Bans(crate::bans::Code),
    License(crate::licenses::Code),
    Source(crate::sources::Code),
    General(general::Code),
}

impl DiagnosticCode {
    pub fn iter() -> impl Iterator<Item = Self> {
        use strum::IntoEnumIterator;
        crate::advisories::Code::iter()
            .map(Self::Advisory)
            .chain(crate::bans::Code::iter().map(Self::Bans))
            .chain(crate::licenses::Code::iter().map(Self::License))
            .chain(crate::sources::Code::iter().map(Self::Source))
            .chain(general::Code::iter().map(Self::General))
    }

    #[inline]
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Advisory(code) => code.into(),
            Self::Bans(code) => code.into(),
            Self::License(code) => code.into(),
            Self::Source(code) => code.into(),
            Self::General(code) => code.into(),
        }
    }
}

use std::fmt;

impl fmt::Display for DiagnosticCode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl std::str::FromStr for DiagnosticCode {
    type Err = strum::ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.parse::<crate::advisories::Code>()
            .map(Self::Advisory)
            .or_else(|_err| s.parse::<crate::bans::Code>().map(Self::Bans))
            .or_else(|_err| s.parse::<crate::licenses::Code>().map(Self::License))
            .or_else(|_err| s.parse::<crate::sources::Code>().map(Self::Source))
            .or_else(|_err| s.parse::<general::Code>().map(Self::General))
    }
}

#[cfg(test)]
mod test {

    #[test]
    fn codes_unique() {
        let mut unique = std::collections::BTreeSet::<&'static str>::new();

        for code in super::DiagnosticCode::iter() {
            if !unique.insert(code.as_str()) {
                panic!("existing code '{code}'");
            }
        }

        insta::assert_debug_snapshot!(unique);
    }
}