use crate::migrate_ruff::migrate_file;
use crate::{RuffDeprecatedFunctionCollector, TypeIntrospectionMethod};
use std::collections::HashMap;
use std::path::Path;
#[test]
fn test_replace_me_on_magic_methods() {
let source = r#"
from dissolve import replace_me
class MyClass:
@replace_me()
def __init__(self, value):
self.__dict__.update(NewClass(value).__dict__)
@replace_me()
def __str__(self):
return str(self.new_representation())
@replace_me()
def __call__(self, *args):
return self.new_call_method(*args)
@replace_me()
def __len__(self):
return self.new_length()
# Usage
obj = MyClass(42)
print(str(obj))
result = obj()
length = len(obj)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("@replace_me()"));
assert!(migrated.contains("def __init__(self, value):"));
assert!(migrated.contains("def __str__(self):"));
assert!(migrated.contains("def __call__(self, *args):"));
assert!(migrated.contains("def __len__(self):"));
}
#[test]
fn test_replace_me_with_multiple_decorators() {
let source = r#"
from dissolve import replace_me
import functools
class MyClass:
@property
@replace_me()
def old_property(self):
return self.new_property
@classmethod
@replace_me()
def old_class_method(cls, value):
return cls.new_class_method(value)
@staticmethod
@functools.lru_cache(maxsize=128)
@replace_me()
def old_static_method(x):
return new_static_method(x * 2)
obj = MyClass()
prop_value = obj.old_property
class_result = MyClass.old_class_method(10)
static_result = MyClass.old_static_method(5)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("@property") && migrated.contains("@replace_me()"));
assert!(migrated.contains("@classmethod") && migrated.contains("@replace_me()"));
}
#[test]
fn test_replace_me_on_nested_inner_functions() {
let source = r#"
from dissolve import replace_me
def outer_function():
@replace_me()
def inner_deprecated(x):
return inner_new(x + 1)
def another_inner():
@replace_me()
def deeply_nested(y):
return deeply_new(y * 2)
return deeply_nested(5)
result1 = inner_deprecated(10)
result2 = another_inner()
return result1, result2
# Call the outer function
final_result = outer_function()
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("@replace_me()"));
assert!(migrated.contains("def inner_deprecated(x):"));
assert!(migrated.contains("def deeply_nested(y):"));
}
#[test]
fn test_replace_me_on_property_setter_deleter() {
let source = r#"
from dissolve import replace_me
class MyClass:
def __init__(self):
self._value = 0
@property
def value(self):
return self._value
@value.setter
@replace_me()
def value(self, val):
self.new_setter(val)
@value.deleter
@replace_me()
def value(self):
self.new_deleter()
obj = MyClass()
obj.value = 42 # Calls setter
del obj.value # Calls deleter
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("@value.setter"));
assert!(migrated.contains("@value.deleter"));
assert!(migrated.contains("@replace_me()"));
}
#[test]
fn test_replace_me_on_metaclass_methods() {
let source = r#"
from dissolve import replace_me
class MetaClass(type):
@replace_me()
def old_meta_method(cls, value):
return cls.new_meta_method(value)
@replace_me()
def __call__(cls, *args, **kwargs):
return cls.new_constructor(*args, **kwargs)
class MyClass(metaclass=MetaClass):
pass
# Usage
result = MyClass.old_meta_method(42)
instance = MyClass(1, 2, 3) # Calls metaclass __call__
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("class MetaClass(type):"));
assert!(migrated.contains("@replace_me()"));
assert!(migrated.contains("def old_meta_method(cls, value):"));
}
#[test]
fn test_replace_me_with_complex_arguments() {
let source = r#"
from dissolve import replace_me
@replace_me(since="1.0", remove_in="2.0", message="Use new_func instead")
def old_func_with_args(x):
return new_func(x)
@replace_me(
since="0.9",
remove_in="1.5",
message="This function is deprecated"
)
def old_func_multiline_args(y):
return new_func_multiline(y)
result1 = old_func_with_args(10)
result2 = old_func_multiline_args(20)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("new_func(10)"));
assert!(migrated.contains("new_func_multiline(20)"));
}
#[test]
fn test_replace_me_in_conditional_blocks() {
let source = r#"
from dissolve import replace_me
import sys
if sys.version_info >= (3, 8):
@replace_me()
def conditional_func(x):
return new_conditional_func(x)
else:
@replace_me()
def conditional_func(x):
return old_fallback_func(x)
# In try/except
try:
@replace_me()
def risky_func(x):
return new_risky_func(x)
except ImportError:
@replace_me()
def risky_func(x):
return fallback_risky_func(x)
result1 = conditional_func(10)
result2 = risky_func(20)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(
migrated.contains("new_conditional_func(10)") || migrated.contains("old_fallback_func(10)")
);
assert!(
migrated.contains("new_risky_func(20)") || migrated.contains("fallback_risky_func(20)")
);
}
#[test]
fn test_replace_me_with_dynamic_method_calls() {
let source = r#"
from dissolve import replace_me
class MyClass:
@replace_me()
def dynamic_method(self, x):
return self.new_dynamic_method(x)
obj = MyClass()
# Dynamic method calls
method = getattr(obj, "dynamic_method")
result1 = method(42)
# Using hasattr check
if hasattr(obj, "dynamic_method"):
result2 = obj.dynamic_method(100)
# Method stored in variable
func_ref = obj.dynamic_method
result3 = func_ref(200)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("obj.new_dynamic_method(100)"));
assert!(migrated.contains("getattr(obj, \"dynamic_method\")"));
}
#[test]
fn test_replace_me_on_operator_overloads() {
let source = r#"
from dissolve import replace_me
class MyClass:
def __init__(self, value):
self.value = value
@replace_me()
def __add__(self, other):
return self.new_add(other)
@replace_me()
def __mul__(self, other):
return self.new_multiply(other)
@replace_me()
def __getitem__(self, key):
return self.new_getitem(key)
@replace_me()
def __setitem__(self, key, value):
self.new_setitem(key, value)
obj1 = MyClass(10)
obj2 = MyClass(20)
# Operator usage
result1 = obj1 + obj2
result2 = obj1 * 3
value = obj1[0]
obj1[1] = 42
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("@replace_me()"));
assert!(migrated.contains("def __add__(self, other):"));
assert!(migrated.contains("def __mul__(self, other):"));
assert!(migrated.contains("def __getitem__(self, key):"));
}
#[test]
fn test_replace_me_with_inheritance_override() {
let source = r#"
from dissolve import replace_me
class BaseClass:
@replace_me()
def deprecated_method(self, x):
return self.base_new_method(x)
class DerivedClass(BaseClass):
@replace_me()
def deprecated_method(self, x):
return super().base_new_method(x * 2)
def another_method(self):
# Call inherited deprecated method
return self.deprecated_method(10)
obj = DerivedClass()
result1 = obj.deprecated_method(5)
result2 = obj.another_method()
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("class BaseClass:"));
assert!(migrated.contains("class DerivedClass(BaseClass):"));
assert!(migrated.contains("@replace_me()"));
assert!(migrated.contains("def deprecated_method(self, x):"));
}
#[test]
fn test_replace_me_with_async_context_managers() {
let source = r#"
from dissolve import replace_me
class AsyncContextManager:
@replace_me()
async def __aenter__(self):
return await self.new_aenter()
@replace_me()
async def __aexit__(self, exc_type, exc_val, exc_tb):
return await self.new_aexit(exc_type, exc_val, exc_tb)
async def test_async_context():
async with AsyncContextManager() as cm:
pass
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("await self.new_aenter()"));
assert!(migrated.contains("await self.new_aexit(exc_type, exc_val, exc_tb)"));
}
#[test]
fn test_replace_me_with_descriptor_protocol() {
let source = r#"
from dissolve import replace_me
class MyDescriptor:
@replace_me()
def __get__(self, obj, objtype=None):
return self.new_get(obj, objtype)
@replace_me()
def __set__(self, obj, value):
self.new_set(obj, value)
@replace_me()
def __delete__(self, obj):
self.new_delete(obj)
class MyClass:
attr = MyDescriptor()
obj = MyClass()
value = obj.attr # Calls __get__
obj.attr = 42 # Calls __set__
del obj.attr # Calls __delete__
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let migrated = migrate_file(
source,
"test_module",
Path::new(&test_ctx.file_path),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("class MyDescriptor:"));
assert!(migrated.contains("@replace_me()"));
assert!(migrated.contains("def __get__(self, obj, objtype=None):"));
assert!(migrated.contains("def __set__(self, obj, value):"));
}