nebius/SWE-bench-extra huggingface dataset:
Repository: asottile/pyupgrade

Issue:
multi-line exception with duplicate BaseException and comments gives invalid code
## before
```python          
try:
    ...
except (
    BaseException,
    BaseException # b
):
    ...
```
## after
```python               
try:
    ...
except (BaseException, BaseException # b):
    ...
```
## info
* flags: none (and any I tried seemed to make no difference)
* version: 3.15.0
* real-life code (although it's an intentionally "bad" test file): https://github.com/Zac-HD/flake8-trio/blob/main/tests/eval_files/trio103.py#L195

I expect you never intend to output invalid code, and similar errors could probably pop up in normal usage. I don't have much of an opinion on whether pyupgrade does nothing, or actually manages to remove the `BaseException` and move the comment somewhere else.

Comments:

Patch:
diff --git a/pyupgrade/_token_helpers.py b/pyupgrade/_token_helpers.py
index 5936ab7..e932c04 100644
--- a/pyupgrade/_token_helpers.py
+++ b/pyupgrade/_token_helpers.py
@@ -369,6 +369,14 @@ def arg_str(tokens: list[Token], start: int, end: int) -> str:
     return tokens_to_src(tokens[start:end]).strip()
 
 
+def _arg_str_no_comment(tokens: list[Token], start: int, end: int) -> str:
+    arg_tokens = [
+        token for token in tokens[start:end]
+        if token.name != 'COMMENT'
+    ]
+    return tokens_to_src(arg_tokens).strip()
+
+
 def _arg_contains_newline(tokens: list[Token], start: int, end: int) -> bool:
     while tokens[start].name in {'NL', 'NEWLINE', UNIMPORTANT_WS}:
         start += 1
@@ -473,7 +481,7 @@ def replace_argument(
 def constant_fold_tuple(i: int, tokens: list[Token]) -> None:
     start = find_op(tokens, i, '(')
     func_args, end = parse_call_args(tokens, start)
-    arg_strs = [arg_str(tokens, *arg) for arg in func_args]
+    arg_strs = [_arg_str_no_comment(tokens, *arg) for arg in func_args]
 
     unique_args = tuple(dict.fromkeys(arg_strs))
 


Test patch:
diff --git a/tests/features/exceptions_test.py b/tests/features/exceptions_test.py
index e855bf2..335e1d4 100644
--- a/tests/features/exceptions_test.py
+++ b/tests/features/exceptions_test.py
@@ -225,6 +225,30 @@ def test_fix_exceptions_version_specific_noop(s, version):
 
             id='leave unrelated error names alone',
         ),
+        pytest.param(
+            'try: ...\n'
+            'except (\n'
+            '    BaseException,\n'
+            '    BaseException # b\n'
+            '): ...\n',
+
+            'try: ...\n'
+            'except BaseException: ...\n',
+
+            id='dedupe with comment.  see #932',
+        ),
+        pytest.param(
+            'try: ...\n'
+            'except (\n'
+            '    A, A,\n'
+            '    B  # b\n'
+            '): ...\n',
+
+            'try: ...\n'
+            'except (A, B): ...\n',
+
+            id='dedupe other exception, one contains comment.  see #932',
+        ),
     ),
 )
 def test_fix_exceptions(s, expected):
Repository: asottile/pyupgrade

Issue:
"".join(shlex.quote(...) ...) to shlex.join(...) is too eager
Consider the following:

```
import shlex

trash_bin = "garbage".join(shlex.quote(a) for a in ["some", "quotable strings"])
```

If I run `pyupgrade --py310-plus` on this, I get `trash_bin = shlex.join(["some", "quotable strings"])`. But this is not equivalent. This is a very contrived case, but there are real cases I could envision,  like `", "`, where one might want to keep the `str.join` 



pyupgrade==3.15.1

Comments:

