use std::path::{Path, PathBuf};
use serde_json::Value;
use xuanji::{Outcome, Polarity, Violation};
use crate::collect::collect_item_async_exposures;
use crate::driver::run_boundaries;
use crate::dsl::AsyncExposureBoundary;
use crate::emit::{
MultiModuleViolationContext, SingleModuleViolationContext, push_multi_module_violations,
push_single_module_violations,
};
use crate::errors::unknown_module_error;
use crate::file_scope::{is_anchor_absent_from_unit, resolve_crate_units};
use crate::finding::{SemanticFact, sort_attributed_facts};
use crate::resolve::collect_uses;
use crate::rules::ASYNC_EXPOSURE_RULE;
use crate::scan::walk_subtree_modules;
use crate::shape_scan::shape_module_findings;
pub fn check_async_exposure(boundaries: &[AsyncExposureBoundary], manifest_path: &Path) -> Outcome {
run_boundaries(boundaries, manifest_path, check_async_exposure_boundary)
}
pub(crate) fn check_async_exposure_boundary(
metadata: &Value,
boundary: &AsyncExposureBoundary,
violations: &mut Vec<Violation>,
) -> Result<(), String> {
let (_package, units) = resolve_crate_units(metadata, &boundary.crate_package)?;
let mut governed_somewhere = false;
let mut deferred: Option<String> = None;
for (root_file, src_dir, unit) in &units {
let unit_outcome = (|| -> Result<(), String> {
let src_dir = src_dir.as_path();
let unit = unit.as_str();
let rule_key = boundary.rule_key();
if boundary.including_submodules() {
let findings = async_exposure_subtree_findings(
src_dir,
root_file,
&boundary.module,
&boundary.crate_package,
)?;
push_multi_module_violations(
violations,
MultiModuleViolationContext {
target: &boundary.module,
rule: ASYNC_EXPOSURE_RULE,
rule_key,
reason: &boundary.reason,
severity: boundary.severity,
anchor: boundary.anchor(),
polarity: Polarity::DenyBreach,
crate_package: &boundary.crate_package,
unit,
},
findings,
);
return Ok(());
}
let findings = async_exposure_module_findings(
src_dir,
root_file,
&boundary.module,
&boundary.crate_package,
)?;
push_single_module_violations(
violations,
SingleModuleViolationContext {
module: &boundary.module,
rule: ASYNC_EXPOSURE_RULE,
rule_key,
reason: &boundary.reason,
severity: boundary.severity,
anchor: boundary.anchor(),
crate_package: &boundary.crate_package,
unit,
},
findings,
);
Ok(())
})();
match unit_outcome {
Ok(()) => governed_somewhere = true,
Err(reason)
if is_anchor_absent_from_unit(
&reason,
&unknown_module_error(&boundary.module, &boundary.crate_package),
) =>
{
if deferred.is_none() {
deferred = Some(reason);
}
}
Err(reason) => return Err(reason),
}
}
match deferred {
Some(reason) if !governed_somewhere => Err(reason),
_ => Ok(()),
}
}
pub(crate) fn async_exposure_subtree_findings(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<Vec<(SemanticFact, String, PathBuf)>, String> {
let modules = walk_subtree_modules(src_dir, root_file, module, crate_package)?;
let mut findings: Vec<(SemanticFact, String, PathBuf)> = Vec::new();
for (mod_path, items, file) in &modules {
let uses = collect_uses(items);
for item in items {
let mut collected = Vec::new();
collect_item_async_exposures(item, mod_path, &uses, 0, &mut collected)?;
findings.extend(
collected
.into_iter()
.map(|finding| (finding, mod_path.clone(), file.clone())),
);
}
}
sort_attributed_facts(&mut findings)?;
Ok(findings)
}
pub(crate) fn async_exposure_module_findings(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<Vec<(SemanticFact, PathBuf)>, String> {
shape_module_findings(
src_dir,
root_file,
module,
crate_package,
collect_item_async_exposures,
|identity| identity,
)
}