dissolve-python 0.3.0

A tool to dissolve deprecated calls in Python codebases
Documentation
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_simple_int_magic_method() {
    let source = r#"
from dissolve import replace_me

class MyClass:
    @replace_me()
    def __int__(self):
        return 42

obj = MyClass()
result = int(obj)
"#;

    let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
    let result = collector.collect_from_source(source.to_string()).unwrap();

    println!("Collected replacements: {:?}", result.replacements);

    // Create a temporary file for type introspection
    let test_ctx = crate::tests::test_utils::TestContext::new(source);

    let mut type_context =
        TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
    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();

    println!("Migrated output:\n{}", migrated);

    // This should replace int(obj) with 42
    assert!(migrated.contains("result = 42"));
}

#[test]
fn test_complex_int_magic_method() {
    let source = r#"
from dissolve import replace_me

class MyClass:
    @replace_me()
    def __int__(self):
        return max(0, int(self.value))

obj = MyClass()
result = int(obj)
"#;

    let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
    let result = collector.collect_from_source(source.to_string()).unwrap();

    println!("Collected replacements: {:?}", result.replacements);

    // Check what's in the replacement
    if let Some(info) = result.replacements.get("test_module.MyClass.__int__") {
        println!("Replacement expression: '{}'", info.replacement_expr);
        println!("Replacement AST: {:?}", info.replacement_ast);
    }

    // Create a temporary file to ensure type introspection works
    let test_ctx = crate::tests::test_utils::TestContext::new(source);

    let mut type_context =
        TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
    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();

    println!("Migrated output:\n{}", migrated);

    // This should replace int(obj) with max(0, int(obj.value))
    assert!(migrated.contains("result = max(0, int(obj.value))"));
}

#[test]
fn test_int_with_self_placeholder() {
    let source = r#"
from dissolve import replace_me

class MyClass:
    @replace_me()
    def __int__(self):
        return int(self.value)

obj = MyClass()
result = int(obj)
"#;

    let collector = RuffDeprecatedFunctionCollector::new("test_module".to_string(), None);
    let result = collector.collect_from_source(source.to_string()).unwrap();

    println!("Collected replacements: {:?}", result.replacements);

    // Create a temporary file for type introspection
    let test_ctx = crate::tests::test_utils::TestContext::new(source);

    let mut type_context =
        TypeIntrospectionContext::new(TypeIntrospectionMethod::PyrightLsp).unwrap();
    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();

    println!("Migrated output:\n{}", migrated);

    // Also check what the actual replacement was
    if migrated.contains("result = obj.value") {
        println!("ERROR: int() wrapper was removed from replacement!");
    }

    // This should replace int(obj) with int(obj.value)
    assert!(migrated.contains("result = int(obj.value)"));
}