Patch:
diff --git a/pyupgrade/_plugins/shlex_join.py b/pyupgrade/_plugins/shlex_join.py
index 1406756..1718ff9 100644
--- a/pyupgrade/_plugins/shlex_join.py
+++ b/pyupgrade/_plugins/shlex_join.py
@@ -39,7 +39,7 @@ def visit_Call(
     if (
             isinstance(node.func, ast.Attribute) and
             isinstance(node.func.value, ast.Constant) and
-            isinstance(node.func.value.value, str) and
+            node.func.value.value == ' ' and
             node.func.attr == 'join' and
             not node.keywords and
             len(node.args) == 1 and


Test patch:
diff --git a/tests/features/shlex_join_test.py b/tests/features/shlex_join_test.py
index fc6abe5..e3835f6 100644
--- a/tests/features/shlex_join_test.py
+++ b/tests/features/shlex_join_test.py
@@ -15,6 +15,12 @@ from pyupgrade._main import _fix_plugins
             (3, 8),
             id='quote from-imported',
         ),
+        pytest.param(
+            'import shlex\n'
+            '"wat".join(shlex.quote(arg) for arg in cmd)\n',
+            (3, 8),
+            id='not joined with space',
+        ),
         pytest.param(
             'import shlex\n'
             '" ".join(shlex.quote(arg) for arg in cmd)\n',
Repository: asottile/setup-cfg-fmt

Issue:
normalize requirements names to be dashed-names

Comments:

Patch:
diff --git a/setup_cfg_fmt.py b/setup_cfg_fmt.py
index be8eb0d..a2b637e 100644
--- a/setup_cfg_fmt.py
+++ b/setup_cfg_fmt.py
@@ -259,7 +259,8 @@ def _normalize_lib(lib: str) -> str:
 def _req_base(lib: str) -> str:
     basem = re.match(BASE_NAME_REGEX, lib)
     assert basem
-    return basem.group(0)
+    # pip replaces _ with - in package names
+    return basem.group(0).replace('_', '-')
 
 
 def _py_classifiers(


Test patch:
diff --git a/tests/setup_cfg_fmt_test.py b/tests/setup_cfg_fmt_test.py
index 05a7b8d..d7e7f36 100644
--- a/tests/setup_cfg_fmt_test.py
+++ b/tests/setup_cfg_fmt_test.py
@@ -72,6 +72,7 @@ def test_noop(tmpdir):
             'name = pkg\n'
             '[options]\n'
             '{} =\n'
+            '    req_req\n'
             '    req03\n'
             '    req05 <= 2,!=1\n'
             '    req06 ;python_version==2.7\n'
@@ -97,6 +98,7 @@ def test_noop(tmpdir):
             '[options]\n'
             '{} =\n'
             '    req>=2\n'
+            '    req-req\n'
             '    req01\n'
             '    req02\n'
             '    req03\n'
Repository: asottile/setup-cfg-fmt

Issue:
configparser is downcasing keys in unrelated sections
for instance this doesn't roundtrip:

```ini
[tool:pytest]
DJANGO_SETTINGS_MODULE = test.test
```

Comments:

Patch:
diff --git a/setup_cfg_fmt.py b/setup_cfg_fmt.py
index 34e3824..afd3a45 100644
--- a/setup_cfg_fmt.py
+++ b/setup_cfg_fmt.py
@@ -75,6 +75,12 @@ TOX_TO_CLASSIFIERS = {
 }
 
 
+class NoTransformConfigParser(configparser.RawConfigParser):
+    def optionxform(self, s: str) -> str:
+        """disable default lower-casing"""
+        return s
+
+
 def _adjacent_filename(setup_cfg: str, filename: str) -> str:
     return os.path.join(os.path.dirname(setup_cfg), filename)
 
@@ -153,7 +159,7 @@ def _parse_python_requires(
 def _tox_envlist(setup_cfg: str) -> Generator[str, None, None]:
     tox_ini = _adjacent_filename(setup_cfg, 'tox.ini')
     if os.path.exists(tox_ini):
-        cfg = configparser.ConfigParser()
+        cfg = NoTransformConfigParser()
         cfg.read(tox_ini)
 
         envlist = cfg.get('tox', 'envlist', fallback='')
@@ -166,7 +172,7 @@ def _tox_envlist(setup_cfg: str) -> Generator[str, None, None]:
 def _python_requires(
         setup_cfg: str, *, min_py3_version: tuple[int, int],
 ) -> str | None:
-    cfg = configparser.ConfigParser()
+    cfg = NoTransformConfigParser()
     cfg.read(setup_cfg)
     current_value = cfg.get('options', 'python_requires', fallback='')
     classifiers = cfg.get('metadata', 'classifiers', fallback='')
@@ -207,7 +213,7 @@ def _python_requires(
 
 
 def _requires(
-        cfg: configparser.ConfigParser, which: str, section: str = 'options',
+        cfg: NoTransformConfigParser, which: str, section: str = 'options',
 ) -> list[str]:
     raw = cfg.get(section, which, fallback='')
 
@@ -356,7 +362,7 @@ def format_file(
     with open(filename) as f:
         contents = f.read()
 
-    cfg = configparser.ConfigParser()
+    cfg = NoTransformConfigParser()
     cfg.read_string(contents)
     _clean_sections(cfg)
 
@@ -467,7 +473,7 @@ def format_file(
     return new_contents != contents
 
 
-def _clean_sections(cfg: configparser.ConfigParser) -> None:
+def _clean_sections(cfg: NoTransformConfigParser) -> None:
     """Removes any empty options and sections."""
     for section in cfg.sections():
         new_options = {k: v for k, v in cfg[section].items() if v}


Test patch:
diff --git a/tests/setup_cfg_fmt_test.py b/tests/setup_cfg_fmt_test.py
index 0bc8310..b3372a7 100644
--- a/tests/setup_cfg_fmt_test.py
+++ b/tests/setup_cfg_fmt_test.py
@@ -951,6 +951,22 @@ def test_imp_classifiers_pypy_only(tmpdir):
     )
 
 
+def test_leaves_casing_of_unrelated_settings(tmpdir):
+    setup_cfg = tmpdir.join('setup.cfg')
+    setup_cfg.write(
+        '[metadata]\n'
+        'name = pkg\n'
+        'version = 1.0\n'
+        'classifiers =\n'
+        '    Programming Language :: Python :: Implementation :: CPython\n'
+        '\n'
+        '[tool:pytest]\n'
+        'DJANGO_SETTINGS_MODULE = test.test\n',
+    )
+
+    assert not main((str(setup_cfg),))
+
+
 def test_natural_sort():
     classifiers = [
         'Programming Language :: Python :: 3',
Repository: asottile/setup-cfg-fmt

Issue:
author and maintainer should be kept beside each other
found via https://github.com/pfmoore/editables/pull/2

Comments:

Patch:
diff --git a/setup_cfg_fmt.py b/setup_cfg_fmt.py
index f7c674e..1dee7b6 100644
--- a/setup_cfg_fmt.py
+++ b/setup_cfg_fmt.py
@@ -20,8 +20,10 @@ KEYS_ORDER: Tuple[Tuple[str, Tuple[str, ...]], ...] = (
         'metadata', (
             'name', 'version', 'description',
             'long_description', 'long_description_content_type',
-            'url', 'author', 'author_email', 'license', 'license_file',
-            'license_files', 'platforms', 'classifiers',
+            'url',
+            'author', 'author_email', 'maintainer', 'maintainer_email',
+            'license', 'license_file', 'license_files',
+            'platforms', 'classifiers',
         ),
     ),
     (


Test patch:
diff --git a/tests/setup_cfg_fmt_test.py b/tests/setup_cfg_fmt_test.py
index 50062d9..36762f3 100644
--- a/tests/setup_cfg_fmt_test.py
+++ b/tests/setup_cfg_fmt_test.py
@@ -168,6 +168,25 @@ def test_rewrite_requires(which, input_tpl, expected_tpl, tmpdir):
 
             id='sorts classifiers',
         ),
+        pytest.param(
+            '[metadata]\n'
+            'maintainer_email = jane@example.com\n'
+            'maintainer = jane\n'
+            'license = foo\n'
+            'name = pkg\n'
+            'author_email = john@example.com\n'
+            'author = john\n',
+
+            '[metadata]\n'
+            'name = pkg\n'
+            'author = john\n'
+            'author_email = john@example.com\n'
+            'maintainer = jane\n'
+            'maintainer_email = jane@example.com\n'
+            'license = foo\n',
+
+            id='orders authors and maintainers',
+        ),
     ),
 )
 def test_rewrite(input_s, expected, tmpdir):
Repository: asottile/setup-cfg-fmt

Issue:
normalize dashed-keys to underscored_keys
setuptools apparently allows both, but canonically spells them with underscores

found in https://github.com/pfmoore/editables/pull/2

Comments:

Patch:
diff --git a/setup_cfg_fmt.py b/setup_cfg_fmt.py
index b1967b2..d541a70 100644
--- a/setup_cfg_fmt.py
+++ b/setup_cfg_fmt.py
@@ -398,11 +398,11 @@ def format_file(
         if section not in cfg:
             continue
 
-        new_section = {
-            k: cfg[section].pop(k) for k in key_order if k in cfg[section]
-        }
+        entries = {k.replace('-', '_'): v for k, v in cfg[section].items()}
+
+        new_section = {k: entries.pop(k) for k in key_order if k in entries}
         # sort any remaining keys
-        new_section.update(sorted(cfg[section].items()))
+        new_section.update(sorted(entries.items()))
 
         sections[section] = new_section
         cfg.pop(section)


Test patch:
diff --git a/tests/setup_cfg_fmt_test.py b/tests/setup_cfg_fmt_test.py
index 6558886..677291b 100644
--- a/tests/setup_cfg_fmt_test.py
+++ b/tests/setup_cfg_fmt_test.py
@@ -192,6 +192,19 @@ def test_rewrite_requires(which, input_tpl, expected_tpl, tmpdir):
 
             id='orders authors and maintainers',
         ),
+        pytest.param(
+            '[metadata]\n'
+            'name = pkg\n'
+            'author-email = john@example.com\n'
+            'maintainer-email = jane@example.com\n',
+
+            '[metadata]\n'
+            'name = pkg\n'
+            'author_email = john@example.com\n'
+            'maintainer_email = jane@example.com\n',
+
+            id='normalize dashes to underscores in keys',
+        ),
     ),
 )
 def test_rewrite(input_s, expected, tmpdir):
Repository: asottile/setup-cfg-fmt

Issue:
Wrongly formating "compatible release" clause
```diff
diff --git a/setup.cfg b/setup.cfg
index efc3a0f..662e52c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -43,15 +43,15 @@ console_scripts =
 [options.extras_require]
 doc =
     furo
-    sphinx~=3.0
-    sphinx-autodoc-typehints~=1.10
-    sphinxcontrib-autoprogram~=0.1.0
+    sphinx-autodoc-typehints~~=1.10
+    sphinxcontrib-autoprogram~~=0.1.0
+    sphinx~~=3.0
 test =
-    filelock~=3.0
+    filelock~~=3.0
     pytest>=5,<=6
-    pytest-cov~=2.0
+    pytest-cov~~=2.0
     pytest-mock>=2,<=3
-    pytest-xdist~=1.34
+    pytest-xdist~~=1.34
 typing =
     mypy==0.790
     typing-extensions>=3.7.4.3
```

https://www.python.org/dev/peps/pep-0440/#compatible-release

Comments:

Patch:
diff --git a/setup_cfg_fmt.py b/setup_cfg_fmt.py
index 44d3cb3..d9f84c8 100644
--- a/setup_cfg_fmt.py
+++ b/setup_cfg_fmt.py
@@ -236,7 +236,7 @@ def _normalize_req(req: str) -> str:
     return f'{normalized};{envs}'
 
 
-BASE_NAME_REGEX = re.compile(r'[^!=><\s@]+')
+BASE_NAME_REGEX = re.compile(r'[^!=><\s@~]+')
 REQ_REGEX = re.compile(r'(===|==|!=|~=|>=?|<=?|@)\s*([^,]+)')
 
 


Test patch:
diff --git a/tests/setup_cfg_fmt_test.py b/tests/setup_cfg_fmt_test.py
index 097f081..ceba336 100644
--- a/tests/setup_cfg_fmt_test.py
+++ b/tests/setup_cfg_fmt_test.py
@@ -231,6 +231,7 @@ def test_rewrite(input_s, expected, tmpdir):
         pytest.param('req05 <= 2,!=1', 'req05!=1,<=2', id='<= cond at end'),
         pytest.param('req13 !=2, >= 7', 'req13!=2,>=7', id='>= cond at end'),
         pytest.param('req14 <=2, >= 1', 'req14>=1,<=2', id='b/w conds sorted'),
+        pytest.param('req15~=2', 'req15~=2', id='compatible release'),
     ),
 )
 def test_normalize_lib(lib, expected):
Repository: asottile/setup-cfg-fmt

Issue:
classifiers should be version sorted
notably they currently sort like this which seems bad:

```
    Programming Language :: Python :: 3
    Programming Language :: Python :: 3 :: Only
    Programming Language :: Python :: 3.10
    Programming Language :: Python :: 3.7
    Programming Language :: Python :: 3.8
    Programming Language :: Python :: 3.9
```

Comments:

Patch:
diff --git a/setup_cfg_fmt.py b/setup_cfg_fmt.py
index d9f84c8..1389aba 100644
--- a/setup_cfg_fmt.py
+++ b/setup_cfg_fmt.py
@@ -339,6 +339,16 @@ def _imp_classifiers(setup_cfg: str) -> str:
     return '\n'.join(sorted(classifiers))
 
 
+def _natural_sort(items: Sequence[str]) -> List[str]:
+    return sorted(
+        set(items),
+        key=lambda s: [
+            int(part) if part.isdigit() else part.lower()
+            for part in re.split(r'(\d+)', s)
+        ],
+    )
+
+
 def format_file(
         filename: str, *,
         min_py3_version: Tuple[int, int],
@@ -418,7 +428,7 @@ def format_file(
 
     # sort the classifiers if present
     if 'classifiers' in cfg['metadata']:
-        classifiers = sorted(set(cfg['metadata']['classifiers'].split('\n')))
+        classifiers = _natural_sort(cfg['metadata']['classifiers'].split('\n'))
         classifiers = _trim_py_classifiers(
             classifiers, requires, max_py_version=max_py_version,
         )


Test patch:
diff --git a/tests/setup_cfg_fmt_test.py b/tests/setup_cfg_fmt_test.py
index ceba336..705b6e3 100644
--- a/tests/setup_cfg_fmt_test.py
+++ b/tests/setup_cfg_fmt_test.py
@@ -4,6 +4,7 @@ import os
 import pytest
 
 from setup_cfg_fmt import _case_insensitive_glob
+from setup_cfg_fmt import _natural_sort
 from setup_cfg_fmt import _normalize_lib
 from setup_cfg_fmt import _ver_type
 from setup_cfg_fmt import main
@@ -921,3 +922,25 @@ def test_imp_classifiers_pypy_only(tmpdir):
         '[options]\n'
         'python_requires = >=3.9\n'
     )
+
+
+def test_natural_sort():
+    classifiers = [
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.10',
+        'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3 :: Only',
+    ]
+
+    sorted_classifiers = _natural_sort(classifiers)
+
+    assert sorted_classifiers == [
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3 :: Only',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
+    ]
Repository: asottile/yesqa

Issue:
Skip # noqa check on typing
I have a usecase that pep8 `max-line-length` is set to `79` and at the same time I have a type annotation that exceeds `79` characters. 

For example:
```
        # type: () -> Optional[List[Tuple[str, str, None, None, None, None, None]]]
```

I tried to search for multiline annotation but got nothing. I wonder whether we can remove end line noqa check on python typing comment?

Comments:

Patch:
diff --git a/yesqa.py b/yesqa.py
index 5beb9c7..919b7f5 100644
--- a/yesqa.py
+++ b/yesqa.py
@@ -39,13 +39,11 @@ def _remove_comment(tokens, i):
 def _remove_comments(tokens):
     tokens = list(tokens)
     for i, token in tokenize_rt.reversed_enumerate(tokens):
-        if (
-                token.name == 'COMMENT' and (
-                    NOQA_RE.search(token.src) or
-                    NOQA_FILE_RE.search(token.src)
-                )
-        ):
-            _remove_comment(tokens, i)
+        if token.name == 'COMMENT':
+            if NOQA_RE.search(token.src):
+                _rewrite_noqa_comment(tokens, i, collections.defaultdict(set))
+            elif NOQA_FILE_RE.search(token.src):
+                _remove_comment(tokens, i)
     return tokens
 
 


Test patch:
diff --git a/tests/yesqa_test.py b/tests/yesqa_test.py
index 25fd493..5563293 100644
--- a/tests/yesqa_test.py
+++ b/tests/yesqa_test.py
@@ -43,6 +43,11 @@ def test_non_utf8_bytes(tmpdir, capsys):
         '"""\n' + 'a' * 40 + ' ' + 'b' * 60 + '\n""" # noqa\n',
         # don't rewrite syntax errors
         'import x  # noqa\nx() = 5\n',
+
+        'A' * 65 + ' = int\n\n\n'
+        'def f():\n'
+        '    # type: () -> ' + 'A' * 65 + '  # noqa\n'
+        '    pass\n',
     ),
 )
 def test_ok(assert_rewrite, src):
