ryo-mutations 0.1.0

[experimental] Code transformation primitives for Rust source code
Documentation
//! rust-analyzer Integration: LSP-based code intelligence for mutations
//!
//! This module provides integration with [rust-analyzer](https://rust-analyzer.github.io/)
//! via the Language Server Protocol (LSP). It enables semantic-aware code transformations
//! that require type information beyond what pure AST analysis can provide.
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────┐
//! │                    rust-analyzer Integration                     │
//! ├─────────────────────────────────────────────────────────────────┤
//! │                                                                  │
//! │  ┌──────────────┐    LSP Protocol    ┌──────────────────────┐  │
//! │  │ ryo-mutations│◄──────────────────►│ rust-analyzer        │  │
//! │  │              │                     │                      │  │
//! │  │ - Mutations  │  textDocument/      │ - Type inference     │  │
//! │  │ - PureFile   │  inlayHint          │ - Semantic tokens    │  │
//! │  │              │  textDocument/      │ - Code actions       │  │
//! │  │              │  codeAction         │ - Diagnostics        │  │
//! │  └──────────────┘                     └──────────────────────┘  │
//! │         │                                       │                │
//! │         ▼                                       ▼                │
//! │  ┌──────────────────────────────────────────────────────────┐  │
//! │  │                    Enriched Mutations                     │  │
//! │  │                                                           │  │
//! │  │  - CloneOnCopy with accurate Copy detection               │  │
//! │  │  - (v0.1.0) AddExplicitType / FillMatchArms /             │  │
//! │  │    AddMissingFields are NOT YET shipped — type-inference  │  │
//! │  │    infrastructure required                                │  │
//! │  └──────────────────────────────────────────────────────────┘  │
//! └─────────────────────────────────────────────────────────────────┘
//! ```
//!
//! # LSP Endpoints Used
//!
//! ## InlayHints (`textDocument/inlayHint`)
//!
//! Provides type information that the compiler infers but isn't written in code:
//!
//! | Hint Kind | Example | Use Case |
//! |-----------|---------|----------|
//! | Type | `let x: i32 = ...` | Add explicit type annotations |
//! | Parameter | `foo(/* name: */ value)` | Parameter name completion |
//! | Chaining | `.map(...): Vec<_>` | Method chain type tracking |
//!
//! ## CodeAction (`textDocument/codeAction`)
//!
//! rust-analyzer "assists" that can be converted to Mutations:
//!
//! | Assist | Mutation | Description |
//! |--------|----------|-------------|
//! | `fill_match_arms` | _(deferred — needs type inference)_ | Add missing match arms |
//! | `add_missing_fields` | _(deferred — needs type inference)_ | Add missing struct fields |
//! | `inline_type_alias` | `InlineTypeAliasMutation` | Expand type alias |
//! | `extract_type_alias` | `ExtractTypeAliasMutation` | Create type alias |
//! | `add_explicit_type` | _(deferred — needs type inference)_ | Add type annotation |
//! | `replace_if_let_with_match` | `IfLetToMatchMutation` | Convert if let to match |
//! | `replace_match_with_if_let` | `MatchToIfLetMutation` | Convert match to if let |
//!
//! `fill_match_arms` / `add_missing_fields` / `add_explicit_type` derivations
//! were removed in v0.1.0 because their AST-level apply paths required
//! type-inference infrastructure that has not yet landed; they will return
//! when that infrastructure is in place.
//!
//! ## Hover (`textDocument/hover`)
//!
//! Type information and documentation for symbols.
//!
//! ## SemanticTokens (`textDocument/semanticTokens`)
//!
//! Token classification for semantic highlighting:
//! - Variable modifiers: `mutable`, `consuming`, `reference`
//! - Type modifiers: `copy`, `associated`
//!
//! # Usage
//!
//! ```rust,ignore
//! use ryo_mutations::analyzer::{AnalyzerClient, InlayHintQuery};
//!
//! // Connect to rust-analyzer
//! let client = AnalyzerClient::new("/path/to/project")?;
//!
//! // Get type hints for a file
//! let hints = client.inlay_hints("src/lib.rs")?;
//!
//! // Use hints to enrich mutations
//! for hint in hints.type_hints() {
//!     if hint.is_copy_type() {
//!         // Can safely apply CloneOnCopy mutation
//!     }
//! }
//!
//! // Get available code actions at a position
//! let actions = client.code_actions("src/lib.rs", line, column)?;
//!
//! // Convert to mutations
//! for action in actions {
//!     if let Some(mutation) = action.to_mutation() {
//!         println!("Available: {}", mutation.describe());
//!     }
//! }
//! ```
//!
//! # Relationship with Clippy Integration
//!
//! | Feature | Clippy | rust-analyzer |
//! |---------|--------|---------------|
//! | Type info | No | Yes (InlayHints) |
//! | Lint fixes | Yes (suggestions) | Yes (CodeActions) |
//! | Refactoring | Limited | Extensive (assists) |
//! | Batch apply | Yes | Per-file |
//!
//! For best results, combine both:
//! - Use Clippy for lint-driven transformations
//! - Use rust-analyzer for semantic-aware refactoring
//!
//! # Performance Considerations
//!
//! - rust-analyzer startup can take several seconds for large projects
//! - InlayHints are computed lazily, request only needed ranges
//! - Consider caching results for repeated operations
//!
//! # Future Extensions
//!
//! - [ ] Workspace-wide rename with type checking
//! - [ ] Automatic import resolution
//! - [ ] Trait implementation scaffolding
//! - [ ] Lifetime inference and annotation

