use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::containment::matches_forbidden;
use crate::crate_scope::{extern_resolution, file_extern_scope, resolve_principal};
use crate::finding::{ExposureKind, SemanticFact, shape_finding, sort_faceted_facts};
use crate::module_resolve::resolve_module_items_with_files;
use crate::resolve::{
ShapeExposure, UseMap, canonical_path_str, collect_uses, validate_path_operands,
};
fn group_items_by_branch(
items_with_files: &[(syn::Item, PathBuf, usize)],
) -> HashMap<usize, Vec<syn::Item>> {
let mut items_by_branch: HashMap<usize, Vec<syn::Item>> = HashMap::new();
for (item, _file, branch) in items_with_files {
items_by_branch
.entry(*branch)
.or_default()
.push(item.clone());
}
items_by_branch
}
fn uses_by_branch(items_with_files: &[(syn::Item, PathBuf, usize)]) -> HashMap<usize, UseMap> {
group_items_by_branch(items_with_files)
.iter()
.map(|(branch, branch_items)| (*branch, collect_uses(branch_items)))
.collect()
}
pub(crate) fn matches_forbidden_principal(
exposure: &ShapeExposure,
uses: &UseMap,
module: &str,
resolution: &crate::crate_scope::ExternResolution,
file_scope: &crate::crate_scope::FileExternScope,
forbidden: &[String],
) -> bool {
forbidden.is_empty()
|| exposure.principals.iter().any(|path| {
resolve_principal(path, uses, module, resolution, file_scope)
.iter()
.any(|canonical| matches_forbidden(canonical, forbidden))
})
}
pub(crate) fn shape_module_findings<E>(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
collect: impl Fn(&syn::Item, &str, &UseMap, usize, &mut Vec<E>) -> Result<(), String>,
render: impl Fn(E) -> SemanticFact,
) -> Result<Vec<(SemanticFact, PathBuf)>, String> {
let items_with_files =
resolve_module_items_with_files(src_dir, root_file, module, crate_package)?;
let uses_by_branch = uses_by_branch(&items_with_files);
let mut collected: Vec<(E, PathBuf)> = Vec::new();
for (ordinal, (item, file, branch)) in items_with_files.iter().enumerate() {
let uses = &uses_by_branch[branch];
let mut buf = Vec::new();
collect(item, module, uses, ordinal, &mut buf)?;
collected.extend(buf.into_iter().map(|exposure| (exposure, file.clone())));
}
let mut findings: Vec<(SemanticFact, PathBuf)> = collected
.into_iter()
.map(|(exposure, file)| (render(exposure), file))
.collect();
sort_faceted_facts(&mut findings)?;
Ok(findings)
}
pub(crate) fn operand_module_findings(
src_dir: &Path,
root_file: &Path,
module: &str,
forbidden: &[String],
crate_package: &str,
dep_names: &[String],
(fact_kind, collect): (
ExposureKind,
impl Fn(&syn::Item, &str, &UseMap, usize, &mut Vec<ShapeExposure>),
),
) -> Result<Vec<(SemanticFact, PathBuf)>, String> {
validate_path_operands(forbidden)?;
let items_with_files =
resolve_module_items_with_files(src_dir, root_file, module, crate_package)?;
let uses_by_branch = uses_by_branch(&items_with_files);
let items_by_branch = group_items_by_branch(&items_with_files);
let resolution = extern_resolution(src_dir, root_file, crate_package, dep_names)?;
let file_scopes: HashMap<usize, crate::crate_scope::FileExternScope> = items_by_branch
.iter()
.map(|(branch, branch_items)| (*branch, file_extern_scope(&resolution, branch_items)))
.collect();
let forbidden: Vec<String> = forbidden.iter().map(|f| canonical_path_str(f)).collect();
let mut exposures: Vec<(ShapeExposure, PathBuf, usize)> = Vec::new();
for (ordinal, (item, file, branch)) in items_with_files.iter().enumerate() {
let uses = &uses_by_branch[branch];
let mut buf = Vec::new();
collect(item, module, uses, ordinal, &mut buf);
exposures.extend(
buf.into_iter()
.map(|exposure| (exposure, file.clone(), *branch)),
);
}
let mut findings: Vec<(SemanticFact, PathBuf)> = exposures
.into_iter()
.filter(|(exposure, _file, branch)| {
let uses = &uses_by_branch[branch];
let file_scope = &file_scopes[branch];
matches_forbidden_principal(exposure, uses, module, &resolution, file_scope, &forbidden)
})
.map(|(exposure, file, _branch)| (shape_finding(exposure, fact_kind), file))
.collect();
sort_faceted_facts(&mut findings)?;
Ok(findings)
}