@@ -76,6 +81,10 @@ def test_ok(assert_rewrite, src):
             '# hello world\n'
             'os\n',
         ),
+        (
+            '# a  # noqa: E501\n',
+            '# a\n',
+        ),
         # file comments
         ('# flake8: noqa\nx = 1\n', 'x = 1\n'),
         ('x = 1  # flake8: noqa\n', 'x = 1\n'),
Repository: asottile/yesqa

Issue:
False positive: removes `noqa` on multi-line string
```python
"""
aaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
"""  # noqa
```

```console
$ flake8 test.py
$ yesqa test.py
Rewriting test.py
$ flake8 test.py
test.py:2:80: E501 line too long (82 > 79 characters)
```

Comments:

Patch:
diff --git a/yesqa.py b/yesqa.py
index 008dc57..40cf153 100644
--- a/yesqa.py
+++ b/yesqa.py
@@ -43,14 +43,28 @@ def _remove_comments(tokens):
 
 
 def _rewrite_noqa_comment(tokens, i, flake8_results):
+    # find logical lines that this noqa comment may affect
+    lines = set()
+    j = i
+    while j >= 0 and tokens[j].name not in {'NL', 'NEWLINE'}:
+        t = tokens[j]
+        if t.line is not None:
+            lines.update(range(t.line, t.line + t.src.count('\n') + 1))
+        j -= 1
+
+    lints = set()
+    for line in lines:
+        lints.update(flake8_results[line])
+
     token = tokens[i]
     match = NOQA_RE.match(token.src)
