@@ -117,11 +117,13 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation.
117117WS_OPTIONAL_OPERATORS = ARITHMETIC_OP .union (['^' , '&' , '|' , '<<' , '>>' , '%' ])
118118# Warn for -> function annotation operator in py3.5+ (issue 803)
119119FUNCTION_RETURN_ANNOTATION_OP = ['->' ] if sys .version_info >= (3 , 5 ) else []
120+ ASSIGNMENT_EXPRESSION_OP = [':=' ] if sys .version_info >= (3 , 8 ) else []
120121WS_NEEDED_OPERATORS = frozenset ([
121122 '**=' , '*=' , '/=' , '//=' , '+=' , '-=' , '!=' , '<>' , '<' , '>' ,
122123 '%=' , '^=' , '&=' , '|=' , '==' , '<=' , '>=' , '<<=' , '>>=' , '=' ,
123124 'and' , 'in' , 'is' , 'or' ] +
124- FUNCTION_RETURN_ANNOTATION_OP )
125+ FUNCTION_RETURN_ANNOTATION_OP +
126+ ASSIGNMENT_EXPRESSION_OP )
125127WHITESPACE = frozenset (' \t ' )
126128NEWLINE = frozenset ([tokenize .NL , tokenize .NEWLINE ])
127129SKIP_TOKENS = NEWLINE .union ([tokenize .INDENT , tokenize .DEDENT ])
@@ -134,7 +136,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation.
134136RERAISE_COMMA_REGEX = re .compile (r'raise\s+\w+\s*,.*,\s*\w+\s*$' )
135137ERRORCODE_REGEX = re .compile (r'\b[A-Z]\d{3}\b' )
136138DOCSTRING_REGEX = re .compile (r'u?r?["\']' )
137- EXTRANEOUS_WHITESPACE_REGEX = re .compile (r'[\[({] | [\]}),;:] ' )
139+ EXTRANEOUS_WHITESPACE_REGEX = re .compile (r'[\[({] | [\]}),;]| :(?!=) ' )
138140WHITESPACE_AFTER_COMMA_REGEX = re .compile (r'[,;:]\s*(?: |\t)' )
139141COMPARE_SINGLETON_REGEX = re .compile (r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)'
140142 r'\s*(?(1)|(None|False|True))\b' )
@@ -495,13 +497,16 @@ def missing_whitespace(logical_line):
495497 line = logical_line
496498 for index in range (len (line ) - 1 ):
497499 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 :
499502 before = line [:index ]
500503 if char == ':' and before .count ('[' ) > before .count (']' ) and \
501504 before .rfind ('{' ) < before .rfind ('[' ):
502505 continue # Slice syntax, no space required
503- if char == ',' and line [ index + 1 ] == ')' :
506+ if char == ',' and next_char == ')' :
504507 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
505510 yield index , "E231 missing whitespace after '%s'" % char
506511
507512
@@ -1077,7 +1082,8 @@ def is_string_literal(line):
10771082 line = line [1 :]
10781083 return line and (line [0 ] == '"' or line [0 ] == "'" )
10791084
1080- allowed_try_keywords = ('try' , 'except' , 'else' , 'finally' )
1085+ allowed_keywords = (
1086+ 'try' , 'except' , 'else' , 'finally' , 'with' , 'if' , 'elif' )
10811087
10821088 if indent_level : # Allow imports in conditional statement/function
10831089 return
@@ -1091,9 +1097,9 @@ def is_string_literal(line):
10911097 yield 0 , "E402 module level import not at top of file"
10921098 elif re .match (DUNDER_REGEX , line ):
10931099 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
10971103 return
10981104 elif is_string_literal (line ):
10991105 # The first literal is a docstring, allow it. Otherwise, report
@@ -1145,7 +1151,9 @@ def compound_statements(logical_line):
11451151 update_counts (line [prev_found :found ], counts )
11461152 if ((counts ['{' ] <= counts ['}' ] and # {'a': 1} (dict)
11471153 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
11491157 lambda_kw = LAMBDA_REGEX .search (line , 0 , found )
11501158 if lambda_kw :
11511159 before = line [:lambda_kw .start ()].rstrip ()
@@ -1209,13 +1217,16 @@ def explicit_line_join(logical_line, tokens):
12091217 parens -= 1
12101218
12111219
1220+ _SYMBOLIC_OPS = frozenset ("()[]{},:.;@=%~" ) | frozenset (("..." ,))
1221+
1222+
12121223def _is_binary_operator (token_type , text ):
12131224 is_op_token = token_type == tokenize .OP
12141225 is_conjunction = text in ['and' , 'or' ]
12151226 # NOTE(sigmavirus24): Previously the not_a_symbol check was executed
12161227 # conditionally. Since it is now *always* executed, text may be
12171228 # 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
12191230 # The % character is strictly speaking a binary operator, but the
12201231 # common usage seems to be to put it next to the format parameters,
12211232 # after a line break.
0 commit comments