use rustc_hash::FxHashMap;
use fallow_types::extract::ModuleInfo;
use crate::discover::FileId;
use crate::graph::ModuleGraph;
use crate::resolve::ResolvedModule;
use crate::results::MixedClientServerBarrel;
use crate::suppress::{IssueKind, SuppressionContext};
use super::server_only::is_server_only_module;
use super::{LineOffsetsMap, byte_offset_to_line_col};
const USE_CLIENT: &str = "use client";
#[must_use]
pub fn find_mixed_client_server_barrels(
graph: &ModuleGraph,
modules: &[ModuleInfo],
resolved_modules: &[ResolvedModule],
declared_deps: &rustc_hash::FxHashSet<String>,
suppressions: &SuppressionContext<'_>,
line_offsets_by_file: &LineOffsetsMap<'_>,
) -> Vec<MixedClientServerBarrel> {
if !crate::analyze::predicates::project_uses_rsc_directives(declared_deps) {
return Vec::new();
}
let modules_by_id: FxHashMap<FileId, &ModuleInfo> =
modules.iter().map(|m| (m.file_id, m)).collect();
let path_by_id: FxHashMap<FileId, &std::path::Path> = graph
.modules
.iter()
.map(|module| (module.file_id, module.path.as_path()))
.collect();
let mut findings = Vec::new();
for resolved in resolved_modules {
if resolved.re_exports.is_empty() {
continue;
}
let mut client: Option<OffendingOrigin<'_>> = None;
let mut server: Option<OffendingOrigin<'_>> = None;
for re in &resolved.re_exports {
if re.info.is_type_only {
continue;
}
let Some(origin_id) = re.target.internal_file_id() else {
continue;
};
let Some(origin) = modules_by_id.get(&origin_id) else {
continue;
};
let span_start = re.info.span.start;
if client.is_none() && origin.directives.iter().any(|d| d == USE_CLIENT) {
client = Some(OffendingOrigin {
source: re.info.source.as_str(),
span_start,
});
} else if server.is_none() && is_server_only_module(origin) {
server = Some(OffendingOrigin {
source: re.info.source.as_str(),
span_start,
});
}
}
let (Some(client), Some(server)) = (client, server) else {
continue;
};
let barrel_id = resolved.file_id;
let Some(path) = path_by_id.get(&barrel_id) else {
continue;
};
let anchor_span = client.span_start.min(server.span_start);
let (line, col) = byte_offset_to_line_col(line_offsets_by_file, barrel_id, anchor_span);
if suppressions.is_suppressed(barrel_id, line, IssueKind::MixedClientServerBarrel) {
continue;
}
findings.push(MixedClientServerBarrel {
path: path.to_path_buf(),
client_origin: client.source.to_string(),
server_origin: server.source.to_string(),
line,
col,
});
}
findings
}
struct OffendingOrigin<'a> {
source: &'a str,
span_start: u32,
}