use std::collections::HashMap;
use std::path::{Path, PathBuf};
use log::debug;
use sqry_core::graph::node::Language;
use sqry_core::graph::unified::build::ExportMap;
use sqry_core::graph::unified::build::StagingGraph;
use sqry_core::graph::unified::concurrent::CodeGraph;
use sqry_core::graph::unified::edge::EdgeKind;
use sqry_core::graph::unified::node::NodeKind;
use sqry_core::graph::unified::storage::metadata::{
ClasspathNodeMetadata, NodeMetadata, NodeMetadataStore,
};
use sqry_core::graph::unified::storage::registry::FileRegistry;
use sqry_core::graph::unified::storage::{NodeEntry, StringInterner};
use sqry_core::graph::unified::{FileId, NodeId, StringId};
use crate::stub::index::ClasspathIndex;
use crate::stub::model::{ClassKind, ClassStub};
use crate::{ClasspathError, ClasspathResult};
use super::provenance::ClasspathProvenance;
struct InternHelper<'a> {
interner: &'a mut StringInterner,
cache: HashMap<String, StringId>,
}
impl<'a> InternHelper<'a> {
fn new(interner: &'a mut StringInterner) -> Self {
Self {
interner,
cache: HashMap::new(),
}
}
fn intern(&mut self, s: &str) -> ClasspathResult<StringId> {
if let Some(&id) = self.cache.get(s) {
return Ok(id);
}
let id = self.interner.intern(s).map_err(|e| {
ClasspathError::EmissionError(format!("string intern failed for '{s}': {e}"))
})?;
self.cache.insert(s.to_owned(), id);
Ok(id)
}
}
#[allow(clippy::trivially_copy_pass_by_ref)] fn access_to_visibility(access: &crate::stub::model::AccessFlags) -> &'static str {
if access.is_public() {
"public"
} else if access.is_protected() {
"protected"
} else if access.is_private() {
"private"
} else {
"package"
}
}
fn class_kind_to_node_kind(kind: ClassKind) -> NodeKind {
match kind {
ClassKind::Class | ClassKind::Record => NodeKind::Class,
ClassKind::Interface => NodeKind::Interface,
ClassKind::Enum => NodeKind::Enum,
ClassKind::Annotation => NodeKind::Annotation,
ClassKind::Module => NodeKind::JavaModule,
}
}
fn register_synthetic_file(
jar_path: &Path,
fqn: &str,
file_registry: &mut FileRegistry,
) -> ClasspathResult<FileId> {
let class_path_str = fqn.replace('.', "/");
let synthetic_path = format!("{}!/{class_path_str}.class", jar_path.display());
let path = PathBuf::from(&synthetic_path);
file_registry
.register_external(&path, Some(Language::Java))
.map_err(|e| {
ClasspathError::EmissionError(format!(
"failed to register synthetic file for {fqn}: {e}"
))
})
}
#[allow(clippy::too_many_lines)]
pub fn emit_classpath_nodes(
index: &ClasspathIndex,
staging: &mut StagingGraph,
file_registry: &mut FileRegistry,
interner: &mut StringInterner,
metadata_store: &mut NodeMetadataStore,
provenance: &[ClasspathProvenance],
) -> ClasspathResult<EmissionResult> {
let mut helper = InternHelper::new(interner);
let mut fqn_to_node: HashMap<String, NodeId> = HashMap::with_capacity(index.classes.len());
let mut fqn_to_nodes: HashMap<String, Vec<ClasspathNodeRef>> =
HashMap::with_capacity(index.classes.len());
let mut file_id_map: HashMap<String, FileId> = HashMap::new();
let prov_map: HashMap<&Path, &ClasspathProvenance> = provenance
.iter()
.map(|p| (p.jar_path.as_path(), p))
.collect();
for stub in &index.classes {
emit_class_stub(
stub,
staging,
file_registry,
&mut helper,
metadata_store,
&prov_map,
&mut fqn_to_node,
&mut fqn_to_nodes,
&mut file_id_map,
)?;
}
Ok(EmissionResult {
fqn_to_node,
fqn_to_nodes,
file_id_map,
})
}
#[derive(Debug, Clone)]
pub struct ClasspathNodeRef {
pub node_id: NodeId,
pub fqn: String,
pub jar_path: PathBuf,
pub file_id: FileId,
}
#[derive(Debug)]
pub struct EmissionResult {
pub fqn_to_node: HashMap<String, NodeId>,
pub fqn_to_nodes: HashMap<String, Vec<ClasspathNodeRef>>,
pub file_id_map: HashMap<String, FileId>,
}
pub fn emit_into_code_graph(
index: &ClasspathIndex,
graph: &mut CodeGraph,
provenance: &[ClasspathProvenance],
) -> ClasspathResult<EmissionResult> {
let mut nodes = graph.nodes().clone();
let edges = graph.edges().clone();
let mut strings = graph.strings().clone();
let mut files = graph.files().clone();
let mut metadata = graph.macro_metadata().clone();
let mut staging = StagingGraph::new();
let emission_result = emit_classpath_nodes(
index,
&mut staging,
&mut files,
&mut strings,
&mut metadata,
provenance,
)?;
create_classpath_edges(
index,
&emission_result.fqn_to_nodes,
provenance,
&mut staging,
);
let id_mapping = staging
.commit_nodes(&mut nodes)
.map_err(|e| ClasspathError::EmissionError(format!("node commit failed: {e}")))?;
for edge in staging.get_remapped_edges(&id_mapping) {
let _delta = edges.add_edge(edge.source, edge.target, edge.kind, edge.file);
}
let mut remapped_fqn_to_node = HashMap::with_capacity(emission_result.fqn_to_node.len());
for (fqn, node_id) in emission_result.fqn_to_node {
let remapped_id = id_mapping.get(&node_id).copied().unwrap_or(node_id);
remapped_fqn_to_node.insert(fqn, remapped_id);
}
let mut remapped_fqn_to_nodes = HashMap::with_capacity(emission_result.fqn_to_nodes.len());
for (fqn, refs) in emission_result.fqn_to_nodes {
let remapped_refs = refs
.into_iter()
.map(|node_ref| ClasspathNodeRef {
node_id: id_mapping
.get(&node_ref.node_id)
.copied()
.unwrap_or(node_ref.node_id),
fqn: node_ref.fqn,
jar_path: node_ref.jar_path,
file_id: node_ref.file_id,
})
.collect();
remapped_fqn_to_nodes.insert(fqn, remapped_refs);
}
if !id_mapping.is_empty() {
let remapped_entries: Vec<_> = metadata
.iter_all()
.map(|((index, generation), value)| {
let node_id = NodeId::new(index, generation);
let remapped_id = id_mapping.get(&node_id).copied().unwrap_or(node_id);
(remapped_id, value.clone())
})
.collect();
metadata = NodeMetadataStore::new();
for (node_id, value) in remapped_entries {
metadata.insert_metadata(node_id, value);
}
}
let _old_nodes = std::mem::replace(graph.nodes_mut(), nodes);
let _old_edges = std::mem::replace(graph.edges_mut(), edges);
let _old_strings = std::mem::replace(graph.strings_mut(), strings);
let _old_files = std::mem::replace(graph.files_mut(), files);
let _old_metadata = std::mem::replace(graph.macro_metadata_mut(), metadata);
Ok(EmissionResult {
fqn_to_node: remapped_fqn_to_node,
fqn_to_nodes: remapped_fqn_to_nodes,
file_id_map: emission_result.file_id_map,
})
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_lines)]
#[allow(clippy::similar_names)] fn emit_class_stub(
stub: &ClassStub,
staging: &mut StagingGraph,
file_registry: &mut FileRegistry,
helper: &mut InternHelper<'_>,
metadata_store: &mut NodeMetadataStore,
prov_map: &HashMap<&Path, &ClasspathProvenance>,
fqn_to_node: &mut HashMap<String, NodeId>,
fqn_to_nodes: &mut HashMap<String, Vec<ClasspathNodeRef>>,
file_id_map: &mut HashMap<String, FileId>,
) -> ClasspathResult<()> {
let jar_path = if let Some(ref src_jar) = stub.source_jar {
PathBuf::from(src_jar)
} else if let Some((&path, _)) = prov_map.iter().next() {
path.to_path_buf()
} else {
PathBuf::from(format!("<classpath>/{}.class", stub.fqn.replace('.', "/")))
};
let file_id = register_synthetic_file(&jar_path, &stub.fqn, file_registry)?;
file_id_map.insert(stub.fqn.clone(), file_id);
let node_kind = class_kind_to_node_kind(stub.kind);
let name_id = helper.intern(&stub.name)?;
let qname_id = helper.intern(&stub.fqn)?;
let vis_id = helper.intern(access_to_visibility(&stub.access))?;
let class_entry = NodeEntry::new(node_kind, name_id, file_id)
.with_qualified_name(qname_id)
.with_visibility(vis_id)
.with_static(stub.access.is_static())
.with_unsafe(false);
let class_node_id = staging.add_node(class_entry);
record_node_ref(
&stub.fqn,
class_node_id,
&jar_path,
file_id,
fqn_to_node,
fqn_to_nodes,
);
let prov = find_provenance_for_jar(&jar_path, prov_map);
let cp_meta = ClasspathNodeMetadata {
coordinates: prov.and_then(|p| p.coordinates.clone()),
jar_path: jar_path.display().to_string(),
fqn: stub.fqn.clone(),
is_direct_dependency: prov.is_some_and(ClasspathProvenance::has_direct_scope),
};
metadata_store.insert_metadata(class_node_id, NodeMetadata::Classpath(cp_meta.clone()));
for method in &stub.methods {
let method_name_id = helper.intern(&method.name)?;
let method_fqn = format!("{}.{}{}", stub.fqn, method.name, method.descriptor);
#[allow(clippy::similar_names)] let method_qname_id = helper.intern(&method_fqn)?;
let method_vis_id = helper.intern(access_to_visibility(&method.access))?;
let method_entry = NodeEntry::new(NodeKind::Method, method_name_id, file_id)
.with_qualified_name(method_qname_id)
.with_visibility(method_vis_id)
.with_static(method.access.is_static());
let method_node_id = staging.add_node(method_entry);
record_node_ref(
&method_fqn,
method_node_id,
&jar_path,
file_id,
fqn_to_node,
fqn_to_nodes,
);
metadata_store.insert_metadata(method_node_id, NodeMetadata::Classpath(cp_meta.clone()));
staging.add_edge(class_node_id, method_node_id, EdgeKind::Defines, file_id);
}
for field in &stub.fields {
let field_name_id = helper.intern(&field.name)?;
let field_fqn = format!("{}.{}", stub.fqn, field.name);
let field_qname_id = helper.intern(&field_fqn)?;
let field_vis_id = helper.intern(access_to_visibility(&field.access))?;
let field_entry = NodeEntry::new(NodeKind::Property, field_name_id, file_id)
.with_qualified_name(field_qname_id)
.with_visibility(field_vis_id)
.with_static(field.access.is_static());
let field_node_id = staging.add_node(field_entry);
record_node_ref(
&field_fqn,
field_node_id,
&jar_path,
file_id,
fqn_to_node,
fqn_to_nodes,
);
metadata_store.insert_metadata(field_node_id, NodeMetadata::Classpath(cp_meta.clone()));
staging.add_edge(class_node_id, field_node_id, EdgeKind::Defines, file_id);
}
for constant_name in &stub.enum_constants {
let const_name_id = helper.intern(constant_name)?;
let const_fqn = format!("{}.{constant_name}", stub.fqn);
let const_qname_id = helper.intern(&const_fqn)?;
let const_entry = NodeEntry::new(NodeKind::EnumConstant, const_name_id, file_id)
.with_qualified_name(const_qname_id)
.with_visibility(helper.intern("public")?);
let const_node_id = staging.add_node(const_entry);
record_node_ref(
&const_fqn,
const_node_id,
&jar_path,
file_id,
fqn_to_node,
fqn_to_nodes,
);
metadata_store.insert_metadata(const_node_id, NodeMetadata::Classpath(cp_meta.clone()));
staging.add_edge(class_node_id, const_node_id, EdgeKind::Defines, file_id);
}
if let Some(ref gen_sig) = stub.generic_signature {
for tp in &gen_sig.type_parameters {
let tp_name_id = helper.intern(&tp.name)?;
let tp_fqn = format!("{}.<{}>", stub.fqn, tp.name);
let tp_qname_id = helper.intern(&tp_fqn)?;
let tp_entry = NodeEntry::new(NodeKind::TypeParameter, tp_name_id, file_id)
.with_qualified_name(tp_qname_id);
let tp_node_id = staging.add_node(tp_entry);
record_node_ref(
&tp_fqn,
tp_node_id,
&jar_path,
file_id,
fqn_to_node,
fqn_to_nodes,
);
metadata_store.insert_metadata(tp_node_id, NodeMetadata::Classpath(cp_meta.clone()));
staging.add_edge(class_node_id, tp_node_id, EdgeKind::TypeArgument, file_id);
}
}
for lambda in &stub.lambda_targets {
let lambda_label = format!("{}::{}", lambda.owner_fqn, lambda.method_name);
let lambda_name_id = helper.intern(&lambda_label)?;
let lambda_fqn = format!("{}.lambda${}", stub.fqn, lambda.method_name);
let lambda_qname_id = helper.intern(&lambda_fqn)?;
let lambda_entry = NodeEntry::new(NodeKind::LambdaTarget, lambda_name_id, file_id)
.with_qualified_name(lambda_qname_id);
let lambda_node_id = staging.add_node(lambda_entry);
record_node_ref(
&lambda_fqn,
lambda_node_id,
&jar_path,
file_id,
fqn_to_node,
fqn_to_nodes,
);
metadata_store.insert_metadata(lambda_node_id, NodeMetadata::Classpath(cp_meta.clone()));
staging.add_edge(class_node_id, lambda_node_id, EdgeKind::Contains, file_id);
}
for inner in &stub.inner_classes {
if inner.outer_fqn.as_deref() == Some(&stub.fqn) {
}
}
if let Some(ref module) = stub.module {
let mod_name_id = helper.intern(&module.name)?;
let mod_fqn = format!("module:{}", module.name);
let mod_qname_id = helper.intern(&mod_fqn)?;
let mod_entry = NodeEntry::new(NodeKind::JavaModule, mod_name_id, file_id)
.with_qualified_name(mod_qname_id);
let mod_node_id = staging.add_node(mod_entry);
record_node_ref(
&mod_fqn,
mod_node_id,
&jar_path,
file_id,
fqn_to_node,
fqn_to_nodes,
);
metadata_store.insert_metadata(mod_node_id, NodeMetadata::Classpath(cp_meta.clone()));
staging.add_edge(class_node_id, mod_node_id, EdgeKind::Contains, file_id);
}
Ok(())
}
fn record_node_ref(
fqn: &str,
node_id: NodeId,
jar_path: &Path,
file_id: FileId,
fqn_to_node: &mut HashMap<String, NodeId>,
fqn_to_nodes: &mut HashMap<String, Vec<ClasspathNodeRef>>,
) {
fqn_to_node.entry(fqn.to_owned()).or_insert(node_id);
fqn_to_nodes
.entry(fqn.to_owned())
.or_default()
.push(ClasspathNodeRef {
node_id,
fqn: fqn.to_owned(),
jar_path: jar_path.to_path_buf(),
file_id,
});
}
fn find_provenance_for_jar<'a>(
jar_path: &Path,
prov_map: &HashMap<&Path, &'a ClasspathProvenance>,
) -> Option<&'a ClasspathProvenance> {
prov_map.get(jar_path).copied()
}
#[allow(clippy::implicit_hasher)] pub fn register_classpath_exports(
fqn_to_nodes: &HashMap<String, Vec<ClasspathNodeRef>>,
export_map: &mut ExportMap,
provenance: &[ClasspathProvenance],
index: &ClasspathIndex,
) {
let class_fqns: std::collections::HashSet<&str> =
index.classes.iter().map(|s| s.fqn.as_str()).collect();
let direct_jars: std::collections::HashSet<&Path> = provenance
.iter()
.filter(|p| p.has_direct_scope())
.map(|p| p.jar_path.as_path())
.collect();
let transitive_jars: std::collections::HashSet<&Path> = provenance
.iter()
.filter(|p| !p.has_direct_scope())
.map(|p| p.jar_path.as_path())
.collect();
register_exports_for_jars(&class_fqns, fqn_to_nodes, export_map, &direct_jars, index);
register_exports_for_jars(
&class_fqns,
fqn_to_nodes,
export_map,
&transitive_jars,
index,
);
}
fn register_exports_for_jars(
class_fqns: &std::collections::HashSet<&str>,
fqn_to_nodes: &HashMap<String, Vec<ClasspathNodeRef>>,
export_map: &mut ExportMap,
jar_filter: &std::collections::HashSet<&Path>,
index: &ClasspathIndex,
) {
for stub in &index.classes {
let Some(source_jar) = stub.source_jar.as_deref() else {
continue;
};
if !jar_filter.contains(Path::new(source_jar)) {
continue;
}
let fqn = stub.fqn.as_str();
if class_fqns.contains(fqn)
&& let Some(node_refs) = fqn_to_nodes.get(fqn)
{
for node_ref in node_refs {
if node_ref.jar_path == Path::new(source_jar) {
export_map.register(fqn.to_owned(), node_ref.file_id, node_ref.node_id);
}
}
}
}
}
#[allow(clippy::too_many_lines)]
#[allow(clippy::implicit_hasher)] pub fn create_classpath_edges(
#[allow(clippy::implicit_hasher)] index: &ClasspathIndex,
fqn_to_nodes: &HashMap<String, Vec<ClasspathNodeRef>>,
provenance: &[ClasspathProvenance],
staging: &mut StagingGraph,
) {
let prov_map: HashMap<&Path, &ClasspathProvenance> = provenance
.iter()
.map(|p| (p.jar_path.as_path(), p))
.collect();
for stub in &index.classes {
let source_jar = stub.source_jar.as_deref().map(Path::new);
let Some(class_node) = select_node_ref(&stub.fqn, source_jar, fqn_to_nodes, &prov_map)
else {
continue;
};
let class_node_id = class_node.node_id;
let file_id = class_node.file_id;
if let Some(ref superclass_fqn) = stub.superclass
&& superclass_fqn != "java.lang.Object"
{
if let Some(super_node) =
select_node_ref(superclass_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let super_node_id = super_node.node_id;
staging.add_edge(class_node_id, super_node_id, EdgeKind::Inherits, file_id);
} else {
log::debug!(
"classpath: skipping Inherits edge for {} → {} (target not in graph)",
stub.fqn,
superclass_fqn
);
}
}
for iface_fqn in &stub.interfaces {
if let Some(iface_node) =
select_node_ref(iface_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let iface_node_id = iface_node.node_id;
staging.add_edge(class_node_id, iface_node_id, EdgeKind::Implements, file_id);
} else {
log::debug!(
"classpath: skipping Implements edge for {} → {} (target not in graph)",
stub.fqn,
iface_fqn
);
}
}
if let Some(ref gen_sig) = stub.generic_signature {
for tp in &gen_sig.type_parameters {
let tp_fqn = format!("{}.<{}>", stub.fqn, tp.name);
let Some(tp_node) = select_node_ref(&tp_fqn, source_jar, fqn_to_nodes, &prov_map)
else {
continue;
};
let tp_node_id = tp_node.node_id;
if let Some(ref bound) = tp.class_bound
&& let Some(bound_fqn) = extract_class_fqn_from_type_sig(bound)
&& let Some(bound_node) =
select_node_ref(bound_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let bound_node_id = bound_node.node_id;
staging.add_edge(tp_node_id, bound_node_id, EdgeKind::GenericBound, file_id);
}
for ibound in &tp.interface_bounds {
if let Some(bound_fqn) = extract_class_fqn_from_type_sig(ibound)
&& let Some(bound_node) =
select_node_ref(bound_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let bound_node_id = bound_node.node_id;
staging.add_edge(
tp_node_id,
bound_node_id,
EdgeKind::GenericBound,
file_id,
);
}
}
}
}
for ann in &stub.annotations {
if let Some(ann_type_node) =
select_node_ref(&ann.type_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let ann_type_node_id = ann_type_node.node_id;
staging.add_edge(
class_node_id,
ann_type_node_id,
EdgeKind::AnnotatedWith,
file_id,
);
}
}
for method in &stub.methods {
let method_fqn = format!("{}.{}{}", stub.fqn, method.name, method.descriptor);
if let Some(method_node) =
select_node_ref(&method_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let method_node_id = method_node.node_id;
for ann in &method.annotations {
if let Some(ann_type_node) =
select_node_ref(&ann.type_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let ann_type_node_id = ann_type_node.node_id;
staging.add_edge(
method_node_id,
ann_type_node_id,
EdgeKind::AnnotatedWith,
file_id,
);
}
}
}
}
for field in &stub.fields {
let field_fqn = format!("{}.{}", stub.fqn, field.name);
if let Some(field_node) =
select_node_ref(&field_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let field_node_id = field_node.node_id;
for ann in &field.annotations {
if let Some(ann_type_node) =
select_node_ref(&ann.type_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let ann_type_node_id = ann_type_node.node_id;
staging.add_edge(
field_node_id,
ann_type_node_id,
EdgeKind::AnnotatedWith,
file_id,
);
}
}
}
}
for inner in &stub.inner_classes {
if inner.outer_fqn.as_deref() == Some(&stub.fqn)
&& let Some(inner_node) =
select_node_ref(&inner.inner_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let inner_node_id = inner_node.node_id;
staging.add_edge(class_node_id, inner_node_id, EdgeKind::Contains, file_id);
}
}
if let Some(ref module) = stub.module {
let mod_fqn = format!("module:{}", module.name);
let Some(mod_node) = select_node_ref(&mod_fqn, source_jar, fqn_to_nodes, &prov_map)
else {
continue;
};
let mod_node_id = mod_node.node_id;
for req in &module.requires {
let req_mod_fqn = format!("module:{}", req.module_name);
if let Some(req_node) =
select_node_ref(&req_mod_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let req_node_id = req_node.node_id;
staging.add_edge(mod_node_id, req_node_id, EdgeKind::ModuleRequires, file_id);
}
}
for exp in &module.exports {
let pkg_classes = index.lookup_package(&exp.package);
for pkg_class in pkg_classes {
for pkg_class_node in
select_node_refs(&pkg_class.fqn, source_jar, fqn_to_nodes, &prov_map)
{
let pkg_class_node_id = pkg_class_node.node_id;
staging.add_edge(
mod_node_id,
pkg_class_node_id,
EdgeKind::ModuleExports,
file_id,
);
}
}
}
for opens in &module.opens {
let pkg_classes = index.lookup_package(&opens.package);
for pkg_class in pkg_classes {
for pkg_class_node in
select_node_refs(&pkg_class.fqn, source_jar, fqn_to_nodes, &prov_map)
{
let pkg_class_node_id = pkg_class_node.node_id;
staging.add_edge(
mod_node_id,
pkg_class_node_id,
EdgeKind::ModuleOpens,
file_id,
);
}
}
}
for provides in &module.provides {
for impl_fqn in &provides.implementations {
if let Some(impl_node) =
select_node_ref(impl_fqn, source_jar, fqn_to_nodes, &prov_map)
{
let impl_node_id = impl_node.node_id;
staging.add_edge(
mod_node_id,
impl_node_id,
EdgeKind::ModuleProvides,
file_id,
);
}
}
}
}
}
}
fn select_node_ref<'a>(
fqn: &str,
source_jar: Option<&Path>,
fqn_to_nodes: &'a HashMap<String, Vec<ClasspathNodeRef>>,
prov_map: &HashMap<&Path, &ClasspathProvenance>,
) -> Option<&'a ClasspathNodeRef> {
let candidates = select_node_refs(fqn, source_jar, fqn_to_nodes, prov_map);
candidates.first().copied()
}
fn select_node_refs<'a>(
fqn: &str,
source_jar: Option<&Path>,
fqn_to_nodes: &'a HashMap<String, Vec<ClasspathNodeRef>>,
prov_map: &HashMap<&Path, &ClasspathProvenance>,
) -> Vec<&'a ClasspathNodeRef> {
let Some(candidates) = fqn_to_nodes.get(fqn) else {
return Vec::new();
};
let Some(source_jar) = source_jar else {
return candidates.iter().collect();
};
if let Some(exact_match) = candidates
.iter()
.find(|candidate| candidate.jar_path.as_path() == source_jar)
{
return vec![exact_match];
}
let scoped: Vec<_> = candidates
.iter()
.filter(|candidate| jars_share_scope(source_jar, candidate.jar_path.as_path(), prov_map))
.collect();
if scoped.is_empty() {
candidates.iter().collect()
} else {
scoped
}
}
fn jars_share_scope(
source_jar: &Path,
target_jar: &Path,
prov_map: &HashMap<&Path, &ClasspathProvenance>,
) -> bool {
if source_jar == target_jar {
return true;
}
let Some(source) = prov_map.get(source_jar) else {
debug!(
"classpath: provenance missing for source jar {}; allowing scope fallback",
source_jar.display()
);
return true;
};
let Some(target) = prov_map.get(target_jar) else {
debug!(
"classpath: provenance missing for target jar {}; allowing scope fallback",
target_jar.display()
);
return true;
};
source.scopes.iter().any(|source_scope| {
target
.scopes
.iter()
.any(|target_scope| source_scope.module_root == target_scope.module_root)
})
}
fn extract_class_fqn_from_type_sig(sig: &crate::stub::model::TypeSignature) -> Option<&str> {
match sig {
crate::stub::model::TypeSignature::Class { fqn, .. } => Some(fqn.as_str()),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stub::model::{
AccessFlags, AnnotationStub, ClassKind, FieldStub, GenericClassSignature, InnerClassEntry,
LambdaTargetStub, MethodStub, ModuleExports, ModuleProvides, ModuleRequires, ModuleStub,
ReferenceKind, TypeParameterStub, TypeSignature,
};
use sqry_core::graph::unified::BidirectionalEdgeStore;
use sqry_core::graph::unified::storage::AuxiliaryIndices;
use sqry_core::graph::unified::storage::NodeArena;
fn make_interner() -> StringInterner {
StringInterner::new()
}
fn make_staging() -> StagingGraph {
StagingGraph::default()
}
fn make_provenance(jar: &str, direct: bool) -> ClasspathProvenance {
ClasspathProvenance {
jar_path: PathBuf::from(jar),
coordinates: Some(format!(
"group:artifact:{}",
if direct { "1.0" } else { "2.0" }
)),
is_direct: direct,
scopes: vec![crate::graph::provenance::ClasspathScope {
module_name: "test".to_owned(),
module_root: PathBuf::from("/repo/test"),
is_direct: direct,
}],
}
}
fn make_stub(fqn: &str) -> ClassStub {
ClassStub {
fqn: fqn.to_owned(),
name: fqn.rsplit('.').next().unwrap_or(fqn).to_owned(),
kind: ClassKind::Class,
access: AccessFlags::new(AccessFlags::ACC_PUBLIC),
superclass: Some("java.lang.Object".to_owned()),
interfaces: vec![],
methods: vec![],
fields: vec![],
annotations: vec![],
generic_signature: None,
inner_classes: vec![],
lambda_targets: vec![],
module: None,
record_components: vec![],
enum_constants: vec![],
source_file: None,
source_jar: None,
kotlin_metadata: None,
scala_signature: None,
}
}
fn make_method(name: &str) -> MethodStub {
MethodStub {
name: name.to_owned(),
access: AccessFlags::new(AccessFlags::ACC_PUBLIC),
descriptor: "()V".to_owned(),
generic_signature: None,
annotations: vec![],
parameter_annotations: vec![],
parameter_names: vec![],
return_type: TypeSignature::Base(crate::stub::model::BaseType::Void),
parameter_types: vec![],
}
}
fn make_field(name: &str) -> FieldStub {
FieldStub {
name: name.to_owned(),
access: AccessFlags::new(AccessFlags::ACC_PRIVATE),
descriptor: "I".to_owned(),
generic_signature: None,
annotations: vec![],
constant_value: None,
}
}
fn run_emission(
stubs: Vec<ClassStub>,
provenance: &[ClasspathProvenance],
) -> (
EmissionResult,
StagingGraph,
FileRegistry,
StringInterner,
NodeMetadataStore,
) {
let default_jar = provenance
.first()
.map(|entry| entry.jar_path.display().to_string());
let normalized_stubs = stubs
.into_iter()
.map(|mut stub| {
if stub.source_jar.is_none() {
stub.source_jar = default_jar.clone();
}
stub
})
.collect();
let index = ClasspathIndex::build(normalized_stubs);
let mut staging = make_staging();
let mut file_registry = FileRegistry::new();
let mut interner = make_interner();
let mut metadata_store = NodeMetadataStore::new();
let result = emit_classpath_nodes(
&index,
&mut staging,
&mut file_registry,
&mut interner,
&mut metadata_store,
provenance,
)
.expect("emission should succeed");
(result, staging, file_registry, interner, metadata_store)
}
#[test]
fn test_simple_class_emits_nodes() {
let mut stub = make_stub("com.example.Foo");
stub.methods = vec![make_method("bar"), make_method("baz")];
stub.fields = vec![make_field("count")];
let prov = vec![make_provenance("/jars/example.jar", true)];
let (result, _staging, _registry, _interner, _meta) = run_emission(vec![stub], &prov);
assert!(
result.fqn_to_node.contains_key("com.example.Foo"),
"class node should be emitted"
);
assert!(
result.fqn_to_node.contains_key("com.example.Foo.bar()V"),
"method 'bar' should be emitted"
);
assert!(
result.fqn_to_node.contains_key("com.example.Foo.baz()V"),
"method 'baz' should be emitted"
);
assert!(
result.fqn_to_node.contains_key("com.example.Foo.count"),
"field 'count' should be emitted"
);
assert_eq!(result.fqn_to_node.len(), 4);
}
#[test]
fn test_inheritance_edge_created() {
let mut list = make_stub("java.util.AbstractList");
list.superclass = Some("java.util.AbstractCollection".to_owned());
let collection = make_stub("java.util.AbstractCollection");
let prov = vec![make_provenance("/jars/rt.jar", true)];
let stubs = vec![list, collection];
let index = ClasspathIndex::build(stubs.clone());
let (result, mut staging, _registry, _interner, _meta) = run_emission(stubs, &prov);
create_classpath_edges(&index, &result.fqn_to_nodes, &prov, &mut staging);
assert!(result.fqn_to_node.contains_key("java.util.AbstractList"));
assert!(
result
.fqn_to_node
.contains_key("java.util.AbstractCollection")
);
let stats = staging.stats();
assert!(
stats.edges_staged > 0,
"should have at least one edge staged"
);
}
#[test]
fn test_implements_edge_created() {
let mut array_list = make_stub("java.util.ArrayList");
array_list.interfaces = vec![
"java.util.List".to_owned(),
"java.io.Serializable".to_owned(),
];
let list_iface = {
let mut s = make_stub("java.util.List");
s.kind = ClassKind::Interface;
s
};
let serializable = {
let mut s = make_stub("java.io.Serializable");
s.kind = ClassKind::Interface;
s
};
let prov = vec![make_provenance("/jars/rt.jar", true)];
let stubs = vec![array_list, list_iface, serializable];
let index = ClasspathIndex::build(stubs.clone());
let (result, mut staging, _registry, _interner, _meta) = run_emission(stubs, &prov);
create_classpath_edges(&index, &result.fqn_to_nodes, &prov, &mut staging);
assert!(result.fqn_to_node.contains_key("java.util.ArrayList"));
assert!(result.fqn_to_node.contains_key("java.util.List"));
assert!(result.fqn_to_node.contains_key("java.io.Serializable"));
}
#[test]
fn test_export_map_registration_and_lookup() {
let mut alpha = make_stub("com.example.Alpha");
alpha.source_jar = Some("/jars/example.jar".to_owned());
let mut beta = make_stub("com.example.Beta");
beta.source_jar = Some("/jars/example.jar".to_owned());
let stubs = vec![alpha, beta];
let prov = vec![make_provenance("/jars/example.jar", true)];
let index = ClasspathIndex::build(stubs.clone());
let (result, _staging, _registry, _interner, _meta) = run_emission(stubs, &prov);
let mut export_map = ExportMap::new();
register_classpath_exports(&result.fqn_to_nodes, &mut export_map, &prov, &index);
let alpha = export_map.lookup("com.example.Alpha");
assert!(alpha.is_some(), "Alpha should be in ExportMap");
let beta = export_map.lookup("com.example.Beta");
assert!(beta.is_some(), "Beta should be in ExportMap");
let missing = export_map.lookup("com.example.DoesNotExist");
assert!(missing.is_none());
}
#[test]
fn test_fqn_precedence_direct_before_transitive() {
let mut direct_stub = make_stub("com.example.Foo");
direct_stub.source_jar = Some("/jars/direct.jar".to_owned());
let mut transitive_stub = make_stub("com.example.Foo");
transitive_stub.source_jar = Some("/jars/transitive.jar".to_owned());
let mut prov_direct = make_provenance("/jars/direct.jar", false);
prov_direct.scopes[0].is_direct = true;
let prov_transitive = make_provenance("/jars/transitive.jar", false);
let index = ClasspathIndex::build(vec![direct_stub, transitive_stub]);
let (result, _staging, _registry, _interner, _meta) = run_emission(
index.classes.clone(),
&[prov_direct.clone(), prov_transitive],
);
let mut export_map = ExportMap::new();
register_classpath_exports(
&result.fqn_to_nodes,
&mut export_map,
&[prov_direct, make_provenance("/jars/transitive.jar", false)],
&index,
);
let entry = export_map
.lookup("com.example.Foo")
.expect("class should be registered");
let (_file_id, node_id) = entry;
let direct_node_id = result
.fqn_to_nodes
.get("com.example.Foo")
.and_then(|node_refs| {
node_refs
.iter()
.find(|node_ref| node_ref.jar_path == PathBuf::from("/jars/direct.jar"))
})
.map(|node_ref| node_ref.node_id)
.expect("direct node should exist");
assert_eq!(node_id, direct_node_id);
}
#[test]
fn test_emit_into_code_graph_commits_edges_and_metadata() {
let mut child = make_stub("java.util.AbstractList");
child.superclass = Some("java.util.AbstractCollection".to_owned());
child.source_jar = Some("/jars/rt.jar".to_owned());
let mut parent = make_stub("java.util.AbstractCollection");
parent.source_jar = Some("/jars/rt.jar".to_owned());
let index = ClasspathIndex::build(vec![child, parent]);
let provenance = vec![make_provenance("/jars/rt.jar", true)];
let mut graph = CodeGraph::new();
let result = emit_into_code_graph(&index, &mut graph, &provenance)
.expect("in-place classpath emission should succeed");
let child_id = *result
.fqn_to_node
.get("java.util.AbstractList")
.expect("child class node should be returned");
let parent_id = *result
.fqn_to_node
.get("java.util.AbstractCollection")
.expect("parent class node should be returned");
assert!(
graph.nodes().get(child_id).is_some(),
"child node should exist"
);
assert!(
graph.edge_count() > 0,
"structural classpath edges should be committed"
);
assert!(
graph.edges().edges_from(child_id).iter().any(|edge| {
matches!(edge.kind, EdgeKind::Inherits) && edge.target == parent_id
}),
"child class should have an inherits edge to its parent"
);
assert!(
matches!(
graph.macro_metadata().get_metadata(child_id),
Some(NodeMetadata::Classpath(_))
),
"classpath metadata should remain attached after node-id remap"
);
}
#[test]
fn test_emit_into_code_graph_preserves_graph_on_failure() {
let mut strings = StringInterner::with_max_ids(2);
let existing_name = strings.intern("existing").unwrap();
let existing_qname = strings.intern("existing::node").unwrap();
let mut files = FileRegistry::new();
let file_id = files.register(Path::new("/existing.rs")).unwrap();
let mut nodes = NodeArena::new();
let existing_node = nodes
.alloc(
NodeEntry::new(NodeKind::Function, existing_name, file_id)
.with_qualified_name(existing_qname),
)
.unwrap();
let mut graph = CodeGraph::from_components(
nodes,
BidirectionalEdgeStore::new(),
strings,
files,
AuxiliaryIndices::new(),
NodeMetadataStore::new(),
);
graph.macro_metadata_mut().insert_metadata(
existing_node,
NodeMetadata::Classpath(ClasspathNodeMetadata {
coordinates: Some("group:existing:1.0".to_owned()),
jar_path: "/existing.jar".to_owned(),
fqn: "existing::node".to_owned(),
is_direct_dependency: true,
}),
);
let snapshot_before = graph.snapshot();
let existing_path_before = snapshot_before
.files()
.resolve(file_id)
.expect("existing file should resolve")
.to_path_buf();
let existing_meta_before = snapshot_before
.macro_metadata()
.get_metadata(existing_node)
.cloned();
let mut failing_stub = make_stub("com.example.WillFail");
failing_stub.source_jar = Some("/jars/failing.jar".to_owned());
let index = ClasspathIndex::build(vec![failing_stub]);
let provenance = vec![make_provenance("/jars/failing.jar", true)];
let error = emit_into_code_graph(&index, &mut graph, &provenance)
.expect_err("emission should fail when the cloned interner is full");
assert!(
error.to_string().contains("string intern failed"),
"unexpected error: {error}"
);
assert_eq!(graph.node_count(), snapshot_before.nodes().len());
assert_eq!(graph.edge_count(), 0);
assert_eq!(graph.strings().len(), snapshot_before.strings().len());
assert_eq!(graph.files().len(), snapshot_before.files().len());
let surviving_node = graph
.nodes()
.get(existing_node)
.expect("existing node must survive failed emission");
assert_eq!(surviving_node.name, existing_name);
assert_eq!(
graph
.files()
.resolve(file_id)
.map(|path| path.to_path_buf()),
Some(existing_path_before)
);
assert_eq!(
graph.macro_metadata().get_metadata(existing_node),
existing_meta_before.as_ref()
);
}
#[test]
fn test_classpath_metadata_attached() {
let stub = make_stub("com.google.common.collect.ImmutableList");
let prov = vec![ClasspathProvenance {
jar_path: PathBuf::from("/home/user/.m2/repository/guava-33.0.0.jar"),
coordinates: Some("com.google.guava:guava:33.0.0".to_owned()),
is_direct: true,
scopes: vec![crate::graph::provenance::ClasspathScope {
module_name: "test".to_owned(),
module_root: PathBuf::from("/repo/test"),
is_direct: true,
}],
}];
let (result, _staging, _registry, _interner, metadata_store) =
run_emission(vec![stub], &prov);
let node_id = result
.fqn_to_node
.get("com.google.common.collect.ImmutableList")
.expect("node should exist");
let metadata = metadata_store
.get_metadata(*node_id)
.expect("metadata should be attached");
match metadata {
NodeMetadata::Classpath(cp) => {
assert_eq!(cp.fqn, "com.google.common.collect.ImmutableList");
assert_eq!(
cp.coordinates.as_deref(),
Some("com.google.guava:guava:33.0.0")
);
assert!(cp.is_direct_dependency);
assert!(cp.jar_path.contains("guava-33.0.0.jar"));
}
NodeMetadata::Macro(_) => panic!("expected Classpath metadata, got Macro"),
}
}
#[test]
fn test_zero_spans_on_classpath_nodes() {
let mut stub = make_stub("com.example.ZeroSpan");
stub.methods = vec![make_method("doWork")];
let prov = vec![make_provenance("/jars/test.jar", true)];
let (result, staging, _registry, _interner, _meta) = run_emission(vec![stub], &prov);
assert!(
!result.fqn_to_node.is_empty(),
"should have emitted at least one node"
);
let stats = staging.stats();
assert!(
stats.nodes_staged >= 2,
"should have at least 2 nodes (class + method)"
);
}
#[test]
fn test_annotation_edges() {
let mut stub = make_stub("com.example.MyService");
stub.annotations = vec![AnnotationStub {
type_fqn: "org.springframework.stereotype.Service".to_owned(),
elements: vec![],
is_runtime_visible: true,
}];
let ann_type = {
let mut s = make_stub("org.springframework.stereotype.Service");
s.kind = ClassKind::Annotation;
s
};
let prov = vec![make_provenance("/jars/app.jar", true)];
let stubs = vec![stub, ann_type];
let index = ClasspathIndex::build(stubs.clone());
let (result, mut staging, _registry, _interner, _meta) = run_emission(stubs, &prov);
create_classpath_edges(&index, &result.fqn_to_nodes, &prov, &mut staging);
assert!(result.fqn_to_node.contains_key("com.example.MyService"));
assert!(
result
.fqn_to_node
.contains_key("org.springframework.stereotype.Service")
);
let stats = staging.stats();
assert!(
stats.edges_staged > 0,
"should have annotation edges staged"
);
}
#[test]
fn test_module_edges() {
let provider_class = make_stub("com.example.spi.MyProvider");
let exported_class = make_stub("com.example.api.MyApi");
let mut module_stub = make_stub("module-info");
module_stub.kind = ClassKind::Module;
module_stub.module = Some(ModuleStub {
name: "com.example".to_owned(),
access: AccessFlags::new(0),
version: None,
requires: vec![ModuleRequires {
module_name: "java.base".to_owned(),
access: AccessFlags::new(0),
version: None,
}],
exports: vec![ModuleExports {
package: "com.example.api".to_owned(),
access: AccessFlags::new(0),
to_modules: vec![],
}],
opens: vec![],
provides: vec![ModuleProvides {
service: "com.example.spi.SomeService".to_owned(),
implementations: vec!["com.example.spi.MyProvider".to_owned()],
}],
uses: vec![],
});
let prov = vec![make_provenance("/jars/example.jar", true)];
let stubs = vec![module_stub, provider_class, exported_class];
let index = ClasspathIndex::build(stubs.clone());
let (result, mut staging, _registry, _interner, _meta) = run_emission(stubs, &prov);
create_classpath_edges(&index, &result.fqn_to_nodes, &prov, &mut staging);
assert!(
result.fqn_to_node.contains_key("module:com.example"),
"module node should be emitted"
);
let stats = staging.stats();
assert!(stats.edges_staged > 0, "should have module edges staged");
}
#[test]
fn test_enum_constants_emitted() {
let mut stub = make_stub("java.time.DayOfWeek");
stub.kind = ClassKind::Enum;
stub.enum_constants = vec![
"MONDAY".to_owned(),
"TUESDAY".to_owned(),
"WEDNESDAY".to_owned(),
];
let prov = vec![make_provenance("/jars/rt.jar", true)];
let (result, _staging, _registry, _interner, _meta) = run_emission(vec![stub], &prov);
assert!(
result
.fqn_to_node
.contains_key("java.time.DayOfWeek.MONDAY")
);
assert!(
result
.fqn_to_node
.contains_key("java.time.DayOfWeek.TUESDAY")
);
assert!(
result
.fqn_to_node
.contains_key("java.time.DayOfWeek.WEDNESDAY")
);
}
#[test]
fn test_type_parameters_emitted() {
let mut stub = make_stub("java.util.HashMap");
stub.generic_signature = Some(GenericClassSignature {
type_parameters: vec![
TypeParameterStub {
name: "K".to_owned(),
class_bound: None,
interface_bounds: vec![],
},
TypeParameterStub {
name: "V".to_owned(),
class_bound: None,
interface_bounds: vec![],
},
],
superclass: TypeSignature::Class {
fqn: "java.util.AbstractMap".to_owned(),
type_arguments: vec![],
},
interfaces: vec![],
});
let prov = vec![make_provenance("/jars/rt.jar", true)];
let (result, _staging, _registry, _interner, _meta) = run_emission(vec![stub], &prov);
assert!(result.fqn_to_node.contains_key("java.util.HashMap.<K>"));
assert!(result.fqn_to_node.contains_key("java.util.HashMap.<V>"));
}
#[test]
fn test_generic_bound_edges() {
let comparable = make_stub("java.lang.Comparable");
let mut stub = make_stub("com.example.Sorted");
stub.generic_signature = Some(GenericClassSignature {
type_parameters: vec![TypeParameterStub {
name: "T".to_owned(),
class_bound: Some(TypeSignature::Class {
fqn: "java.lang.Comparable".to_owned(),
type_arguments: vec![],
}),
interface_bounds: vec![],
}],
superclass: TypeSignature::Class {
fqn: "java.lang.Object".to_owned(),
type_arguments: vec![],
},
interfaces: vec![],
});
let prov = vec![make_provenance("/jars/rt.jar", true)];
let stubs = vec![stub, comparable];
let index = ClasspathIndex::build(stubs.clone());
let (result, mut staging, _registry, _interner, _meta) = run_emission(stubs, &prov);
create_classpath_edges(&index, &result.fqn_to_nodes, &prov, &mut staging);
assert!(result.fqn_to_node.contains_key("com.example.Sorted.<T>"));
assert!(result.fqn_to_node.contains_key("java.lang.Comparable"));
}
#[test]
fn test_lambda_targets_emitted() {
let mut stub = make_stub("com.example.Processor");
stub.lambda_targets = vec![LambdaTargetStub {
owner_fqn: "java.lang.String".to_owned(),
method_name: "toUpperCase".to_owned(),
method_descriptor: "()Ljava/lang/String;".to_owned(),
reference_kind: ReferenceKind::InvokeVirtual,
}];
let prov = vec![make_provenance("/jars/app.jar", true)];
let (result, _staging, _registry, _interner, _meta) = run_emission(vec![stub], &prov);
assert!(
result
.fqn_to_node
.contains_key("com.example.Processor.lambda$toUpperCase")
);
}
#[test]
fn test_inner_class_contains_edge() {
let outer = {
let mut s = make_stub("com.example.Outer");
s.inner_classes = vec![InnerClassEntry {
inner_fqn: "com.example.Outer.Inner".to_owned(),
outer_fqn: Some("com.example.Outer".to_owned()),
inner_name: Some("Inner".to_owned()),
access: AccessFlags::new(AccessFlags::ACC_PUBLIC),
}];
s
};
let inner = make_stub("com.example.Outer.Inner");
let prov = vec![make_provenance("/jars/app.jar", true)];
let stubs = vec![outer, inner];
let index = ClasspathIndex::build(stubs.clone());
let (result, mut staging, _registry, _interner, _meta) = run_emission(stubs, &prov);
create_classpath_edges(&index, &result.fqn_to_nodes, &prov, &mut staging);
assert!(result.fqn_to_node.contains_key("com.example.Outer"));
assert!(result.fqn_to_node.contains_key("com.example.Outer.Inner"));
}
#[test]
fn test_empty_index_no_nodes() {
let prov: Vec<ClasspathProvenance> = vec![];
let (result, staging, _registry, _interner, _meta) = run_emission(vec![], &prov);
assert!(result.fqn_to_node.is_empty());
assert_eq!(staging.stats().nodes_staged, 0);
}
#[test]
fn test_synthetic_file_path_convention() {
let stub = make_stub("com.example.Foo");
let prov = vec![make_provenance("/jars/example.jar", true)];
let (result, _staging, file_registry, _interner, _meta) = run_emission(vec![stub], &prov);
let file_id = result
.file_id_map
.get("com.example.Foo")
.expect("file ID should exist");
let path = file_registry
.resolve(*file_id)
.expect("file should be resolvable");
let path_str = path.to_string_lossy();
assert!(
path_str.contains("!/com/example/Foo.class"),
"synthetic path should follow JAR convention, got: {path_str}"
);
}
#[test]
fn test_interface_kind_mapping() {
let mut stub = make_stub("java.util.List");
stub.kind = ClassKind::Interface;
let prov = vec![make_provenance("/jars/rt.jar", true)];
let (result, _staging, _registry, _interner, _meta) = run_emission(vec![stub], &prov);
assert!(result.fqn_to_node.contains_key("java.util.List"));
}
#[test]
fn test_method_level_annotation_edge() {
let override_ann = {
let mut s = make_stub("java.lang.Override");
s.kind = ClassKind::Annotation;
s
};
let mut stub = make_stub("com.example.Foo");
stub.methods = vec![MethodStub {
name: "toString".to_owned(),
access: AccessFlags::new(AccessFlags::ACC_PUBLIC),
descriptor: "()Ljava/lang/String;".to_owned(),
generic_signature: None,
annotations: vec![AnnotationStub {
type_fqn: "java.lang.Override".to_owned(),
elements: vec![],
is_runtime_visible: true,
}],
parameter_annotations: vec![],
parameter_names: vec![],
return_type: TypeSignature::Base(crate::stub::model::BaseType::Void),
parameter_types: vec![],
}];
let prov = vec![make_provenance("/jars/rt.jar", true)];
let stubs = vec![stub, override_ann];
let index = ClasspathIndex::build(stubs.clone());
let (result, mut staging, _registry, _interner, _meta) = run_emission(stubs, &prov);
create_classpath_edges(&index, &result.fqn_to_nodes, &prov, &mut staging);
assert!(
result
.fqn_to_node
.contains_key("com.example.Foo.toString()Ljava/lang/String;")
);
assert!(result.fqn_to_node.contains_key("java.lang.Override"));
}
#[test]
fn test_no_provenance_still_emits() {
let stub = make_stub("com.example.NoProv");
let (result, _staging, _registry, _interner, _meta) = run_emission(vec![stub], &[]);
assert!(result.fqn_to_node.contains_key("com.example.NoProv"));
}
#[test]
fn test_visibility_mapping() {
assert_eq!(
access_to_visibility(&AccessFlags::new(AccessFlags::ACC_PUBLIC)),
"public"
);
assert_eq!(
access_to_visibility(&AccessFlags::new(AccessFlags::ACC_PROTECTED)),
"protected"
);
assert_eq!(
access_to_visibility(&AccessFlags::new(AccessFlags::ACC_PRIVATE)),
"private"
);
assert_eq!(access_to_visibility(&AccessFlags::empty()), "package");
}
}