summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt16
-rw-r--r--LICENSE2
-rw-r--r--Makefile16
-rwxr-xr-xpycodestyle.py55
-rw-r--r--testsuite/E30not.py26
-rw-r--r--testsuite/E71.py9
6 files changed, 97 insertions, 27 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index aa2d181..bf90329 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,7 +1,21 @@
Changelog
=========
-2.6.0a1 (2020-04-02)
+2.6.0 (2020-05-11)
+------------------
+
+Announcements:
+
+* Anthony Sottile (@asottile) joined the team as a core developer. :tada:
+
+Changes:
+
+* E306: fix detection inside ``async def``. PR #929.
+* E301: fix regression disallowing decorated one-liners. PR #927.
+* E714: fix false positive with chained ``is not``. PR #931.
+
+
+2.6.0a1 (2020-04-23)
--------------------
New checks:
diff --git a/LICENSE b/LICENSE
index 30ea057..72d9921 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
Copyright © 2006-2009 Johann C. Rocholl <johann@rocholl.net>
Copyright © 2009-2014 Florent Xicluna <florent.xicluna@gmail.com>
-Copyright © 2014-2018 Ian Lee <IanLee1521@gmail.com>
+Copyright © 2014-2020 Ian Lee <IanLee1521@gmail.com>
Licensed under the terms of the Expat License
diff --git a/Makefile b/Makefile
index d24a595..263ea36 100644
--- a/Makefile
+++ b/Makefile
@@ -1,16 +1,6 @@
release:
umask 022 && chmod -R a+rX . && python setup.py sdist bdist_wheel
+ # twine upload dist/*
-test :
- python pycodestyle.py --testsuite testsuite
-
-selftest :
- python pycodestyle.py --statistics pycodestyle.py
-
-doctest :
- python pycodestyle.py --doctest
-
-unittest :
- python -m testsuite.test_all
-
-alltest : test selftest doctest unittest
+test:
+ tox
diff --git a/pycodestyle.py b/pycodestyle.py
index fe45548..deb4539 100755
--- a/pycodestyle.py
+++ b/pycodestyle.py
@@ -78,7 +78,7 @@ try:
except ImportError:
from ConfigParser import RawConfigParser
-__version__ = '2.6.0a1'
+__version__ = '2.6.0'
DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox'
DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504'
@@ -140,7 +140,8 @@ 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')
-COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+[^][)(}{ ]+\s+(in|is)\s')
+COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?<!is\s)(not)\s+[^][)(}{ ]+\s+'
+ r'(in|is)\s')
COMPARE_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s+type(?:s.\w+Type'
r'|\s*\(\s*([^)]*[^ )])\s*\))')
KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS))
@@ -314,6 +315,41 @@ def maximum_line_length(physical_line, max_line_length, multiline,
########################################################################
+def _is_one_liner(logical_line, indent_level, lines, line_number):
+ if not STARTSWITH_TOP_LEVEL_REGEX.match(logical_line):
+ return False
+
+ line_idx = line_number - 1
+
+ if line_idx < 1:
+ prev_indent = 0
+ else:
+ prev_indent = expand_indent(lines[line_idx - 1])
+
+ if prev_indent > indent_level:
+ return False
+
+ while line_idx < len(lines):
+ line = lines[line_idx].strip()
+ if not line.startswith('@') and STARTSWITH_TOP_LEVEL_REGEX.match(line):
+ break
+ else:
+ line_idx += 1
+ else:
+ return False # invalid syntax: EOF while searching for def/class
+
+ next_idx = line_idx + 1
+ while next_idx < len(lines):
+ if lines[next_idx].strip():
+ break
+ else:
+ next_idx += 1
+ else:
+ return True # line is last in the file
+
+ return expand_indent(lines[next_idx]) <= indent_level
+
+
@register_check
def blank_lines(logical_line, blank_lines, indent_level, line_number,
blank_before, previous_logical,
@@ -360,16 +396,11 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number,
):
yield 0, "E303 too many blank lines (%d)" % blank_lines
elif STARTSWITH_TOP_LEVEL_REGEX.match(logical_line):
- # If this is a one-liner (i.e. this is not a decorator and the
- # next line is not more indented), and the previous line is also
- # not deeper (it would be better to check if the previous line
- # is part of another def/class at the same level), don't require
- # blank lines around this.
- prev_line = lines[line_number - 2] if line_number >= 2 else ''
- next_line = lines[line_number] if line_number < len(lines) else ''
- if (not logical_line.startswith("@") and
- expand_indent(prev_line) <= indent_level and
- expand_indent(next_line) <= indent_level):
+ # allow a group of one-liners
+ if (
+ _is_one_liner(logical_line, indent_level, lines, line_number) and
+ blank_before == 0
+ ):
return
if indent_level:
if not (blank_before == method_lines or
diff --git a/testsuite/E30not.py b/testsuite/E30not.py
index a86b99e..9c33236 100644
--- a/testsuite/E30not.py
+++ b/testsuite/E30not.py
@@ -177,6 +177,32 @@ def foo():
# for no E30x being emitted.
def bar(): pass
def baz(): pass
+#: E704:8:1 E704:10:1
+from typing import overload
+from typing import Union
+
+
+# This emits the (ignored-by-default) E704, but here we're testing
+# for no E30x being emitted.
+@overload
+def f(x: int) -> int: ...
+@overload
+def f(x: str) -> str: ...
+
+
+def f(x: Union[int, str]) -> Union[int, str]:
+ return x
+#: E704:8:5 E704:10:5
+from typing import Protocol
+
+
+class C(Protocol):
+ # This emits the (ignored-by-default) E704, but here we're testing
+ # for no E30x being emitted.
+ @property
+ def f(self) -> int: ...
+ @property
+ def g(self) -> str: ...
#: Okay
#!python
# -*- coding: utf-8 -*-
diff --git a/testsuite/E71.py b/testsuite/E71.py
index 7464da9..abf4e7a 100644
--- a/testsuite/E71.py
+++ b/testsuite/E71.py
@@ -64,6 +64,12 @@ if not X is Y:
#: E714
if not X.B is Y:
pass
+#: E714
+if not X is Y is not Z:
+ pass
+#: E714
+if not X is not Y:
+ pass
#
#: Okay
@@ -79,6 +85,9 @@ if not (X in Y):
if x is not y:
pass
+if X is not Y is not Z:
+ pass
+
if TrueElement.get_element(True) == TrueElement.get_element(False):
pass