use crate::checker::CheckResult;
use crate::core::ReplaceInfo;
use crate::ruff_parser::apply_replacements;
use crate::rustpython_visitor::RustPythonFunctionCallReplacer;
use crate::stub_collector::RuffDeprecatedFunctionCollector;
use anyhow::Result;
use std::collections::HashMap;
pub fn migrate_file(
source: &str,
module_name: &str,
file_path: &std::path::Path,
type_context: &mut crate::type_introspection_context::TypeIntrospectionContext,
mut all_replacements: HashMap<String, ReplaceInfo>,
dependency_inheritance_map: HashMap<String, Vec<String>>,
) -> Result<String> {
type_context.open_file(file_path, source)?;
let collector = RuffDeprecatedFunctionCollector::new(module_name.to_string(), Some(file_path));
let builtins = collector.builtins().clone();
let collector_result = collector.collect_from_source(source.to_string())?;
for (key, value) in collector_result.replacements {
all_replacements.insert(key, value);
}
let ast = rustpython_parser::parse(source, rustpython_parser::Mode::Module, "<test>")
.map_err(|e| anyhow::anyhow!("Failed to parse: {:?}", e))?;
let mut merged_inheritance_map = collector_result.inheritance_map;
merged_inheritance_map.extend(dependency_inheritance_map);
let mut replacer = RustPythonFunctionCallReplacer::new_with_context(
all_replacements,
type_context,
file_path.to_string_lossy().into_owned(),
module_name.to_string(),
source.to_string(),
merged_inheritance_map,
builtins,
)?;
if let rustpython_ast::Mod::Module(module) = ast {
replacer.visit_module(&module.body);
}
let replacements = replacer.get_replacements();
tracing::debug!("Applying {} replacements", replacements.len());
for (range, replacement) in &replacements {
if range.start < source.len() && range.end <= source.len() {
let original = &source[range.clone()];
tracing::debug!("Replacing '{}' with '{}'", original, replacement);
} else {
tracing::warn!(
"Invalid range {:?} for source length {}",
range,
source.len()
);
}
}
let migrated_source = apply_replacements(source, replacements.clone());
if let Err(e) =
rustpython_parser::parse(&migrated_source, rustpython_parser::Mode::Module, "<test>")
{
tracing::error!("Generated invalid Python: {}", e);
tracing::error!("Migrated source:\n{}", migrated_source);
}
if !replacements.is_empty() {
type_context.update_file(file_path, &migrated_source)?;
}
Ok(migrated_source)
}
pub fn migrate_file_interactive(
source: &str,
module_name: &str,
file_path: &std::path::Path,
type_context: &mut crate::type_introspection_context::TypeIntrospectionContext,
all_replacements: HashMap<String, ReplaceInfo>,
inheritance_map: HashMap<String, Vec<String>>,
) -> Result<String> {
migrate_file(
source,
module_name,
file_path,
type_context,
all_replacements,
inheritance_map,
)
}
pub fn check_file(
source: &str,
module_name: &str,
file_path: &std::path::Path,
) -> Result<CheckResult> {
let collector = RuffDeprecatedFunctionCollector::new(module_name.to_string(), Some(file_path));
let result = collector.collect_from_source(source.to_string())?;
let mut check_result = CheckResult::new();
for func_name in result.replacements.keys() {
check_result.add_checked_function(func_name.clone());
}
for func_name in result.unreplaceable.keys() {
check_result.add_checked_function(func_name.clone());
}
for (func_name, unreplaceable) in result.unreplaceable {
check_result.add_error(format!(
"Function '{}' cannot be replaced: {:?}",
func_name, unreplaceable.reason
));
}
Ok(check_result)
}