pub mod cfg;
mod diags;
use cfg::ValidConfig;
pub use diags::Code;
use crate::{
LintLevel,
diag::{CfgCoord, Check, ErrorSink, Label, Pack},
};
const CRATES_IO_URL: &str = "https://github.com/rust-lang/crates.io-index";
pub fn check(ctx: crate::CheckCtx<'_, ValidConfig>, sink: impl Into<ErrorSink>) {
use bitvec::prelude::*;
if ctx.cfg.unknown_registry == LintLevel::Allow && ctx.cfg.unknown_git == LintLevel::Allow {
return;
}
let mut sink = sink.into();
let mut source_hits: BitVec = BitVec::repeat(false, ctx.cfg.allowed_sources.len());
let mut org_hits: BitVec = BitVec::repeat(false, ctx.cfg.allowed_orgs.len());
let min_git_spec = ctx.cfg.required_git_spec.as_ref().map(|rgs| {
(
rgs.value,
CfgCoord {
span: rgs.span,
file: ctx.cfg.file_id,
},
)
});
for krate in ctx.krates.krates() {
let source = match &krate.source {
Some(source) => source,
None => continue,
};
let mut pack = Pack::with_kid(Check::Sources, krate.id.clone());
let mut sl = None;
let label = || {
let span = ctx.krate_spans.lock_span(&krate.id);
Label::primary(ctx.krate_spans.lock_id, span.source).with_message("source")
};
let (lint_level, type_name) = if source.is_registry() {
(ctx.cfg.unknown_registry, "registry")
} else if let Some(spec) = source.git_spec() {
if let Some((min, cfg_coord)) = &min_git_spec {
if spec < *min {
pack.push(diags::BelowMinimumRequiredSpec {
src_label: sl.get_or_insert_with(label),
min_spec: *min,
actual_spec: spec,
min_spec_cfg: cfg_coord.clone(),
});
}
}
(ctx.cfg.unknown_git, "git")
} else {
continue;
};
let diag: crate::diag::Diag = if let Some(ind) = ctx
.cfg
.allowed_sources
.iter()
.position(|src| krate.matches_url(&src.url.value, src.exact))
{
source_hits.as_mut_bitslice().set(ind, true);
if krate.is_crates_io() {
continue;
}
diags::ExplicitlyAllowedSource {
src_label: sl.get_or_insert_with(label),
type_name,
allow_cfg: CfgCoord {
file: ctx.cfg.file_id,
span: ctx.cfg.allowed_sources[ind].url.span,
},
}
.into()
} else if let Some((orgt, orgname)) = krate.source.as_ref().and_then(|s| {
let crate::Source::Git { url, .. } = s else {
return None;
};
get_org(url)
}) {
let lowered = (!orgname.is_ascii()).then(|| orgname.to_lowercase());
if let Some(ind) = ctx.cfg.allowed_orgs.iter().position(|(sorgt, sorgn)| {
let s = sorgn.value.as_str();
if orgt != *sorgt || s.len() != orgname.len() {
return false;
}
if let Some(orgname_lower) = &lowered {
orgname_lower == &s.to_lowercase()
} else {
s.eq_ignore_ascii_case(orgname)
}
}) {
org_hits.as_mut_bitslice().set(ind, true);
diags::SourceAllowedByOrg {
src_label: sl.get_or_insert_with(label),
org_cfg: CfgCoord {
file: ctx.cfg.file_id,
span: ctx.cfg.allowed_orgs[ind].1.span,
},
}
.into()
} else {
diags::SourceNotExplicitlyAllowed {
src_label: sl.get_or_insert_with(label),
lint_level,
type_name,
}
.into()
}
} else {
diags::SourceNotExplicitlyAllowed {
src_label: sl.get_or_insert_with(label),
lint_level,
type_name,
}
.into()
};
pack.push(diag);
sink.push(pack);
}
let mut pack = Pack::new(Check::Sources);
for src in source_hits
.into_iter()
.zip(ctx.cfg.allowed_sources.into_iter())
.filter_map(|(hit, src)| if !hit { Some(src) } else { None })
{
if src.url.as_ref().as_str() == CRATES_IO_URL {
continue;
}
pack.push(diags::UnmatchedAllowSource {
allow_src_cfg: CfgCoord {
span: src.url.span,
file: ctx.cfg.file_id,
},
});
}
for (org_type, orgs) in org_hits
.into_iter()
.zip(ctx.cfg.allowed_orgs.into_iter())
.filter_map(|(hit, src)| if !hit { Some(src) } else { None })
{
pack.push(diags::UnmatchedAllowOrg {
allow_org_cfg: CfgCoord {
span: orgs.span,
file: ctx.cfg.file_id,
},
org_type,
});
}
if !pack.is_empty() {
sink.push(pack);
}
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum OrgType {
Github,
Gitlab,
Bitbucket,
}
use std::fmt;
impl fmt::Display for OrgType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Github => "github.com",
Self::Gitlab => "gitlab.com",
Self::Bitbucket => "bitbucket.org",
})
}
}
fn get_org(url: &url::Url) -> Option<(OrgType, &str)> {
url.domain().and_then(|domain| {
let org_type = if domain.eq_ignore_ascii_case("github.com") {
OrgType::Github
} else if domain.eq_ignore_ascii_case("gitlab.com") {
OrgType::Gitlab
} else if domain.eq_ignore_ascii_case("bitbucket.org") {
OrgType::Bitbucket
} else {
return None;
};
url.path_segments()
.and_then(|mut f| f.next())
.map(|org| (org_type, org))
})
}