ruff/crates/ruff_linter/resources/test/fixtures/pyupgrade
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
..
UP001.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP003.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP004.py Improve handling of builtin symbols in linter rules (#10919) 2024-04-16 11:37:31 +01:00
UP005.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP006_0.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP006_1.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP006_2.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP006_3.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP007.py Fix unnecessary parentheses in UP007 fix (#8610) 2023-11-10 19:15:09 -05:00
UP008.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_0.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_1.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_2.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_3.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_4.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_5.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_6.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_7.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_8.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_9.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP009_10.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP010.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP011.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP012.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP013.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP014.py Avoid triggering `NamedTuple` rewrite with starred annotation (#8434) 2023-11-02 03:30:52 +00:00
UP015.py Don't suggest replacing `builtin.open()` with `Path.open()` if the latter doesn't support all options (#7637) 2023-09-26 09:07:35 +00:00
UP017.py Avoid failures due to non-deterministic binding ordering (#10478) 2024-03-19 18:01:33 +00:00
UP018.py [`pyupgrade`] Detect literals with unary operators (`UP018`) (#10060) 2024-02-20 18:21:06 +00:00
UP019.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP020.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP021.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP022.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP023.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP024_0.py Treat `os.error` as an `OSError` alias (#7582) 2023-09-21 21:18:14 +00:00
UP024_1.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP024_2.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP024_3.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP024_4.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP025.py Remove special pre-visit for module docstrings (#9261) 2023-12-23 10:03:12 -05:00
UP026.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP027.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP028_0.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP028_1.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP029.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP030_0.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP030_1.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP031_0.py [UP031] When encountering `"%s" % var` offer unsafe fix (#11019) 2024-04-22 08:40:51 -07:00
UP031_1.py [UP031] When encountering `"%s" % var` offer unsafe fix (#11019) 2024-04-22 08:40:51 -07:00
UP032_0.py Avoid repeating function calls in f-string conversions (#10265) 2024-03-06 23:33:19 -05:00
UP032_1.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP032_2.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP032_3.py Avoid converting f-strings within Django `gettext` calls (#7898) 2023-10-10 16:31:09 +00:00
UP033_0.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP033_1.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP034.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP035.py Add some additional Python 3.12 typing members to `deprecated-import` (#9445) 2024-01-09 12:52:01 -05:00
UP036_0.py Skip LibCST parsing for standard dedent adjustments (#9769) 2024-02-02 18:13:46 +00:00
UP036_1.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP036_2.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP036_3.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP036_4.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP036_5.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP037.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP038.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP039.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00
UP040.py Respect duplicates when rewriting type aliases (#9905) 2024-02-09 14:02:41 +00:00
UP040.pyi Update `non-pep695-type-alias` to require `--unsafe-fixes` outside of stub files (#7836) 2023-10-06 14:56:40 -05:00
UP041.py Add pyupgrade `UP041` to replace `TimeoutError` aliases (#8476) 2023-11-03 17:24:47 +00:00
UP042.py [`pyupgrade`] Replace `str, Enum` with `StrEnum` (`UP042`) (#10713) 2024-04-06 01:56:28 +00:00
future_annotations.py Rename `ruff` crate to `ruff_linter` (#7529) 2023-09-20 08:38:27 +02:00