use std::collections::HashMap;
use syn::{Item, ItemImpl};
pub struct ScopeAnalyzer {
type_locations: HashMap<String, TypeLocation>,
impl_blocks: Vec<ImplBlockInfo>,
}
#[derive(Debug, Clone)]
pub struct TypeLocation {
#[allow(dead_code)]
pub type_name: String,
#[allow(dead_code)]
pub module_path: String,
pub needs_impl_module: bool,
}
#[derive(Clone)]
pub struct ImplBlockInfo {
pub type_name: String,
#[allow(dead_code)]
pub impl_item: ItemImpl,
pub suggested_module: String,
pub method_count: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ImplOrganizationStrategy {
Inline,
Submodule {
parent_module: String,
impl_modules: Vec<String>,
},
Wrapper {
module_name: String,
},
}
impl Default for ScopeAnalyzer {
fn default() -> Self {
Self::new()
}
}
impl ScopeAnalyzer {
pub fn new() -> Self {
Self {
type_locations: HashMap::new(),
impl_blocks: Vec::new(),
}
}
pub fn analyze_types(&mut self, items: &[Item]) {
for item in items {
match item {
Item::Struct(s) => {
self.register_type(&s.ident.to_string(), "types");
}
Item::Enum(e) => {
self.register_type(&e.ident.to_string(), "types");
}
_ => {}
}
}
}
fn register_type(&mut self, type_name: &str, module_path: &str) {
self.type_locations.insert(
type_name.to_string(),
TypeLocation {
type_name: type_name.to_string(),
module_path: module_path.to_string(),
needs_impl_module: false,
},
);
}
pub fn mark_needs_impl_module(&mut self, type_name: &str) {
if let Some(location) = self.type_locations.get_mut(type_name) {
location.needs_impl_module = true;
}
}
pub fn register_impl_block(
&mut self,
type_name: String,
impl_item: ItemImpl,
suggested_module: String,
method_count: usize,
) {
self.impl_blocks.push(ImplBlockInfo {
type_name,
impl_item,
suggested_module,
method_count,
});
}
pub fn determine_strategy(&self, type_name: &str) -> ImplOrganizationStrategy {
let impl_blocks: Vec<_> = self
.impl_blocks
.iter()
.filter(|b| b.type_name == type_name)
.collect();
if impl_blocks.is_empty() {
return ImplOrganizationStrategy::Inline;
}
let total_methods: usize = impl_blocks.iter().map(|b| b.method_count).sum();
if total_methods < 10 {
ImplOrganizationStrategy::Inline
} else if impl_blocks.len() == 1 {
ImplOrganizationStrategy::Wrapper {
module_name: format!("{}_module", type_name.to_lowercase()),
}
} else {
let impl_modules: Vec<String> = impl_blocks
.iter()
.map(|b| b.suggested_module.clone())
.collect();
ImplOrganizationStrategy::Submodule {
parent_module: format!("{}_type", type_name.to_lowercase()),
impl_modules,
}
}
}
#[allow(dead_code)]
pub fn generate_module_structure(&self, type_name: &str) -> ModuleStructure {
let strategy = self.determine_strategy(type_name);
match strategy {
ImplOrganizationStrategy::Inline => ModuleStructure {
type_module: format!("{}_type", type_name.to_lowercase()),
needs_path_attributes: false,
path_includes: Vec::new(),
re_exports: vec![format!(
"pub use {}::*;",
format!("{}_type", type_name.to_lowercase())
)],
},
ImplOrganizationStrategy::Wrapper { module_name } => ModuleStructure {
type_module: module_name.clone(),
needs_path_attributes: false,
path_includes: Vec::new(),
re_exports: vec![format!("pub use {}::*;", module_name)],
},
ImplOrganizationStrategy::Submodule {
parent_module,
impl_modules,
} => {
let path_includes: Vec<String> = impl_modules
.iter()
.map(|m| format!("#[path = \"{}.rs\"]\nmod {};", m, m.replace('-', "_")))
.collect();
ModuleStructure {
type_module: parent_module.clone(),
needs_path_attributes: true,
path_includes,
re_exports: vec![format!("pub use {}::*;", parent_module)],
}
}
}
}
#[allow(dead_code)]
pub fn generate_type_module_content(
&self,
type_name: &str,
type_item: &Item,
_struct_visibility: &str,
) -> String {
let strategy = self.determine_strategy(type_name);
let mut content = String::new();
content.push_str("//! Auto-generated type module\n\n");
content.push_str("use std::collections::{HashMap, HashSet};\n");
content.push_str("use super::super::types::*;\n\n");
match type_item {
Item::Struct(s) => {
let struct_code = quote::quote! { #s }.to_string();
content.push_str(&struct_code);
content.push_str("\n\n");
}
Item::Enum(e) => {
let enum_code = quote::quote! { #e }.to_string();
content.push_str(&enum_code);
content.push_str("\n\n");
}
_ => {}
}
if let ImplOrganizationStrategy::Submodule { impl_modules, .. } = strategy {
content.push_str("// Include impl block modules\n");
for module in impl_modules {
content.push_str(&format!("#[path = \"{}.rs\"]\n", module));
content.push_str(&format!("mod {};\n", module.replace('-', "_")));
}
}
content
}
pub fn infer_field_visibility(&self, type_name: &str) -> FieldVisibility {
let strategy = self.determine_strategy(type_name);
match strategy {
ImplOrganizationStrategy::Inline => FieldVisibility::Private,
ImplOrganizationStrategy::Wrapper { .. } => FieldVisibility::Private,
ImplOrganizationStrategy::Submodule { .. } => {
FieldVisibility::PubSuper
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FieldVisibility {
Private,
PubSuper,
#[allow(dead_code)]
PubCrate,
#[allow(dead_code)]
Pub,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ModuleStructure {
pub type_module: String,
pub needs_path_attributes: bool,
pub path_includes: Vec<String>,
pub re_exports: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scope_analyzer_creation() {
let analyzer = ScopeAnalyzer::new();
assert!(analyzer.type_locations.is_empty());
assert!(analyzer.impl_blocks.is_empty());
}
#[test]
fn test_register_type() {
let mut analyzer = ScopeAnalyzer::new();
analyzer.register_type("TestStruct", "types");
assert_eq!(analyzer.type_locations.len(), 1);
assert!(analyzer.type_locations.contains_key("TestStruct"));
}
#[test]
fn test_strategy_inline_few_methods() {
let mut analyzer = ScopeAnalyzer::new();
analyzer.register_type("SmallStruct", "types");
let impl_item = syn::parse_quote! {
impl SmallStruct {
pub fn method1(&self) {}
pub fn method2(&self) {}
}
};
analyzer.register_impl_block(
"SmallStruct".to_string(),
impl_item,
"smallstruct_methods".to_string(),
2,
);
let strategy = analyzer.determine_strategy("SmallStruct");
assert_eq!(strategy, ImplOrganizationStrategy::Inline);
}
#[test]
fn test_field_visibility_inference() {
let mut analyzer = ScopeAnalyzer::new();
analyzer.register_type("TestStruct", "types");
let visibility = analyzer.infer_field_visibility("TestStruct");
assert_eq!(visibility, FieldVisibility::Private);
}
}