use rustc_hash::FxHashMap;
use fallow_types::extract::ModuleInfo;
use crate::discover::FileId;
use crate::graph::ModuleGraph;
use crate::results::MisplacedDirective;
use crate::suppress::{IssueKind, SuppressionContext};
use super::{LineOffsetsMap, byte_offset_to_line_col};
const USE_CLIENT: &str = "use client";
const USE_SERVER: &str = "use server";
#[must_use]
pub fn find_misplaced_directives(
graph: &ModuleGraph,
modules: &[ModuleInfo],
declared_deps: &rustc_hash::FxHashSet<String>,
suppressions: &SuppressionContext<'_>,
line_offsets_by_file: &LineOffsetsMap<'_>,
) -> Vec<MisplacedDirective> {
if !crate::analyze::predicates::project_uses_rsc_directives(declared_deps) {
return Vec::new();
}
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 module in modules {
if module.misplaced_directives.is_empty() {
continue;
}
let Some(path) = path_by_id.get(&module.file_id) else {
continue;
};
for site in &module.misplaced_directives {
let (line, col) =
byte_offset_to_line_col(line_offsets_by_file, module.file_id, site.span_start);
if suppressions.is_suppressed(module.file_id, line, IssueKind::MisplacedDirective) {
continue;
}
let directive = if site.is_server {
USE_SERVER
} else {
USE_CLIENT
};
findings.push(MisplacedDirective {
path: path.to_path_buf(),
directive: directive.to_string(),
line,
col,
});
}
}
findings
}
#[cfg(test)]
mod tests {
use rustc_hash::{FxHashMap, FxHashSet};
use crate::graph::ModuleGraph;
use crate::suppress::SuppressionContext;
use super::find_misplaced_directives;
#[test]
fn rsc_gate_returns_empty_without_rsc_bundler_dependency() {
let graph = ModuleGraph::build(&[], &[], &[]);
let modules = Vec::new();
let declared: FxHashSet<String> = std::iter::once("react".to_string()).collect();
let suppressions = SuppressionContext::empty();
let offsets = FxHashMap::default();
let findings =
find_misplaced_directives(&graph, &modules, &declared, &suppressions, &offsets);
assert!(
findings.is_empty(),
"no RSC bundler dependency means no findings"
);
}
}