dissolve-python 0.3.0

A tool to dissolve deprecated calls in Python codebases
Documentation
// Copyright (C) 2024 Jelmer Vernooij <jelmer@samba.org>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Common AST utility functions shared across different collectors and visitors

use rustpython_ast as ast;
use std::collections::HashSet;

/// Format a constant value to its string representation
pub fn format_constant(constant: &ast::Constant) -> String {
    match constant {
        ast::Constant::Str(s) => {
            // Escape special characters properly
            let escaped = s
                .chars()
                .map(|c| match c {
                    '\\' => "\\\\".to_string(),
                    '"' => "\\\"".to_string(),
                    '\n' => "\\n".to_string(),
                    '\r' => "\\r".to_string(),
                    '\t' => "\\t".to_string(),
                    c if c.is_control() => format!("\\x{:02x}", c as u8),
                    c => c.to_string(),
                })
                .collect::<String>();
            format!("\"{}\"", escaped)
        }
        ast::Constant::Bytes(b) => {
            // Format bytes literal
            let escaped = b
                .iter()
                .map(|byte| {
                    // Allow printable ASCII characters including space
                    if (*byte == b' ' || byte.is_ascii_graphic()) && *byte != b'\\' && *byte != b'"'
                    {
                        (*byte as char).to_string()
                    } else if *byte == b'\'' {
                        "'".to_string() // Single quotes don't need escaping in double-quoted strings
                    } else {
                        match *byte {
                            b'\n' => "\\n".to_string(),
                            b'\r' => "\\r".to_string(),
                            b'\t' => "\\t".to_string(),
                            b'\\' => "\\\\".to_string(),
                            b'"' => "\\\"".to_string(),
                            _ => format!("\\x{:02x}", byte),
                        }
                    }
                })
                .collect::<String>();
            format!("b\"{}\"", escaped)
        }
        ast::Constant::Int(i) => i.to_string(),
        ast::Constant::Float(f) => f.to_string(),
        ast::Constant::Bool(b) => {
            if *b {
                "True".to_string()
            } else {
                "False".to_string()
            }
        }
        ast::Constant::None => "None".to_string(),
        ast::Constant::Complex { real, imag } => {
            // Format complex numbers
            if *real == 0.0 {
                format!("{}j", imag)
            } else if *imag >= 0.0 {
                format!("{} + {}j", real, imag)
            } else {
                format!("{} - {}j", real, imag.abs())
            }
        }
        ast::Constant::Ellipsis => "...".to_string(),
        _ => "".to_string(),
    }
}

/// Format a name with optional placeholder wrapping
pub fn format_name_with_placeholders(name: &str, param_names: &HashSet<String>) -> String {
    if param_names.contains(name) {
        format!("{{{}}}", name)
    } else {
        name.to_string()
    }
}