use crate::dependency_collector::{
clear_module_cache, collect_deprecated_from_dependencies_with_paths,
};
use crate::migrate_ruff;
use crate::TypeIntrospectionMethod;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
fn create_module(dir: &std::path::Path, rel_path: &str, content: &str) -> PathBuf {
let full_path = dir.join(rel_path);
if let Some(parent) = full_path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&full_path, content).unwrap();
full_path
}
#[test]
fn test_simple_function_cross_module() {
clear_module_cache();
let temp_dir = TempDir::new().unwrap();
let deprecated_module = r#"
from dissolve import replace_me
@replace_me()
def old_function(x, y):
return new_function(x, y)
def new_function(x, y):
return x + y
"#;
let user_module = r#"
from testpkg.deprecated import old_function
def test():
result = old_function(1, 2)
return result
"#;
create_module(temp_dir.path(), "testpkg/__init__.py", "");
create_module(temp_dir.path(), "testpkg/deprecated.py", deprecated_module);
let user_path = create_module(temp_dir.path(), "testpkg/user.py", user_module);
let pyright_config = r#"{
"include": ["testpkg"],
"pythonVersion": "3.8",
"pythonPlatform": "All",
"typeCheckingMode": "basic",
"useLibraryCodeForTypes": true
}"#;
fs::write(temp_dir.path().join("pyrightconfig.json"), pyright_config).unwrap();
let additional_paths = vec![temp_dir.path().to_string_lossy().to_string()];
let dep_result = collect_deprecated_from_dependencies_with_paths(
user_module,
"testpkg.user",
5,
&additional_paths,
)
.unwrap();
assert!(dep_result
.replacements
.contains_key("testpkg.deprecated.old_function"));
let mut type_context = crate::tests::test_utils::create_test_type_context_with_workspace(
TypeIntrospectionMethod::PyrightLsp,
temp_dir.path().to_str().unwrap(),
);
type_context
.open_file(&temp_dir.path().join("testpkg/__init__.py"), "")
.unwrap();
type_context
.open_file(
&temp_dir.path().join("testpkg/deprecated.py"),
deprecated_module,
)
.unwrap();
let result = migrate_ruff::migrate_file(
user_module,
"testpkg.user",
&user_path,
&mut type_context,
dep_result.replacements,
dep_result.inheritance_map,
)
.unwrap();
type_context.shutdown().unwrap();
assert!(result.contains("from testpkg.deprecated import"));
assert!(result.contains("new_function(1, 2)"));
assert!(!result.contains("old_function(1, 2)"));
}
#[test]
fn test_class_method_cross_module() {
clear_module_cache();
let temp_dir = TempDir::new().unwrap();
let deprecated_module = r#"
from dissolve import replace_me
class OldAPI:
@replace_me()
def old_method(self, data):
return self.new_method(data)
def new_method(self, data):
return data
"#;
let user_module = r#"
from testpkg.api import OldAPI
def process():
api = OldAPI()
api.old_method("test")
def process_with_variable():
api = OldAPI()
obj = api
obj.old_method("data")
"#;
create_module(temp_dir.path(), "testpkg/__init__.py", "");
create_module(temp_dir.path(), "testpkg/api.py", deprecated_module);
let client_path = create_module(temp_dir.path(), "testpkg/client.py", user_module);
let additional_paths = vec![temp_dir.path().to_string_lossy().to_string()];
let dep_result = collect_deprecated_from_dependencies_with_paths(
user_module,
"testpkg.client",
5,
&additional_paths,
)
.unwrap();
assert!(dep_result
.replacements
.contains_key("testpkg.api.OldAPI.old_method"));
let mut type_context = crate::tests::test_utils::create_test_type_context_with_workspace(
TypeIntrospectionMethod::PyrightLsp,
temp_dir.path().to_str().unwrap(),
);
let result = migrate_ruff::migrate_file(
user_module,
"testpkg.client",
&client_path,
&mut type_context,
dep_result.replacements,
dep_result.inheritance_map,
)
.unwrap();
type_context.shutdown().unwrap();
if !result.contains("api.new_method(\"test\")") {
eprintln!("Expected api.new_method(\"test\"), but got:");
eprintln!("{}", result);
}
assert!(result.contains("api.new_method(\"test\")"));
assert!(result.contains("obj.new_method(\"data\")"));
assert!(!result.contains("old_method"));
}
#[test]
fn test_classmethod_cross_module() {
clear_module_cache();
let temp_dir = TempDir::new().unwrap();
let deprecated_module = r#"
from dissolve import replace_me
class Factory:
@classmethod
@replace_me()
def old_create(cls, name):
return cls.new_create(name)
@classmethod
def new_create(cls, name):
return cls(name)
"#;
let user_module = r#"
from testpkg.factory import Factory
def create_instance():
return Factory.old_create("test")
"#;
create_module(temp_dir.path(), "testpkg/__init__.py", "");
create_module(temp_dir.path(), "testpkg/factory.py", deprecated_module);
let user_path = create_module(temp_dir.path(), "testpkg/user.py", user_module);
let pyright_config = r#"{
"include": ["testpkg"],
"pythonVersion": "3.8",
"pythonPlatform": "All",
"typeCheckingMode": "basic",
"useLibraryCodeForTypes": true
}"#;
fs::write(temp_dir.path().join("pyrightconfig.json"), pyright_config).unwrap();
let additional_paths = vec![temp_dir.path().to_string_lossy().to_string()];
let dep_result = collect_deprecated_from_dependencies_with_paths(
user_module,
"testpkg.user",
5,
&additional_paths,
)
.unwrap();
assert!(dep_result
.replacements
.contains_key("testpkg.factory.Factory.old_create"));
let mut type_context = crate::tests::test_utils::create_test_type_context_with_workspace(
TypeIntrospectionMethod::PyrightLsp,
temp_dir.path().to_str().unwrap(),
);
type_context
.open_file(&temp_dir.path().join("testpkg/__init__.py"), "")
.unwrap();
type_context
.open_file(
&temp_dir.path().join("testpkg/factory.py"),
deprecated_module,
)
.unwrap();
let result = migrate_ruff::migrate_file(
user_module,
"testpkg.user",
&user_path,
&mut type_context,
dep_result.replacements,
dep_result.inheritance_map,
)
.unwrap();
type_context.shutdown().unwrap();
assert!(result.contains("Factory.new_create(\"test\")"));
assert!(!result.contains("old_create"));
}
#[test]
fn test_staticmethod_cross_module() {
clear_module_cache();
let temp_dir = TempDir::new().unwrap();
let deprecated_module = r#"
from dissolve import replace_me
class Utils:
@staticmethod
@replace_me()
def old_helper(x):
return new_helper(x)
def new_helper(x):
return x * 2
"#;
let user_module = r#"
from testpkg.utils import Utils
def calculate():
return Utils.old_helper(5)
"#;
create_module(temp_dir.path(), "testpkg/__init__.py", "");
create_module(temp_dir.path(), "testpkg/utils.py", deprecated_module);
let user_path = create_module(temp_dir.path(), "testpkg/user.py", user_module);
let additional_paths = vec![temp_dir.path().to_string_lossy().to_string()];
let dep_result = collect_deprecated_from_dependencies_with_paths(
user_module,
"testpkg.user",
5,
&additional_paths,
)
.unwrap();
assert!(dep_result
.replacements
.contains_key("testpkg.utils.Utils.old_helper"));
let mut type_context = crate::tests::test_utils::create_test_type_context_with_workspace(
TypeIntrospectionMethod::PyrightLsp,
temp_dir.path().to_str().unwrap(),
);
type_context
.open_file(&temp_dir.path().join("testpkg/utils.py"), deprecated_module)
.unwrap();
let result = migrate_ruff::migrate_file(
user_module,
"testpkg.user",
&user_path,
&mut type_context,
dep_result.replacements,
dep_result.inheritance_map,
)
.unwrap();
type_context.shutdown().unwrap();
assert!(result.contains("new_helper(5)"));
assert!(!result.contains("old_helper"));
}
#[test]
fn test_import_alias() {
clear_module_cache();
let temp_dir = TempDir::new().unwrap();
let deprecated_module = r#"
from dissolve import replace_me
@replace_me()
def old_function(x):
return new_function(x)
def new_function(x):
return x * 2
"#;
let user_module = r#"
from testpkg.deprecated import old_function as legacy_func
def test():
return legacy_func(42)
"#;
create_module(temp_dir.path(), "testpkg/__init__.py", "");
create_module(temp_dir.path(), "testpkg/deprecated.py", deprecated_module);
let user_path = create_module(temp_dir.path(), "testpkg/user.py", user_module);
let additional_paths = vec![temp_dir.path().to_string_lossy().to_string()];
let dep_result = collect_deprecated_from_dependencies_with_paths(
user_module,
"testpkg.user",
5,
&additional_paths,
)
.unwrap();
assert!(dep_result
.replacements
.contains_key("testpkg.deprecated.old_function"));
println!(
"Replacements found: {:?}",
dep_result.replacements.keys().collect::<Vec<_>>()
);
let mut type_context = crate::tests::test_utils::create_test_type_context_with_workspace(
TypeIntrospectionMethod::PyrightLsp,
temp_dir.path().to_str().unwrap(),
);
let result = migrate_ruff::migrate_file(
user_module,
"testpkg.user",
&user_path,
&mut type_context,
dep_result.replacements,
dep_result.inheritance_map,
)
.unwrap();
type_context.shutdown().unwrap();
if !result.contains("legacy_func(42)") {
eprintln!("Expected legacy_func(42) to remain unchanged, but got:");
eprintln!("{}", result);
}
assert!(result.contains("legacy_func(42)"));
assert!(result.contains("from testpkg.deprecated import old_function as legacy_func"));
}
#[test]
fn test_with_statement_context_manager() {
clear_module_cache();
let temp_dir = TempDir::new().unwrap();
let deprecated_module = r#"
from dissolve import replace_me
class Resource:
@replace_me()
def old_close(self):
return self.new_close()
def new_close(self):
pass
def __enter__(self):
return self
def __exit__(self, *args):
pass
"#;
let user_module = r#"
from testpkg.resource import Resource
def use_resource():
with Resource() as res:
# do something
res.old_close()
"#;
create_module(temp_dir.path(), "testpkg/__init__.py", "");
create_module(temp_dir.path(), "testpkg/resource.py", deprecated_module);
let user_path = create_module(temp_dir.path(), "testpkg/user.py", user_module);
let additional_paths = vec![temp_dir.path().to_string_lossy().to_string()];
let dep_result = collect_deprecated_from_dependencies_with_paths(
user_module,
"testpkg.user",
5,
&additional_paths,
)
.unwrap();
assert!(dep_result
.replacements
.contains_key("testpkg.resource.Resource.old_close"));
let mut type_context = crate::tests::test_utils::create_test_type_context_with_workspace(
TypeIntrospectionMethod::PyrightLsp,
temp_dir.path().to_str().unwrap(),
);
let result = migrate_ruff::migrate_file(
user_module,
"testpkg.user",
&user_path,
&mut type_context,
dep_result.replacements,
dep_result.inheritance_map,
)
.unwrap();
type_context.shutdown().unwrap();
assert!(result.contains("res.new_close()"));
assert!(!result.contains("old_close"));
}
#[test]
fn test_scan_dependencies_disabled() {
clear_module_cache();
let source = r#"
from testpkg.api import old_function
def test():
old_function()
"#;
let test_ctx = crate::tests::test_utils::TestContext::new(source);
let mut type_context = test_ctx.create_type_context(TypeIntrospectionMethod::PyrightLsp);
let result = migrate_ruff::migrate_file(
source,
"testmodule",
Path::new(&test_ctx.file_path),
&mut type_context,
HashMap::new(),
HashMap::new(),
)
.unwrap();
type_context.shutdown().unwrap();
assert!(result.contains("old_function()"));
}