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

Code before:
        })
        .zip(call.arguments.args.iter().skip(msg_pos + 1))
    {
        // Check if the argument is a call to eagerly format a value
        if let Expr::Call(ast::ExprCall {
            func,
            arguments: str_call_args,
            ..
        }) = arg
        {
            let CFormatType::String(format_conversion) = spec.format_type else {
                continue;
            };

            // Check for various eager conversion patterns
            match format_conversion {
                // %s with str() - remove str() call
                // Only flag if str() has exactly one argument (positional or keyword) that is not unpacked
                FormatConversion::Str
                    if checker.semantic().match_builtin_expr(func.as_ref(), "str")
                        && ((str_call_args.args.len() == 1
                            && !str_call_args.args[0].is_starred_expr()
                            && str_call_args.keywords.is_empty())
                            || (str_call_args.args.is_empty()
                                && str_call_args.keywords.len() == 1
                                && str_call_args.keywords[0].arg.is_some())) =>
                {
                    checker.report_diagnostic(
                        LoggingEagerConversion {
                            format_conversion,
                            function_name: None,
                        },
                        arg.range(),
                    );
                }
                // %s with repr() - suggest using %r instead
                FormatConversion::Str
                    if checker.semantic().match_builtin_expr(func.as_ref(), "repr") =>
                {
                    checker.report_diagnostic(
                        LoggingEagerConversion {
                            format_conversion: FormatConversion::Repr,
                            function_name: None,
                        },
                        arg.range(),
                    );
                }
                // %s with ascii() - suggest using %a instead
                FormatConversion::Str
                    if checker
                        .semantic()

Review comment (refactor):
This looks pretty complicated. Can we just use `find_argument` and a check that `str_call_args.len() == 1`?


```suggestion
                    if checker.semantic().match_builtin_expr(func.as_ref(), "str")
                        && str_call_args.len() == 1
                        && str_call_args.find_argument("object", 0).is_some_and(|arg| !arg.is_variadic()) => 
```

with 

```rust
impl<'a> ArgOrKeyword<'a> {
    pub const fn is_variadic(self) -> bool {
        match self {
            ArgOrKeyword::Arg(expr) => expr.is_starred_expr(),
            ArgOrKeyword::Keyword(keyword) => keyword.arg.is_none(),
        }
    }
}
````

This comes up a lot, so that might be a nice helper to have around.

Hopefully this will help with the `object` case, but I'm not sure.

Code after:
        })
        .zip(call.arguments.args.iter().skip(msg_pos + 1))
    {
        // Check if the argument is a call to eagerly format a value
        if let Expr::Call(ast::ExprCall {
            func,
            arguments: str_call_args,
            ..
        }) = arg
        {
            let CFormatType::String(format_conversion) = spec.format_type else {
                continue;
            };

            // Check for various eager conversion patterns
            match format_conversion {
                // %s with str() - remove str() call
                // Only flag if str() has exactly one argument (positional or keyword) that is not unpacked
                FormatConversion::Str
                    if checker.semantic().match_builtin_expr(func.as_ref(), "str")
                        && str_call_args.len() == 1
                        && str_call_args
                            .find_argument("object", 0)
                            .is_some_and(|arg| !arg.is_variadic()) =>
                {
                    checker.report_diagnostic(
                        LoggingEagerConversion {
                            format_conversion,
                            function_name: None,
                        },
                        arg.range(),
                    );
                }
                // %s with repr() - suggest using %r instead
                FormatConversion::Str
                    if checker.semantic().match_builtin_expr(func.as_ref(), "repr") =>
                {
                    checker.report_diagnostic(
                        LoggingEagerConversion {
                            format_conversion: FormatConversion::Repr,
                            function_name: None,
                        },
                        arg.range(),
                    );
                }
                // %s with ascii() - suggest using %a instead
                FormatConversion::Str
                    if checker
                        .semantic()
                        .match_builtin_expr(func.as_ref(), "ascii") =>
                {