debtmap 0.17.0

Code complexity and technical debt analyzer
Documentation
use crate::analyzers::function_registry::{
    BuilderInfo, FunctionSignature, FunctionSignatureRegistry, MethodSignature, ReturnTypeInfo,
    VisibilityInfo,
};
use syn::visit::Visit;
use syn::{File, FnArg, ImplItem, ImplItemFn, Item, ItemFn, ItemImpl, Type};

/// Extracts function signatures from a Rust AST
pub struct SignatureExtractor {
    pub registry: FunctionSignatureRegistry,
    current_module_path: Vec<String>,
    current_impl_type: Option<String>,
}

impl Default for SignatureExtractor {
    fn default() -> Self {
        Self::new()
    }
}

impl SignatureExtractor {
    pub fn new() -> Self {
        Self {
            registry: FunctionSignatureRegistry::new(),
            current_module_path: Vec::new(),
            current_impl_type: None,
        }
    }

    /// Extract signatures from a parsed file
    pub fn extract_from_file(&mut self, file: &File) {
        self.visit_file(file);
        self.detect_builder_patterns();
    }

    /// Extract function signature from ItemFn
    fn extract_function_signature(&self, item_fn: &ItemFn) -> FunctionSignature {
        let name = item_fn.sig.ident.to_string();
        let return_type = ReturnTypeInfo::from_syn_return(&item_fn.sig.output);
        let generic_params = item_fn
            .sig
            .generics
            .params
            .iter()
            .filter_map(|param| {
                if let syn::GenericParam::Type(type_param) = param {
                    Some(type_param.ident.to_string())
                } else {
                    None
                }
            })
            .collect();
        let is_async = item_fn.sig.asyncness.is_some();
        let visibility = VisibilityInfo::from(&item_fn.vis);

        FunctionSignature {
            name,
            return_type,
            generic_params,
            is_async,
            visibility,
            module_path: self.current_module_path.clone(),
        }
    }

    /// Extract method signature from ImplItemFn
    fn extract_method_signature(&self, impl_fn: &ImplItemFn) -> MethodSignature {
        let name = impl_fn.sig.ident.to_string();
        let mut return_type = ReturnTypeInfo::from_syn_return(&impl_fn.sig.output);

        // Handle methods that return Self
        if return_type.is_self {
            if let Some(impl_type) = &self.current_impl_type {
                return_type.type_name = impl_type.clone();
                return_type.is_self = false;
            }
        }

        let generic_params = impl_fn
            .sig
            .generics
            .params
            .iter()
            .filter_map(|param| {
                if let syn::GenericParam::Type(type_param) = param {
                    Some(type_param.ident.to_string())
                } else {
                    None
                }
            })
            .collect();

        let is_async = impl_fn.sig.asyncness.is_some();
        let visibility = VisibilityInfo::from(&impl_fn.vis);

        let (takes_self, takes_mut_self) = self.analyze_self_parameter(&impl_fn.sig.inputs);
        let param_types = self.extract_parameter_types(&impl_fn.sig.inputs);

        MethodSignature {
            name,
            return_type,
            generic_params,
            is_async,
            takes_self,
            takes_mut_self,
            visibility,
            param_types,
        }
    }

    /// Analyze the self parameter of a method
    fn analyze_self_parameter(
        &self,
        inputs: &syn::punctuated::Punctuated<FnArg, syn::token::Comma>,
    ) -> (bool, bool) {
        if let Some(FnArg::Receiver(receiver)) = inputs.first() {
            let takes_self = true;
            let takes_mut_self = receiver.mutability.is_some();
            (takes_self, takes_mut_self)
        } else {
            (false, false)
        }
    }

