Repository: astral-sh/ruff
File: crates/ruff_linter/src/rules/flake8_use_pathlib/helpers.rs (Rust)

Code before:
    let range = call.range();

    let mut diagnostic = checker.report_diagnostic(violation, call.func.range());

    if !fix_enabled {
        return;
    }

    diagnostic.try_set_fix(|| {
        let (import_edit, binding) = checker.importer().get_or_import_symbol(
            &ImportRequest::import("pathlib", "Path"),
            call.start(),
            checker.semantic(),
        )?;

        let replacement = if is_pathlib_path_call(checker, arg) {
            format!("{arg_code}.{attr}")
        } else {
            format!("{binding}({arg_code}).{attr}")
        };

        let edit = Edit::range_replacement(replacement, range);

        let applicability = match applicability {
            Applicability::DisplayOnly => Applicability::DisplayOnly,
            _ if checker.comment_ranges().intersects(range) || is_statement(checker) => {
                Applicability::Unsafe
            }
            _ => applicability,
        };

        let fix = Fix::applicable_edits(edit, [import_edit], applicability);

        Ok(fix)
    });
}

pub(crate) fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> {
    match expr {
        Expr::Name(name) => Some(name),
        Expr::Call(ExprCall { func, .. }) => get_name_expr(func),
        _ => None,
    }
}

/// Returns `true` if the given expression looks like a file descriptor, i.e., if it is an integer.
pub(crate) fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool {
    if matches!(
        expr,
        Expr::NumberLiteral(ast::ExprNumberLiteral {
            value: ast::Number::Int(_),

Review comment (refactor):
I think I would prefer to keep this check only in the rules that are affected. This helper is used in many additional PTH rules. Should all of those have unsafe fixes, or only the four in the issue?

To me, only the comment check is general enough to share here.

Code after:
    let range = call.range();

    let mut diagnostic = checker.report_diagnostic(violation, call.func.range());

    if !fix_enabled {
        return;
    }

    diagnostic.try_set_fix(|| {
        let (import_edit, binding) = checker.importer().get_or_import_symbol(
            &ImportRequest::import("pathlib", "Path"),
            call.start(),
            checker.semantic(),
        )?;

        let replacement = if is_pathlib_path_call(checker, arg) {
            format!("{arg_code}.{attr}")
        } else {
            format!("{binding}({arg_code}).{attr}")
        };

        let edit = Edit::range_replacement(replacement, range);

        let applicability = match applicability {
            Applicability::DisplayOnly => Applicability::DisplayOnly,
            _ if checker.comment_ranges().intersects(range) => Applicability::Unsafe,
            _ => applicability,
        };

        let fix = Fix::applicable_edits(edit, [import_edit], applicability);

        Ok(fix)
    });
}

pub(crate) fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> {
    match expr {
        Expr::Name(name) => Some(name),
        Expr::Call(ExprCall { func, .. }) => get_name_expr(func),
        _ => None,
    }
}

/// Returns `true` if the given expression looks like a file descriptor, i.e., if it is an integer.
pub(crate) fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool {
    if matches!(
        expr,
        Expr::NumberLiteral(ast::ExprNumberLiteral {
            value: ast::Number::Int(_),
            ..
        })