+
     # exclude all lints on the line but no lints
-    if token.line not in flake8_results:
+    if not lints:
         _remove_comment(tokens, i)
     elif match.group().lower() != '# noqa':
         codes = set(SEP_RE.split(match.group(1)[2:]))
-        expected_codes = codes & flake8_results[token.line]
+        expected_codes = codes & lints
         if expected_codes != codes:
             comment = '# noqa: {}'.format(','.join(sorted(expected_codes)))
             tokens[i] = token._replace(src=NOQA_RE.sub(comment, token.src))


Test patch:
diff --git a/tests/yesqa_test.py b/tests/yesqa_test.py
index e0225c8..9043a86 100644
--- a/tests/yesqa_test.py
+++ b/tests/yesqa_test.py
@@ -32,9 +32,11 @@ def test_non_utf8_bytes(tmpdir, capsys):
     (
         '',  # noop
         '# hello\n',  # comment at beginning of file
-        'import os  # noqa\n',  # still needed
-        'import os  # NOQA\n',  # still needed
-        'import os  # noqa: F401\n',  # still needed
+        # still needed
+        'import os  # noqa\n',
+        'import os  # NOQA\n',
+        'import os  # noqa: F401\n',
+        '"""\n' + 'a' * 40 + ' ' + 'b' * 60 + '\n""" # noqa\n',
     ),
 )
 def test_ok(assert_rewrite, src):