    /// Extract parameter types from function inputs
    fn extract_parameter_types(
        &self,
        inputs: &syn::punctuated::Punctuated<FnArg, syn::token::Comma>,
    ) -> Vec<String> {
        inputs
            .iter()
            .filter_map(|arg| match arg {
                FnArg::Receiver(_) => None, // Skip self parameter
                FnArg::Typed(pat_type) => Self::get_type_name(&pat_type.ty),
            })
            .collect()
    }

    /// Detect builder patterns in registered methods
    fn detect_builder_patterns(&mut self) {
        let builders = self
            .registry
            .get_all_methods()
            .filter_map(|(type_name, methods)| Self::detect_builder(type_name, methods))
            .collect::<Vec<_>>();

        for builder in builders {
            self.registry.register_builder(builder);
        }
    }

    fn detect_builder(type_name: &str, methods: &[MethodSignature]) -> Option<BuilderInfo> {
        let chain_methods = Self::chain_method_names(type_name, methods);
        let build_method = methods
            .iter()
            .find(|method| Self::is_build_method(method))?;
        let target_type = build_method.return_type.type_name.clone();

        (!chain_methods.is_empty() && Self::returns_distinct_target(type_name, &target_type)).then(
            || BuilderInfo {
                builder_type: type_name.to_string(),
                target_type,
                build_method: build_method.name.clone(),
                chain_methods,
            },
        )
    }

    fn chain_method_names(type_name: &str, methods: &[MethodSignature]) -> Vec<String> {
        methods
            .iter()
            .filter(|method| Self::returns_builder_type(method, type_name))
            .map(|method| method.name.clone())
            .collect()
    }

    fn returns_builder_type(method: &MethodSignature, type_name: &str) -> bool {
        method.return_type.type_name == type_name || method.return_type.type_name == "Self"
    }

    fn is_build_method(method: &MethodSignature) -> bool {
        matches!(method.name.as_str(), "build" | "finish" | "complete")
    }

    fn returns_distinct_target(type_name: &str, target_type: &str) -> bool {
        target_type != type_name && target_type != "Self"
    }

    /// Get the type name from a Type
    fn get_type_name(ty: &Type) -> Option<String> {
        match ty {
            Type::Path(type_path) => {
                let segments = &type_path.path.segments;
                if segments.is_empty() {
                    None
                } else {
                    Some(
                        segments
                            .iter()
                            .map(|seg| seg.ident.to_string())
                            .collect::<Vec<_>>()
                            .join("::"),
                    )
                }
            }
            _ => None,
        }
    }
}

impl<'ast> Visit<'ast> for SignatureExtractor {
    fn visit_item(&mut self, item: &'ast Item) {
        match item {
            Item::Fn(item_fn) => {
                let signature = self.extract_function_signature(item_fn);
                self.registry.register_function(signature);
                syn::visit::visit_item_fn(self, item_fn);
            }
            Item::Impl(item_impl) => {
                self.visit_item_impl(item_impl);
            }
            Item::Mod(item_mod) => {
                if let Some((_, items)) = &item_mod.content {
                    let mod_name = item_mod.ident.to_string();
                    self.current_module_path.push(mod_name);
                    for item in items {
                        self.visit_item(item);
                    }
                    self.current_module_path.pop();
                }
            }
            _ => {
                syn::visit::visit_item(self, item);
            }
        }
    }

    fn visit_item_impl(&mut self, item_impl: &'ast ItemImpl) {
        // Extract the type being implemented
        let impl_type = if let Type::Path(type_path) = &*item_impl.self_ty {
            Self::get_type_name(&Type::Path(type_path.clone()))
        } else {
            None
        };

        if let Some(type_name) = impl_type {
            self.current_impl_type = Some(type_name.clone());

            // Process all methods in the impl block
            for item in &item_impl.items {
                if let ImplItem::Fn(impl_fn) = item {
                    let method = self.extract_method_signature(impl_fn);
                    self.registry.register_method(type_name.clone(), method);
                }
            }

            self.current_impl_type = None;
        }

        syn::visit::visit_item_impl(self, item_impl);
    }
}