use crate::migrate_ruff::migrate_file;
use crate::type_introspection_context::TypeIntrospectionContext;
use crate::{RuffDeprecatedFunctionCollector, TypeIntrospectionMethod};
use std::collections::HashMap;
use std::path::Path;
#[test]
fn test_nested_attribute_access_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_nested(data):
return transform(data.values.items.first)
# Complex nested attribute access
result = process_nested(my_obj.nested.deep.structure)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("transform(my_obj.nested.deep.structure.values.items.first)"));
}
#[test]
fn test_dictionary_and_list_indexing_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_indexed(item, key):
return lookup(item[key])
# Dictionary and list indexing
result1 = process_indexed(my_dict, "name")
result2 = process_indexed(my_list, 0)
result3 = process_indexed(nested["data"], "id")
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
if !migrated.contains(r#"lookup(my_dict["name"])"#) {
eprintln!("Expected lookup(my_dict[\"name\"]), got:\n{}", migrated);
}
assert!(migrated.contains(r#"lookup(my_dict["name"])"#));
assert!(migrated.contains("lookup(my_list[0])"));
assert!(migrated.contains(r#"lookup(nested["data"]["id"])"#));
}
#[test]
fn test_lambda_expressions_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def apply_transform(func, data):
return execute(func, data)
# Lambda expressions
result1 = apply_transform(lambda x: x * 2, [1, 2, 3])
result2 = apply_transform(lambda x, y: x + y, (10, 20))
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("execute(lambda x: x * 2, [1, 2, 3])"));
assert!(migrated.contains("execute(lambda x, y: x + y, (10, 20))"));
}
#[test]
fn test_comprehensions_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_collection(items):
return analyze(items)
# Various comprehensions
result1 = process_collection([x * 2 for x in range(10)])
result2 = process_collection({x: x**2 for x in range(5)})
result3 = process_collection({x for x in data if x > 0})
result4 = process_collection((x for x in items)) # generator expression
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("analyze([x * 2 for x in range(10)])"));
assert!(migrated.contains("analyze({x: x ** 2 for x in range(5)})"));
assert!(migrated.contains("analyze({x for x in data if x > 0})"));
assert!(migrated.contains("analyze((x for x in items))"));
}
#[test]
fn test_conditional_expressions_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_conditional(value, fallback):
return handle(value if value else fallback)
# Conditional expressions
result1 = process_conditional(x if x > 0 else -x, 0)
result2 = process_conditional(name if name else "Anonymous", "Unknown")
result3 = process_conditional(a if condition else b, c if condition else d)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
println!("Conditional expression test migrated output:");
for line in migrated.lines() {
if line.contains("handle") || line.contains("result") {
println!(" {}", line);
}
}
assert!(
migrated.contains("handle((x if x > 0 else -x) if (x if x > 0 else -x) else 0)")
|| migrated.contains("handle(x if x > 0 else -x if x if x > 0 else -x else 0)")
);
}
#[test]
fn test_f_string_and_format_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def log_message(msg):
return logger.info(msg)
# F-strings and format strings
name = "Alice"
count = 42
result1 = log_message(f"Hello {name}!")
result2 = log_message(f"Count: {count:03d}")
result3 = log_message("Total: {}".format(count))
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
println!("F-string test migrated output:\n{}", migrated);
assert!(migrated.contains(r#"logger.info(f"Hello {name}!")"#));
assert!(migrated.contains(r#"logger.info(f"Count: {count:03d}")"#));
assert!(migrated.contains(r#"logger.info("Total: {}".format(count))"#));
}
#[test]
fn test_slice_operations_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_slice(data):
return transform(data)
# Various slice operations
result1 = process_slice(my_list[1:5])
result2 = process_slice(my_list[::2])
result3 = process_slice(my_list[:-1])
result4 = process_slice(my_string[start:end:step])
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("transform(my_list[1:5])"));
assert!(migrated.contains("transform(my_list[::2])"));
assert!(migrated.contains("transform(my_list[:-1])"));
assert!(migrated.contains("transform(my_string[start:end:step])"));
}
#[test]
fn test_boolean_operations_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def check_condition(cond):
return validate(cond)
# Boolean operations
result1 = check_condition(a and b or c)
result2 = check_condition(not x or (y and z))
result3 = check_condition(x is None or y is not None)
result4 = check_condition(a in list1 and b not in list2)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
println!("Migrated output:\n{}", migrated);
assert!(migrated.contains("validate(a and b or c)"));
assert!(migrated.contains("validate(not x or y and z)"));
assert!(migrated.contains("validate(x is None or y is not None)"));
assert!(migrated.contains("validate(a in list1 and b not in list2)"));
}
#[test]
fn test_yield_and_yield_from_in_replacements() {
let source = r#"
from dissolve import replace_me
@replace_me()
def old_generator(items):
for item in new_generator(items):
yield item
@replace_me()
def old_delegator(items):
yield from new_delegator(items)
# Usage
gen1 = old_generator([1, 2, 3])
gen2 = old_delegator([4, 5, 6])
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
println!("Migrated:\n{}", migrated);
assert!(migrated.contains("yield"));
}
#[test]
fn test_named_expressions_walrus_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_with_side_effect(value):
return handle(value)
# Walrus operator in parameters
if result := process_with_side_effect((x := expensive_calc()) + x):
print(result)
# In comprehensions
data = [process_with_side_effect(y) for x in items if (y := transform(x)) > 0]
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
let expected = r#"
from dissolve import replace_me
@replace_me()
def process_with_side_effect(value):
return handle(value)
# Walrus operator in parameters
if result := handle((x := expensive_calc()) + x):
print(result)
# In comprehensions
data = [handle(y) for x in items if (y := transform(x)) > 0]
"#;
assert_eq!(migrated, expected);
}
#[test]
fn test_type_annotations_in_parameters() {
let source = r#"
from dissolve import replace_me
from typing import List, Dict, Optional
@replace_me()
def process_typed(data: List[int], config: Optional[Dict[str, str]] = None):
return transform(data, config or {})
# With type-annotated variables
numbers: List[int] = [1, 2, 3]
settings: Dict[str, str] = {"mode": "fast"}
result = process_typed(numbers, settings)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
if !migrated.contains("transform(numbers, settings or {})") {
eprintln!(
"Expected transform(numbers, settings or {{}}), got:\n{}",
migrated
);
}
assert!(migrated.contains("transform(numbers, settings or {})"));
}
#[test]
fn test_multiline_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_many(a, b, c, d, e):
return handle_all(a, b, c, d, e)
# Multiline call
result = process_many(
very_long_parameter_name_1,
very_long_parameter_name_2,
very_long_parameter_name_3,
very_long_parameter_name_4,
very_long_parameter_name_5
)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("handle_all("));
assert!(migrated.contains("very_long_parameter_name_1,"));
assert!(migrated.contains("very_long_parameter_name_5"));
}
#[test]
fn test_exception_expressions_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def safe_process(value, default):
return safe_transform(value, default)
# Using helper functions that might raise
def get_or_default(key):
try:
return data[key]
except KeyError:
return None
result = safe_process(get_or_default("key"), "default")
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains(r#"safe_transform(get_or_default("key"), "default")"#));
}
#[test]
fn test_set_operations_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_sets(s1, s2):
return analyze_sets(s1 | s2, s1 & s2)
# Set operations
result = process_sets({1, 2, 3}, {2, 3, 4})
result2 = process_sets(set(list1), set(list2))
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains("analyze_sets({1, 2, 3} | {2, 3, 4}, {1, 2, 3} & {2, 3, 4})"));
}
#[test]
fn test_bytes_and_raw_strings_in_parameters() {
let source = r##"
from dissolve import replace_me
@replace_me()
def process_bytes(data):
return handle_bytes(data)
@replace_me()
def process_raw(path):
return handle_path(path)
# Special string types
result1 = process_bytes(b"Hello\x00World")
result2 = process_raw(r"C:\Users\test\file.txt")
result3 = process_bytes(b'\xde\xad\xbe\xef')
"##;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(migrated.contains(r#"handle_bytes(b"Hello\x00World")"#));
assert!(migrated.contains(r#"handle_path("C:\\Users\\test\\file.txt")"#));
}
#[test]
fn test_decorator_expressions_as_parameters() {
let source = r#"
from dissolve import replace_me
def decorator_factory(name):
def decorator(func):
return func
return decorator
@replace_me()
def apply_decorator(dec, func):
return dec(func)
# Using decorator as parameter
result = apply_decorator(decorator_factory("test"), lambda x: x)
"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
if !migrated.contains(r#"decorator_factory("test")(lambda x: x)"#) {
eprintln!(
"Expected decorator_factory(\"test\")(lambda x: x), got:\n{}",
migrated
);
}
assert!(migrated.contains(r#"decorator_factory("test")(lambda x: x)"#));
}
#[test]
fn test_expression_conversion_in_migration() {
let source = r#"from dissolve import replace_me
@replace_me()
def process_name(x):
return transform(x)
# Test call
result = process_name(data)"#;
let expected = r#"from dissolve import replace_me
@replace_me()
def process_name(x):
return transform(x)
# Test call
result = transform(data)"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert_eq!(
migrated, expected,
"Migration should convert 'process_name(data)' to 'transform(data)', not 'xyzzy'"
);
}
#[test]
fn test_complex_expression_conversion() {
let source = r#"from dissolve import replace_me
@replace_me()
def process(obj):
return handler(obj.method(x, y))
result = process(instance)"#;
let expected = r#"from dissolve import replace_me
@replace_me()
def process(obj):
return handler(obj.method(x, y))
result = handler(instance.method(x, y))"#;
let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
let result = collector.collect_from_source(source.to_string()).unwrap();
let mut type_context =
TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
let migrated = migrate_file(
source,
"test_module",
Path::new("test.py"),
&mut type_context,
result.replacements,
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert_eq!(
migrated, expected,
"Complex expressions should be properly converted, not replaced with 'xyzzy'"
);
}