diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE1300_bad_string_format_character.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE1300_bad_string_format_character.py.snap index f34f9cc876..410f5ae953 100644 --- a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE1300_bad_string_format_character.py.snap +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLE1300_bad_string_format_character.py.snap @@ -51,14 +51,13 @@ bad_string_format_character.py:15:1: PLE1300 Unsupported format character 'y' 17 | "{:*^30s}".format("centered") # OK | -bad_string_format_character.py:20:1: PLE1300 Unsupported format character 'y' +bad_string_format_character.py:19:1: PLE1300 Unsupported format character 'y' | +17 | "{:*^30s}".format("centered") # OK 18 | "{:{s}}".format("hello", s="s") # OK (nested replacement value not checked) -19 | -20 | "{:{s:y}}".format("hello", s="s") # [bad-format-character] (nested replacement format spec checked) +19 | "{:{s:y}}".format("hello", s="s") # [bad-format-character] (nested replacement format spec checked) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLE1300 -21 | -22 | ## f-strings +20 | "{0:.{prec}g}".format(1.23, prec=15) # OK (cannot validate after nested replacement) | diff --git a/crates/ruff_python_literal/src/format.rs b/crates/ruff_python_literal/src/format.rs index 673ce5cf9e..3a1876c3b5 100644 --- a/crates/ruff_python_literal/src/format.rs +++ b/crates/ruff_python_literal/src/format.rs @@ -334,8 +334,33 @@ fn parse_nested_placeholder<'a>( } } +/// +fn consume_remaining_placeholders<'a>( + placeholders: &mut Vec, + text: &'a str, +) -> Result<&'a str, FormatSpecError> { + let mut chars = text.chars(); + let mut placeholder_count = placeholders.len(); + + while chars.clone().contains(&'{') { + dbg!(&chars, placeholder_count); + let text = parse_nested_placeholder(placeholders, chars.as_str())?; + chars = text.chars(); + // If we did not parse a placeholder, consume a character + if placeholder_count == placeholders.len() { + chars.next(); + } else { + placeholder_count = placeholders.len(); + } + } + + Ok(chars.as_str()) +} + impl FormatSpec { pub fn parse(text: &str) -> Result { + println!(); + dbg!(text); let mut replacements = vec![]; let text = parse_nested_placeholder(&mut replacements, text)?; let (conversion, text) = FormatConversion::parse(text); @@ -353,7 +378,7 @@ impl FormatSpec { let (grouping_option, text) = FormatGrouping::parse(text); let text = parse_nested_placeholder(&mut replacements, text)?; let (precision, text) = parse_precision(text)?; - let text = parse_nested_placeholder(&mut replacements, text)?; + let text = consume_remaining_placeholders(&mut replacements, text)?; let (format_type, _text) = if text.is_empty() { (None, text) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__with_statement.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__with_statement.snap index abae0fb4bc..1e47c917a7 100644 --- a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__with_statement.snap +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__parser__tests__with_statement.snap @@ -477,7 +477,7 @@ expression: "parse_suite(source, \"\").unwrap()" is_async: false, items: [ WithItem { - range: 239..243, + range: 239..240, context_expr: Constant( ExprConstant { range: 239..240, @@ -490,7 +490,7 @@ expression: "parse_suite(source, \"\").unwrap()" optional_vars: None, }, WithItem { - range: 239..243, + range: 242..243, context_expr: Constant( ExprConstant { range: 242..243,