diff options
-rw-r--r-- | CHANGES.txt | 16 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | Makefile | 16 | ||||
-rwxr-xr-x | pycodestyle.py | 55 | ||||
-rw-r--r-- | testsuite/E30not.py | 26 | ||||
-rw-r--r-- | testsuite/E71.py | 9 |
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: @@ -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 @@ -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 |