> ## Documentation Index
> Fetch the complete documentation index at: https://docs.refactron.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# format_to_fstring

> Convert old-style %-formatting and .format() calls into f-strings.

**Transform ID:** `format_to_fstring`
**Language:** Python

## What it does

Rewrites legacy string-formatting expressions to PEP 498 f-strings. Both the `%` operator (`"hello %s" % name`) and `str.format()` calls (`"hello {}".format(name)`) are converted in place. The `%` operator is handled by Refactron's own printf-grammar converter, which covers the full conversion grammar — `%s`, `%r`, `%d`, `%x` / `%o` / `%e` / `%g`, width and precision specifiers, and literal `%%` — not just plain `%s`. `str.format()` calls go through LibCST's `ConvertFormatStringCommand`.

## Detector pattern

The detector at `src/analyze/detectors/python/old-string-format.ts` finds `BinaryOperator(left=String, op='%', right=...)` and `Call(func=Attribute(value=String, attr='format'), ...)` nodes, anchoring each finding on the operator (not the opening quote, which on a multi-line literal can sit many lines above).

## Preconditions

1. The expression is one of the two recognised forms above.
2. The substituted values are safe to inline as f-string fields — an argument containing a backslash, a brace, or a quote matching the literal's is conservatively skipped, as are mapping `%(name)s` specifiers and dynamic `*` width/precision.
3. The string literal is not an existing f-string.

## Before / after

<CodeGroup>
  ```python before.py theme={null}
  def greet(name):
      # Old percent-formatting style.
      return "hello %s" % name


  def format_value(x):
      # Old str.format style.
      return "value is {}".format(x)
  ```

  ```python after.py theme={null}
  def greet(name):
      # Old percent-formatting style.
      return f"hello {name}"


  def format_value(x):
      # Old str.format style.
      return f"value is {x}"
  ```
</CodeGroup>

## Edge cases handled

* Both `%` and `.format()` styles in the same file.
* Returns `null` (no change) for plain strings without format operators.
* Survives complex `.format()` patterns that the underlying `ConvertFormatStringCommand` cannot handle — the file is left unchanged rather than crashed.

## Edge cases NOT handled (skip via precondition)

* Nested `.format()` calls inside dict/list literals (per ADR-006, complex shapes may be skipped by LibCST's converter).
* Format strings with chained attribute access in the substitution slot (e.g. `"{}".format(a.b.c)`).
* Conditional formats (`format` called via `getattr` or aliased).

<Note>
  When the underlying converter cannot safely produce an equivalent f-string, the transform reports preconditions but leaves the source untouched — it never emits a half-converted file.
</Note>
