From 6fddf7399d70627c46d1cc82bb3c02da2d708ec4 Mon Sep 17 00:00:00 2001
From: Anthony Sottile <asottile@umich.edu>
Date: Sat, 17 Jun 2023 09:58:23 -0400
Subject: [PATCH] 3.12: format specs are not an error
---
pycodestyle.py | 53 +++++++++++++++++++++++++++---------------
testsuite/E12.py | 2 +-
testsuite/python312.py | 2 ++
3 files changed, 37 insertions(+), 20 deletions(-)
diff --git a/pycodestyle.py b/pycodestyle.py
index 2ee3dac..f9c236a 100755
--- a/pycodestyle.py
+++ b/pycodestyle.py
@@ -150,6 +150,13 @@ STARTSWITH_INDENT_STATEMENT_REGEX = re.compile(
DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ")
BLANK_EXCEPT_REGEX = re.compile(r"except\s*:")
+if sys.version_info >= (3, 12):
+ FSTRING_START = tokenize.FSTRING_START
+ FSTRING_MIDDLE = tokenize.FSTRING_MIDDLE
+ FSTRING_END = tokenize.FSTRING_END
+else:
+ FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1
+
_checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}}
@@ -494,7 +501,7 @@ def missing_whitespace_after_keyword(logical_line, tokens):
@register_check
-def missing_whitespace(logical_line):
+def missing_whitespace(logical_line, tokens):
r"""Each comma, semicolon or colon should be followed by whitespace.
Okay: [a, b]
@@ -508,20 +515,31 @@ def missing_whitespace(logical_line):
E231: foo(bar,baz)
E231: [{'a':'b'}]
"""
- line = logical_line
- for index in range(len(line) - 1):
- char = line[index]
- next_char = line[index + 1]
- if char in ',;:' and next_char not in WHITESPACE:
- before = line[:index]
- if char == ':' and before.count('[') > before.count(']') and \
- before.rfind('{') < before.rfind('['):
- continue # Slice syntax, no space required
- if char == ',' and next_char in ')]':
- continue # Allow tuple with only one element: (3,)
- if char == ':' and next_char == '=' and sys.version_info >= (3, 8):
- continue # Allow assignment expression
- yield index, "E231 missing whitespace after '%s'" % char
+ brace_stack = []
+ for tok in tokens:
+ if tok.type == tokenize.OP and tok.string in {'[', '(', '{'}:
+ brace_stack.append(tok.string)
+ elif tok.type == FSTRING_START:
+ brace_stack.append('f')
+ elif brace_stack:
+ if tok.type == tokenize.OP and tok.string in {']', ')', '}'}:
+ brace_stack.pop()
+ elif tok.type == FSTRING_END:
+ brace_stack.pop()
+
+ if tok.type == tokenize.OP and tok.string in {',', ';', ':'}:
+ next_char = tok.line[tok.end[1]:tok.end[1] + 1]
+ if next_char not in WHITESPACE and next_char not in '\r\n':
+ # slice
+ if tok.string == ':' and brace_stack[-1:] == ['[']:
+ continue
+ # 3.12+ fstring format specifier
+ elif tok.string == ':' and brace_stack[-2:] == ['f', '{']:
+ continue
+ # tuple (and list for some reason?)
+ elif tok.string == ',' and next_char in ')]':
+ continue
+ yield tok.end, f'E231 missing whitespace after {tok.string!r}'
@register_check
@@ -2010,10 +2028,7 @@ class Checker:
continue
if token_type == tokenize.STRING:
text = mute_string(text)
- elif (
- sys.version_info >= (3, 12) and
- token_type == tokenize.FSTRING_MIDDLE
- ):
+ elif token_type == FSTRING_MIDDLE:
text = 'x' * len(text)
if prev_row:
(start_row, start_col) = start
diff --git a/testsuite/E12.py b/testsuite/E12.py
index 968382c..241bad0 100644
--- a/testsuite/E12.py
+++ b/testsuite/E12.py
@@ -366,7 +366,7 @@ print dedent(
# more stuff
)
)
-#: E701:1:8 E122:2:1 E203:4:8 E128:5:1
+#: E701:1:8 E231:1:9 E122:2:1 E203:4:8 E128:5:1
if True:\
print(True)
diff --git a/testsuite/python312.py b/testsuite/python312.py
index d2e73bc..aabb6a4 100644
--- a/testsuite/python312.py
+++ b/testsuite/python312.py
@@ -27,3 +27,5 @@ f'{
} {f'{other} {thing}'}'
#: E201:1:4 E202:1:17
f'{ an_error_now }'
+#: Okay
+f'{x:02x}'
--
2.41.0