150
150
DUNDER_REGEX = re .compile (r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = " )
151
151
BLANK_EXCEPT_REGEX = re .compile (r"except\s*:" )
152
152
153
+ if sys .version_info >= (3 , 12 ):
154
+ FSTRING_START = tokenize .FSTRING_START
155
+ FSTRING_MIDDLE = tokenize .FSTRING_MIDDLE
156
+ FSTRING_END = tokenize .FSTRING_END
157
+ else :
158
+ FSTRING_START = FSTRING_MIDDLE = FSTRING_END = - 1
159
+
153
160
_checks = {'physical_line' : {}, 'logical_line' : {}, 'tree' : {}}
154
161
155
162
@@ -494,7 +501,7 @@ def missing_whitespace_after_keyword(logical_line, tokens):
494
501
495
502
496
503
@register_check
497
- def missing_whitespace (logical_line ):
504
+ def missing_whitespace (logical_line , tokens ):
498
505
r"""Each comma, semicolon or colon should be followed by whitespace.
499
506
500
507
Okay: [a, b]
@@ -508,20 +515,31 @@ def missing_whitespace(logical_line):
508
515
E231: foo(bar,baz)
509
516
E231: [{'a':'b'}]
510
517
"""
511
- line = logical_line
512
- for index in range (len (line ) - 1 ):
513
- char = line [index ]
514
- next_char = line [index + 1 ]
515
- if char in ',;:' and next_char not in WHITESPACE :
516
- before = line [:index ]
517
- if char == ':' and before .count ('[' ) > before .count (']' ) and \
518
- before .rfind ('{' ) < before .rfind ('[' ):
519
- continue # Slice syntax, no space required
520
- if char == ',' and next_char in ')]' :
521
- continue # Allow tuple with only one element: (3,)
522
- if char == ':' and next_char == '=' and sys .version_info >= (3 , 8 ):
523
- continue # Allow assignment expression
524
- yield index , "E231 missing whitespace after '%s'" % char
518
+ brace_stack = []
519
+ for tok in tokens :
520
+ if tok .type == tokenize .OP and tok .string in {'[' , '(' , '{' }:
521
+ brace_stack .append (tok .string )
522
+ elif tok .type == FSTRING_START :
523
+ brace_stack .append ('f' )
524
+ elif brace_stack :
525
+ if tok .type == tokenize .OP and tok .string in {']' , ')' , '}' }:
526
+ brace_stack .pop ()
527
+ elif tok .type == FSTRING_END :
528
+ brace_stack .pop ()
529
+
530
+ if tok .type == tokenize .OP and tok .string in {',' , ';' , ':' }:
531
+ next_char = tok .line [tok .end [1 ]:tok .end [1 ] + 1 ]
532
+ if next_char not in WHITESPACE and next_char not in '\r \n ' :
533
+ # slice
534
+ if tok .string == ':' and brace_stack [- 1 :] == ['[' ]:
535
+ continue
536
+ # 3.12+ fstring format specifier
537
+ elif tok .string == ':' and brace_stack [- 2 :] == ['f' , '{' ]:
538
+ continue
539
+ # tuple (and list for some reason?)
540
+ elif tok .string == ',' and next_char in ')]' :
541
+ continue
542
+ yield tok .end , f'E231 missing whitespace after { tok .string !r} '
525
543
526
544
527
545
@register_check
@@ -2010,10 +2028,7 @@ def build_tokens_line(self):
2010
2028
continue
2011
2029
if token_type == tokenize .STRING :
2012
2030
text = mute_string (text )
2013
- elif (
2014
- sys .version_info >= (3 , 12 ) and
2015
- token_type == tokenize .FSTRING_MIDDLE
2016
- ):
2031
+ elif token_type == FSTRING_MIDDLE :
2017
2032
text = 'x' * len (text )
2018
2033
if prev_row :
2019
2034
(start_row , start_col ) = start
0 commit comments