summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Serba <nikitaserba@icloud.com>2020-01-22 20:34:06 +0200
committerGitHub <noreply@github.com>2020-01-22 20:34:06 +0200
commit2c29cc4f4f08906b111e306e6e806b5af628fbbd (patch)
tree5d2d5d421edd7872f5d8f4d4b63542e8cce9186f
parent2f0ca0b42660d7a6ee239f3a372a8783ad20c37a (diff)
parentd219c684f117be77927d33146e76a5364161e518 (diff)
downloadpep8-2c29cc4f4f08906b111e306e6e806b5af628fbbd.tar.gz
Merge branch 'master' into master
-rw-r--r--.travis.yml2
-rwxr-xr-xpycodestyle.py31
-rw-r--r--testsuite/E40.py15
-rw-r--r--testsuite/python3.py7
-rw-r--r--testsuite/python38.py12
-rw-r--r--tox.ini2
6 files changed, 58 insertions, 11 deletions
diff --git a/.travis.yml b/.travis.yml
index ce324be..696c1c0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,6 +17,8 @@ matrix:
env: TOXENV=py36
- python: 3.7
env: TOXENV=py37
+ - python: 3.8
+ env: TOXENV=py38
- python: pypy2.7-6.0
env: TOXENV=pypy
- python: pypy3.5-6.0
diff --git a/pycodestyle.py b/pycodestyle.py
index 7b5b4f8..b7e0b2f 100755
--- a/pycodestyle.py
+++ b/pycodestyle.py
@@ -117,11 +117,13 @@ ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-'])
WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%'])
# Warn for -> function annotation operator in py3.5+ (issue 803)
FUNCTION_RETURN_ANNOTATION_OP = ['->'] if sys.version_info >= (3, 5) else []
+ASSIGNMENT_EXPRESSION_OP = [':='] if sys.version_info >= (3, 8) else []
WS_NEEDED_OPERATORS = frozenset([
'**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>',
'%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=',
'and', 'in', 'is', 'or'] +
- FUNCTION_RETURN_ANNOTATION_OP)
+ FUNCTION_RETURN_ANNOTATION_OP +
+ ASSIGNMENT_EXPRESSION_OP)
WHITESPACE = frozenset(' \t')
NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE])
SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT])
@@ -134,7 +136,7 @@ RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,')
RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,.*,\s*\w+\s*$')
ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b')
DOCSTRING_REGEX = re.compile(r'u?r?["\']')
-EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[\[({] | [\]}),;:]')
+EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[\[({] | [\]}),;]| :(?!=)')
WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)')
COMPARE_SINGLETON_REGEX = re.compile(r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)'
r'\s*(?(1)|(None|False|True))\b')
@@ -495,13 +497,16 @@ def missing_whitespace(logical_line):
line = logical_line
for index in range(len(line) - 1):
char = line[index]
- if char in ',;:' and line[index + 1] not in WHITESPACE:
+ 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 line[index + 1] == ')':
+ if char == ',' and next_char == ')':
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
@@ -1077,7 +1082,8 @@ def module_imports_on_top_of_file(
line = line[1:]
return line and (line[0] == '"' or line[0] == "'")
- allowed_try_keywords = ('try', 'except', 'else', 'finally')
+ allowed_keywords = (
+ 'try', 'except', 'else', 'finally', 'with', 'if', 'elif')
if indent_level: # Allow imports in conditional statement/function
return
@@ -1091,9 +1097,9 @@ def module_imports_on_top_of_file(
yield 0, "E402 module level import not at top of file"
elif re.match(DUNDER_REGEX, line):
return
- elif any(line.startswith(kw) for kw in allowed_try_keywords):
- # Allow try, except, else, finally keywords intermixed with
- # imports in order to support conditional importing
+ elif any(line.startswith(kw) for kw in allowed_keywords):
+ # Allow certain keywords intermixed with imports in order to
+ # support conditional or filtered importing
return
elif is_string_literal(line):
# The first literal is a docstring, allow it. Otherwise, report
@@ -1145,7 +1151,9 @@ def compound_statements(logical_line):
update_counts(line[prev_found:found], counts)
if ((counts['{'] <= counts['}'] and # {'a': 1} (dict)
counts['['] <= counts[']'] and # [1:2] (slice)
- counts['('] <= counts[')'])): # (annotation)
+ counts['('] <= counts[')']) and # (annotation)
+ not (sys.version_info >= (3, 8) and
+ line[found + 1] == '=')): # assignment expression
lambda_kw = LAMBDA_REGEX.search(line, 0, found)
if lambda_kw:
before = line[:lambda_kw.start()].rstrip()
@@ -1209,13 +1217,16 @@ def explicit_line_join(logical_line, tokens):
parens -= 1
+_SYMBOLIC_OPS = frozenset("()[]{},:.;@=%~") | frozenset(("...",))
+
+
def _is_binary_operator(token_type, text):
is_op_token = token_type == tokenize.OP
is_conjunction = text in ['and', 'or']
# NOTE(sigmavirus24): Previously the not_a_symbol check was executed
# conditionally. Since it is now *always* executed, text may be
# None. In that case we get a TypeError for `text not in str`.
- not_a_symbol = text and text not in "()[]{},:.;@=%~"
+ not_a_symbol = text and text not in _SYMBOLIC_OPS
# The % character is strictly speaking a binary operator, but the
# common usage seems to be to put it next to the format parameters,
# after a line break.
diff --git a/testsuite/E40.py b/testsuite/E40.py
index f9a18fc..6c71fa2 100644
--- a/testsuite/E40.py
+++ b/testsuite/E40.py
@@ -34,6 +34,21 @@ finally:
print('made attempt to import foo')
import bar
+#: Okay
+with warnings.catch_warnings():
+ warnings.filterwarnings("ignore", DeprecationWarning)
+ import foo
+
+import bar
+#: Okay
+if False:
+ import foo
+elif not True:
+ import bar
+else:
+ import mwahaha
+
+import bar
#: E402
VERSION = '1.2.3'
diff --git a/testsuite/python3.py b/testsuite/python3.py
index be7c58f..709695a 100644
--- a/testsuite/python3.py
+++ b/testsuite/python3.py
@@ -37,3 +37,10 @@ class Class:
def m(self):
xs: List[int] = []
+
+
+# Used to trigger W504
+def f(
+ x: str = ...
+):
+ ...
diff --git a/testsuite/python38.py b/testsuite/python38.py
index adf217d..5f62a3f 100644
--- a/testsuite/python38.py
+++ b/testsuite/python38.py
@@ -1,3 +1,15 @@
#: Okay
def f(a, /, b):
pass
+#: Okay
+if x := 1:
+ print(x)
+if m and (token := m.group(1)):
+ pass
+stuff = [[y := f(x), x / y] for x in range(5)]
+#: E225:1:5
+if x:= 1:
+ pass
+#: E225:1:18
+if False or (x :=1):
+ pass
diff --git a/tox.ini b/tox.ini
index 742888c..fc42fc1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
-envlist = py27, py34, py35, py36, py37, pypy, pypy3, jython
+envlist = py27, py34, py35, py36, py37, py38, pypy, pypy3, jython
skipsdist = True
skip_missing_interpreters = True