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_ellipsis_literal_in_parameters() {
let source = r#"
from dissolve import replace_me
from typing import Tuple
@replace_me()
def process_ellipsis(data, slice_val):
return handle(data[slice_val])
# Ellipsis usage
result1 = process_ellipsis(array, ...)
result2 = process_ellipsis(tensor[:, ..., :], slice(None))
"#;
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(array[...])"));
}
#[test]
fn test_matrix_multiplication_operator() {
let source = r#"
from dissolve import replace_me
@replace_me()
def matrix_op(a, b):
return compute(a @ b)
# Matrix multiplication
result = matrix_op(matrix1, matrix2)
result2 = matrix_op(A @ B, C)
"#;
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("compute(matrix1 @ matrix2)"));
assert!(migrated.contains("compute(A @ B @ C)"));
}
#[test]
fn test_complex_number_literals() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_complex(num):
return calculate(num)
# Complex numbers
result1 = process_complex(3+4j)
result2 = process_complex(1.5-2.5j)
result3 = process_complex(5j)
"#;
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("calculate(3 + 4j)"));
assert!(migrated.contains("calculate(1.5 - 2.5j)"));
assert!(migrated.contains("calculate(5j)"));
}
#[test]
fn test_chained_comparisons() {
let source = r#"
from dissolve import replace_me
@replace_me()
def check_range(val):
return validate(val)
# Chained comparisons
result1 = check_range(0 < x < 10)
result2 = check_range(a <= b < c <= d)
result3 = check_range(x == y == z)
"#;
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("validate(0 < x < 10)"));
assert!(migrated.contains("validate(a <= b < c <= d)"));
assert!(migrated.contains("validate(x == y == z)"));
}
#[test]
fn test_dict_merge_operators() {
let source = r#"
from dissolve import replace_me
@replace_me()
def merge_data(data):
return process(data)
# Dict merge operations
result1 = merge_data({**base_dict})
result2 = merge_data({**dict1, **dict2})
result3 = merge_data({**config, "key": "value", **overrides})
result4 = merge_data({"a": 1, **{"b": 2}, "c": 3})
"#;
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("process({**base_dict})"));
assert!(migrated.contains("process({**dict1, **dict2})"));
}
#[test]
fn test_long_integer_literals_with_underscores() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_number(num):
return calculate(num)
# Long integers with underscores
result1 = process_number(1_000_000)
result2 = process_number(0xFF_FF_FF)
result3 = process_number(0b1111_0000_1111_0000)
"#;
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("calculate(1000000)"));
}
#[test]
fn test_empty_collections() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_collection(coll):
return handle(coll)
# Empty collections
result1 = process_collection([])
result2 = process_collection({})
result3 = process_collection(())
result4 = process_collection(set())
"#;
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([])"));
assert!(migrated.contains("handle({})"));
assert!(migrated.contains("handle(())"));
assert!(migrated.contains("handle(set())"));
}
#[test]
fn test_nested_comprehensions_with_multiple_for_clauses() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_nested(data):
return analyze(data)
# Nested comprehensions
result1 = process_nested([x * y for x in range(3) for y in range(3)])
result2 = process_nested({(x, y): x*y for x in range(3) for y in range(3) if x != y})
result3 = process_nested([
[x + y for y in row]
for x, row in enumerate(matrix)
if sum(row) > 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();
assert!(migrated.contains("analyze([x * y for x in range(3) for y in range(3)])"));
}
#[test]
fn test_class_attribute_access() {
let source = r#"
from dissolve import replace_me
class Config:
DEFAULT_VALUE = 42
settings = {"debug": True}
@replace_me()
def get_config(key):
return fetch(key)
# Class attribute access
result1 = get_config(Config.DEFAULT_VALUE)
result2 = get_config(Config.settings["debug"])
result3 = get_config(MyClass.__name__)
"#;
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("fetch(Config.DEFAULT_VALUE)"));
assert!(migrated.contains(r#"fetch(Config.settings["debug"])"#));
assert!(migrated.contains("fetch(MyClass.__name__)"));
}
#[test]
fn test_tuple_unpacking_in_parameters() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_tuple(data):
return handle(*data)
@replace_me()
def process_args(a, b, c):
return compute(a, b, c)
# Tuple unpacking
coords = (10, 20, 30)
result1 = process_tuple(coords)
result2 = process_args(*coords)
"#;
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(*coords)")); assert!(migrated.contains("compute(*coords")); }
#[test]
fn test_nested_walrus_operators() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_values(val):
return compute(val)
# Nested walrus operators
if x := process_values((y := get_value()) + (z := get_other())):
print(x, y, z)
# In nested comprehensions
data = [process_values(inner)
for outer in items
if (inner := transform(outer)) and (check := validate(inner))]
"#;
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("compute((y := get_value()) + (z := get_other()))"));
}
#[test]
fn test_unicode_identifiers() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_data(données):
return traiter(données)
# Unicode identifiers
π = 3.14159
result = process_data(π)
λ_function = lambda x: x * 2
result2 = process_data(λ_function(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("traiter(π)"));
assert!(migrated.contains("traiter(λ_function(5))"));
}
#[test]
fn test_nested_f_strings() {
let source = r#"
from dissolve import replace_me
@replace_me()
def log_nested(msg):
return logger.log(msg)
# Nested f-strings and complex expressions
name = "test"
result = log_nested(f"Processing {f'item_{name}'} with value {x if x > 0 else 'negative'}")
result2 = log_nested(f"Result: {','.join(f'{k}={v}' for k, v in data.items())}")
"#;
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("logger.log(f\""));
}
#[test]
fn test_starred_expressions_in_lists() {
let source = r#"
from dissolve import replace_me
@replace_me()
def process_list(items):
return handle(items)
# Starred expressions
first = [1, 2, 3]
second = [4, 5, 6]
result1 = process_list([*first, *second])
result2 = process_list([0, *first, 7, 8, *second, 9])
result3 = process_list((*first, *second))
"#;
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([*first, *second])"));
assert!(migrated.contains("handle([0, *first, 7, 8, *second, 9])"));
}
#[test]
fn test_async_comprehensions() {
let source = r#"
from dissolve import replace_me
@replace_me()
async def process_async_data(data):
return await handle_async(data)
# Async comprehensions
async def test():
result = await process_async_data([x async for x in async_generator()])
result2 = await process_async_data({k: v async for k, v in async_items()})
"#;
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()
async def process_async_data(data):
return await handle_async(data)
# Async comprehensions
async def test():
result = await handle_async([x async for x in async_generator()])
result2 = await handle_async({k: v async for (k, v) in async_items()})
"#;
assert_eq!(migrated, expected);
}
#[test]
fn test_power_operator_with_negative_base() {
let source = r#"
from dissolve import replace_me
@replace_me()
def calculate_power(expr):
return compute(expr)
# Power operator edge cases
result1 = calculate_power(-2 ** 3) # Should be -(2**3) = -8
result2 = calculate_power((-2) ** 3) # Should be (-2)**3 = -8
result3 = calculate_power(2 ** -3) # Should be 2**(-3) = 0.125
result4 = calculate_power(2 ** 3 ** 2) # Right associative: 2**(3**2)
"#;
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("compute(-2 ** 3)"));
assert!(migrated.contains("compute(-2 ** 3)") && migrated.contains("result2 ="));
}