use ryo_source::pure::{PureFile, PureItem};
use ryo_symbol::WorkspaceFilePath;
use slotmap::SecondaryMap;
use std::collections::HashSet;
use std::sync::Arc;
use crate::symbol::{SymbolId, SymbolPath, SymbolRegistry};
#[derive(Debug, Clone, Default)]
pub struct ASTRegistry {
items: SecondaryMap<SymbolId, PureItem>,
module_children: SecondaryMap<SymbolId, Vec<SymbolId>>,
inline_modules: HashSet<SymbolId>,
}
impl ASTRegistry {
pub fn new() -> Self {
Self {
items: SecondaryMap::new(),
module_children: SecondaryMap::new(),
inline_modules: HashSet::new(),
}
}
pub fn get(&self, id: SymbolId) -> Option<&PureItem> {
self.items.get(id)
}
pub fn get_mut(&mut self, id: SymbolId) -> Option<&mut PureItem> {
self.items.get_mut(id)
}
pub fn set(&mut self, id: SymbolId, item: PureItem) {
self.items.insert(id, item);
}
pub fn remove(&mut self, id: SymbolId) -> Option<PureItem> {
self.items.remove(id)
}
pub fn contains(&self, id: SymbolId) -> bool {
self.items.contains_key(id)
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (SymbolId, &PureItem)> {
self.items.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (SymbolId, &mut PureItem)> {
self.items.iter_mut()
}
pub fn clear(&mut self) {
self.items.clear();
self.module_children.clear();
self.inline_modules.clear();
}
pub fn mark_inline_module(&mut self, id: SymbolId) {
self.inline_modules.insert(id);
}
pub fn is_inline_module(&self, id: SymbolId) -> bool {
self.inline_modules.contains(&id)
}
pub fn inline_module_ids(&self) -> impl Iterator<Item = SymbolId> + '_ {
self.inline_modules.iter().copied()
}
pub fn get_module_children(&self, module_id: SymbolId) -> Option<&Vec<SymbolId>> {
self.module_children.get(module_id)
}
pub fn get_module_children_mut(&mut self, module_id: SymbolId) -> Option<&mut Vec<SymbolId>> {
self.module_children.get_mut(module_id)
}
pub fn set_module_children(&mut self, module_id: SymbolId, children: Vec<SymbolId>) {
self.module_children.insert(module_id, children);
}
pub fn add_child_to_module(&mut self, module_id: SymbolId, child_id: SymbolId) {
self.module_children
.entry(module_id)
.expect("caller must supply a valid SymbolId registered in the SlotMap")
.or_default()
.push(child_id);
}
pub fn remove_child_from_module(&mut self, module_id: SymbolId, child_id: SymbolId) {
if let Some(children) = self.module_children.get_mut(module_id) {
children.retain(|&id| id != child_id);
}
}
pub fn has_module_children(&self, module_id: SymbolId) -> bool {
self.module_children.contains_key(module_id)
}
pub fn iter_module_children(&self) -> impl Iterator<Item = (SymbolId, &Vec<SymbolId>)> {
self.module_children.iter()
}
pub fn get_module_items(&self, module_id: SymbolId) -> Option<&Vec<PureItem>> {
match self.items.get(module_id) {
Some(PureItem::Mod(m)) => Some(&m.items),
_ => None,
}
}
pub fn get_module_items_mut(&mut self, module_id: SymbolId) -> Option<&mut Vec<PureItem>> {
match self.items.get_mut(module_id) {
Some(PureItem::Mod(m)) => Some(&mut m.items),
_ => None,
}
}
pub fn set_module_items(&mut self, module_id: SymbolId, items: Vec<PureItem>) {
use ryo_source::pure::{PureMod, PureVis};
match self.items.get_mut(module_id) {
Some(PureItem::Mod(m)) => {
m.items = items;
}
_ => {
if items.is_empty() {
return;
}
self.items.insert(
module_id,
PureItem::Mod(PureMod {
attrs: vec![],
vis: PureVis::Public, name: String::new(), items,
}),
);
}
}
}
pub fn has_module_items(&self, module_id: SymbolId) -> bool {
match self.items.get(module_id) {
Some(PureItem::Mod(m)) => !m.items.is_empty(),
_ => false,
}
}
pub fn iter_module_items(&self) -> impl Iterator<Item = (SymbolId, &Vec<PureItem>)> {
self.items.iter().filter_map(|(id, item)| {
if let PureItem::Mod(m) = item {
Some((id, &m.items))
} else {
None
}
})
}
pub fn iter_module_items_mut(
&mut self,
) -> impl Iterator<Item = (SymbolId, &mut Vec<PureItem>)> {
self.items.iter_mut().filter_map(|(id, item)| {
if let PureItem::Mod(m) = item {
Some((id, &mut m.items))
} else {
None
}
})
}
pub fn build_from_files(
files: &im::HashMap<WorkspaceFilePath, Arc<PureFile>>,
registry: &SymbolRegistry,
_crate_name: &str,
) -> Self {
use ryo_symbol::SymbolPathResolver;
let mut ast_registry = Self::new();
for (file_path, file) in files {
let file_crate_name = file_path.crate_name().as_str();
let resolver = SymbolPathResolver::new(file_crate_name);
let module_path_str = resolver.module_path_str(file_path);
let module_path = match SymbolPath::parse(&module_path_str) {
Ok(p) => p,
Err(_) => continue,
};
ast_registry.store_items_from_file(&module_path, file.as_ref(), registry);
}
ast_registry
}
fn store_items_from_file(
&mut self,
module_path: &SymbolPath,
file: &PureFile,
registry: &SymbolRegistry,
) {
self.store_items_recursive(module_path, &file.items, registry);
}
fn store_items_recursive(
&mut self,
module_path: &SymbolPath,
items: &[PureItem],
registry: &SymbolRegistry,
) {
use ryo_source::pure::PureImpl;
use std::collections::HashMap;
let mut impl_map: HashMap<(String, Option<String>), PureImpl> = HashMap::new();
let mut merged_items: Vec<PureItem> = Vec::new();
for item in items {
if let PureItem::Impl(impl_block) = item {
let key = (impl_block.self_ty.clone(), impl_block.trait_.clone());
if let Some(existing) = impl_map.get_mut(&key) {
existing.items.extend(impl_block.items.clone());
} else {
impl_map.insert(key, impl_block.clone());
}
} else {
merged_items.push(item.clone());
}
}
for impl_block in impl_map.into_values() {
merged_items.push(PureItem::Impl(impl_block));
}
let mut child_ids: Vec<SymbolId> = Vec::new();
for item in &merged_items {
if let PureItem::Impl(impl_block) = item {
use ryo_source::pure::PureImplItem;
let (impl_path_str, method_base_path) =
if let Some(ref trait_name) = &impl_block.trait_ {
let impl_path = format!(
"{}::<impl {} for {}>",
module_path, trait_name, impl_block.self_ty
);
(impl_path.clone(), impl_path)
} else {
let impl_path = format!("{}::<impl {}>", module_path, impl_block.self_ty);
let base_type = impl_block
.self_ty
.split('<')
.next()
.unwrap_or(&impl_block.self_ty)
.trim();
let method_path = format!("{}::{}", module_path, base_type);
(impl_path, method_path)
};
if let Ok(impl_path) = SymbolPath::parse(&impl_path_str) {
if let Some(id) = registry.lookup(&impl_path) {
self.set(id, item.clone());
child_ids.push(id);
}
}
for impl_item in &impl_block.items {
let (item_name, pure_item) = match impl_item {
PureImplItem::Fn(m) => (m.name.clone(), PureItem::Fn(m.clone())),
PureImplItem::Const(c) => (c.name.clone(), PureItem::Const(c.clone())),
PureImplItem::Type(t) => (t.name.clone(), PureItem::Type(t.clone())),
PureImplItem::Other(_) => continue,
};
let item_path_str = format!("{}::{}", method_base_path, item_name);
if let Ok(item_path) = SymbolPath::parse(&item_path_str) {
if let Some(id) = registry.lookup(&item_path) {
self.set(id, pure_item);
}
}
}
continue;
}
if let PureItem::Mod(m) = item {
if m.items.is_empty() {
continue;
}
let mod_path = match module_path.child(&m.name) {
Ok(p) => p,
Err(_) => continue,
};
if let Some(id) = registry.lookup(&mod_path) {
child_ids.push(id);
self.mark_inline_module(id);
self.store_items_recursive(&mod_path, &m.items, registry);
self.set(id, item.clone());
}
continue;
}
let name = match item_name(item) {
Some(n) => n,
None => continue,
};
let item_path = match module_path.child(&name) {
Ok(p) => p,
Err(_) => continue,
};
if let Some(id) = registry.lookup(&item_path) {
self.set(id, item.clone());
child_ids.push(id);
}
}
if let Some(module_id) = registry.lookup(module_path) {
let vis = registry
.visibility(module_id)
.map(|v| match v {
ryo_symbol::Visibility::Public => ryo_source::pure::PureVis::Public,
_ => ryo_source::pure::PureVis::Private,
})
.unwrap_or_default();
let pure_mod = ryo_source::pure::PureMod {
attrs: vec![],
vis,
name: module_path
.segment_refs()
.last()
.map(|s| s.name().to_string())
.unwrap_or_default(),
items: merged_items,
};
self.set(module_id, PureItem::Mod(pure_mod));
self.set_module_children(module_id, child_ids);
}
}
}
fn item_name(item: &PureItem) -> Option<String> {
match item {
PureItem::Struct(s) => Some(s.name.clone()),
PureItem::Enum(e) => Some(e.name.clone()),
PureItem::Fn(f) => Some(f.name.clone()),
PureItem::Mod(m) => Some(m.name.clone()),
PureItem::Trait(t) => Some(t.name.clone()),
PureItem::Type(t) => Some(t.name.clone()),
PureItem::Const(c) => Some(c.name.clone()),
PureItem::Static(s) => Some(s.name.clone()),
PureItem::Impl(i) => {
let impl_suffix = match &i.trait_ {
Some(trait_name) => format!("impl_{}", trait_name.replace("::", "_")),
None => "impl".to_string(),
};
Some(format!("{}::{}", i.self_ty, impl_suffix))
}
PureItem::Use(_) | PureItem::Macro(_) | PureItem::Other(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use ryo_source::pure::{PureFields, PureStruct, PureVis};
use slotmap::SlotMap;
fn make_symbol_id() -> SymbolId {
let mut sm: SlotMap<SymbolId, ()> = SlotMap::with_key();
sm.insert(())
}
#[test]
fn test_inline_module_recursive() {
use ryo_source::pure::PureFile;
let source = r#"
pub struct Config {
pub name: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config() {
let c = Config { name: "test".to_string() };
assert_eq!(c.name, "test");
}
}
"#;
let file = PureFile::from_source(source).unwrap();
assert_eq!(file.items.len(), 2, "Should have Config and tests module");
let tests_mod = file.items.iter().find_map(|item| {
if let PureItem::Mod(m) = item {
if m.name == "tests" {
return Some(m);
}
}
None
});
let tests_mod = tests_mod.expect("Should have tests module");
assert!(
!tests_mod.items.is_empty(),
"tests module should have items"
);
let has_fn = tests_mod
.items
.iter()
.any(|item| matches!(item, PureItem::Fn(f) if f.name == "test_config"));
assert!(
has_fn,
"tests module should have test_config function, got: {:?}",
tests_mod
.items
.iter()
.map(std::mem::discriminant)
.collect::<Vec<_>>()
);
}
#[test]
fn test_cfg_test_attr_preserved_in_registry() {
use crate::SymbolKind;
use ryo_source::pure::{PureAttrMeta, PureFile};
let source = r#"
pub struct Config {
pub name: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config() {
let c = Config { name: "test".to_string() };
assert_eq!(c.name, "test");
}
}
"#;
let file = PureFile::from_source(source).unwrap();
let mut symbol_registry = SymbolRegistry::new();
symbol_registry
.register(SymbolPath::parse("my_crate").unwrap(), SymbolKind::Mod)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::Config").unwrap(),
SymbolKind::Struct,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests").unwrap(),
SymbolKind::Mod,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests::test_config").unwrap(),
SymbolKind::Function,
)
.unwrap();
let mut ast_registry = ASTRegistry::new();
let module_path = SymbolPath::parse("my_crate").unwrap();
ast_registry.store_items_from_file(&module_path, &file, &symbol_registry);
let tests_path = SymbolPath::parse("my_crate::tests").unwrap();
let tests_id = symbol_registry
.lookup(&tests_path)
.expect("tests should exist");
let tests_item = ast_registry
.get(tests_id)
.expect("tests AST should be registered");
if let PureItem::Mod(m) = tests_item {
let has_cfg_test = m.attrs.iter().any(|attr| {
attr.path == "cfg" && matches!(&attr.meta, PureAttrMeta::List(s) if s == "test")
});
assert!(
has_cfg_test,
"tests module should have #[cfg(test)] attribute, got attrs: {:?}",
m.attrs
);
} else {
panic!("tests should be a module, got: {:?}", tests_item);
}
assert!(
ast_registry.is_inline_module(tests_id),
"tests should be marked as inline module"
);
let test_config_path = SymbolPath::parse("my_crate::tests::test_config").unwrap();
let test_config_id = symbol_registry
.lookup(&test_config_path)
.expect("test_config should exist");
assert!(
ast_registry.contains(test_config_id),
"test_config AST should be registered"
);
if let Some(PureItem::Fn(f)) = ast_registry.get(test_config_id) {
let has_test_attr = f.attrs.iter().any(|attr| attr.path == "test");
assert!(
has_test_attr,
"test_config should have #[test] attribute, got attrs: {:?}",
f.attrs
);
} else {
panic!(
"test_config should be a function, got: {:?}",
ast_registry.get(test_config_id)
);
}
let module_items = ast_registry
.get_module_items(tests_id)
.expect("tests module should have items");
let has_use = module_items
.iter()
.any(|item| matches!(item, PureItem::Use(_)));
assert!(
has_use,
"tests module items should contain use statement, got: {:?}",
module_items.iter().map(item_name).collect::<Vec<_>>()
);
let has_fn = module_items
.iter()
.any(|item| matches!(item, PureItem::Fn(f) if f.name == "test_config"));
assert!(
has_fn,
"tests module items should contain test_config function"
);
}
#[test]
fn test_nested_inline_test_modules() {
use crate::SymbolKind;
use ryo_source::pure::{PureAttrMeta, PureFile};
let source = r#"
pub struct Outer;
#[cfg(test)]
mod tests {
use super::*;
mod nested {
use super::*;
#[test]
fn test_nested() {}
}
#[test]
fn test_outer() {}
}
"#;
let file = PureFile::from_source(source).unwrap();
let mut symbol_registry = SymbolRegistry::new();
symbol_registry
.register(SymbolPath::parse("my_crate").unwrap(), SymbolKind::Mod)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::Outer").unwrap(),
SymbolKind::Struct,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests").unwrap(),
SymbolKind::Mod,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests::nested").unwrap(),
SymbolKind::Mod,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests::test_outer").unwrap(),
SymbolKind::Function,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests::nested::test_nested").unwrap(),
SymbolKind::Function,
)
.unwrap();
let mut ast_registry = ASTRegistry::new();
let module_path = SymbolPath::parse("my_crate").unwrap();
ast_registry.store_items_from_file(&module_path, &file, &symbol_registry);
let tests_path = SymbolPath::parse("my_crate::tests").unwrap();
let tests_id = symbol_registry.lookup(&tests_path).unwrap();
if let Some(PureItem::Mod(m)) = ast_registry.get(tests_id) {
let has_cfg_test = m.attrs.iter().any(|attr| {
attr.path == "cfg" && matches!(&attr.meta, PureAttrMeta::List(s) if s == "test")
});
assert!(
has_cfg_test,
"tests module should have #[cfg(test)], got: {:?}",
m.attrs
);
} else {
panic!("tests should be a module");
}
assert!(ast_registry.is_inline_module(tests_id));
let nested_path = SymbolPath::parse("my_crate::tests::nested").unwrap();
let nested_id = symbol_registry.lookup(&nested_path).unwrap();
assert!(ast_registry.is_inline_module(nested_id));
let nested_fn_path = SymbolPath::parse("my_crate::tests::nested::test_nested").unwrap();
let nested_fn_id = symbol_registry.lookup(&nested_fn_path).unwrap();
assert!(
ast_registry.contains(nested_fn_id),
"nested test function should be registered"
);
let tests_children = ast_registry.get_module_children(tests_id).unwrap();
assert!(
tests_children.contains(&nested_id),
"tests children should contain nested module"
);
}
#[test]
fn test_inline_test_module_with_impl() {
use crate::SymbolKind;
use ryo_source::pure::{PureAttrMeta, PureFile};
let source = r#"
pub struct Config {
pub name: String,
}
#[cfg(test)]
mod tests {
use super::*;
struct TestHelper {
value: i32,
}
impl TestHelper {
fn new(value: i32) -> Self {
Self { value }
}
}
#[test]
fn test_with_helper() {
let helper = TestHelper::new(42);
assert_eq!(helper.value, 42);
}
}
"#;
let file = PureFile::from_source(source).unwrap();
let mut symbol_registry = SymbolRegistry::new();
symbol_registry
.register(SymbolPath::parse("my_crate").unwrap(), SymbolKind::Mod)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::Config").unwrap(),
SymbolKind::Struct,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests").unwrap(),
SymbolKind::Mod,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests::TestHelper").unwrap(),
SymbolKind::Struct,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests::<impl TestHelper>").unwrap(),
SymbolKind::Impl,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests::TestHelper::new").unwrap(),
SymbolKind::Function,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests::test_with_helper").unwrap(),
SymbolKind::Function,
)
.unwrap();
let mut ast_registry = ASTRegistry::new();
let module_path = SymbolPath::parse("my_crate").unwrap();
ast_registry.store_items_from_file(&module_path, &file, &symbol_registry);
let tests_path = SymbolPath::parse("my_crate::tests").unwrap();
let tests_id = symbol_registry.lookup(&tests_path).unwrap();
if let Some(PureItem::Mod(m)) = ast_registry.get(tests_id) {
let has_cfg_test = m.attrs.iter().any(|attr| {
attr.path == "cfg" && matches!(&attr.meta, PureAttrMeta::List(s) if s == "test")
});
assert!(
has_cfg_test,
"tests module should have #[cfg(test)], got: {:?}",
m.attrs
);
} else {
panic!("tests should be a module");
}
let helper_path = SymbolPath::parse("my_crate::tests::TestHelper").unwrap();
let helper_id = symbol_registry.lookup(&helper_path).unwrap();
assert!(
ast_registry.contains(helper_id),
"TestHelper struct should be registered"
);
let impl_path = SymbolPath::parse("my_crate::tests::<impl TestHelper>").unwrap();
let impl_id = symbol_registry.lookup(&impl_path).unwrap();
assert!(
ast_registry.contains(impl_id),
"impl TestHelper should be registered"
);
let new_path = SymbolPath::parse("my_crate::tests::TestHelper::new").unwrap();
let new_id = symbol_registry.lookup(&new_path).unwrap();
assert!(
ast_registry.contains(new_id),
"TestHelper::new method should be registered"
);
let module_items = ast_registry
.get_module_items(tests_id)
.expect("tests module should have items");
assert!(
module_items
.iter()
.any(|item| matches!(item, PureItem::Use(_))),
"module_items should contain use statement"
);
assert!(
module_items
.iter()
.any(|item| matches!(item, PureItem::Struct(s) if s.name == "TestHelper")),
"module_items should contain TestHelper struct"
);
assert!(
module_items
.iter()
.any(|item| matches!(item, PureItem::Impl(i) if i.self_ty == "TestHelper")),
"module_items should contain impl TestHelper"
);
assert!(
module_items
.iter()
.any(|item| matches!(item, PureItem::Fn(f) if f.name == "test_with_helper")),
"module_items should contain test_with_helper function"
);
}
#[test]
fn test_store_items_recursive_with_registry() {
use crate::SymbolKind;
use ryo_source::pure::PureFile;
let source = r#"
pub struct Config {
pub name: String,
}
mod tests {
fn test_config() {}
}
"#;
let file = PureFile::from_source(source).unwrap();
let mut symbol_registry = SymbolRegistry::new();
symbol_registry
.register(SymbolPath::parse("my_crate").unwrap(), SymbolKind::Mod)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::Config").unwrap(),
SymbolKind::Struct,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests").unwrap(),
SymbolKind::Mod,
)
.unwrap();
symbol_registry
.register(
SymbolPath::parse("my_crate::tests::test_config").unwrap(),
SymbolKind::Function,
)
.unwrap();
let mut ast_registry = ASTRegistry::new();
let module_path = SymbolPath::parse("my_crate").unwrap();
ast_registry.store_items_from_file(&module_path, &file, &symbol_registry);
let config_path = SymbolPath::parse("my_crate::Config").unwrap();
let config_id = symbol_registry
.lookup(&config_path)
.expect("Config should exist");
assert!(
ast_registry.contains(config_id),
"Config AST should be registered"
);
let tests_path = SymbolPath::parse("my_crate::tests").unwrap();
let tests_id = symbol_registry
.lookup(&tests_path)
.expect("tests should exist");
assert!(
ast_registry.contains(tests_id),
"tests AST should be registered"
);
let test_config_path = SymbolPath::parse("my_crate::tests::test_config").unwrap();
let test_config_id = symbol_registry
.lookup(&test_config_path)
.expect("test_config should exist");
assert!(
ast_registry.contains(test_config_id),
"test_config AST should be registered via recursive processing"
);
if let Some(PureItem::Fn(f)) = ast_registry.get(test_config_id) {
assert_eq!(f.name, "test_config");
} else {
panic!(
"test_config should be a function, got: {:?}",
ast_registry.get(test_config_id)
);
}
}
#[test]
fn test_basic_operations() {
let mut registry = ASTRegistry::new();
let id = make_symbol_id();
assert!(registry.is_empty());
assert!(!registry.contains(id));
assert!(registry.get(id).is_none());
let item = PureItem::Struct(PureStruct {
attrs: vec![],
vis: PureVis::Public,
name: "TestStruct".to_string(),
generics: Default::default(),
fields: PureFields::Unit,
});
registry.set(id, item);
assert!(!registry.is_empty());
assert!(registry.contains(id));
assert!(registry.get(id).is_some());
assert_eq!(registry.len(), 1);
if let Some(PureItem::Struct(s)) = registry.get_mut(id) {
s.name = "ModifiedStruct".to_string();
}
if let Some(PureItem::Struct(s)) = registry.get(id) {
assert_eq!(s.name, "ModifiedStruct");
} else {
panic!("Expected struct");
}
let removed = registry.remove(id);
assert!(removed.is_some());
assert!(registry.is_empty());
}
#[test]
fn test_iteration() {
let mut registry = ASTRegistry::new();
let mut sm: SlotMap<SymbolId, ()> = SlotMap::with_key();
let id1 = sm.insert(());
let id2 = sm.insert(());
registry.set(
id1,
PureItem::Struct(PureStruct {
attrs: vec![],
vis: PureVis::Public,
name: "Struct1".to_string(),
generics: Default::default(),
fields: PureFields::Unit,
}),
);
registry.set(
id2,
PureItem::Struct(PureStruct {
attrs: vec![],
vis: PureVis::Private,
name: "Struct2".to_string(),
generics: Default::default(),
fields: PureFields::Unit,
}),
);
let items: Vec<_> = registry.iter().collect();
assert_eq!(items.len(), 2);
}
#[test]
fn test_is_binary_entry() {
let main_rs = WorkspaceFilePath::new_for_test("src/main.rs", "/workspace", "my_crate");
assert!(main_rs.is_binary_entry());
let lib_rs = WorkspaceFilePath::new_for_test("src/lib.rs", "/workspace", "my_crate");
assert!(!lib_rs.is_binary_entry());
let bin_foo = WorkspaceFilePath::new_for_test("src/bin/foo.rs", "/workspace", "my_crate");
assert!(bin_foo.is_binary_entry());
let module = WorkspaceFilePath::new_for_test("src/utils/mod.rs", "/workspace", "my_crate");
assert!(!module.is_binary_entry());
let workspace_main =
WorkspaceFilePath::new_for_test("crates/my_app/src/main.rs", "/workspace", "my_app");
assert!(workspace_main.is_binary_entry());
}
}