pub mod analyzer;
pub mod basic;
pub mod clippy;
pub mod debugger;
pub mod idiom;
pub mod serializable;
use ryo_source::pure::PureFile;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum ValidationLevel {
Info,
Warning,
Conflict,
Fatal,
}
impl ValidationLevel {
pub fn is_fatal(&self) -> bool {
*self == ValidationLevel::Fatal
}
pub fn is_blocking(&self) -> bool {
*self >= ValidationLevel::Conflict
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ValidationCode {
TargetNotFound,
DuplicateSymbol,
InvalidSyntax,
MissingDependency,
SymbolReferenced,
SameSymbolTarget,
ImplTargetNotFound,
PotentiallyUnusedImport,
VisibilityMismatch,
NamingConvention,
NoOp,
UnreferencedSymbol,
}
impl ValidationCode {
pub fn default_level(&self) -> ValidationLevel {
match self {
ValidationCode::TargetNotFound
| ValidationCode::DuplicateSymbol
| ValidationCode::InvalidSyntax
| ValidationCode::MissingDependency => ValidationLevel::Fatal,
ValidationCode::SymbolReferenced
| ValidationCode::SameSymbolTarget
| ValidationCode::ImplTargetNotFound => ValidationLevel::Conflict,
ValidationCode::PotentiallyUnusedImport
| ValidationCode::VisibilityMismatch
| ValidationCode::NamingConvention => ValidationLevel::Warning,
ValidationCode::NoOp | ValidationCode::UnreferencedSymbol => ValidationLevel::Info,
}
}
pub fn user_friendly_message(&self) -> &'static str {
match self {
ValidationCode::TargetNotFound => {
"The symbol you're trying to modify doesn't exist in the codebase"
}
ValidationCode::DuplicateSymbol => "A symbol with this name already exists",
ValidationCode::InvalidSyntax => "The transformation would produce invalid Rust code",
ValidationCode::MissingDependency => "A required type or trait is not available",
ValidationCode::SymbolReferenced => {
"This symbol is used elsewhere; removing it may break other code"
}
ValidationCode::SameSymbolTarget => "Multiple mutations are targeting the same symbol",
ValidationCode::ImplTargetNotFound => "The type for this impl block was not found",
ValidationCode::PotentiallyUnusedImport => "This import might not be used anywhere",
ValidationCode::VisibilityMismatch => {
"The visibility might not be sufficient for intended usage"
}
ValidationCode::NamingConvention => "The name doesn't follow Rust naming conventions",
ValidationCode::NoOp => "No changes are needed (already in desired state)",
ValidationCode::UnreferencedSymbol => "This symbol has no references (safe to remove)",
}
}
pub fn suggestion(&self) -> Option<&'static str> {
match self {
ValidationCode::TargetNotFound => Some(
"Check the symbol name for typos, or use 'ryo discover' to find available symbols"
),
ValidationCode::DuplicateSymbol => Some(
"Choose a different name, or remove the existing symbol first"
),
ValidationCode::InvalidSyntax => Some(
"Review the transformation parameters; ensure the generated code is valid Rust"
),
ValidationCode::MissingDependency => Some(
"Add the required dependency to Cargo.toml, or import the type/trait"
),
ValidationCode::SymbolReferenced => Some(
"Update all references before removing, or use 'ryo graph cascade' to see impact"
),
ValidationCode::SameSymbolTarget => Some(
"Merge mutations into a single operation, or execute them sequentially"
),
ValidationCode::ImplTargetNotFound => Some(
"Ensure the target type is defined in the same file, or specify the full path"
),
ValidationCode::NamingConvention => Some(
"Use snake_case for functions/variables, PascalCase for types, SCREAMING_CASE for constants"
),
ValidationCode::PotentiallyUnusedImport => None,
ValidationCode::VisibilityMismatch => None,
ValidationCode::NoOp => None,
ValidationCode::UnreferencedSymbol => None,
}
}
pub fn example(&self) -> Option<&'static str> {
match self {
ValidationCode::TargetNotFound => Some(
r#"Example: { "intent": "RenameIdent", "from": "existing_fn", "to": "new_name" }"#,
),
ValidationCode::DuplicateSymbol => {
Some(r#"Example: First rename or remove 'foo', then add the new symbol"#)
}
ValidationCode::NamingConvention => {
Some(r#"Examples: fn my_function(), struct MyStruct, const MAX_SIZE"#)
}
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationIssue {
pub level: ValidationLevel,
pub code: ValidationCode,
pub message: String,
pub affected_symbol: Option<String>,
}
impl ValidationIssue {
pub fn new(code: ValidationCode, message: impl Into<String>) -> Self {
Self {
level: code.default_level(),
code,
message: message.into(),
affected_symbol: None,
}
}
pub fn with_level(mut self, level: ValidationLevel) -> Self {
self.level = level;
self
}
pub fn with_symbol(mut self, symbol: impl Into<String>) -> Self {
self.affected_symbol = Some(symbol.into());
self
}
pub fn target_not_found(symbol: &str) -> Self {
Self::new(
ValidationCode::TargetNotFound,
format!("Target symbol '{}' not found", symbol),
)
.with_symbol(symbol)
}
pub fn duplicate_symbol(symbol: &str) -> Self {
Self::new(
ValidationCode::DuplicateSymbol,
format!("Symbol '{}' already exists", symbol),
)
.with_symbol(symbol)
}
pub fn symbol_referenced(symbol: &str, ref_count: usize) -> Self {
Self::new(
ValidationCode::SymbolReferenced,
format!(
"Symbol '{}' is referenced {} time(s); removal may break code",
symbol, ref_count
),
)
.with_symbol(symbol)
}
pub fn no_op(reason: &str) -> Self {
Self::new(ValidationCode::NoOp, reason.to_string())
}
pub fn format_user_friendly(&self) -> String {
let mut output = String::new();
let level_indicator = match self.level {
ValidationLevel::Fatal => "[ERROR]",
ValidationLevel::Conflict => "[CONFLICT]",
ValidationLevel::Warning => "[WARNING]",
ValidationLevel::Info => "[INFO]",
};
output.push_str(&format!(
"{} {}\n",
level_indicator,
self.code.user_friendly_message()
));
if let Some(ref symbol) = self.affected_symbol {
output.push_str(&format!(" Symbol: {}\n", symbol));
}
if !self.message.is_empty() && self.message != self.code.user_friendly_message() {
output.push_str(&format!(" Detail: {}\n", self.message));
}
if let Some(suggestion) = self.code.suggestion() {
output.push_str(&format!(" Suggestion: {}\n", suggestion));
}
if let Some(example) = self.code.example() {
output.push_str(&format!(" {}\n", example));
}
output
}
}
impl std::fmt::Display for ValidationIssue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.format_user_friendly())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ValidationResult {
pub issues: Vec<ValidationIssue>,
}
impl ValidationResult {
pub fn new() -> Self {
Self { issues: Vec::new() }
}
pub fn ok() -> Self {
Self::new()
}
pub fn with_issue(mut self, issue: ValidationIssue) -> Self {
self.issues.push(issue);
self
}
pub fn add(&mut self, issue: ValidationIssue) {
self.issues.push(issue);
}
pub fn is_ok(&self) -> bool {
self.issues.is_empty()
}
pub fn has_fatal(&self) -> bool {
self.issues
.iter()
.any(|i| i.level == ValidationLevel::Fatal)
}
pub fn has_conflicts(&self) -> bool {
self.issues
.iter()
.any(|i| i.level >= ValidationLevel::Conflict)
}
pub fn has_warnings(&self) -> bool {
self.issues
.iter()
.any(|i| i.level >= ValidationLevel::Warning)
}
pub fn max_level(&self) -> Option<ValidationLevel> {
self.issues.iter().map(|i| i.level).max()
}
pub fn by_level(&self, level: ValidationLevel) -> Vec<&ValidationIssue> {
self.issues.iter().filter(|i| i.level == level).collect()
}
pub fn format_user_friendly(&self) -> String {
if self.issues.is_empty() {
return String::new();
}
let mut output = String::new();
let fatal: Vec<_> = self.by_level(ValidationLevel::Fatal);
let conflicts: Vec<_> = self.by_level(ValidationLevel::Conflict);
let warnings: Vec<_> = self.by_level(ValidationLevel::Warning);
let info: Vec<_> = self.by_level(ValidationLevel::Info);
if !fatal.is_empty() {
output.push_str(&format!("=== {} Error(s) ===\n", fatal.len()));
for issue in fatal {
output.push_str(&issue.format_user_friendly());
output.push('\n');
}
}
if !conflicts.is_empty() {
output.push_str(&format!("=== {} Conflict(s) ===\n", conflicts.len()));
for issue in conflicts {
output.push_str(&issue.format_user_friendly());
output.push('\n');
}
}
if !warnings.is_empty() {
output.push_str(&format!("=== {} Warning(s) ===\n", warnings.len()));
for issue in warnings {
output.push_str(&issue.format_user_friendly());
output.push('\n');
}
}
if !info.is_empty() {
output.push_str(&format!("=== {} Info ===\n", info.len()));
for issue in info {
output.push_str(&issue.format_user_friendly());
output.push('\n');
}
}
output
}
}
impl std::fmt::Display for ValidationResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.format_user_friendly())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ValidationStrategy {
AllowAll,
#[default]
BlockFatalOnly,
BlockConflicts,
Strict,
}
impl ValidationStrategy {
pub fn can_proceed(&self, result: &ValidationResult) -> bool {
match self {
ValidationStrategy::AllowAll => true,
ValidationStrategy::BlockFatalOnly => !result.has_fatal(),
ValidationStrategy::BlockConflicts => !result.has_conflicts(),
ValidationStrategy::Strict => result.is_ok(),
}
}
pub fn blocking_level(&self) -> Option<ValidationLevel> {
match self {
ValidationStrategy::AllowAll => None,
ValidationStrategy::BlockFatalOnly => Some(ValidationLevel::Fatal),
ValidationStrategy::BlockConflicts => Some(ValidationLevel::Conflict),
ValidationStrategy::Strict => Some(ValidationLevel::Info),
}
}
}
pub use basic::{
AddConstMutation, AddDeriveMutation, AddEnumMutation, AddFieldMutation, AddFunctionMutation,
AddImplMutation, AddItemMutation, AddMatchArmMutation, AddMethodMutation, AddPureItemsMutation,
AddStructLiteralFieldMutation, AddStructMutation, AddTypeAliasMutation, AddUseMutation,
AddVariantMutation, ChangeVisibilityMutation, CreateModMutation, EnumToTraitMutation,
EnumToTraitStrategy, ExtractTraitMutation, FieldInfo, InlineTraitMutation, MatchHandling,
MoveItemMutation, RemoveConstMutation, RemoveDeriveMutation, RemoveEnumMutation,
RemoveFieldMutation, RemoveFunctionMutation, RemoveImplMutation, RemoveItemMutation,
RemoveMatchArmMutation, RemoveMethodMutation, RemoveModMutation,
RemoveStructLiteralFieldMutation, RemoveStructMutation, RemoveTraitMutation,
RemoveTypeAliasMutation, RemoveUseMutation, RemoveVariantMutation, RenameMutation,
ReplaceMatchArmMutation, VariantInfo,
};
pub use idiom::{
AssignOpMutation,
BoolSimplifyMutation,
CloneOnCopyMutation,
CollapsibleIfMutation,
ComparisonToMethodMutation,
DefaultMutation,
DeriveDefaultMutation,
FilterNextMutation,
FindDuplicateExpressions,
IntroduceVariableMutation,
LockScopeMutation,
LoopPattern,
LoopToIteratorMutation,
ManualMapMutation,
MapUnwrapOrMutation,
MatchToIfLetMutation,
NoOpArmToTodoMutation,
OrganizeImportsMutation,
RedundantClosureMutation,
UnwrapToQuestionMutation,
UseAtomicMutation,
UseRwLockMutation,
};
pub use idiom::detect::{
create_default_registry, Detect, DetectCategory, DetectLocation, DetectOperation,
DetectOpportunity, DetectRegistry,
};
pub use basic::stmt::{
InsertPosition, InsertStatementMutation, RemoveStatementMutation, ReplaceExprAtMutation,
ReplaceExprMutation, ReplaceStatementMutation, WrapExprMutation,
};
pub use debugger::{
DbgWrapMutation, DebugMarker, DebugSession, InsertInspectMutation, RemovalTarget,
RemoveDebugLogsMutation, MARKER_PREFIX,
};
pub use serializable::{SerializableMutation, ToSerializable};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MutationResult {
pub mutation_type: String,
pub changes: usize,
pub description: String,
}
pub trait Mutation: Send + Sync {
fn validate(&self, _file: &PureFile) -> ValidationResult {
ValidationResult::ok()
}
fn can_proceed(&self, file: &PureFile, strategy: ValidationStrategy) -> bool {
strategy.can_proceed(&self.validate(file))
}
fn describe(&self) -> String;
fn mutation_type(&self) -> &'static str;
fn box_clone(&self) -> Box<dyn Mutation>;
}
pub fn clone_mutation(mutation: &dyn Mutation) -> Box<dyn Mutation> {
mutation.box_clone()
}
#[derive(Debug)]
pub struct MutationBatch {
pub mutations: Vec<Box<dyn Mutation>>,
}
impl std::fmt::Debug for Box<dyn Mutation> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Mutation({})", self.mutation_type())
}
}
impl MutationBatch {
pub fn new() -> Self {
Self {
mutations: Vec::new(),
}
}
pub fn with_mutation<M: Mutation + 'static>(mut self, mutation: M) -> Self {
self.mutations.push(Box::new(mutation));
self
}
}
impl Default for MutationBatch {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
fn _use_path_buf(_: PathBuf) {}