ruff/crates
Zanie Blue d0f2a8e424
Add support for nested replacements inside format specifications (#6616)
Closes https://github.com/astral-sh/ruff/issues/6442

Python string formatting like `"hello {place}".format(place="world")`
supports format specifications for replaced content such as `"hello
{place:>10}".format(place="world")` which will align the text to the
right in a container filled up to ten characters.

Ruff parses formatted strings into `FormatPart`s each of which is either
a `Field` (content in `{...}`) or a `Literal` (the normal content).
Fields are parsed into name and format specifier sections (we'll ignore
conversion specifiers for now).

There are a myriad of specifiers that can be used in a `FormatSpec`.
Unfortunately for linters, the specifier values can be dynamically set.
For example, `"hello {place:{align}{width}}".format(place="world",
align=">", width=10)` and `"hello {place:{fmt}}".format(place="world",
fmt=">10")` will yield the same string as before but variables can be
used to determine the formatting. In this case, when parsing the format
specifier we can't know what _kind_ of specifier is being used as their
meaning is determined by both position and value.

Ruff does not support nested replacements and our current data model
does not support the concept. Here the data model is updated to support
this concept, although linting of specifications with replacements will
be inherently limited. We could split format specifications into two
types, one without any replacements that we can perform lints with and
one with replacements that we cannot inspect. However, it seems
excessive to drop all parsing of format specifiers due to the presence
of a replacement. Instead, I've opted to parse replacements eagerly and
ignore their possible effect on other format specifiers. This will allow
us to retain a simple interface for `FormatSpec` and most syntax checks.
We may need to add some handling to relax errors if a replacement was
seen previously.

It's worth noting that the nested replacement _can_ also include a
format specification although it may fail at runtime if you produce an
invalid outer format specification. For example, `"hello
{place:{fmt:<2}}".format(place="world", fmt=">10")` is valid so we need
to represent each nested replacement as a full `FormatPart`.

## Test plan

Adding unit tests for `FormatSpec` parsing and snapshots for PLE1300
2023-08-17 09:07:30 -05:00
..
flake8_to_ruff Bump version to 0.0.284 (#6453) 2023-08-09 13:32:33 -05:00
ruff Add support for nested replacements inside format specifications (#6616) 2023-08-17 09:07:30 -05:00
ruff_benchmark Remove some extraneous newlines in Cargo.toml (#6577) 2023-08-14 23:39:41 +00:00
ruff_cache Error on zero tab width (#6429) 2023-08-08 16:51:37 -04:00
ruff_cli Respect .ipynb and .pyi sources when linting from stdin (#6628) 2023-08-16 20:33:59 +00:00
ruff_dev Use a faster diffing library for the formatter ecosystem checks (#6497) 2023-08-11 15:51:54 +02:00
ruff_diagnostics Skip partial duplicates when applying multi-edit fixes (#6144) 2023-07-29 12:11:57 +00:00
ruff_formatter Estimate expected VecBuffer size (#6612) 2023-08-16 15:31:31 +02:00
ruff_index Add unreachable code rule (#5384) 2023-07-04 14:27:23 +00:00
ruff_macros Remove parser dependency from ruff-python-ast (#6096) 2023-07-26 17:47:22 +02:00
ruff_python_ast Introduce ExpressionRef (#6637) 2023-08-17 10:07:16 -04:00
ruff_python_codegen Remove Stmt::TryStar (#6566) 2023-08-14 13:39:44 -04:00
ruff_python_formatter Introduce ExpressionRef (#6637) 2023-08-17 10:07:16 -04:00
ruff_python_index Remove some extraneous newlines in Cargo.toml (#6577) 2023-08-14 23:39:41 +00:00
ruff_python_literal Add support for nested replacements inside format specifications (#6616) 2023-08-17 09:07:30 -05:00
ruff_python_parser Remove Stmt::TryStar (#6566) 2023-08-14 13:39:44 -04:00
ruff_python_resolver Replace .map_or(false, $closure) with .is_some_and(closure) (#6244) 2023-08-01 19:29:42 +02:00
ruff_python_semantic Remove Stmt::TryStar (#6566) 2023-08-14 13:39:44 -04:00
ruff_python_stdlib Replace .map_or(false, $closure) with .is_some_and(closure) (#6244) 2023-08-01 19:29:42 +02:00
ruff_python_trivia Add support for multi-character operator tokens to SimpleTokenizer (#6563) 2023-08-16 09:09:19 -04:00
ruff_shrinking Use Jupyter mode while parsing Notebook files (#5552) 2023-08-05 00:32:07 +00:00
ruff_source_file Skip BOM when determining Locator's line starts (#6159) 2023-07-29 11:47:13 +00:00
ruff_text_size Pull in RustPython parser (#6099) 2023-07-27 09:29:11 +00:00
ruff_wasm Set a default on PythonVersion (#6446) 2023-08-09 15:19:27 +00:00