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

ERA001: autofix removes valid comments that do not contain code or removes not everything #4845

Closed
saippuakauppias opened this issue Jun 3, 2023 · 16 comments · Fixed by #7769

Comments

@saippuakauppias
Copy link

Example:

# Example of headers:
# X-token: <token_id>
# X-scope: no-scope
# End of comment about headers


# {
#    "some-key": {
#         '1': 1,
#         '2': 2',
#     }
# }


# some = 1
# awesome = 2
#
# foobar = 3
# barfoo = 4


# example code from IPython:
# In [20]: parse.parse_qsl('&l=55.123;123.1&count=3', keep_blank_values=1)
# Out[20]: [('l', '55.123'), ('123.1', ''), ('count', '3')]
# End of comment


# (пример, пример2) = (example1, example2)


# if not success:  # TODO: comment
#    errors.append('Ошибка: error')


# comment
# 1. [{пример первый}]
# 2. [[пример]]
# 3. [] - пусто


# Пример: {"description": "weight: "}


# comment about calculation
# 100 / 2 = 50
# end of comment


# comment:
# - tuple: ()
# - list: []
# - set: set()
# - dict: {}


# example of output:
# 'OK: message sent. Message-ID: 123456'


# MySQL: enum('positive', 'negative')


# query: lock + user.id

Ruff output:

$ ruff --fix --select ERA001 ruff_errors/ERA001.py
Found 24 errors (24 fixed, 0 remaining).

Contents of the file after fix:

# Example of headers:
# X-token: <token_id>
# End of comment about headers


#    "some-key": {


#


# example code from IPython:
# End of comment




# if not success:  # TODO: comment


# comment
# 1. [{пример первый}]
# 3. [] - пусто




# comment about calculation
# end of comment


# comment:


# example of output:



@charliermarsh
Copy link
Member

I don't think we're in a position to improve this much, the logic itself is derived from eradicate, and I'd bet eradicate has the same or very similar behavior. Perhaps we should remove the autofix from this rule though. It seems correct to flag that there's some commented-out code here, but rely on users to remove it.

Alternatively, we could only autofix if all lines in a comment block are identified as code.

@dhruvmanila
Copy link
Member

We could make this as a Suggested (Applicability::Suggested) fix.

    /// The fix may be what the user intended, but it is uncertain.
    /// The fix should result in valid code if it is applied.
    /// The fix can be applied with user opt-in.

@zanieb
Copy link
Member

zanieb commented Jun 5, 2023

I like

Alternatively, we could only autofix if all lines in a comment block are identified as code.

Perhaps we could use Suggested in that case and Manual otherwise?

@karolinepauls
Copy link

Another example: it thinks the comment is code even though params isn't a valid keyword

# params: abc
query = """
SELECT :abc;
"""

@dhruvmanila
Copy link
Member

Another example: it thinks the comment is code even though params isn't a valid keyword

# params: abc
query = """
SELECT :abc;
"""

I believe this is considered as a valid code (variable: annotation):

params: int

@bodograumann
Copy link
Contributor

I am absolutely in favor of not automatically removing these comments in any situation. Yes, commented-out code is usually a mistake, but you really don't want to loose actual comments accidentally.

Our example:

# foo and bar (JIRA-9999)

@zanieb
Copy link
Member

zanieb commented Aug 24, 2023

@bodograumann what is the benefit of enabling the ERA001 rule for you then?

@bodograumann
Copy link
Contributor

It will still warn me and usually correctly so, but I still need to decide myself whether a line needs to be removed.

@zanieb
Copy link
Member

zanieb commented Aug 24, 2023

We've updated the applicability of the fix here to "Manual" which will never automatically apply once we merge #5119 — I don't think there's more to do here this issue will be resolved once applicability is respected by the CLI per #4185

@jaap3
Copy link
Contributor

jaap3 commented Aug 30, 2023

Was about to open a new issue, but I think this fits here:

While testing out ruff, I ran into some false positives for ERA001 that confused me a bit.

Some examples:

# Keep this list sorted alphabetically
# (it is used in example)
# Check if the ip address in either:
# local (loopback) or internal (private)
##
# Client (consumers)
##

...

