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

Code before:
    let ast::Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = &**value else {
        return false;
    };
    value.as_slice().contains(literal)
}

/// Returns `true` if `literal` is likely an f-string with a missing `f` prefix.
/// See [`MissingFStringSyntax`] for the validation criteria.
fn should_be_fstring(
    literal: &ast::StringLiteral,
    locator: &Locator,
    semantic: &SemanticModel,
    target_version: PythonVersion,
) -> bool {
    if !has_brackets(&literal.value) {
        return false;
    }

    let fstring_expr = format!("f{}", locator.slice(literal));
    let Ok(parsed) = parse_expression(&fstring_expr) else {
        return false;
    };

    // For Python < 3.12, reject if the parser detected any PEP 701 f-string
    // features (backslashes or comments in interpolations). This correctly handles
    // all nesting levels, including cases like `{1:{x #}}` where a comment appears
    // inside a nested interpolation within a format spec.
    if target_version < PythonVersion::PY312 {
        let has_pep701 = parsed
            .unsupported_syntax_errors()
            .iter()
            .any(|e| matches!(e.kind, UnsupportedSyntaxErrorKind::Pep701FString(_)));
        if has_pep701 {
            return false;
        }
    }

    // Note: Range offsets for `value` are based on `fstring_expr`
    let ast::Expr::FString(ast::ExprFString { value, .. }) = parsed.expr() else {
        return false;
    };

    let mut arg_names = FxHashSet::default();
    for expr in semantic
        .current_expressions()
        .filter_map(ast::Expr::as_call_expr)
    {
        let ast::Arguments { keywords, args, .. } = &expr.arguments;
        for keyword in keywords {
            if let Some(ident) = keyword.arg.as_ref() {
                arg_names.insert(&ident.id);

Review comment (suggestion):
Nice, I hadn't realized we could use this here! I think I would trim this comment down a bit, but otherwise this is great to reuse code from the parser.


```suggestion
    // For Python < 3.12, reject if the parser detected any PEP 701 f-string
    // features.
    if target_version < PythonVersion::PY312 {
```

The one downside of this is that we're taking advantage of the implicit behavior of `parse_expression` using the `PythonVersion::default()` value, which happens to be 3.10 and work for this case. I think that's okay, and we can always add a `parse_expression_with_settings` or similar if needed in the future.

Code after:
    let ast::Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = &**value else {
        return false;
    };
    value.as_slice().contains(literal)
}

/// Returns `true` if `literal` is likely an f-string with a missing `f` prefix.
/// See [`MissingFStringSyntax`] for the validation criteria.
fn should_be_fstring(
    literal: &ast::StringLiteral,
    locator: &Locator,
    semantic: &SemanticModel,
    target_version: PythonVersion,
) -> bool {
    if !has_brackets(&literal.value) {
        return false;
    }

    let fstring_expr = format!("f{}", locator.slice(literal));
    let Ok(parsed) = parse_expression(&fstring_expr) else {
        return false;
    };

    // For Python < 3.12, reject if the parser detected any PEP 701 f-string
    // features.
    if target_version < PythonVersion::PY312 {
        let has_pep701 = parsed
            .unsupported_syntax_errors()
            .iter()
            .any(|e| matches!(e.kind, UnsupportedSyntaxErrorKind::Pep701FString(_)));
        if has_pep701 {
            return false;
        }
    }

    // Note: Range offsets for `value` are based on `fstring_expr`
    let ast::Expr::FString(ast::ExprFString { value, .. }) = parsed.expr() else {
        return false;
    };

    let mut arg_names = FxHashSet::default();
    for expr in semantic
        .current_expressions()
        .filter_map(ast::Expr::as_call_expr)
    {
        let ast::Arguments { keywords, args, .. } = &expr.arguments;
        for keyword in keywords {
            if let Some(ident) = keyword.arg.as_ref() {
                arg_names.insert(&ident.id);
            }
        }