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

Code before:

    let path_code = checker.locator().slice(path_expr.range());
    let second_code = checker.locator().slice(second_expr.range());

    if fix_enabled {
        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, path_expr) {
                format!("{path_code}.{attr}({second_code})")
            } else {
                format!("{binding}({path_code}).{attr}({second_code})")
            };

            let applicability = if checker.comment_ranges().intersects(range) {
                Applicability::Unsafe
            } else if is_top_level_expression_call(checker, call) {
                // Safe when the call is a top-level expression (return value not used)
                Applicability::Safe
            } else {
                // Unsafe because the return type changes (None -> Path)
                Applicability::Unsafe
            };

            Ok(Fix::applicable_edits(
                Edit::range_replacement(replacement, range),
                [import_edit],
                applicability,
            ))
        });
    }
}

pub(crate) fn has_unknown_keywords_or_starred_expr(
    arguments: &Arguments,
    allowed: &[&str],
) -> bool {
    if arguments.args.iter().any(Expr::is_starred_expr) {
        return true;
    }

    arguments.keywords.iter().any(|kw| match &kw.arg {
        Some(arg) => !allowed.contains(&arg.as_str()),
        None => true,
    })
}


Review comment (refactor):
As above, this helper also affects rules outside of PTH{104,105,109,115}. We should narrow the check to the affected rules and pass that applicability into the helper.

Code after:
            let replacement = if is_pathlib_path_call(checker, path_expr) {
                format!("{path_code}.{attr}({second_code})")
            } else {
                format!("{binding}({path_code}).{attr}({second_code})")
            };

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

            Ok(Fix::applicable_edits(
                Edit::range_replacement(replacement, range),
                [import_edit],
                applicability,
            ))
        });
    }
}

pub(crate) fn has_unknown_keywords_or_starred_expr(
    arguments: &Arguments,
    allowed: &[&str],
) -> bool {
    if arguments.args.iter().any(Expr::is_starred_expr) {
        return true;
    }

    arguments.keywords.iter().any(|kw| match &kw.arg {
        Some(arg) => !allowed.contains(&arg.as_str()),
        None => true,
    })
}

/// Returns `true` if argument `name` is set to a non-default `None` value.
pub(crate) fn is_argument_non_default(arguments: &Arguments, name: &str, position: usize) -> bool {
    arguments
        .find_argument_value(name, position)
        .is_some_and(|expr| !expr.is_none_literal_expr())
}

/// Returns `true` if the given call is a top-level expression in its statement.
/// This means the call's return value is not used, so return type changes don't matter.
pub(crate) fn is_top_level_expression_call(checker: &Checker) -> bool {
    checker.semantic().current_expression_parent().is_none()
}