ruff/crates
plredmond a9919707d4
[UP031] When encountering "%s" % var offer unsafe fix (#11019)
Resolves #10187

<details>
<summary>Old PR description; accurate through commit e86dd7d; probably
best to leave this fold closed</summary>

## Description of change

In the case of a printf-style format string with only one %-placeholder
and a variable at right (e.g. `"%s" % var`):

* The new behavior attempts to dereference the variable and then match
on the bound expression to distinguish between a 1-tuple (fix), n-tuple
(bug 🐛), or a non-tuple (fix). Dereferencing is via
`analyze::typing::find_binding_value`.
* If the variable cannot be dereferenced, then the type-analysis routine
is called to distinguish only tuple (no-fix) or non-tuple (fix). Type
analysis is via `analyze::typing::is_tuple`.
* If any of the above fails, the rule still fires, but no fix is
offered.

## Alternatives

* If the reviewers think that singling out the 1-tuple case is too
complicated, I will remove that.
* The ecosystem results show that no new fixes are detected. So I could
probably delete all the variable dereferencing code and code that tries
to generate fixes, tbh.

## Changes to existing behavior

**All the previous rule-firings and fixes are unchanged except for** the
"false negatives" in
`crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_1.py`. Those
previous "false negatives" are now true positives and so I moved them to
`crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py`.

<details>
<summary>Existing false negatives that are now true positives</summary>

```
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py:134:1: UP031 Use format specifiers instead of percent format
    |
133 | # UP031 (no longer false negatives)
134 | 'Hello %s' % bar
    | ^^^^^^^^^^^^^^^^ UP031
135 |
136 | 'Hello %s' % bar.baz
    |
    = help: Replace with format specifiers

crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py:136:1: UP031 Use format specifiers instead of percent format
    |
134 | 'Hello %s' % bar
135 |
136 | 'Hello %s' % bar.baz
    | ^^^^^^^^^^^^^^^^^^^^ UP031
137 |
138 | 'Hello %s' % bar['bop']
    |
    = help: Replace with format specifiers

crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py:138:1: UP031 Use format specifiers instead of percent format
    |
136 | 'Hello %s' % bar.baz
137 |
138 | 'Hello %s' % bar['bop']
    | ^^^^^^^^^^^^^^^^^^^^^^^ UP031
    |
    = help: Replace with format specifiers
```
One of them newly offers a fix.
```
 # UP031 (no longer false negatives)
-'Hello %s' % bar
+'Hello {}'.format(bar)
```
This fix occurs because the new code dereferences `bar` to where it was
defined earlier in the file as a non-tuple:
```python
bar = {"bar": y}
```

---

</details>

## Behavior requiring new tests

Additionally, we now handle a few cases that we didn't previously test.
These cases are when a string has a single %-placeholder and the
righthand operand to the modulo operator is a variable **which can be
dereferenced.** One of those was shown in the previous section (the
"dereference non-tuple" case).

<details>
<summary>New cases handled</summary>

```
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py:126:1: UP031 [*] Use format specifiers instead of percent format
    |
125 | t1 = (x,)
126 | "%s" % t1
    | ^^^^^^^^^ UP031
127 | # UP031: deref t1 to 1-tuple, offer fix
    |
    = help: Replace with format specifiers

crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py:130:1: UP031 Use format specifiers instead of percent format
    |
129 | t2 = (x,y)
130 | "%s" % t2
    | ^^^^^^^^^ UP031
131 | # UP031: deref t2 to n-tuple, this is a bug
    |
    = help: Replace with format specifiers
```
One of these offers a fix.
```
 t1 = (x,)
-"%s" % t1
+"{}".format(t1[0])
 # UP031: deref t1 to 1-tuple, offer fix
```
The other doesn't offer a fix because it's a bug.

---

</details>

---

</details>


## Changes to existing behavior

In the case of a string with a single %-placeholder and a single
ambiguous righthand argument to the modulo operator, (e.g. `"%s" % var`)
the rule now fires and offers a fix. We explain about this in the "fix
safety" section of the updated documentation.


## Documentation changes

I swapped the order of the "known problems" and the "examples" sections
so that the examples which describe the rule are first, before the
exceptions to the rule are described. I also tweaked the language to be
more explicit, as I had trouble understanding the documentation at
first. The "known problems" section is now "fix safety" but the content
is largely similar.

The diff of the documentation changes looks a little difficult unless
you look at the individual commits.
2024-04-22 08:40:51 -07:00
..
ruff Bump version to 0.4.1 (#11035) 2024-04-19 17:42:02 +05:30
ruff_benchmark Run doctests as part of CI pipeline (#9939) 2024-02-12 10:18:58 +01:00
ruff_cache chore: remove repetitive words (#10952) 2024-04-15 12:57:48 +00:00
ruff_dev Docs: Link inline settings when not part of options section (#10499) 2024-03-21 16:33:58 +00:00
ruff_diagnostics Respect # noqa directives on __all__ openers (#10798) 2024-04-06 14:51:17 +00:00
ruff_formatter chore: remove repetitive words (#10952) 2024-04-15 12:57:48 +00:00
ruff_index Run doctests as part of CI pipeline (#9939) 2024-02-12 10:18:58 +01:00
ruff_linter [UP031] When encountering "%s" % var offer unsafe fix (#11019) 2024-04-22 08:40:51 -07:00
ruff_macros Fix comment copy/paste typo in newtype_index (#10892) 2024-04-11 18:43:52 -06:00
ruff_notebook Remove unused dependencies (#10475) 2024-03-19 17:33:47 +01:00
ruff_python_ast Reduce size of Stmt from 144 to 120 bytes (#11051) 2024-04-19 17:02:17 -06:00
ruff_python_codegen Track casing of r-string prefixes in the tokenizer and AST (#10314) 2024-03-18 17:18:04 +00:00
ruff_python_formatter Reduce size of Stmt from 144 to 120 bytes (#11051) 2024-04-19 17:02:17 -06:00
ruff_python_index Fix Indexer fails to identify continuation preceded by newline #10351 (#10354) 2024-03-12 00:35:41 -04:00
ruff_python_literal Remove unused dependencies (#10475) 2024-03-19 17:33:47 +01:00
ruff_python_parser Reduce size of Stmt from 144 to 120 bytes (#11051) 2024-04-19 17:02:17 -06:00
ruff_python_resolver chore(deps): update rust crate insta to v1.38.0 (#10701) 2024-04-01 15:44:30 +00:00
ruff_python_semantic Replace LALRPOP parser with hand-written parser (#10036) 2024-04-18 17:57:39 +05:30
ruff_python_stdlib Improve handling of builtin symbols in linter rules (#10919) 2024-04-16 11:37:31 +01:00
ruff_python_trivia Remove unused dependencies (#10475) 2024-03-19 17:33:47 +01:00
ruff_server ruff server: fix Neovim setup guide command (#11021) 2024-04-19 08:24:09 +05:30
ruff_shrinking Bump version to 0.4.1 (#11035) 2024-04-19 17:42:02 +05:30
ruff_source_file Remove unused dependencies (#10475) 2024-03-19 17:33:47 +01:00
ruff_text_size Range formatting: Fix invalid syntax after parenthesizing expression (#9751) 2024-02-02 17:56:25 +01:00
ruff_wasm Run doctests as part of CI pipeline (#9939) 2024-02-12 10:18:58 +01:00
ruff_workspace Improve documentation around custom isort sections (#11050) 2024-04-19 22:26:55 +00:00