#![allow(dead_code)]
use crate::file_analyzer::{TraitImplInfo, TypeInfo};
use crate::import_analyzer::ImportAnalyzer;
use crate::method_analyzer::MethodGroup;
use crate::scope_analyzer;
use crate::trait_method_tracker::TraitMethodTracker;
use anyhow::Result;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use syn::{File, Item};
#[derive(Clone)]
pub(crate) struct Module {
pub(crate) name: String,
pub(crate) types: Vec<TypeInfo>,
pub(crate) standalone_items: Vec<Item>,
pub(crate) impl_type_name: Option<String>,
pub(crate) impl_self_ty: Option<Box<syn::Type>>,
pub(crate) impl_generics: Option<syn::Generics>,
pub(crate) impl_attrs: Vec<syn::Attribute>,
pub(crate) method_group: Option<MethodGroup>,
pub(crate) field_visibility: Option<scope_analyzer::FieldVisibility>,
pub(crate) type_name_for_traits: Option<String>,
pub(crate) trait_impls: Vec<TraitImplInfo>,
}
impl Module {
pub(crate) fn new(name: String) -> Self {
Self {
name,
types: Vec::new(),
standalone_items: Vec::new(),
impl_type_name: None,
impl_self_ty: None,
impl_generics: None,
impl_attrs: Vec::new(),
method_group: None,
field_visibility: None,
type_name_for_traits: None,
trait_impls: Vec::new(),
}
}
pub(crate) fn get_exported_types(&self) -> Vec<String> {
let mut exported = Vec::new();
for type_info in &self.types {
exported.push(type_info.name.clone());
}
if let Some(type_name) = &self.impl_type_name {
if self.types.iter().any(|t| &t.name == type_name) {
} else {
}
}
for item in &self.standalone_items {
match item {
Item::Fn(f) => exported.push(f.sig.ident.to_string()),
Item::Const(c) => exported.push(c.ident.to_string()),
Item::Static(s) => exported.push(s.ident.to_string()),
Item::Type(t) => exported.push(t.ident.to_string()),
Item::Trait(t) => exported.push(t.ident.to_string()),
Item::Enum(e) => exported.push(e.ident.to_string()),
Item::Struct(s) => exported.push(s.ident.to_string()),
Item::Macro(m) => {
if let Some(ident) = &m.ident {
exported.push(ident.to_string());
}
}
_ => {}
}
}
exported
}
fn collect_used_symbols(&self) -> HashSet<String> {
let mut symbols = HashSet::new();
for type_info in &self.types {
Self::extract_symbols_from_item(&type_info.item, &mut symbols);
for impl_item in &type_info.impls {
Self::extract_symbols_from_item(impl_item, &mut symbols);
}
}
for item in &self.standalone_items {
Self::extract_symbols_from_item(item, &mut symbols);
}
for trait_impl in &self.trait_impls {
Self::extract_symbols_from_item(&trait_impl.impl_item, &mut symbols);
}
if let Some(method_group) = &self.method_group {
for method in &method_group.methods {
let method_item = &method.item;
let method_str = quote::quote!(#method_item).to_string();
Self::extract_symbols_from_code(&method_str, &mut symbols);
}
}
symbols
}
fn extract_symbols_from_item(item: &Item, symbols: &mut HashSet<String>) {
let item_str = quote::quote!(#item).to_string();
Self::extract_symbols_from_code(&item_str, symbols);
}
fn extract_symbols_from_code(code: &str, symbols: &mut HashSet<String>) {
for word in code.split(|c: char| !c.is_alphanumeric() && c != '_') {
let word = word.trim();
if !word.is_empty() {
if word
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
{
symbols.insert(word.to_string());
}
if word.contains("::") {
for part in word.split("::") {
if !part.is_empty() {
symbols.insert(part.to_string());
}
}
}
}
}
}
fn is_use_needed(&self, use_item: &Item, used_symbols: &HashSet<String>) -> bool {
if let Item::Use(use_stmt) = use_item {
let use_str = quote::quote!(#use_stmt).to_string();
if use_str.contains("::*") || use_str.contains(":: *") {
return true;
}
let imported = Self::extract_imported_symbols(&use_str);
for sym in imported {
if used_symbols.contains(&sym) {
return true;
}
}
false
} else {
false
}
}
fn extract_imported_symbols(use_str: &str) -> Vec<String> {
let mut symbols = Vec::new();
let trimmed = use_str
.trim()
.trim_start_matches("use ")
.trim_end_matches(';')
.trim();
if let Some(brace_start) = trimmed.find('{') {
if let Some(brace_end) = trimmed.find('}') {
let group = &trimmed[brace_start + 1..brace_end];
for item in group.split(',') {
let item = item.trim();
let name = if let Some(as_pos) = item.find(" as ") {
item[as_pos + 4..].trim()
} else {
item
};
if !name.is_empty() && name != "self" {
symbols.push(name.to_string());
}
}
}
} else {
if let Some(last_segment) = trimmed.split("::").last() {
let name = if let Some(as_pos) = last_segment.find(" as ") {
last_segment[as_pos + 4..].trim()
} else {
last_segment.trim()
};
if !name.is_empty() && name != "*" && name != "self" {
symbols.push(name.to_string());
}
}
}
symbols
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn generate_content(
&self,
original_file: &File,
original_use_statements: &[Item],
type_to_module: &std::collections::HashMap<String, String>,
needs_pub_super: &HashSet<String>,
cross_module_imports: Option<&HashMap<String, Vec<String>>>,
fields_need_pub_super: &HashMap<String, HashSet<String>>,
trait_tracker: Option<&TraitMethodTracker>,
) -> String {
let mut content = String::new();
if let Some(type_name) = &self.type_name_for_traits {
content.push_str(&format!(
"//! # {} - Trait Implementations\n//!\n",
type_name
));
content.push_str(&format!(
"//! This module contains trait implementations for `{}`.\n//!\n",
type_name
));
content.push_str("//! ## Implemented Traits\n//!\n");
for trait_impl in &self.trait_impls {
content.push_str(&format!("//! - `{}`\n", trait_impl.trait_name));
}
content.push_str("//!\n");
content.push_str(
"//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)\n\n",
);
} else if let Some(type_name) = &self.impl_type_name {
if let Some(method_group) = &self.method_group {
content.push_str(&format!(
"//! # {} - {} Methods\n//!\n",
type_name,
method_group.suggest_name()
));
content.push_str(&format!(
"//! This module contains method implementations for `{}`.\n//!\n",
type_name
));
content.push_str(
"//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)\n\n",
);
} else {
content.push_str("//! Auto-generated module\n\n");
}
} else {
content.push_str("//! Auto-generated module\n//!\n");
content.push_str(
"//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)\n\n",
);
}
let mut import_analyzer = ImportAnalyzer::new();
import_analyzer.analyze_file(original_file);
let used_symbols = self.collect_used_symbols();
let mut use_items: Vec<Item> = Vec::new();
for use_item in original_use_statements {
if self.is_use_needed(use_item, &used_symbols) {
use_items.push(use_item.clone());
}
}
let mut already_imported: HashSet<String> = HashSet::new();
for orig_item in original_use_statements.iter() {
let orig_str = quote::quote!(#orig_item).to_string();
if orig_str.contains("collections") {
for sym in Self::extract_imported_symbols(&orig_str) {
already_imported.insert(sym);
}
}
}
if !use_items.is_empty() {
for item in &use_items {
let item_str = prettyplease::unparse(&syn::File {
shebang: None,
attrs: Vec::new(),
items: vec![item.clone()],
});
if item_str.contains("std::collections") {
for sym in Self::extract_imported_symbols(&item_str) {
already_imported.insert(sym);
}
}
}
let formatted = prettyplease::unparse(&syn::File {
shebang: None,
attrs: Vec::new(),
items: use_items,
});
content.push_str(&formatted);
content.push('\n');
}
let my_exports: HashSet<String> = self.get_exported_types().into_iter().collect();
let mut super_imports: Vec<(String, String)> = Vec::new();
for symbol in &used_symbols {
if my_exports.contains(symbol) {
continue;
}
if let Some(module_name) = type_to_module.get(symbol) {
if module_name != &self.name {
super_imports.push((module_name.clone(), symbol.clone()));
}
}
}
let mut imports_by_module: std::collections::HashMap<String, Vec<String>> =
std::collections::HashMap::new();
for (module_name, type_name) in super_imports {
imports_by_module
.entry(module_name)
.or_default()
.push(type_name);
}
let mut has_super_imports = !imports_by_module.is_empty();
for (module_name, mut types) in imports_by_module {
types.sort();
types.dedup();
for t in &types {
already_imported.insert(t.clone());
}
if types.len() == 1 {
content.push_str(&format!("use super::{}::{};\n", module_name, types[0]));
} else {
content.push_str(&format!(
"use super::{}::{{{}}};\n",
module_name,
types.join(", ")
));
}
}
if let Some(fn_imports) = cross_module_imports {
for (source_module, mut functions) in fn_imports.clone() {
functions.sort();
functions.dedup();
has_super_imports = true;
if functions.len() == 1 {
content.push_str(&format!(
"use super::{}::{};\n",
source_module, functions[0]
));
} else {
content.push_str(&format!(
"use super::{}::{{{}}};\n",
source_module,
functions.join(", ")
));
}
}
}
if let Some(tracker) = trait_tracker {
let trait_imports =
tracker.get_required_trait_imports(&self.standalone_items, &self.name);
for (trait_name, trait_module) in trait_imports {
if trait_module != self.name && !already_imported.contains(&trait_name) {
content.push_str(&format!("use super::{}::{};\n", trait_module, trait_name));
already_imported.insert(trait_name);
has_super_imports = true;
}
}
}
if has_super_imports {
content.push('\n');
}
if let Some(_type_name) = &self.type_name_for_traits {
for trait_impl in &self.trait_impls {
let formatted = prettyplease::unparse(&syn::File {
shebang: None,
attrs: Vec::new(),
items: vec![trait_impl.impl_item.clone()],
});
content.push_str(&formatted);
content.push('\n');
}
return content;
}
if let Some(type_name) = &self.impl_type_name {
if used_symbols.contains("HashMap") || used_symbols.contains("HashSet") {
let mut collections: Vec<&str> = Vec::new();
if used_symbols.contains("HashMap") && !already_imported.contains("HashMap") {
collections.push("HashMap");
}
if used_symbols.contains("HashSet") && !already_imported.contains("HashSet") {
collections.push("HashSet");
}
if !collections.is_empty() {
for c in &collections {
already_imported.insert(c.to_string());
}
content.push_str(&format!(
"use std::collections::{{{}}};\n",
collections.join(", ")
));
}
}
if !already_imported.contains(type_name) {
if let Some(module_name) = type_to_module.get(type_name) {
if module_name != &self.name {
content.push_str(&format!("use super::{}::{};\n", module_name, type_name));
already_imported.insert(type_name.clone());
}
} else {
let type_module_name = format!("{}_type", type_name.to_lowercase());
content.push_str(&format!(
"use super::{}::{};\n",
type_module_name, type_name
));
already_imported.insert(type_name.clone());
}
content.push('\n');
}
}
if let Some(method_group) = &self.method_group {
if let Some(type_name) = &self.impl_type_name {
let mut impl_items = Vec::new();
for method in &method_group.methods {
impl_items.push(syn::ImplItem::Fn(method.item.clone()));
}
let impl_block = syn::ItemImpl {
attrs: self.impl_attrs.clone(),
defaultness: None,
unsafety: None,
impl_token: Default::default(),
generics: self.impl_generics.clone().unwrap_or_default(),
trait_: None,
self_ty: self.impl_self_ty.clone().unwrap_or_else(|| {
Box::new(syn::parse_str::<syn::Type>(type_name).unwrap_or_else(|_| {
let ident = quote::format_ident!("{}", type_name);
syn::Type::Path(syn::TypePath {
qself: None,
path: syn::Path::from(ident),
})
}))
}),
brace_token: Default::default(),
items: impl_items,
};
let formatted = prettyplease::unparse(&syn::File {
shebang: None,
attrs: Vec::new(),
items: vec![syn::Item::Impl(impl_block)],
});
content.push_str(&formatted);
return content;
}
}
let mut types_used = std::collections::HashSet::new();
for type_info in &self.types {
if let Item::Struct(s) = &type_info.item {
for field in &s.fields {
extract_type_names(&field.ty, &mut types_used);
}
} else if let Item::Enum(e) = &type_info.item {
for variant in &e.variants {
for field in &variant.fields {
extract_type_names(&field.ty, &mut types_used);
}
}
}
}
if !types_used.is_empty() {
let needs_collections = types_used.iter().any(|t| {
(t == "HashMap"
|| t == "HashSet"
|| t == "BTreeMap"
|| t == "BTreeSet"
|| t == "VecDeque")
&& !already_imported.contains(t.as_str())
});
if needs_collections {
let mut collection_types: Vec<String> = types_used
.iter()
.filter(|t| {
["HashMap", "HashSet", "BTreeMap", "BTreeSet", "VecDeque"]
.contains(&t.as_str())
&& !already_imported.contains(t.as_str())
})
.cloned()
.collect();
collection_types.sort();
if !collection_types.is_empty() {
for c in &collection_types {
already_imported.insert(c.clone());
}
content.push_str(&format!(
"use std::collections::{{{}}};\n",
collection_types.join(", ")
));
}
}
content.push('\n');
}
let mut items = Vec::new();
for type_info in &self.types {
let mut item = type_info.item.clone();
if let Some(fields_to_upgrade) = fields_need_pub_super.get(&type_info.name) {
if !fields_to_upgrade.is_empty() {
item =
apply_specific_field_visibility(item, &type_info.name, fields_to_upgrade);
}
}
else if let Some(ref vis) = self.field_visibility {
item = apply_field_visibility(item, vis);
}
items.push(item);
items.extend(type_info.impls.clone());
}
for item in &self.standalone_items {
let upgraded_item = upgrade_function_visibility(item.clone(), needs_pub_super);
items.push(upgraded_item);
}
if !items.is_empty() {
let formatted = prettyplease::unparse(&syn::File {
shebang: None,
attrs: Vec::new(),
items,
});
content.push_str(&formatted);
}
content
}
}
fn extract_type_names(ty: &syn::Type, types: &mut HashSet<String>) {
match ty {
syn::Type::Path(type_path) => {
if let Some(segment) = type_path.path.segments.last() {
let type_name = segment.ident.to_string();
types.insert(type_name);
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(inner_ty) = arg {
extract_type_names(inner_ty, types);
}
}
}
}
}
syn::Type::Reference(type_ref) => {
extract_type_names(&type_ref.elem, types);
}
syn::Type::Slice(type_slice) => {
extract_type_names(&type_slice.elem, types);
}
syn::Type::Array(type_array) => {
extract_type_names(&type_array.elem, types);
}
syn::Type::Ptr(type_ptr) => {
extract_type_names(&type_ptr.elem, types);
}
syn::Type::Tuple(type_tuple) => {
for elem in &type_tuple.elems {
extract_type_names(elem, types);
}
}
_ => {}
}
}
fn apply_field_visibility(item: Item, visibility: &scope_analyzer::FieldVisibility) -> Item {
match item {
Item::Struct(mut s) => {
match visibility {
scope_analyzer::FieldVisibility::PubSuper => {
for field in &mut s.fields {
if matches!(field.vis, syn::Visibility::Inherited) {
field.vis = syn::parse_quote!(pub(super));
}
}
}
scope_analyzer::FieldVisibility::PubCrate => {
for field in &mut s.fields {
if matches!(field.vis, syn::Visibility::Inherited) {
field.vis = syn::parse_quote!(pub(crate));
}
}
}
scope_analyzer::FieldVisibility::Pub => {
for field in &mut s.fields {
if matches!(field.vis, syn::Visibility::Inherited) {
field.vis = syn::parse_quote!(pub);
}
}
}
scope_analyzer::FieldVisibility::Private => {
}
}
Item::Struct(s)
}
Item::Enum(mut e) => {
match visibility {
scope_analyzer::FieldVisibility::PubSuper => {
for variant in &mut e.variants {
for field in &mut variant.fields {
if matches!(field.vis, syn::Visibility::Inherited) {
field.vis = syn::parse_quote!(pub(super));
}
}
}
}
scope_analyzer::FieldVisibility::PubCrate => {
for variant in &mut e.variants {
for field in &mut variant.fields {
if matches!(field.vis, syn::Visibility::Inherited) {
field.vis = syn::parse_quote!(pub(crate));
}
}
}
}
scope_analyzer::FieldVisibility::Pub => {
for variant in &mut e.variants {
for field in &mut variant.fields {
if matches!(field.vis, syn::Visibility::Inherited) {
field.vis = syn::parse_quote!(pub);
}
}
}
}
scope_analyzer::FieldVisibility::Private => {
}
}
Item::Enum(e)
}
other => other, }
}
fn upgrade_function_visibility(item: Item, needs_pub_super: &HashSet<String>) -> Item {
match item {
Item::Fn(mut f) => {
let fn_name = f.sig.ident.to_string();
if needs_pub_super.contains(&fn_name) && matches!(f.vis, syn::Visibility::Inherited) {
f.vis = syn::parse_quote!(pub(super));
}
Item::Fn(f)
}
other => other,
}
}
fn apply_specific_field_visibility(
item: Item,
struct_name: &str,
fields_to_upgrade: &HashSet<String>,
) -> Item {
match item {
Item::Struct(mut s) => {
if s.ident == struct_name {
for field in &mut s.fields {
if let Some(ident) = &field.ident {
let field_name = ident.to_string();
if fields_to_upgrade.contains(&field_name)
&& matches!(field.vis, syn::Visibility::Inherited)
{
field.vis = syn::parse_quote!(pub(super));
}
}
}
}
Item::Struct(s)
}
other => other,
}
}
pub(crate) fn generate_mod_rs(
modules: &[Module],
_output_dir: &Path,
test_module_path: Option<&str>,
) -> Result<String> {
let mut content = String::from("//! Auto-generated module structure\n\n");
for module in modules {
content.push_str(&format!("pub mod {};\n", module.name));
}
content.push_str("\n// Re-export all types\n");
for module in modules {
content.push_str(&format!("pub use {}::*;\n", module.name));
}
if let Some(test_path) = test_module_path {
content.push_str("\n#[cfg(test)]\n");
content.push_str(&format!("#[path = \"{}\"]\n", test_path));
content.push_str("mod tests;\n");
}
Ok(content)
}
pub(crate) fn extract_test_module_path(file: &File) -> Option<String> {
for item in &file.items {
if let Item::Mod(mod_item) = item {
let mut path_attr: Option<String> = None;
let mut is_test = false;
for attr in &mod_item.attrs {
let meta_path = attr.path();
if let Some(ident) = meta_path.get_ident() {
if ident == "cfg" {
if let syn::Meta::List(meta_list) = &attr.meta {
let tokens = meta_list.tokens.to_string();
if tokens.contains("test") {
is_test = true;
}
}
} else if ident == "path" {
if let syn::Meta::NameValue(nv) = &attr.meta {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit_str),
..
}) = &nv.value
{
path_attr = Some(lit_str.value());
}
}
}
}
}
if is_test && path_attr.is_some() {
return path_attr;
}
}
}
None
}