Repository: asottile/yesqa

Issue:
Can convert limited noqa's to generic noqa's
If you have a line of code that has a flake8 error, but different from the one listed in a noqa comment, the tool will convert `# noqa: X000` to `# noqa:` which then masks the other error.

Example:
```
if x==2:  # noqa: E101
    pass
```
becomes:
```
if x==2:  # noqa: 
    pass
```
which now suppresses E225


Comments:

Patch:
diff --git a/yesqa.py b/yesqa.py
index 478cbe3..431a8f8 100644
--- a/yesqa.py
+++ b/yesqa.py
@@ -72,18 +72,25 @@ def _rewrite_noqa_comment(
     match = NOQA_RE.search(token.src)
     assert match is not None
 
+    def _remove_noqa() -> None:
+        assert match is not None
+        if match.group() == token.src:
+            _remove_comment(tokens, i)
+        else:
+            src = NOQA_RE.sub('', token.src).strip()
+            if not src.startswith('#'):
+                src = f'# {src}'
+            tokens[i] = token._replace(src=src)
+
     # exclude all lints on the line but no lints
-    if not lints and match.group() == token.src:
-        _remove_comment(tokens, i)
-    elif not lints:
-        src = NOQA_RE.sub('', token.src).strip()
-        if not src.startswith('#'):
-            src = f'# {src}'
-        tokens[i] = token._replace(src=src)
+    if not lints:
+        _remove_noqa()
     elif match.group().lower() != '# noqa':
         codes = set(SEP_RE.split(match.group(1)[1:]))
         expected_codes = codes & lints
-        if expected_codes != codes:
+        if not expected_codes:
+            _remove_noqa()
+        elif expected_codes != codes:
             comment = f'# noqa: {", ".join(sorted(expected_codes))}'
             tokens[i] = token._replace(src=NOQA_RE.sub(comment, token.src))
 


Test patch:
diff --git a/tests/yesqa_test.py b/tests/yesqa_test.py
index 84a604e..6e90ada 100644
--- a/tests/yesqa_test.py
+++ b/tests/yesqa_test.py
@@ -81,6 +81,13 @@ def test_ok(assert_rewrite, src):
             '# a  # noqa: E501\n',
             '# a\n',
         ),
+        pytest.param(
+            'if x==1:  # noqa: F401\n'
+            '    pass\n',
+            'if x==1:\n'
+            '    pass\n',
+            id='wrong noqa',
+        ),
         # file comments
         ('# flake8: noqa\nx = 1\n', 'x = 1\n'),
         ('x = 1  # flake8: noqa\n', 'x = 1\n'),