##
# Server (protocol)
##

All trigger ERA001 even though they are not really code.

I had also hoped that code fragments that are part of a larger comment wouldn't trigger ERA001, yet all of these do:

# XXX: Cannot use:
# self.example(foo, 'bar')
# because of issue #1337
# XXX: Example category has a duplicate 'code' and is omitted
# EXAMPLE_CATEGORY = '1.0', 'Example Category'
# Note: Admin site instance should be passed when calling
# ExampleAdminView.as_view(site=site)
# main categories themselves are not part of the return value of
# get_subcategories()

This is all using ruff 0.0.286.

I've already marked ERA001 as unfixable in my configuration file. I'm hoping it is possible to tweak the heuristics of ERA001 a bit so it takes the surrounding context into account or something?

For now I'll just be working around this by rewording/reformatting these comments until they don't trigger anymore.

@zanieb
Copy link
Member

zanieb commented Aug 30, 2023

Yeah the ERA001 heuristics are very prone to false positives, unfortunately. I think there's room for improvement, but it's not entirely clear how.

def foo():
    x = 1
    # add to x
    # x += 1
   return x

I think this should raise as an ERA001 violation but if we ignore comments that are part of a larger non-code context then we won't raise here. Maybe that's worth the false negative to reduce the false positives.

@jaap3
Copy link
Contributor

jaap3 commented Aug 31, 2023

This seems like a bug:

A single parenthesis triggers ERA001:

echo "# )" | ruff --select ERA001,RUF100 --stdin-filename test.py
test.py:1:1: ERA001 [*] Found commented-out code

Adding a noqa to that line triggers RUF100:

echo "# )  # noqa: ERA001" | ruff --select ERA001,RUF100 --stdin-filename test.py
test.py:1:6: RUF100 [*] Unused `noqa` directive (unused: `ERA001`)

@charliermarsh
Copy link
Member

@jaap3 - Funnily enough that specific case was reported recently and is fixed on main.

zanieb added a commit that referenced this issue Oct 6, 2023
Rebase of #5119 authored by
@evanrittenhouse with additional refinements.

## Changes

- Adds `--unsafe-fixes` / `--no-unsafe-fixes` flags to `ruff check`
- Violations with unsafe fixes are not shown as fixable unless opted-in
- Fix applicability is respected now
    - `Applicability::Never` fixes are no longer applied
    - `Applicability::Sometimes` fixes require opt-in
    - `Applicability::Always` fixes are unchanged
- Hints for availability of `--unsafe-fixes` added to `ruff check`
output

## Examples

Check hints at hidden unsafe fixes
```
❯ ruff check example.py --no-cache --select F601,W292
example.py:1:14: F601 Dictionary key literal `'a'` repeated
example.py:2:15: W292 [*] No newline at end of file
Found 2 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
```

We could add an indicator for which violations have hidden fixes in the
future.

Check treats unsafe fixes as applicable with opt-in
```
❯ ruff check example.py --no-cache --select F601,W292 --unsafe-fixes
example.py:1:14: F601 [*] Dictionary key literal `'a'` repeated
example.py:2:15: W292 [*] No newline at end of file
Found 2 errors.
[*] 2 fixable with the --fix option.
```

Also can be enabled in the config file

```
❯ cat ruff.toml
unsafe-fixes = true
```

And opted-out per invocation

```
❯ ruff check example.py --no-cache --select F601,W292 --no-unsafe-fixes
example.py:1:14: F601 Dictionary key literal `'a'` repeated
example.py:2:15: W292 [*] No newline at end of file
Found 2 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
```

Diff does not include unsafe fixes
```
❯ ruff check example.py --no-cache --select F601,W292 --diff
--- example.py
+++ example.py
@@ -1,2 +1,2 @@
 x = {'a': 1, 'a': 1}
-print(('foo'))
+print(('foo'))
\ No newline at end of file

Would fix 1 error.
```

Unless there is opt-in
```
❯ ruff check example.py --no-cache --select F601,W292 --diff --unsafe-fixes
--- example.py
+++ example.py
@@ -1,2 +1,2 @@
-x = {'a': 1}
-print(('foo'))
+x = {'a': 1, 'a': 1}
+print(('foo'))
\ No newline at end of file

Would fix 2 errors.
```

