@@ -117,11 +117,13 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation.
117
117
WS_OPTIONAL_OPERATORS = ARITHMETIC_OP .union (['^' , '&' , '|' , '<<' , '>>' , '%' ])
118
118
# Warn for -> function annotation operator in py3.5+ (issue 803)
119
119
FUNCTION_RETURN_ANNOTATION_OP = ['->' ] if sys .version_info >= (3 , 5 ) else []
120
+ ASSIGNMENT_EXPRESSION_OP = [':=' ] if sys .version_info >= (3 , 8 ) else []
120
121
WS_NEEDED_OPERATORS = frozenset ([
121
122
'**=' , '*=' , '/=' , '//=' , '+=' , '-=' , '!=' , '<>' , '<' , '>' ,
122
123
'%=' , '^=' , '&=' , '|=' , '==' , '<=' , '>=' , '<<=' , '>>=' , '=' ,
123
124
'and' , 'in' , 'is' , 'or' ] +
124
- FUNCTION_RETURN_ANNOTATION_OP )
125
+ FUNCTION_RETURN_ANNOTATION_OP +
126
+ ASSIGNMENT_EXPRESSION_OP )
125
127
WHITESPACE = frozenset (' \t ' )
126
128
NEWLINE = frozenset ([tokenize .NL , tokenize .NEWLINE ])
127
129
SKIP_TOKENS = NEWLINE .union ([tokenize .INDENT , tokenize .DEDENT ])
@@ -134,7 +136,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation.
134
136
RERAISE_COMMA_REGEX = re .compile (r'raise\s+\w+\s*,.*,\s*\w+\s*$' )
135
137
ERRORCODE_REGEX = re .compile (r'\b[A-Z]\d{3}\b' )
136
138
DOCSTRING_REGEX = re .compile (r'u?r?["\']' )
137
- EXTRANEOUS_WHITESPACE_REGEX = re .compile (r'[\[({] | [\]}),;:] ' )
139
+ EXTRANEOUS_WHITESPACE_REGEX = re .compile (r'[\[({] | [\]}),;]| :(?!=) ' )
138
140
WHITESPACE_AFTER_COMMA_REGEX = re .compile (r'[,;:]\s*(?: |\t)' )
139
141
COMPARE_SINGLETON_REGEX = re .compile (r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)'
140
142
r'\s*(?(1)|(None|False|True))\b' )
@@ -495,13 +497,16 @@ def missing_whitespace(logical_line):
495
497
line = logical_line
496
498
for index in range (len (line ) - 1 ):
497
499
char = line [index ]
498
- if char in ',;:' and line [index + 1 ] not in WHITESPACE :
500
+ next_char = line [index + 1 ]
501
+ if char in ',;:' and next_char not in WHITESPACE :
499
502
before = line [:index ]
500
503
if char == ':' and before .count ('[' ) > before .count (']' ) and \
501
504
before .rfind ('{' ) < before .rfind ('[' ):
502
505
continue # Slice syntax, no space required
503
- if char == ',' and line [ index + 1 ] == ')' :
506
+ if char == ',' and next_char == ')' :
504
507
continue # Allow tuple with only one element: (3,)
508
+ if char == ':' and next_char == '=' and sys .version_info >= (3 , 8 ):
509
+ continue # Allow assignment expression
505
510
yield index , "E231 missing whitespace after '%s'" % char
506
511
507
512
@@ -1077,7 +1082,8 @@ def is_string_literal(line):
1077
1082
line = line [1 :]
1078
1083
return line and (line [0 ] == '"' or line [0 ] == "'" )
1079
1084
1080
- allowed_try_keywords = ('try' , 'except' , 'else' , 'finally' )
1085
+ allowed_keywords = (
1086
+ 'try' , 'except' , 'else' , 'finally' , 'with' , 'if' , 'elif' )
1081
1087
1082
1088
if indent_level : # Allow imports in conditional statement/function
1083
1089
return
@@ -1091,9 +1097,9 @@ def is_string_literal(line):
1091
1097
yield 0 , "E402 module level import not at top of file"
1092
1098
elif re .match (DUNDER_REGEX , line ):
1093
1099
return
1094
- elif any (line .startswith (kw ) for kw in allowed_try_keywords ):
1095
- # Allow try, except, else, finally keywords intermixed with
1096
- # imports in order to support conditional importing
1100
+ elif any (line .startswith (kw ) for kw in allowed_keywords ):
1101
+ # Allow certain keywords intermixed with imports in order to
1102
+ # support conditional or filtered importing
1097
1103
return
1098
1104
elif is_string_literal (line ):
1099
1105
# The first literal is a docstring, allow it. Otherwise, report
@@ -1145,7 +1151,9 @@ def compound_statements(logical_line):
1145
1151
update_counts (line [prev_found :found ], counts )
1146
1152
if ((counts ['{' ] <= counts ['}' ] and # {'a': 1} (dict)
1147
1153
counts ['[' ] <= counts [']' ] and # [1:2] (slice)
1148
- counts ['(' ] <= counts [')' ])): # (annotation)
1154
+ counts ['(' ] <= counts [')' ]) and # (annotation)
1155
+ not (sys .version_info >= (3 , 8 ) and
1156
+ line [found + 1 ] == '=' )): # assignment expression
1149
1157
lambda_kw = LAMBDA_REGEX .search (line , 0 , found )
1150
1158
if lambda_kw :
1151
1159
before = line [:lambda_kw .start ()].rstrip ()
@@ -1209,13 +1217,16 @@ def explicit_line_join(logical_line, tokens):
1209
1217
parens -= 1
1210
1218
1211
1219
1220
+ _SYMBOLIC_OPS = frozenset ("()[]{},:.;@=%~" ) | frozenset (("..." ,))
1221
+
1222
+
1212
1223
def _is_binary_operator (token_type , text ):
1213
1224
is_op_token = token_type == tokenize .OP
1214
1225
is_conjunction = text in ['and' , 'or' ]
1215
1226
# NOTE(sigmavirus24): Previously the not_a_symbol check was executed
1216
1227
# conditionally. Since it is now *always* executed, text may be
1217
1228
# None. In that case we get a TypeError for `text not in str`.
1218
- not_a_symbol = text and text not in "()[]{},:.;@=%~"
1229
+ not_a_symbol = text and text not in _SYMBOLIC_OPS
1219
1230
# The % character is strictly speaking a binary operator, but the
1220
1231
# common usage seems to be to put it next to the format parameters,
1221
1232
# after a line break.
0 commit comments