Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new rule - fstring syntax in non-fstring #8151

Closed
DetachHead opened this issue Oct 24, 2023 · 13 comments · Fixed by #9728
Closed

new rule - fstring syntax in non-fstring #8151

DetachHead opened this issue Oct 24, 2023 · 13 comments · Fixed by #9728
Assignees
Labels
accepted Ready for implementation help wanted Contributions especially welcome rule Implementing or modifying a lint rule

Comments

@DetachHead
Copy link

DetachHead commented Oct 24, 2023

asdf = 1
"value is {asdf}" # the user probably meant to make this an f string

to avoid false positives, it should only complain when the expression inside the {} refers to an existing variable. that way, most usages of the format function won't trigger false positives:

x = "{foo}" # no error because `foo` variable does not exist
x.format(foo="bar")
@dhruvmanila
Copy link
Member

Interesting! Although not sure how to reliably detect this. One way could be to pass the string as a f-string to the parser and check if there are any ExprFormattedValue node in it.

@charliermarsh charliermarsh added rule Implementing or modifying a lint rule needs-decision Awaiting a decision from a maintainer labels Oct 30, 2023
@charliermarsh
Copy link
Member

I'm worried about false positives on this one.

@DetachHead
Copy link
Author

i feel like they would be rare as long as it only complains when the expression inside the {} refers to an existing variable

@MichaReiser
Copy link
Member

MichaReiser commented Oct 31, 2023

I would find this useful. Coming from Rust and JavaScript, I wrote "{value}" too many times to then notice my debug print statements are completely useless. So I went back and added the f in front of my strings.

@dhruvmanila
Copy link
Member

I'm worried about false positives on this one.

Can you give an example where there would be false positives? Maybe we could only highlight when the node within {...} is an identifier and skip everything else.

@zanieb
Copy link
Member

zanieb commented Oct 31, 2023

My common use false positive

x = "{foo}"
x.format(foo="bar")

@DetachHead
Copy link
Author

@zanieb in that case it shouldn't trigger a false positive if the rule works how i suggested in #8151 (comment), because there's no variable in scope called foo.

imo the format method should only be used instead of f-strings in cases where the variables being formatted are not in scope. i've updated the OP to clarify this

@zanieb
Copy link
Member

zanieb commented Oct 31, 2023

I agree requiring the variables to be in scope is a reasonable heuristic for avoiding false positives. I'm not sure I can come up with a common one that meets that requirement.

I found https://github.com/PrefectHQ/prefect/blob/f654a1b23eb5662afb5b8bfd08c9c86668e817ee/tests/agent/test_agent_queue_selection.py#L36 but that probably ought to be an f-string anyway. We can probably hint something else when format is being called instead of using a f-string.

@dhruvmanila
Copy link
Member

We could just skip if the said string is part of a large .format call or % operator.

I think then we could support this as:

  1. Skip if this string contains { / }
  2. Skip if it's part of .format call or % operator
  3. Parse the string with the f prefix
  4. Visit each ExprFormattedValue node. If there are none, return
  5. Create a diagnostic message only if all of the expression inside the formatted node is
    a. An identifier
    b. It's defined in the scope

What do you think? @zanieb

@peterjc
Copy link

peterjc commented Nov 1, 2023

I'd welcome this. Cross reference peterjc/flake8-sfs#5

See also the opposite request, f-strings without any variables in them - briefly implemented as flake8-pie's PIE782 code but withdrawn due to false positives. Cross reference peterjc/flake8-sfs#3 and sbdchd/flake8-pie#24

@dhruvmanila
Copy link
Member

See also the opposite request, f-strings without any variables in them

We do support this - https://docs.astral.sh/ruff/rules/f-string-missing-placeholders/ :)

@hauntsaninja
Copy link
Contributor

pyanalyze supports this check: https://github.com/quora/pyanalyze/blob/647c64e8c9b4eaa81a5309d8e76e72bafe61d687/pyanalyze/name_check_visitor.py#L3096

Here are the heuristics it uses to avoid false positives:

  • At least one name is used in the expression and all names used inside the expression are bound
  • The string literal is not immediately .format()-ed
  • It's not standalone expression (i.e. doesn't look like a docstring)
  • It's not in a call that passes kwargs corresponding to all names (to avoid false positives with interpolation functions that work like .format)

@charliermarsh charliermarsh added accepted Ready for implementation and removed needs-decision Awaiting a decision from a maintainer labels Dec 31, 2023
@charliermarsh
Copy link
Member

I'm supportive of adding this with the above heuristics.

@zanieb zanieb added the help wanted Contributions especially welcome label Jan 24, 2024
@snowsignal snowsignal self-assigned this Jan 29, 2024
snowsignal added a commit that referenced this issue Feb 3, 2024
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Fixes #8151

This PR implements a new rule, `RUF027`.

## What it does
Checks for strings that contain f-string syntax but are not f-strings.

### Why is this bad?
An f-string missing an `f` at the beginning won't format anything, and
instead treat the interpolation syntax as literal.

### Example

```python
name = "Sarah"
dayofweek = "Tuesday"
msg = "Hello {name}! It is {dayofweek} today!"
```

It should instead be:
```python
name = "Sarah"
dayofweek = "Tuesday"
msg = f"Hello {name}! It is {dayofweek} today!"
```

## Heuristics
Since there are many possible string literals which contain syntax
similar to f-strings yet are not intended to be,
this lint will disqualify any literal that satisfies any of the
following conditions:
1. The string literal is a standalone expression. For example, a
docstring.
2. The literal is part of a function call with keyword arguments that
match at least one variable (for example: `format("Message: {value}",
value = "Hello World")`)
3. The literal (or a parent expression of the literal) has a direct
method call on it (for example: `"{value}".format(...)`)
4. The string has no `{...}` expression sections, or uses invalid
f-string syntax.
5. The string references variables that are not in scope, or it doesn't
capture variables at all.
6. Any format specifiers in the potential f-string are invalid.

## Test Plan

I created a new test file, `RUF027.py`, which is both an example of what
the lint should catch and a way to test edge cases that may trigger
false positives.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted Ready for implementation help wanted Contributions especially welcome rule Implementing or modifying a lint rule
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants