Skip to content

Commit c722166

Browse files
authored
B907: fix crash and test failures on py312 (#399)
1 parent f820ba8 commit c722166

File tree

4 files changed

+90
-76
lines changed

4 files changed

+90
-76
lines changed

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,12 @@ MIT
329329
Change Log
330330
----------
331331

332+
Unreleased
333+
~~~~~~~~~~
334+
335+
* Fix a crash and several test failures on Python 3.12, all relating to the B907
336+
check.
337+
332338
23.6.5
333339
~~~~~~
334340

bugbear.py

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,36 +1245,34 @@ def myunparse(node: ast.AST) -> str: # pragma: no cover
12451245
current_mark = None
12461246
variable = None
12471247
for value in node.values:
1248-
# check for quote mark after pre-marked variable
1249-
if (
1250-
current_mark is not None
1251-
and variable is not None
1252-
and isinstance(value, ast.Constant)
1253-
and isinstance(value.value, str)
1254-
and value.value[0] == current_mark
1255-
):
1256-
self.errors.append(
1257-
B907(
1258-
variable.lineno,
1259-
variable.col_offset,
1260-
vars=(myunparse(variable.value),),
1261-
)
1262-
)
1263-
current_mark = variable = None
1264-
# don't continue with length>1, so we can detect a new pre-mark
1265-
# in the same string as a post-mark, e.g. `"{foo}" "{bar}"`
1266-
if len(value.value) == 1:
1248+
if isinstance(value, ast.Constant) and isinstance(value.value, str):
1249+
if not value.value:
12671250
continue
12681251

1269-
# detect pre-mark
1270-
if (
1271-
isinstance(value, ast.Constant)
1272-
and isinstance(value.value, str)
1273-
and value.value[-1] in quote_marks
1274-
):
1275-
current_mark = value.value[-1]
1276-
variable = None
1277-
continue
1252+
# check for quote mark after pre-marked variable
1253+
if (
1254+
current_mark is not None
1255+
and variable is not None
1256+
and value.value[0] == current_mark
1257+
):
1258+
self.errors.append(
1259+
B907(
1260+
variable.lineno,
1261+
variable.col_offset,
1262+
vars=(myunparse(variable.value),),
1263+
)
1264+
)
1265+
current_mark = variable = None
1266+
# don't continue with length>1, so we can detect a new pre-mark
1267+
# in the same string as a post-mark, e.g. `"{foo}" "{bar}"`
1268+
if len(value.value) == 1:
1269+
continue
1270+
1271+
# detect pre-mark
1272+
if value.value[-1] in quote_marks:
1273+
current_mark = value.value[-1]
1274+
variable = None
1275+
continue
12781276

12791277
# detect variable, if there's a pre-mark
12801278
if (

tests/b907.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ def foo():
1717
f'a "{foo()}" b'
1818

1919
# fmt: off
20-
k = (f'"' # error emitted on this line since all values are assigned the same lineno
20+
k = (f'"' # Error emitted here on <py312 (all values assigned the same lineno)
2121
f'{var}'
2222
f'"'
2323
f'"')
2424

25-
k = (f'"' # error emitted on this line
25+
k = (f'"' # error emitted on this line on <py312
2626
f'{var}'
2727
'"'
2828
f'"')

tests/test_bugbear.py

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
from argparse import Namespace
99
from pathlib import Path
1010

11-
from hypothesis import HealthCheck, given, settings
12-
from hypothesmith import from_grammar
13-
1411
from bugbear import (
1512
B001,
1613
B002,
@@ -524,43 +521,51 @@ def test_b907(self):
524521
filename = Path(__file__).absolute().parent / "b907.py"
525522
bbc = BugBearChecker(filename=str(filename))
526523
errors = list(bbc.run())
524+
py39 = sys.version_info >= (3, 9)
525+
py312 = sys.version_info >= (3, 12)
526+
527+
def on_py312(number):
528+
"""F-string nodes have column numbers set to 0 on <py312"""
529+
return number if py312 else 0
530+
527531
expected = self.errors(
528-
B907(8, 0, vars=("var",)),
529-
B907(9, 0, vars=("var",)),
530-
B907(10, 0, vars=("var",)),
531-
B907(12, 0, vars=("var",)),
532-
B907(13, 0, vars=("var",)),
533-
B907(14, 0, vars=("var",)),
534-
B907(16, 0, vars=("'hello'",)),
535-
B907(17, 0, vars=("foo()",)),
536-
B907(20, 5, vars=("var",)),
537-
B907(25, 5, vars=("var",)),
538-
B907(31, 0, vars=("var",)),
539-
B907(32, 0, vars=("var",)),
540-
B907(33, 0, vars=("var",)),
541-
B907(33, 0, vars=("var2",)),
542-
B907(34, 0, vars=("var",)),
543-
B907(34, 0, vars=("var2",)),
544-
B907(35, 0, vars=("var",)),
545-
B907(35, 0, vars=("var2",)),
546-
B907(38, 0, vars=("var2",)),
547-
B907(41, 0, vars=("var",)),
548-
B907(42, 0, vars=("var.__str__",)),
549-
B907(43, 0, vars=("var.__str__.__repr__",)),
550-
B907(44, 0, vars=("3 + 5" if sys.version_info >= (3, 9) else "BinOp",)),
551-
B907(45, 0, vars=("foo()",)),
552-
B907(46, 0, vars=("None",)),
553-
B907(47, 0, vars=("..." if sys.version_info >= (3, 9) else "Ellipsis",)),
554-
B907(48, 0, vars=("True",)),
555-
B907(51, 0, vars=("var",)),
556-
B907(52, 0, vars=("var",)),
557-
B907(53, 0, vars=("var",)),
558-
B907(54, 0, vars=("var",)),
559-
B907(57, 0, vars=("var",)),
560-
B907(60, 0, vars=("var",)),
561-
B907(64, 0, vars=("var",)),
562-
B907(66, 0, vars=("var",)),
563-
B907(68, 0, vars=("var",)),
532+
B907(8, on_py312(9), vars=("var",)),
533+
B907(9, on_py312(3), vars=("var",)),
534+
B907(10, on_py312(9), vars=("var",)),
535+
B907(12, on_py312(9), vars=("var",)),
536+
B907(13, on_py312(3), vars=("var",)),
537+
B907(14, on_py312(9), vars=("var",)),
538+
B907(16, on_py312(5), vars=("'hello'",)),
539+
B907(17, on_py312(5), vars=("foo()",)),
540+
# Multiline f-strings have lineno changes as well as colno changes on py312+
541+
B907(21 if py312 else 20, 7 if py312 else 5, vars=("var",)),
542+
B907(26 if py312 else 25, 7 if py312 else 5, vars=("var",)),
543+
B907(31, on_py312(12), vars=("var",)),
544+
B907(32, on_py312(3), vars=("var",)),
545+
B907(33, on_py312(3), vars=("var",)),
546+
B907(33, on_py312(29), vars=("var2",)),
547+
B907(34, on_py312(3), vars=("var",)),
548+
B907(34, on_py312(15), vars=("var2",)),
549+
B907(35, on_py312(3), vars=("var",)),
550+
B907(35, on_py312(10), vars=("var2",)),
551+
B907(38, on_py312(13), vars=("var2",)),
552+
B907(41, on_py312(3), vars=("var",)),
553+
B907(42, on_py312(3), vars=("var.__str__",)),
554+
B907(43, on_py312(3), vars=("var.__str__.__repr__",)),
555+
B907(44, on_py312(3), vars=("3 + 5" if py39 else "BinOp",)),
556+
B907(45, on_py312(3), vars=("foo()",)),
557+
B907(46, on_py312(3), vars=("None",)),
558+
B907(47, on_py312(3), vars=("..." if py39 else "Ellipsis",)),
559+
B907(48, on_py312(3), vars=("True",)),
560+
B907(51, on_py312(3), vars=("var",)),
561+
B907(52, on_py312(3), vars=("var",)),
562+
B907(53, on_py312(3), vars=("var",)),
563+
B907(54, on_py312(3), vars=("var",)),
564+
B907(57, on_py312(3), vars=("var",)),
565+
B907(60, on_py312(3), vars=("var",)),
566+
B907(64, on_py312(5), vars=("var",)),
567+
B907(66, on_py312(3), vars=("var",)),
568+
B907(68, on_py312(3), vars=("var",)),
564569
)
565570
self.assertEqual(errors, expected)
566571

@@ -795,13 +800,18 @@ def test_selfclean_test_bugbear(self):
795800

796801

797802
class TestFuzz(unittest.TestCase):
798-
@settings(suppress_health_check=[HealthCheck.too_slow])
799-
@given(from_grammar().map(ast.parse))
800-
def test_does_not_crash_on_any_valid_code(self, syntax_tree):
801-
# Given any syntatically-valid source code, flake8-bugbear should
802-
# not crash. This tests doesn't check that we do the *right* thing,
803-
# just that we don't crash on valid-if-poorly-styled code!
804-
BugBearVisitor(filename="<string>", lines=[]).visit(syntax_tree)
803+
# TODO: enable this test on py312 once hypothesmith supports py312
804+
if sys.version_info < (3, 12):
805+
from hypothesis import HealthCheck, given, settings
806+
from hypothesmith import from_grammar
807+
808+
@settings(suppress_health_check=[HealthCheck.too_slow])
809+
@given(from_grammar().map(ast.parse))
810+
def test_does_not_crash_on_any_valid_code(self, syntax_tree):
811+
# Given any syntatically-valid source code, flake8-bugbear should
812+
# not crash. This tests doesn't check that we do the *right* thing,
813+
# just that we don't crash on valid-if-poorly-styled code!
814+
BugBearVisitor(filename="<string>", lines=[]).visit(syntax_tree)
805815

806816
def test_does_not_crash_on_site_code(self):
807817
# Because the generator isn't perfect, we'll also test on all the code

0 commit comments

Comments
 (0)