mod code_action;
mod inlay_hints;

pub use code_action::{AnalyzerCodeAction, CodeActionKind, CodeActionToMutation};
pub use inlay_hints::{InlayHint, InlayHintKind, InlayHintQuery, TypeHint};

use std::path::Path;

/// Known rust-analyzer assist IDs that map to mutations
pub mod assists {
    pub const FILL_MATCH_ARMS: &str = "fill_match_arms";
    pub const ADD_MISSING_FIELDS: &str = "add_missing_impl_members";
    pub const ADD_EXPLICIT_TYPE: &str = "add_explicit_type";
    pub const INLINE_TYPE_ALIAS: &str = "inline_type_alias";
    pub const EXTRACT_TYPE_ALIAS: &str = "extract_type_alias";
    pub const REPLACE_IF_LET_WITH_MATCH: &str = "replace_if_let_with_match";
    pub const REPLACE_MATCH_WITH_IF_LET: &str = "replace_match_with_if_let";
    pub const CONVERT_TO_GUARDED_RETURN: &str = "convert_to_guarded_return";
    pub const INLINE_FUNCTION: &str = "inline_into_callers";
    pub const EXTRACT_FUNCTION: &str = "extract_function";
}

/// Common Copy types for heuristic detection
pub mod copy_types {
    /// Primitive types that are always Copy
    pub const PRIMITIVES: &[&str] = &[
        "bool", "char", "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64",
        "u128", "usize", "f32", "f64",
    ];

    /// Common std types that are Copy
    pub const STD_COPY: &[&str] = &[
        "NonZeroI8",
        "NonZeroI16",
        "NonZeroI32",
        "NonZeroI64",
        "NonZeroI128",
        "NonZeroIsize",
        "NonZeroU8",
        "NonZeroU16",
        "NonZeroU32",
        "NonZeroU64",
        "NonZeroU128",
        "NonZeroUsize",
        "Duration",
        "Instant",
        "IpAddr",
        "Ipv4Addr",
        "Ipv6Addr",
        "SocketAddr",
        "SocketAddrV4",
        "SocketAddrV6",
        "Ordering",
        "TypeId",
    ];

    /// Check if a type name is a known Copy type
    pub fn is_known_copy(type_name: &str) -> bool {
        // Strip references and generics for basic check
        let base = type_name
            .trim_start_matches('&')
            .trim_start_matches("mut ")
            .split('<')
            .next()
            .unwrap_or(type_name)
            .trim();

        PRIMITIVES.contains(&base) || STD_COPY.contains(&base)
    }
}

/// Client for communicating with rust-analyzer
///
/// This is a lightweight wrapper that can use an existing LSP connection
/// or spawn a new rust-analyzer instance.
pub struct AnalyzerClient {
    workspace_root: std::path::PathBuf,
    // In the future, this will hold the LSP connection
    // For now, we provide utilities that work with LSP responses
}

impl AnalyzerClient {
    /// Create a new analyzer client for a workspace
    pub fn new(workspace_root: impl AsRef<Path>) -> Self {
        Self {
            workspace_root: workspace_root.as_ref().to_path_buf(),
        }
    }

    /// Get the workspace root
    pub fn workspace_root(&self) -> &Path {
        &self.workspace_root
    }

    /// Check if a type is Copy based on known patterns
    ///
    /// This is a heuristic check. For accurate results, use InlayHints
    /// with actual type information from rust-analyzer.
    pub fn is_likely_copy(&self, type_name: &str) -> bool {
        copy_types::is_known_copy(type_name)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_known_copy_primitives() {
        assert!(copy_types::is_known_copy("i32"));
        assert!(copy_types::is_known_copy("bool"));
        assert!(copy_types::is_known_copy("f64"));
        assert!(copy_types::is_known_copy("usize"));
    }

    #[test]
    fn test_is_known_copy_with_reference() {
        assert!(copy_types::is_known_copy("&i32"));
        assert!(copy_types::is_known_copy("&mut bool"));
    }

    #[test]
    fn test_is_known_copy_std_types() {
        assert!(copy_types::is_known_copy("Duration"));
        assert!(copy_types::is_known_copy("Ordering"));
    }

    #[test]
    fn test_is_not_copy() {
        assert!(!copy_types::is_known_copy("String"));
        assert!(!copy_types::is_known_copy("Vec<i32>"));
        assert!(!copy_types::is_known_copy("Box<dyn Trait>"));
    }
}