#7790 will improve the diff
messages following this pull request

Similarly, `--fix` and `--fix-only` require the `--unsafe-fixes` flag to
apply unsafe fixes.

## Related

Replaces #5119
Closes #4185
Closes #7214
Closes #4845
Closes #3863
Addresses #6835
Addresses #7019
Needs follow-up #6962
Needs follow-up #4845
Needs follow-up #7436
Needs follow-up #7025
Needs follow-up #6434
Follow-up #7790 
Follow-up #7792

---------

Co-authored-by: Evan Rittenhouse <evanrittenhouse@gmail.com>
konstin pushed a commit that referenced this issue Oct 11, 2023
Rebase of #5119 authored by
@evanrittenhouse with additional refinements.

## Changes

- Adds `--unsafe-fixes` / `--no-unsafe-fixes` flags to `ruff check`
- Violations with unsafe fixes are not shown as fixable unless opted-in
- Fix applicability is respected now
    - `Applicability::Never` fixes are no longer applied
    - `Applicability::Sometimes` fixes require opt-in
    - `Applicability::Always` fixes are unchanged
- Hints for availability of `--unsafe-fixes` added to `ruff check`
output

## Examples

Check hints at hidden unsafe fixes
```
❯ ruff check example.py --no-cache --select F601,W292
example.py:1:14: F601 Dictionary key literal `'a'` repeated
example.py:2:15: W292 [*] No newline at end of file
Found 2 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
```

We could add an indicator for which violations have hidden fixes in the
future.

Check treats unsafe fixes as applicable with opt-in
```
❯ ruff check example.py --no-cache --select F601,W292 --unsafe-fixes
example.py:1:14: F601 [*] Dictionary key literal `'a'` repeated
example.py:2:15: W292 [*] No newline at end of file
Found 2 errors.
[*] 2 fixable with the --fix option.
```

Also can be enabled in the config file

```
❯ cat ruff.toml
unsafe-fixes = true
```

And opted-out per invocation

```
❯ ruff check example.py --no-cache --select F601,W292 --no-unsafe-fixes
example.py:1:14: F601 Dictionary key literal `'a'` repeated
example.py:2:15: W292 [*] No newline at end of file
Found 2 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
```

Diff does not include unsafe fixes
```
❯ ruff check example.py --no-cache --select F601,W292 --diff
--- example.py
+++ example.py
@@ -1,2 +1,2 @@
 x = {'a': 1, 'a': 1}
-print(('foo'))
+print(('foo'))
\ No newline at end of file

Would fix 1 error.
```

Unless there is opt-in
```
❯ ruff check example.py --no-cache --select F601,W292 --diff --unsafe-fixes
--- example.py
+++ example.py
@@ -1,2 +1,2 @@
-x = {'a': 1}
-print(('foo'))
+x = {'a': 1, 'a': 1}
+print(('foo'))
\ No newline at end of file

Would fix 2 errors.
```

#7790 will improve the diff
messages following this pull request

Similarly, `--fix` and `--fix-only` require the `--unsafe-fixes` flag to
apply unsafe fixes.

## Related

Replaces #5119
Closes #4185
Closes #7214
Closes #4845
Closes #3863
Addresses #6835
Addresses #7019
Needs follow-up #6962
Needs follow-up #4845
Needs follow-up #7436
Needs follow-up #7025
Needs follow-up #6434
Follow-up #7790 
Follow-up #7792

---------

Co-authored-by: Evan Rittenhouse <evanrittenhouse@gmail.com>
@tusharsadhwani
Copy link
Contributor

Is there scope for a PR for better algorithms, and a better set of false positive rules here?

It will deviate from the eradicate plugin, would that be okay or should it be a different rule entirely then?

@charliermarsh
Copy link
Member

Yeah definitely fine to fold in improvements even if it deviates from eradicate.

@tusharsadhwani
Copy link
Contributor

Self-documenting here for later.

Examples of false negatives:

# if foo:

# elif bar:

# if (
#  x
# ):

# else:

Examples of false positives:

# continue
# pragma: cover
# (something)
# O(1)
# this is O(1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants