ryo-analysis 0.1.0

Code graph and discovery engine for the RYO project
Documentation
//! CascadeSpec: Cascade分析結果の仕様定義
//!
//! # 責務
//!
//! CascadeSpecは「何をすべきか」を表現するPolicy層の型。
//! 実際の実行方法(MutationSpecへの変換)はryo-executorで実装される。
//!
//! # 設計意図
//!
//! - ryo-analysisはPolicy(方針)を決定する
//! - ryo-executorは実装(変換・実行)を担当する
//! - この分離により、分析ロジックと実行ロジックが疎結合になる
//!
//! # 例
//!
//! ```rust,ignore
//! use ryo_analysis::cascade::{CascadeAnalyzer, CascadeSpec, CascadeStrategy};
//!
//! let analyzer = CascadeAnalyzer::new(&graph, &registry, &derive_index);
//! let specs: Vec<CascadeSpec> = analyzer.cascade_add_derive(
//!     &target_path,
//!     &["Default".to_string()],
//!     &CascadeStrategy::eager(),
//! );
//!
//! // ryo-executor側でMutationSpecに変換
//! let mutation_specs: Vec<MutationSpec> = specs.into_iter()
//!     .map(Into::into)
//!     .collect();
//! ```

use crate::SymbolPath;
use ryo_symbol::SymbolId;

/// Cascade分析結果として生成される変更仕様
///
/// MutationSpecへの変換はryo-executorの`From<CascadeSpec>`で実装される。
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CascadeSpec {
    /// derive macroを追加
    ///
    /// 例: `#[derive(Default)]` を追加し、フィールド型にも伝播
    ///
    /// **Note**: `symbol_id` is required. `request_*` fields are for diagnostics only.
    AddDerive {
        /// SymbolId - required for O(1) lookup
        symbol_id: SymbolId,
        /// 追加するderive macro名
        derives: Vec<String>,
    },

    /// manual impl blockを生成
    ///
    /// derive不可能な場合(フィールド型がderive未対応)に使用
    GenerateImpl {
        /// 対象の型へのパス
        target: SymbolPath,
        /// 実装するtrait名
        trait_name: String,
        /// new()を自動生成呼び出しするか
        call_new: bool,
    },

    /// visibilityを変更
    ///
    /// cross-module accessエラーの修正に使用
    ChangeVisibility {
        /// SymbolId - required for O(1) lookup
        symbol_id: SymbolId,
        /// 新しいvisibility
        visibility: Visibility,
    },

    /// use文を追加
    ///
    /// 型参照の解決に使用
    AddUse {
        /// use文を追加するモジュール
        target_module: SymbolPath,
        /// 追加するパス
        path: String,
    },

    /// match式に新しいarmを追加
    ///
    /// enum variantを追加した際の網羅性エラー修正に使用
    AddMatchArm {
        /// 対象モジュールへのSymbolPath
        target: SymbolPath,
        /// match式を含む関数名
        function_name: String,
        /// 対象のenum型名
        enum_name: String,
        /// 追加するパターン (e.g., "Status::Cancelled")
        pattern: String,
        /// armのbody (e.g., "todo!()")
        body: String,
    },

    /// match式からarmを削除
    ///
    /// enum variantを削除した際の不要arm除去に使用
    RemoveMatchArm {
        /// 対象モジュールへのSymbolPath
        target: SymbolPath,
        /// match式を含む関数名
        function_name: String,
        /// 対象のenum型名
        enum_name: String,
        /// 削除するパターン (e.g., "Status::Cancelled")
        pattern: String,
    },
}

/// Visibility レベル
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Visibility {
    /// Private (default)
    Private,
    /// pub(crate)
    Crate,
    /// pub(super)
    Super,
    /// pub
    Public,
}

impl CascadeSpec {
    /// AddDeriveを作成
    pub fn add_derive(symbol_id: SymbolId, derives: Vec<String>) -> Self {
        Self::AddDerive { symbol_id, derives }
    }

    /// GenerateImplを作成
    pub fn generate_impl(
        target: SymbolPath,
        trait_name: impl Into<String>,
        call_new: bool,
    ) -> Self {
        Self::GenerateImpl {
            target,
            trait_name: trait_name.into(),
            call_new,
        }
    }

    /// ChangeVisibilityを作成
    pub fn change_visibility(symbol_id: SymbolId, visibility: Visibility) -> Self {
        Self::ChangeVisibility {
            symbol_id,
            visibility,
        }
    }

    /// AddUseを作成
    pub fn add_use(target_module: SymbolPath, path: impl Into<String>) -> Self {
        Self::AddUse {
            target_module,
            path: path.into(),
        }
    }

    /// AddMatchArmを作成
    pub fn add_match_arm(
        target: SymbolPath,
        function_name: impl Into<String>,
        enum_name: impl Into<String>,
        pattern: impl Into<String>,
        body: impl Into<String>,
    ) -> Self {
        Self::AddMatchArm {
            target,
            function_name: function_name.into(),
            enum_name: enum_name.into(),
            pattern: pattern.into(),
            body: body.into(),
        }
    }

    /// RemoveMatchArmを作成
    pub fn remove_match_arm(
        target: SymbolPath,
        function_name: impl Into<String>,
        enum_name: impl Into<String>,
        pattern: impl Into<String>,
    ) -> Self {
        Self::RemoveMatchArm {
            target,
            function_name: function_name.into(),
            enum_name: enum_name.into(),
            pattern: pattern.into(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ryo_symbol::{SymbolKind, SymbolRegistry};

    /// Create a dummy SymbolId for testing purposes
    fn dummy_symbol_id(name: &str) -> SymbolId {
        let mut registry = SymbolRegistry::new();
        let path = SymbolPath::parse(&format!("test_crate::test_{}", name)).unwrap();
        registry.register(path, SymbolKind::Struct).unwrap()
    }

    #[test]
    fn test_cascade_spec_add_derive() {
        let symbol_id = dummy_symbol_id("config");
        let spec = CascadeSpec::add_derive(symbol_id, vec!["Default".to_string()]);

        match spec {
            CascadeSpec::AddDerive {
                symbol_id: sid,
                derives,
            } => {
                assert_eq!(sid, symbol_id);
                assert_eq!(derives, vec!["Default".to_string()]);
            }
            _ => panic!("Expected AddDerive"),
        }
    }

    #[test]
    fn test_cascade_spec_add_match_arm() {
        let target_path = SymbolPath::parse("test_crate::handlers").unwrap();
        let spec = CascadeSpec::add_match_arm(
            target_path.clone(),
            "process_status",
            "Status",
            "Status::Cancelled",
            "todo!()",
        );

        match &spec {
            CascadeSpec::AddMatchArm {
                target,
                function_name,
                enum_name,
                pattern,
                body,
            } => {
                assert_eq!(target, &target_path);
                assert_eq!(function_name, "process_status");
                assert_eq!(enum_name, "Status");
                assert_eq!(pattern, "Status::Cancelled");
                assert_eq!(body, "todo!()");
            }
            _ => panic!("Expected AddMatchArm"),
        }
    }
}