blob: e9fdd6a81e39f26366ec85effc7478a053c1de66 [file] [log] [blame]
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);
}
}