blob: 00bd2535a8d31ec7c3fd88b639fa6b73c42c0b91 [file] [log] [blame]
pub mod cfg;
mod diags;
use cfg::ValidConfig;
pub use diags::Code;
use crate::{
diag::{CfgCoord, Check, ErrorSink, Label, Pack},
LintLevel,
};
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::*;
// early out if everything is allowed
if ctx.cfg.unknown_registry == LintLevel::Allow && ctx.cfg.unknown_git == LintLevel::Allow {
return;
}
let mut sink = sink.into();
// scan through each crate and check the source of it
// keep track of which sources are actually encountered, so we can emit a
// warning if the user has listed a source that no crates are actually using
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 (i, krate) in ctx.krates.krates().enumerate() {
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[i];
Label::primary(ctx.krate_spans.file_id, span.source..span.total.end)
.with_message("source")
};
// get allowed list of sources to check
let (lint_level, type_name) = if source.is_registry() {
(ctx.cfg.unknown_registry, "registry")
} else if let Some(spec) = source.git_spec() {
// Ensure the git source has at least the minimum specification
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;
};
// check if the source URL is in the list of allowed sources
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);
// Show the location of the config that allowed this source, unless
// it's crates.io since that will be a vast majority of crates and
// is the default, so we might not have a real source location anyways
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)
}) {
if let Some(ind) = ctx
.cfg
.allowed_orgs
.iter()
.position(|(sorgt, sorgn)| orgt == *sorgt && sorgn.value.as_str() == 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 someone in is in a situation that they want to disallow crates
// from crates.io, they should set the allowed registries manually
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))
})
}