From bbefb19a036e1048dbdd460432dc1f973be63504 Mon Sep 17 00:00:00 2001 From: Darioush Jalalinasab Date: Fri, 8 Aug 2014 14:00:00 -0700 Subject: [PATCH 001/412] Fixing compound_statement not to be quadratic in # of :s --- pep8.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/pep8.py b/pep8.py index bb46c220..79a81edd 100755 --- a/pep8.py +++ b/pep8.py @@ -919,22 +919,21 @@ def compound_statements(logical_line): line = logical_line last_char = len(line) - 1 found = line.find(':') + prev_found = 0 + counts = dict((char, 0) for char in '{}[]()') while -1 < found < last_char: - before = line[:found] - if ((before.count('{') <= before.count('}') and # {'a': 1} (dict) - before.count('[') <= before.count(']') and # [1:2] (slice) - before.count('(') <= before.count(')'))): # (annotation) - lambda_kw = LAMBDA_REGEX.search(before) - if lambda_kw: - before = line[:lambda_kw.start()].rstrip() - if before[-1:] == '=' and isidentifier(before[:-1].strip()): - yield 0, ("E731 do not assign a lambda expression, use a " - "def") + update_counts(line[prev_found:found], counts) + if ((counts['{'] <= counts['}'] and # {'a': 1} (dict) + counts['['] <= counts[']'] and # [1:2] (slice) + counts['('] <= counts[')'])): # (annotation) + if LAMBDA_REGEX.search(line, 0, found): + yield 0, "E731 do not assign a lambda expression, use a def" break - if before.startswith('def '): + if line.startswith('def '): yield 0, "E704 multiple statements on one line (def)" else: yield found, "E701 multiple statements on one line (colon)" + prev_found = found found = line.find(':', found + 1) found = line.find(';') while -1 < found: @@ -1238,6 +1237,14 @@ def filename_match(filename, patterns, default=True): return any(fnmatch(filename, pattern) for pattern in patterns) +def update_counts(s, counts): + r"""Adds one to the counts of each appearence of characters in s, + for characters in counts""" + for char in s: + if char in counts: + counts[char] += 1 + + if COMMENT_WITH_NL: def _is_eol_token(token): return (token[0] in NEWLINE or From 18b12f0eb143b501389e7a65bb2f84ea9bc18067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Czy=C5=BCykowski?= Date: Thu, 19 Feb 2015 15:59:27 +0100 Subject: [PATCH 002/412] Fix problem with treating ~ operator as binary. --- pep8.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index d7907e53..bca7985b 100755 --- a/pep8.py +++ b/pep8.py @@ -1017,13 +1017,14 @@ def break_around_binary_operator(logical_line, tokens): Okay: x = '''\n''' + '' Okay: foo(x,\n -y) Okay: foo(x, # comment\n -y) + Okay: var = (1 &\n ~2) """ def is_binary_operator(token_type, text): # 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. return ((token_type == tokenize.OP or text in ['and', 'or']) and - text not in "()[]{},:.;@=%") + text not in "()[]{},:.;@=%~") line_break = False unary_context = True From 91a7eff7216e02efc4b7e5fd5262c81f7554b8d9 Mon Sep 17 00:00:00 2001 From: Alasdair Nicol Date: Tue, 16 Feb 2016 14:57:04 +0000 Subject: [PATCH 003/412] Changed link from pep257 to pydocstyle now that project has been renamed. --- docs/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.rst b/docs/intro.rst index 97b0a68c..6a579b17 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -47,7 +47,7 @@ the ``pep8`` library: `pep8-naming extension `_ to use this feature. * **docstring conventions**: they are not in the scope of this library; - see the `pep257 project `_. + see the `pydocstyle project `_. * **automatic fixing**: see the section *PEP8 Fixers* in the :ref:`related tools ` page. From 2c872940709a0f9d6156f29d2a2fd5a1c5a642ab Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 16 Feb 2016 19:49:39 -0800 Subject: [PATCH 004/412] Incremented version number marking new work after 1.7 release --- CHANGES.txt | 3 +++ pep8.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 860b6ad3..47f0ace2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,9 @@ Changelog ========= +1.8.0 (unreleased) +------------------ + 1.7.0 (2016-01-12) ------------------ diff --git a/pep8.py b/pep8.py index 3c950d49..fceb2456 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.7.0' +__version__ = '1.8.0-dev' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' From 44e48c1f71b916615466c628fc6bd056df56804e Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 16 Feb 2016 19:54:39 -0800 Subject: [PATCH 005/412] Added missing python envs to tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 50a0d007..b3bb37b6 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, pypy, jython +envlist = py26, py27, py32, py33, py34, py35, pypy, pypy3, jython [testenv] commands = From b688c787c88884b8aca9fe38bb5b9c9095b39d7a Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Wed, 17 Feb 2016 21:58:19 +0100 Subject: [PATCH 006/412] fix broken link --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b3bb37b6..5661fbbb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ -# Tox (http://codespeak.net/~hpk/tox/) is a tool for running tests +# Tox (https://testrun.org/tox/latest/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. From 2344a34a628b2f2e9df1c2539d9316005a35053d Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 22 Feb 2016 15:36:44 -0800 Subject: [PATCH 007/412] Updated source code links to point to new repository name; issue #466 / #481 --- CHANGES.txt | 4 ++++ README.rst | 2 +- docs/advanced.rst | 2 +- docs/developer.rst | 6 +++--- docs/index.rst | 2 +- docs/intro.rst | 2 +- pep8.py | 2 +- 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 47f0ace2..6e853eb7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,10 @@ Changelog 1.8.0 (unreleased) ------------------ +Announcements: + +* Repository renamed to `pycodestyle`; Issue #466 / $481. + 1.7.0 (2016-01-12) ------------------ diff --git a/README.rst b/README.rst index 4204b05a..e311d4a8 100644 --- a/README.rst +++ b/README.rst @@ -88,4 +88,4 @@ Links * `Read the documentation `_ -* `Fork me on GitHub `_ +* `Fork me on GitHub `_ diff --git a/docs/advanced.rst b/docs/advanced.rst index 1c3a7e16..62c1b90e 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -42,7 +42,7 @@ Skip file header ---------------- Another example is related to the `feature request #143 -`_: skip a number of lines +`_: skip a number of lines at the beginning and the end of a file. This use case is easy to implement through a custom wrapper for the PEP 8 library:: diff --git a/docs/developer.rst b/docs/developer.rst index 4ffc132f..55a88f60 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -11,13 +11,13 @@ Source code The source code is currently `available on GitHub`_ under the terms and conditions of the :ref:`Expat license `. Fork away! -* `Source code `_ and - `issue tracker `_ on GitHub. +* `Source code `_ and + `issue tracker `_ on GitHub. * `Continuous tests `_ against Python 2.6 through 3.4 and PyPy, on `Travis-CI platform `_. -.. _available on GitHub: https://github.com/pycqa/pep8 +.. _available on GitHub: https://github.com/pycqa/pycodestyle Direction diff --git a/docs/index.rst b/docs/index.rst index 5e4a4c5f..d9770222 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,7 +22,7 @@ Contents: developer * Online documentation: http://pep8.readthedocs.org/ -* Source code and issue tracker: https://github.com/pycqa/pep8 +* Source code and issue tracker: https://github.com/pycqa/pycodestyle Indices and tables diff --git a/docs/intro.rst b/docs/intro.rst index 6a579b17..6d1b191d 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -431,6 +431,6 @@ The `flake8 checker `_ is a wrapper around ``pep8`` and similar tools. It supports plugins. Other tools which use ``pep8`` are referenced in the Wiki: `list of related -tools `_. +tools `_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ diff --git a/pep8.py b/pep8.py index fceb2456..ebf70bc1 100755 --- a/pep8.py +++ b/pep8.py @@ -31,7 +31,7 @@ $ python pep8.py -h This program and its regression test suite live here: -https://github.com/pycqa/pep8 +https://github.com/pycqa/pycodestyle Groups of errors and warnings: E errors From ac548869abe75e8d2ca9f6da7f1fdfd287807880 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 23 Feb 2016 22:26:39 -0600 Subject: [PATCH 008/412] Add link to PyCQA code of conduct --- docs/developer.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/developer.rst b/docs/developer.rst index 55a88f60..bbafb801 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -101,7 +101,10 @@ Then be sure to pass the tests:: $ python pep8.py --doctest $ python pep8.py --verbose pep8.py +When contributing to pycodestyle, please observe our `Code of Conduct`_. + .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ +.. _Code of Conduct: http://meta.pycqa.org/en/latest/code-of-conduct.html Changes From 394a061457ab6a591c7cad5bcb0d33de8a0209d2 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 24 Feb 2016 16:09:05 -0600 Subject: [PATCH 009/412] Identify binary operators used as unary operators Previously we handled the case where binary operators were being used as unary operators except in the case where they followed another binary operator, e.g., foo = (1 + -10 * 2 / -5) This change updates the check for W503 to track the previous non-whitespace token type and token text to check if it is in fact also a binary operator (because you should never have two binary operators in a row). This does not handle invalid syntax, e.g., foo = (bar / /baz) But the false-positive generated for other cases was more harmful than not catching what will instead be caught by the interpreter. Closes gh-484 --- pep8.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index bfbea7f2..39eb4267 100755 --- a/pep8.py +++ b/pep8.py @@ -1018,6 +1018,8 @@ def break_around_binary_operator(logical_line, tokens): Okay: foo(x,\n -y) Okay: foo(x, # comment\n -y) Okay: var = (1 &\n ~2) + Okay: var = (1 /\n -2) + Okay: var = (1 +\n -1 +\n -2) """ def is_binary_operator(token_type, text): # The % character is strictly speaking a binary operator, but the @@ -1028,6 +1030,9 @@ def is_binary_operator(token_type, text): line_break = False unary_context = True + # Previous non-newline token types and text + previous_token_type = None + previous_text = None for token_type, text, start, end, line in tokens: if token_type == tokenize.COMMENT: continue @@ -1035,10 +1040,14 @@ def is_binary_operator(token_type, text): line_break = True else: if (is_binary_operator(token_type, text) and line_break and - not unary_context): + not unary_context and + not is_binary_operator(previous_token_type, + previous_text)): yield start, "W503 line break before binary operator" unary_context = text in '([{,;' line_break = False + previous_token_type = token_type + previous_text = text def comparison_to_singleton(logical_line, noqa): From 2e151c544a4312ec9721c6abedd0f1e4067f9231 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 24 Feb 2016 20:01:42 -0800 Subject: [PATCH 010/412] Updated changelog from last few merges --- CHANGES.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 6e853eb7..d25315a7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,16 @@ Changelog Announcements: * Repository renamed to `pycodestyle`; Issue #466 / $481. +* Added joint Code of Conduct as member of PyCQA; #483 + +Changes: + +* Added tox test support for Python 3.5 and pypy3 + +Bugs: + +* Fixed bug with treating `~` operator as binary; #384 +* Identify binary operators as unary; #484 1.7.0 (2016-01-12) ------------------ From 96de5f8551262e7347dfff434dd737485130681c Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Wed, 24 Feb 2016 17:34:03 -0800 Subject: [PATCH 011/412] Update Readme to reflect pycodestyle name change --- README.rst | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index e311d4a8..5619c793 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,16 @@ -pep8 - Python style guide checker -================================= +pycodestyle (formerly called pep8) - Python style guide checker +=============================================================== -pep8 is a tool to check your Python code against some of the style +pycodestyle is a tool to check your Python code against some of the style conventions in `PEP 8`_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ +.. note:: + + This package used to be called ``pep8`` but was renamed to ``pycodestyle`` + to reduce confusion. Further discussion `here + `_. Features -------- @@ -15,18 +20,18 @@ Features * Parseable output: Jump to error location in your editor. * Small: Just one Python file, requires only stdlib. You can use just - the pep8.py file for this purpose. + the pycodestyle.py file for this purpose. * Comes with a comprehensive test suite. Installation ------------ -You can install, upgrade, uninstall pep8.py with these commands:: +You can install, upgrade, uninstall pycodestyle.py with these commands:: - $ pip install pep8 - $ pip install --upgrade pep8 - $ pip uninstall pep8 + $ pip install pycodestyle + $ pip install --upgrade pycodestyle + $ pip uninstall pycodestyle There's also a package for Debian/Ubuntu, but it's not always the latest version. @@ -36,7 +41,7 @@ Example usage and output :: - $ pep8 --first optparse.py + $ pycodestyle --first optparse.py optparse.py:69:11: E401 multiple imports on one line optparse.py:77:1: E302 expected 2 blank lines, found 1 optparse.py:88:5: E301 expected 1 blank line, found 0 @@ -46,10 +51,10 @@ Example usage and output optparse.py:472:29: E221 multiple spaces before operator optparse.py:544:21: W601 .has_key() is deprecated, use 'in' -You can also make pep8.py show the source code for each error, and +You can also make pycodestyle.py show the source code for each error, and even the relevant text from PEP 8:: - $ pep8 --show-source --show-pep8 testsuite/E40.py + $ pycodestyle --show-source --show-pep8 testsuite/E40.py testsuite/E40.py:2:10: E401 multiple imports on one line import os, sys ^ @@ -61,7 +66,7 @@ even the relevant text from PEP 8:: Or you can display how often each error was found:: - $ pep8 --statistics -qq Python-2.5/Lib + $ pycodestyle --statistics -qq Python-2.5/Lib 232 E201 whitespace after '[' 599 E202 whitespace before ')' 631 E203 whitespace before ',' @@ -78,14 +83,14 @@ Or you can display how often each error was found:: Links ----- -.. image:: https://api.travis-ci.org/PyCQA/pep8.png?branch=master - :target: https://travis-ci.org/PyCQA/pep8 +.. image:: https://api.travis-ci.org/PyCQA/pycodestyle.png?branch=master + :target: https://travis-ci.org/PyCQA/pycodestyle :alt: Build status -.. image:: https://pypip.in/wheel/pep8/badge.png?branch=master - :target: https://pypi.python.org/pypi/pep8 +.. image:: https://pypip.in/wheel/pycodestyle/badge.png?branch=master + :target: https://pypi.python.org/pypi/pycodestyle :alt: Wheel Status -* `Read the documentation `_ +* `Read the documentation `_ * `Fork me on GitHub `_ From 1dd1127605bc4dfe52e6a9ff23cba7e2b0fe7fea Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 28 Feb 2016 01:13:05 -0800 Subject: [PATCH 012/412] Patched pep8 to comply with pydocstyle; Closes #228. Purposely ignoring D102 and D203 error codes. - D102 appears to be a bug with D102 - D203 should be ignored by default by pydocstyle) --- pep8.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pep8.py b/pep8.py index 39eb4267..ef6c7cfe 100755 --- a/pep8.py +++ b/pep8.py @@ -837,7 +837,7 @@ def whitespace_before_comment(logical_line, tokens): def imports_on_separate_lines(logical_line): - r"""Imports should usually be on separate lines. + r"""Place imports on separate lines. Okay: import os\nimport sys E401: import sys, os @@ -857,8 +857,10 @@ def imports_on_separate_lines(logical_line): def module_imports_on_top_of_file( logical_line, indent_level, checker_state, noqa): - r"""Imports are always put at the top of the file, just after any module - comments and docstrings, and before module globals and constants. + r"""Place imports at the top of the file. + + Always put imports at the top of the file, just after any module comments + and docstrings, and before module globals and constants. Okay: import os Okay: # this is a comment\nimport os @@ -1166,7 +1168,7 @@ def python_3000_not_equal(logical_line): def python_3000_backticks(logical_line): - r"""Backticks are removed in Python 3: use repr() instead. + r"""Use repr() instead of backticks in Python 3. Okay: val = repr(1 + 2) W604: val = `1 + 2` @@ -1205,7 +1207,9 @@ def readlines(filename): isidentifier = str.isidentifier def stdin_get_value(): + """Read the value from stdin.""" return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() + noqa = re.compile(r'# no(?:qa|pep8)\b', re.I).search @@ -1439,7 +1443,7 @@ def run_check(self, check, argument_names): return check(*arguments) def init_checker_state(self, name, argument_names): - """ Prepares a custom state for the specific checker plugin.""" + """Prepare custom state for the specific checker plugin.""" if 'checker_state' in argument_names: self.checker_state = self._checker_states.setdefault(name, {}) @@ -1721,6 +1725,7 @@ def print_benchmark(self): class FileReport(BaseReport): """Collect the results of the checks and print only the filenames.""" + print_filename = True @@ -1928,6 +1933,7 @@ def get_checks(self, argument_name): def get_parser(prog='pep8', version=__version__): + """Create the parser for the program.""" parser = OptionParser(prog=prog, version=version, usage="%prog [options] input ...") parser.config_options = [ @@ -1989,7 +1995,7 @@ def get_parser(prog='pep8', version=__version__): def read_config(options, args, arglist, parser): - """Read and parse configurations + """Read and parse configurations. If a config file is specified on the command line with the "--config" option, then only it is used for configuration. From b7bf8c80608baa9ebbc6ab5c7692a58243c6f775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Rogalski?= Date: Wed, 23 Mar 2016 23:24:51 +0100 Subject: [PATCH 013/412] complain about missing space before opening parentheses of import statement Solves issue #489 --- docs/intro.rst | 2 ++ pep8.py | 17 +++++++++++++++++ testsuite/E27.py | 14 ++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/docs/intro.rst b/docs/intro.rst index 6d1b191d..e26daf7c 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -313,6 +313,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E274 | tab before keyword | +------------+----------------------------------------------------------------------+ +| E275 | missing whitespace after keyword | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **E3** | *Blank line* | +------------+----------------------------------------------------------------------+ diff --git a/pep8.py b/pep8.py index 39eb4267..ec19dc54 100755 --- a/pep8.py +++ b/pep8.py @@ -325,6 +325,23 @@ def whitespace_around_keywords(logical_line): yield match.start(2), "E271 multiple spaces after keyword" +def missing_whitespace_after_import_keyword(logical_line): + r"""Multiple imports in form from x import (a, b, c) should have space + between import statement and parenthesised name list. + + Okay: from foo import (bar, baz) + E275: from foo import(bar, baz) + E275: from importable.module import(bar, baz) + """ + line = logical_line + indicator = ' import(' + if line.startswith('from '): + found = line.find(indicator) + if -1 < found: + pos = found + len(indicator) - 1 + yield pos, "E275 missing whitespace after keyword" + + def missing_whitespace(logical_line): r"""Each comma, semicolon or colon should be followed by whitespace. diff --git a/testsuite/E27.py b/testsuite/E27.py index f9d3e8e1..888b3a80 100644 --- a/testsuite/E27.py +++ b/testsuite/E27.py @@ -28,3 +28,17 @@ a and b #: E273 E274 this and False +#: Okay +from u import (a, b) +from v import c, d +#: E271 +from w import (e, f) +#: E275 +from w import(e, f) +#: E275 +from importable.module import(e, f) +#: E275 +try: + from importable.module import(e, f) +except ImportError: + pass From 4a37706b95a49ff61a44202f9775146de1e895db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Fri, 8 Apr 2016 14:54:57 +0300 Subject: [PATCH 014/412] Fix false E251 error involving square brackets Fixed a case where a comma is erroneously interpreted as an argument separator when square brackets are used in an argument annotation with a default value. Closes #496 --- pep8.py | 4 ++-- testsuite/E25.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pep8.py b/pep8.py index ec19dc54..8ec21e1d 100755 --- a/pep8.py +++ b/pep8.py @@ -794,9 +794,9 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): if start != prev_end: yield (prev_end, message) if token_type == tokenize.OP: - if text == '(': + if text in '([': parens += 1 - elif text == ')': + elif text in ')]': parens -= 1 elif in_def and text == ':' and parens == 1: annotated_func_arg = True diff --git a/testsuite/E25.py b/testsuite/E25.py index ad8db882..7d00310a 100644 --- a/testsuite/E25.py +++ b/testsuite/E25.py @@ -32,5 +32,6 @@ def foo(bar = False): # Annotated Function Definitions #: Okay -def munge(input: AnyStr, sep: AnyStr = None, limit=1000) -> AnyStr: +def munge(input: AnyStr, sep: AnyStr = None, limit=1000, + extra: Union[str, dict] = None) -> AnyStr: pass From bac13b8187e5f1d120ff692897b2ac539593ebb4 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 20 Apr 2016 08:29:36 -0500 Subject: [PATCH 015/412] Add W503 to default ignore list Closes #498 --- docs/intro.rst | 2 +- pep8.py | 2 +- testsuite/test_api.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index e26daf7c..2ce1eb6e 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -392,7 +392,7 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | **W5** | *Line break warning* | +------------+----------------------------------------------------------------------+ -| W503 | line break occurred before a binary operator | +| W503 (*) | line break occurred before a binary operator | +------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **W6** | *Deprecation warning* | diff --git a/pep8.py b/pep8.py index 8ec21e1d..499c3701 100755 --- a/pep8.py +++ b/pep8.py @@ -65,7 +65,7 @@ __version__ = '1.8.0-dev' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' -DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' +DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' try: if sys.platform == 'win32': USER_CONFIG = os.path.expanduser(r'~\.pep8') diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 1cb0d4b5..0b83c4ee 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -181,7 +181,7 @@ def parse_argv(argstring): self.assertEqual(options.select, ()) self.assertEqual( options.ignore, - ('E121', 'E123', 'E126', 'E226', 'E24', 'E704') + ('E121', 'E123', 'E126', 'E226', 'E24', 'E704', 'W503') ) options = parse_argv('--doctest').options From 93209d17931da6bf4dbe2160ed761788e9aab860 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 28 Apr 2016 09:33:09 -0700 Subject: [PATCH 016/412] Updated text list of "ignored by default" codes --- docs/intro.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 2ce1eb6e..5f7e9cd1 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -408,8 +408,8 @@ This is the current list of error and warning codes: **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, -**E133**, **E226**, **E241**, **E242** and **E704** are ignored because they -are not rules unanimously accepted, and `PEP 8`_ does not enforce them. The +**E133**, **E226**, **E241**, **E242**, **E704** and **W503** are ignored because +they are not rules unanimously accepted, and `PEP 8`_ does not enforce them. The check **E133** is mutually exclusive with check **E123**. Use switch ``--hang- closing`` to report **E133** instead of **E123**. From 69d88e3ee24f980172d87612435ab86428de818e Mon Sep 17 00:00:00 2001 From: Nicholas Chammas Date: Thu, 21 Apr 2016 13:08:40 -0400 Subject: [PATCH 017/412] add examples showing StyleGuide config options --- docs/advanced.rst | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 62c1b90e..10c09f8b 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -8,7 +8,7 @@ Advanced usage Automated tests --------------- -You can also execute `pep8` tests from Python code. For example, this +You can also execute ``pep8`` tests from Python code. For example, this can be highly useful for automated testing of coding style conformance in your project:: @@ -25,7 +25,7 @@ in your project:: self.assertEqual(result.total_errors, 0, "Found code style errors (and warnings).") -If you are using `nosetests` for running tests, remove `quiet=True` +If you are using ``nosetests`` for running tests, remove ``quiet=True`` since Nose suppresses stdout. There's also a shortcut for checking a single file:: @@ -38,6 +38,23 @@ There's also a shortcut for checking a single file:: print("Found %s errors (and warnings)" % file_errors) +Configuring tests +................. + +You can configure automated ``pep8`` tests in a variety of ways. + +For example, you can pass in a path to a configuration file that ``pep8`` +should use:: + + import pep8 + + pep8style = pep8.StyleGuide(config_file='/path/to/tox.ini') + +You can also set specific options explicitly:: + + pep8style = pep8.StyleGuide(ignore=['E501']) + + Skip file header ---------------- @@ -46,7 +63,6 @@ Another example is related to the `feature request #143 at the beginning and the end of a file. This use case is easy to implement through a custom wrapper for the PEP 8 library:: - #!python import pep8 LINES_SLICE = slice(14, -20) From 2f6c43bcb3a6bac525198646e99f12b013e4e460 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 23 May 2016 11:58:11 -0500 Subject: [PATCH 018/412] Allow spaces around = in async definitions In pull request gh-361, we allowed spaces around = for default arguments with annotations. Python 3.5 added the async keyword for a function definition and the allowance we made in gh-361 was failing. This allows a function definition to start with either 'def' or 'async def' now and accommodates both cases. Closes gh-507 --- pep8.py | 3 ++- testsuite/E25.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 499c3701..247a6263 100755 --- a/pep8.py +++ b/pep8.py @@ -776,6 +776,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): Okay: boolean(a <= b) Okay: boolean(a >= b) Okay: def foo(arg: int = 42): + Okay: async def foo(arg: int = 42): E251: def complex(real, imag = 0.0): E251: return magic(r = real, i = imag) @@ -784,7 +785,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): no_space = False prev_end = None annotated_func_arg = False - in_def = logical_line.startswith('def') + in_def = logical_line.startswith(('def', 'async def')) message = "E251 unexpected spaces around keyword / parameter equals" for token_type, text, start, end, line in tokens: if token_type == tokenize.NL: diff --git a/testsuite/E25.py b/testsuite/E25.py index 7d00310a..7a536b57 100644 --- a/testsuite/E25.py +++ b/testsuite/E25.py @@ -35,3 +35,6 @@ def foo(bar = False): def munge(input: AnyStr, sep: AnyStr = None, limit=1000, extra: Union[str, dict] = None) -> AnyStr: pass +#: Okay +async def add(a: int = 0, b: int = 0) -> int: + return a + b From 05627df9e1e79cb1dcad5354615ecca863647100 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 24 May 2016 08:46:16 -0700 Subject: [PATCH 019/412] Re-added shebang line that was removed --- docs/advanced.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/advanced.rst b/docs/advanced.rst index 10c09f8b..71153b35 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -63,6 +63,7 @@ Another example is related to the `feature request #143 at the beginning and the end of a file. This use case is easy to implement through a custom wrapper for the PEP 8 library:: + #!python import pep8 LINES_SLICE = slice(14, -20) From cec28bef2c30100b3d7cc669cd58ad8b3adcd618 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 24 May 2016 08:46:48 -0700 Subject: [PATCH 020/412] Changed heading line format for consistency --- docs/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 71153b35..de3be691 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -39,7 +39,7 @@ There's also a shortcut for checking a single file:: Configuring tests -................. +----------------- You can configure automated ``pep8`` tests in a variety of ways. From 4f42869cf8b72e54a0c252a4eda2e17c9ae96e03 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Wed, 25 May 2016 21:06:27 +0200 Subject: [PATCH 021/412] Fix the "Further discussion" URL --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 5619c793..3ba16199 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ conventions in `PEP 8`_. This package used to be called ``pep8`` but was renamed to ``pycodestyle`` to reduce confusion. Further discussion `here - `_. + `_. Features -------- From a8f1c782763d1c43e4804733354857c76e3b7b0b Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Wed, 25 May 2016 21:43:29 +0200 Subject: [PATCH 022/412] Fix typo --- testsuite/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 0b83c4ee..baafff76 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -137,7 +137,7 @@ def test_styleguide(self): self.reset() def test_styleguide_options(self): - # Instanciate a simple checker + # Instantiate a simple checker pep8style = pep8.StyleGuide(paths=[E11]) # Check style's attributes From 481c233ffe07c13ff164bb4ce25e6791ec3e1494 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Wed, 25 May 2016 21:44:33 +0200 Subject: [PATCH 023/412] Fix grammar "allow" is a transitive verb, which requires an object, so "allow to " is ungrammatical. --- CHANGES.txt | 6 +++--- pep8.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d25315a7..d22214f7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -350,7 +350,7 @@ Bug fixes: * Initiate a graceful shutdown on ``Control+C``. -* Allow to change the ``checker_class`` for the ``StyleGuide``. +* Allow changing the ``checker_class`` for the ``StyleGuide``. 1.4.2 (2013-02-10) @@ -360,7 +360,7 @@ Bug fixes: * Register new checkers with ``register_check(func_or_cls, codes)``. -* Allow to construct a ``StyleGuide`` with a custom parser. +* Allow constructing a ``StyleGuide`` with a custom parser. * Accept visual indentation without parenthesis after the ``if`` statement. (Issue #151) @@ -569,7 +569,7 @@ Bug fixes: The ``--repeat`` flag becomes obsolete because it is the default behaviour. (Issue #6) -* Allow to specify ``--max-line-length``. (Issue #36) +* Allow specifying ``--max-line-length``. (Issue #36) * Make the shebang more flexible. (Issue #26) diff --git a/pep8.py b/pep8.py index 0d329f32..207af158 100755 --- a/pep8.py +++ b/pep8.py @@ -582,7 +582,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, break assert len(indent) == depth + 1 if start[1] not in indent_chances: - # allow to line up tokens + # allow lining up tokens indent_chances[start[1]] = text last_token_multiline = (start[0] != end[0]) From 21783518e7fe4ed7d41a777fee83c962c26dd3d3 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 28 Feb 2016 22:12:11 -0800 Subject: [PATCH 024/412] Added pycodestyle entry_point Converted pep8 entry point to emit a deprecation warning for users --- pep8.py | 15 +++++++++++++++ setup.py | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 207af158..4af3c3ef 100755 --- a/pep8.py +++ b/pep8.py @@ -2181,5 +2181,20 @@ def _main(): sys.stderr.write(str(report.total_errors) + '\n') sys.exit(1) + +def _main_pep8(): + """Entrypoint for pep8 commandline tool. + + Warn of deprecation and advise users to switch to pycodestyle. + """ + print( + 'Deprecation Warning:\n' + 'pep8 has been renamed to pycodestyle and the use of the pep8 ' + 'executable will be removed in a future release. Please use ' + '`pycodestyle` instead.\n' + ) + _main() + + if __name__ == '__main__': _main() diff --git a/setup.py b/setup.py index 29182c6d..b7671195 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,8 @@ def get_long_description(): ], entry_points={ 'console_scripts': [ - 'pep8 = pep8:_main', + 'pycodestyle = pep8:_main', + 'pep8 = pep8:_main_pep8', ], }, classifiers=[ From 3f9ed3a9e62f8fdb4443fa849cca228a501ed1f4 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 28 Feb 2016 22:14:27 -0800 Subject: [PATCH 025/412] Made 'pep8style' more generically 'style_guide' --- pep8.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pep8.py b/pep8.py index 4af3c3ef..6776303c 100755 --- a/pep8.py +++ b/pep8.py @@ -2158,14 +2158,14 @@ def _main(): except AttributeError: pass # not supported on Windows - pep8style = StyleGuide(parse_argv=True) - options = pep8style.options + style_guide = StyleGuide(parse_argv=True) + options = style_guide.options if options.doctest or options.testsuite: from testsuite.support import run_tests - report = run_tests(pep8style) + report = run_tests(style_guide) else: - report = pep8style.check_files() + report = style_guide.check_files() if options.statistics: report.print_statistics() From 1a764175c78d387b66cfe02551d10f3da395a22c Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 10:44:37 -0700 Subject: [PATCH 026/412] Updated pycodestyle file name and setup.py --- pep8.py => pycodestyle.py | 0 setup.py | 12 ++++++------ 2 files changed, 6 insertions(+), 6 deletions(-) rename pep8.py => pycodestyle.py (100%) diff --git a/pep8.py b/pycodestyle.py similarity index 100% rename from pep8.py rename to pycodestyle.py diff --git a/setup.py b/setup.py index b7671195..c0afd373 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ def get_version(): - with open('pep8.py') as f: + with open('pycodestyle.py') as f: for line in f: if line.startswith('__version__'): return eval(line.split('=')[-1]) @@ -19,16 +19,16 @@ def get_long_description(): setup( - name='pep8', + name='pycodestyle', version=get_version(), description="Python style guide checker", long_description=get_long_description(), - keywords='pep8', + keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', author='Johann C. Rocholl', author_email='johann@rocholl.net', url='http://pep8.readthedocs.org/', license='Expat license', - py_modules=['pep8'], + py_modules=['pycodestyle'], namespace_packages=[], include_package_data=True, zip_safe=False, @@ -38,8 +38,8 @@ def get_long_description(): ], entry_points={ 'console_scripts': [ - 'pycodestyle = pep8:_main', - 'pep8 = pep8:_main_pep8', + 'pycodestyle = pycodestyle:_main', + 'pep8 = pycodestyle:_main_pep8', ], }, classifiers=[ From 59a9af64174132e69e101e625e30e0c7a9254a17 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 10:45:03 -0700 Subject: [PATCH 027/412] Updated header comment from pep8 -> pycodestyle --- pycodestyle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 6776303c..87f8f628 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1,5 +1,6 @@ #!/usr/bin/env python -# pep8.py - Check Python source code formatting, according to PEP 8 +# pycodestyle.py - Check Python source code formatting, according to PEP 8 +# # Copyright (C) 2006-2009 Johann C. Rocholl # Copyright (C) 2009-2014 Florent Xicluna # Copyright (C) 2014-2016 Ian Lee From 07647a06e45157caf86ea50a8e9d6d1951f32425 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 10:47:39 -0700 Subject: [PATCH 028/412] Fixed import in testsuite --- testsuite/support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/support.py b/testsuite/support.py index 6bc795d7..61a5427b 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -3,7 +3,7 @@ import re import sys -from pep8 import Checker, BaseReport, StandardReport, readlines +from pycodestyle import Checker, BaseReport, StandardReport, readlines SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) From 4d5f3e405cc3e587cdbc52d4e0828a6ece4eaf95 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 11:43:02 -0700 Subject: [PATCH 029/412] Updated Travis configuration for package rename --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3149cbd2..8771cca3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,9 @@ install: - pip install -e . - pip list script: - - python pep8.py --testsuite testsuite - - python pep8.py --statistics pep8.py - - python pep8.py --doctest + - python pycodestyle.py --testsuite testsuite + - python pycodestyle.py --statistics pep8.py + - python pycodestyle.py --doctest - python setup.py test notifications: From e2f023d6bce383228a65b8c033b8fa8fc31bfcf9 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 11:43:27 -0700 Subject: [PATCH 030/412] Updated tox for package rename --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 5661fbbb..958b172e 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ envlist = py26, py27, py32, py33, py34, py35, pypy, pypy3, jython [testenv] commands = {envpython} setup.py install - {envpython} pep8.py --testsuite testsuite - {envpython} pep8.py --statistics pep8.py - {envpython} pep8.py --doctest + {envpython} pycodestyle.py --testsuite testsuite + {envpython} pycodestyle.py --statistics pep8.py + {envpython} pycodestyle.py --doctest {envpython} -m testsuite.test_all From fed323d41fd8d2da73b8dd6db8ef1759a0d6b9c0 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 11:44:17 -0700 Subject: [PATCH 031/412] Updated makefile for package rename --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 29b243d4..146a2cc4 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ test : - python pep8.py --testsuite testsuite + python pycodestyle.py --testsuite testsuite selftest : - python pep8.py --statistics pep8.py + python pycodestyle.py --statistics pep8.py doctest : - python pep8.py --doctest + python pycodestyle.py --doctest unittest : python -m testsuite.test_all From fba2cc63736fa9d522fdc90d2d3cae1ff4aeaa90 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 22 May 2016 00:35:09 -0700 Subject: [PATCH 032/412] Fixed additional reference to pep8.py in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8771cca3..ee069e88 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: - pip list script: - python pycodestyle.py --testsuite testsuite - - python pycodestyle.py --statistics pep8.py + - python pycodestyle.py --statistics pycodestyle.py - python pycodestyle.py --doctest - python setup.py test From 90d83aa28ae6ed0ebc49cc83079364b172405fb8 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 25 May 2016 10:49:18 -0700 Subject: [PATCH 033/412] Updated file names tested --- Makefile | 2 +- pycodestyle.py | 2 +- testsuite/support.py | 2 +- tox.ini | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 146a2cc4..366f5807 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ test : python pycodestyle.py --testsuite testsuite selftest : - python pycodestyle.py --statistics pep8.py + python pycodestyle.py --statistics pycodestyle.py doctest : python pycodestyle.py --doctest diff --git a/pycodestyle.py b/pycodestyle.py index 87f8f628..5e90dcf5 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -29,7 +29,7 @@ Check Python source code formatting, according to PEP 8. For usage and a list of options, try this: -$ python pep8.py -h +$ python pycodestyle.py -h This program and its regression test suite live here: https://github.com/pycqa/pycodestyle diff --git a/testsuite/support.py b/testsuite/support.py index 61a5427b..003f181b 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -119,7 +119,7 @@ def selftest(options): print("%s: %s" % (code, source)) else: count_failed += 1 - print("pep8.py: %s:" % error) + print("pycodestyle.py: %s:" % error) for line in checker.lines: print(line.rstrip()) return count_failed, count_all diff --git a/tox.ini b/tox.ini index 958b172e..a4992e99 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,6 @@ envlist = py26, py27, py32, py33, py34, py35, pypy, pypy3, jython commands = {envpython} setup.py install {envpython} pycodestyle.py --testsuite testsuite - {envpython} pycodestyle.py --statistics pep8.py + {envpython} pycodestyle.py --statistics pycodestyle.py {envpython} pycodestyle.py --doctest {envpython} -m testsuite.test_all From ccc7f14000d92e8dc08bbdf34b9fac3435425087 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 25 May 2016 11:25:57 -0700 Subject: [PATCH 034/412] Updated tests and doc examples -> pycodestyle --- docs/advanced.rst | 22 +++--- testsuite/test_all.py | 16 +++-- testsuite/test_api.py | 145 ++++++++++++++++++++------------------- testsuite/test_parser.py | 4 +- testsuite/test_shell.py | 26 +++---- testsuite/test_util.py | 20 +++--- 6 files changed, 119 insertions(+), 114 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index de3be691..4f72e6e3 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -13,14 +13,14 @@ can be highly useful for automated testing of coding style conformance in your project:: import unittest - import pep8 + import pycodestyle class TestCodeFormat(unittest.TestCase): def test_pep8_conformance(self): """Test that we conform to PEP8.""" - pep8style = pep8.StyleGuide(quiet=True) + pep8style = pycodestyle.StyleGuide(quiet=True) result = pep8style.check_files(['file1.py', 'file2.py']) self.assertEqual(result.total_errors, 0, "Found code style errors (and warnings).") @@ -30,9 +30,9 @@ since Nose suppresses stdout. There's also a shortcut for checking a single file:: - import pep8 + import pycodestyle - fchecker = pep8.Checker('testsuite/E27.py', show_source=True) + fchecker = pycodestyle.Checker('testsuite/E27.py', show_source=True) file_errors = fchecker.check_all() print("Found %s errors (and warnings)" % file_errors) @@ -46,13 +46,13 @@ You can configure automated ``pep8`` tests in a variety of ways. For example, you can pass in a path to a configuration file that ``pep8`` should use:: - import pep8 + import pycodestyle - pep8style = pep8.StyleGuide(config_file='/path/to/tox.ini') + pep8style = pycodestyle.StyleGuide(config_file='/path/to/tox.ini') You can also set specific options explicitly:: - pep8style = pep8.StyleGuide(ignore=['E501']) + pep8style = pycodestyle.StyleGuide(ignore=['E501']) Skip file header @@ -64,19 +64,19 @@ at the beginning and the end of a file. This use case is easy to implement through a custom wrapper for the PEP 8 library:: #!python - import pep8 + import pycodestyle LINES_SLICE = slice(14, -20) - class PEP8(pep8.StyleGuide): - """This subclass of pep8.StyleGuide will skip the first and last lines + class PEP8(pycodestyle.StyleGuide): + """This subclass of pycodestyle.StyleGuide will skip the first and last lines of each file.""" def input_file(self, filename, lines=None, expected=None, line_offset=0): if lines is None: assert line_offset == 0 line_offset = LINES_SLICE.start or 0 - lines = pep8.readlines(filename)[LINES_SLICE] + lines = pycodestyle.readlines(filename)[LINES_SLICE] return super(PEP8, self).input_file( filename, lines=lines, expected=expected, line_offset=line_offset) diff --git a/testsuite/test_all.py b/testsuite/test_all.py index bfb61d5f..bd18943f 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -4,24 +4,26 @@ import sys import unittest -import pep8 +import pycodestyle from testsuite.support import init_tests, selftest, ROOT_DIR # Note: please only use a subset of unittest methods which were present # in Python 2.5: assert(True|False|Equal|NotEqual|Raises) -class Pep8TestCase(unittest.TestCase): +class PycodestyleTestCase(unittest.TestCase): """Test the standard errors and warnings (E and W).""" def setUp(self): - self._style = pep8.StyleGuide( + self._style = pycodestyle.StyleGuide( paths=[os.path.join(ROOT_DIR, 'testsuite')], select='E,W', quiet=True) def test_doctest(self): import doctest - fail_d, done_d = doctest.testmod(pep8, verbose=False, report=False) + fail_d, done_d = doctest.testmod( + pycodestyle, verbose=False, report=False + ) self.assertTrue(done_d, msg='tests not found') self.assertFalse(fail_d, msg='%s failure(s)' % fail_d) @@ -37,9 +39,9 @@ def test_checkers_testsuite(self): msg='%s failure(s)' % report.total_errors) def test_own_dog_food(self): - files = [pep8.__file__.rstrip('oc'), __file__.rstrip('oc'), + files = [pycodestyle.__file__.rstrip('oc'), __file__.rstrip('oc'), os.path.join(ROOT_DIR, 'setup.py')] - report = self._style.init_report(pep8.StandardReport) + report = self._style.init_report(pycodestyle.StandardReport) report = self._style.check_files(files) self.assertFalse(report.total_errors, msg='Failures: %s' % report.messages) @@ -49,7 +51,7 @@ def suite(): from testsuite import test_api, test_parser, test_shell, test_util suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(Pep8TestCase)) + suite.addTest(unittest.makeSuite(PycodestyleTestCase)) suite.addTest(unittest.makeSuite(test_api.APITestCase)) suite.addTest(unittest.makeSuite(test_parser.ParserTestCase)) suite.addTest(unittest.makeSuite(test_shell.ShellTestCase)) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index baafff76..6549a46c 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -4,7 +4,7 @@ import sys import unittest -import pep8 +import pycodestyle from testsuite.support import ROOT_DIR, PseudoFile E11 = os.path.join(ROOT_DIR, 'testsuite', 'E11.py') @@ -25,17 +25,18 @@ class APITestCase(unittest.TestCase): def setUp(self): self._saved_stdout = sys.stdout self._saved_stderr = sys.stderr - self._saved_checks = pep8._checks + self._saved_checks = pycodestyle._checks sys.stdout = PseudoFile() sys.stderr = PseudoFile() - pep8._checks = dict((k, dict((f, (vals[0][:], vals[1])) - for (f, vals) in v.items())) - for (k, v) in self._saved_checks.items()) + pycodestyle._checks = dict( + (k, dict((f, (vals[0][:], vals[1])) for (f, vals) in v.items())) + for (k, v) in self._saved_checks.items() + ) def tearDown(self): sys.stdout = self._saved_stdout sys.stderr = self._saved_stderr - pep8._checks = self._saved_checks + pycodestyle._checks = self._saved_checks def reset(self): del sys.stdout[:], sys.stderr[:] @@ -44,14 +45,14 @@ def test_register_physical_check(self): def check_dummy(physical_line, line_number): if False: yield - pep8.register_check(check_dummy, ['Z001']) + pycodestyle.register_check(check_dummy, ['Z001']) - self.assertTrue(check_dummy in pep8._checks['physical_line']) - codes, args = pep8._checks['physical_line'][check_dummy] + self.assertTrue(check_dummy in pycodestyle._checks['physical_line']) + codes, args = pycodestyle._checks['physical_line'][check_dummy] self.assertTrue('Z001' in codes) self.assertEqual(args, ['physical_line', 'line_number']) - options = pep8.StyleGuide().options + options = pycodestyle.StyleGuide().options self.assertTrue(any(func == check_dummy for name, func, args in options.physical_checks)) @@ -59,32 +60,32 @@ def test_register_logical_check(self): def check_dummy(logical_line, tokens): if False: yield - pep8.register_check(check_dummy, ['Z401']) + pycodestyle.register_check(check_dummy, ['Z401']) - self.assertTrue(check_dummy in pep8._checks['logical_line']) - codes, args = pep8._checks['logical_line'][check_dummy] + self.assertTrue(check_dummy in pycodestyle._checks['logical_line']) + codes, args = pycodestyle._checks['logical_line'][check_dummy] self.assertTrue('Z401' in codes) self.assertEqual(args, ['logical_line', 'tokens']) - pep8.register_check(check_dummy, []) - pep8.register_check(check_dummy, ['Z402', 'Z403']) - codes, args = pep8._checks['logical_line'][check_dummy] + pycodestyle.register_check(check_dummy, []) + pycodestyle.register_check(check_dummy, ['Z402', 'Z403']) + codes, args = pycodestyle._checks['logical_line'][check_dummy] self.assertEqual(codes, ['Z401', 'Z402', 'Z403']) self.assertEqual(args, ['logical_line', 'tokens']) - options = pep8.StyleGuide().options + options = pycodestyle.StyleGuide().options self.assertTrue(any(func == check_dummy for name, func, args in options.logical_checks)) def test_register_ast_check(self): - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) - self.assertTrue(DummyChecker in pep8._checks['tree']) - codes, args = pep8._checks['tree'][DummyChecker] + self.assertTrue(DummyChecker in pycodestyle._checks['tree']) + codes, args = pycodestyle._checks['tree'][DummyChecker] self.assertTrue('Z701' in codes) self.assertTrue(args is None) - options = pep8.StyleGuide().options + options = pycodestyle.StyleGuide().options self.assertTrue(any(cls == DummyChecker for name, cls, args in options.ast_checks)) @@ -96,23 +97,23 @@ def __init__(self, filename): def check_dummy(logical, tokens): if False: yield - pep8.register_check(InvalidChecker, ['Z741']) - pep8.register_check(check_dummy, ['Z441']) + pycodestyle.register_check(InvalidChecker, ['Z741']) + pycodestyle.register_check(check_dummy, ['Z441']) - for checkers in pep8._checks.values(): + for checkers in pycodestyle._checks.values(): self.assertTrue(DummyChecker not in checkers) self.assertTrue(check_dummy not in checkers) - self.assertRaises(TypeError, pep8.register_check) + self.assertRaises(TypeError, pycodestyle.register_check) def test_styleguide(self): - report = pep8.StyleGuide().check_files() + report = pycodestyle.StyleGuide().check_files() self.assertEqual(report.total_errors, 0) self.assertFalse(sys.stdout) self.assertFalse(sys.stderr) self.reset() - report = pep8.StyleGuide().check_files(['missing-file']) + report = pycodestyle.StyleGuide().check_files(['missing-file']) stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 1) @@ -121,7 +122,7 @@ def test_styleguide(self): self.assertFalse(sys.stderr) self.reset() - report = pep8.StyleGuide().check_files([E11]) + report = pycodestyle.StyleGuide().check_files([E11]) stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 17) @@ -129,7 +130,7 @@ def test_styleguide(self): self.reset() # Passing the paths in the constructor gives same result - report = pep8.StyleGuide(paths=[E11]).check_files() + report = pycodestyle.StyleGuide(paths=[E11]).check_files() stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 17) @@ -137,11 +138,11 @@ def test_styleguide(self): self.reset() def test_styleguide_options(self): - # Instantiate a simple checker - pep8style = pep8.StyleGuide(paths=[E11]) + # Instanciate a simple checker + pep8style = pycodestyle.StyleGuide(paths=[E11]) # Check style's attributes - self.assertEqual(pep8style.checker_class, pep8.Checker) + self.assertEqual(pep8style.checker_class, pycodestyle.Checker) self.assertEqual(pep8style.paths, [E11]) self.assertEqual(pep8style.runner, pep8style.input_file) self.assertEqual(pep8style.options.ignore_code, pep8style.ignore_code) @@ -173,7 +174,7 @@ def parse_argv(argstring): _saved_argv = sys.argv sys.argv = shlex.split('pep8 %s /dev/null' % argstring) try: - return pep8.StyleGuide(parse_argv=True) + return pycodestyle.StyleGuide(parse_argv=True) finally: sys.argv = _saved_argv @@ -208,22 +209,22 @@ def parse_argv(argstring): self.assertEqual(options.select, ('E24',)) self.assertEqual(options.ignore, ('',)) - pep8style = pep8.StyleGuide(paths=[E11]) + pep8style = pycodestyle.StyleGuide(paths=[E11]) self.assertFalse(pep8style.ignore_code('E112')) self.assertFalse(pep8style.ignore_code('W191')) self.assertTrue(pep8style.ignore_code('E241')) - pep8style = pep8.StyleGuide(select='E', paths=[E11]) + pep8style = pycodestyle.StyleGuide(select='E', paths=[E11]) self.assertFalse(pep8style.ignore_code('E112')) self.assertTrue(pep8style.ignore_code('W191')) self.assertFalse(pep8style.ignore_code('E241')) - pep8style = pep8.StyleGuide(select='W', paths=[E11]) + pep8style = pycodestyle.StyleGuide(select='W', paths=[E11]) self.assertTrue(pep8style.ignore_code('E112')) self.assertFalse(pep8style.ignore_code('W191')) self.assertTrue(pep8style.ignore_code('E241')) - pep8style = pep8.StyleGuide(select=('F401',), paths=[E11]) + pep8style = pycodestyle.StyleGuide(select=('F401',), paths=[E11]) self.assertEqual(pep8style.options.select, ('F401',)) self.assertEqual(pep8style.options.ignore, ('',)) self.assertFalse(pep8style.ignore_code('F')) @@ -231,7 +232,7 @@ def parse_argv(argstring): self.assertTrue(pep8style.ignore_code('F402')) def test_styleguide_excluded(self): - pep8style = pep8.StyleGuide(paths=[E11]) + pep8style = pycodestyle.StyleGuide(paths=[E11]) self.assertFalse(pep8style.excluded('./foo/bar')) self.assertFalse(pep8style.excluded('./foo/bar/main.py')) @@ -248,7 +249,7 @@ def test_styleguide_excluded(self): self.assertFalse(pep8style.excluded('./CVS/subdir')) def test_styleguide_checks(self): - pep8style = pep8.StyleGuide(paths=[E11]) + pep8style = pycodestyle.StyleGuide(paths=[E11]) # Default lists of checkers self.assertTrue(len(pep8style.options.physical_checks) > 4) @@ -264,49 +265,51 @@ def test_styleguide_checks(self): self.assertEqual(args[0], 'logical_line') # Do run E11 checks - options = pep8.StyleGuide().options - self.assertTrue(any(func == pep8.indentation + options = pycodestyle.StyleGuide().options + self.assertTrue(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(select=['E']).options - self.assertTrue(any(func == pep8.indentation + options = pycodestyle.StyleGuide(select=['E']).options + self.assertTrue(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(ignore=['W']).options - self.assertTrue(any(func == pep8.indentation + options = pycodestyle.StyleGuide(ignore=['W']).options + self.assertTrue(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(ignore=['E12']).options - self.assertTrue(any(func == pep8.indentation + options = pycodestyle.StyleGuide(ignore=['E12']).options + self.assertTrue(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) # Do not run E11 checks - options = pep8.StyleGuide(select=['W']).options - self.assertFalse(any(func == pep8.indentation + options = pycodestyle.StyleGuide(select=['W']).options + self.assertFalse(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(ignore=['E']).options - self.assertFalse(any(func == pep8.indentation + options = pycodestyle.StyleGuide(ignore=['E']).options + self.assertFalse(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(ignore=['E11']).options - self.assertFalse(any(func == pep8.indentation + options = pycodestyle.StyleGuide(ignore=['E11']).options + self.assertFalse(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) def test_styleguide_init_report(self): - pep8style = pep8.StyleGuide(paths=[E11]) + style = pycodestyle.StyleGuide(paths=[E11]) + + standard_report = pycodestyle.StandardReport - self.assertEqual(pep8style.options.reporter, pep8.StandardReport) - self.assertEqual(type(pep8style.options.report), pep8.StandardReport) + self.assertEqual(style.options.reporter, standard_report) + self.assertEqual(type(style.options.report), standard_report) - class MinorityReport(pep8.BaseReport): + class MinorityReport(pycodestyle.BaseReport): pass - report = pep8style.init_report(MinorityReport) - self.assertEqual(pep8style.options.report, report) + report = style.init_report(MinorityReport) + self.assertEqual(style.options.report, report) self.assertEqual(type(report), MinorityReport) - pep8style = pep8.StyleGuide(paths=[E11], reporter=MinorityReport) - self.assertEqual(type(pep8style.options.report), MinorityReport) - self.assertEqual(pep8style.options.reporter, MinorityReport) + style = pycodestyle.StyleGuide(paths=[E11], reporter=MinorityReport) + self.assertEqual(type(style.options.report), MinorityReport) + self.assertEqual(style.options.reporter, MinorityReport) def test_styleguide_check_files(self): - pep8style = pep8.StyleGuide(paths=[E11]) + pep8style = pycodestyle.StyleGuide(paths=[E11]) report = pep8style.check_files() self.assertTrue(report.total_errors) @@ -317,12 +320,12 @@ def test_styleguide_check_files(self): def test_check_unicode(self): # Do not crash if lines are Unicode (Python 2.x) - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) source = '#\n' if hasattr(source, 'decode'): source = source.decode('ascii') - pep8style = pep8.StyleGuide() + pep8style = pycodestyle.StyleGuide() count_errors = pep8style.input_file('stdin', lines=[source]) self.assertFalse(sys.stdout) @@ -330,9 +333,9 @@ def test_check_unicode(self): self.assertEqual(count_errors, 0) def test_check_nullbytes(self): - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) - pep8style = pep8.StyleGuide() + pep8style = pycodestyle.StyleGuide() count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() @@ -351,13 +354,13 @@ def test_check_nullbytes(self): self.assertEqual(count_errors, 1) def test_styleguide_unmatched_triple_quotes(self): - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) lines = [ 'def foo():\n', ' """test docstring""\'\n', ] - pep8style = pep8.StyleGuide() + pep8style = pycodestyle.StyleGuide() pep8style.input_file('stdin', lines=lines) stdout = sys.stdout.getvalue() @@ -365,7 +368,7 @@ def test_styleguide_unmatched_triple_quotes(self): self.assertTrue(expected in stdout) def test_styleguide_continuation_line_outdented(self): - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) lines = [ 'def foo():\n', ' pass\n', @@ -376,7 +379,7 @@ def test_styleguide_continuation_line_outdented(self): ' pass\n', ] - pep8style = pep8.StyleGuide() + pep8style = pycodestyle.StyleGuide() count_errors = pep8style.input_file('stdin', lines=lines) self.assertEqual(count_errors, 2) stdout = sys.stdout.getvalue() diff --git a/testsuite/test_parser.py b/testsuite/test_parser.py index 1d9e1ac5..26a45fc6 100644 --- a/testsuite/test_parser.py +++ b/testsuite/test_parser.py @@ -2,14 +2,14 @@ import tempfile import unittest -import pep8 +import pycodestyle def _process_file(contents): with tempfile.NamedTemporaryFile(delete=False) as f: f.write(contents) - options, args = pep8.process_options(config_file=f.name) + options, args = pycodestyle.process_options(config_file=f.name) os.remove(f.name) return options, args diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index e536852f..760c2284 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -3,7 +3,7 @@ import sys import unittest -import pep8 +import pycodestyle from testsuite.support import ROOT_DIR, PseudoFile @@ -14,9 +14,9 @@ def setUp(self): self._saved_argv = sys.argv self._saved_stdout = sys.stdout self._saved_stderr = sys.stderr - self._saved_pconfig = pep8.PROJECT_CONFIG - self._saved_cpread = pep8.RawConfigParser._read - self._saved_stdin_get_value = pep8.stdin_get_value + self._saved_pconfig = pycodestyle.PROJECT_CONFIG + self._saved_cpread = pycodestyle.RawConfigParser._read + self._saved_stdin_get_value = pycodestyle.stdin_get_value self._config_filenames = [] self.stdin = '' sys.argv = ['pep8'] @@ -25,16 +25,16 @@ def setUp(self): def fake_config_parser_read(cp, fp, filename): self._config_filenames.append(filename) - pep8.RawConfigParser._read = fake_config_parser_read - pep8.stdin_get_value = self.stdin_get_value + pycodestyle.RawConfigParser._read = fake_config_parser_read + pycodestyle.stdin_get_value = self.stdin_get_value def tearDown(self): sys.argv = self._saved_argv sys.stdout = self._saved_stdout sys.stderr = self._saved_stderr - pep8.PROJECT_CONFIG = self._saved_pconfig - pep8.RawConfigParser._read = self._saved_cpread - pep8.stdin_get_value = self._saved_stdin_get_value + pycodestyle.PROJECT_CONFIG = self._saved_pconfig + pycodestyle.RawConfigParser._read = self._saved_cpread + pycodestyle.stdin_get_value = self._saved_stdin_get_value def stdin_get_value(self): return self.stdin @@ -43,7 +43,7 @@ def pep8(self, *args): del sys.stdout[:], sys.stderr[:] sys.argv[1:] = args try: - pep8._main() + pycodestyle._main() errorcode = None except SystemExit: errorcode = sys.exc_info()[1].code @@ -88,7 +88,7 @@ def test_check_simple(self): self.assertTrue('setup.cfg' in config_filenames) def test_check_stdin(self): - pep8.PROJECT_CONFIG = () + pycodestyle.PROJECT_CONFIG = () stdout, stderr, errcode = self.pep8('-') self.assertFalse(errcode) self.assertFalse(stderr) @@ -111,7 +111,7 @@ def test_check_non_existent(self): def test_check_noarg(self): # issue #170: do not read stdin by default - pep8.PROJECT_CONFIG = () + pycodestyle.PROJECT_CONFIG = () stdout, stderr, errcode = self.pep8() self.assertEqual(errcode, 2) self.assertEqual(stderr.splitlines(), @@ -120,7 +120,7 @@ def test_check_noarg(self): self.assertFalse(self._config_filenames) def test_check_diff(self): - pep8.PROJECT_CONFIG = () + pycodestyle.PROJECT_CONFIG = () diff_lines = [ "--- testsuite/E11.py 2006-06-01 08:49:50 +0500", "+++ testsuite/E11.py 2008-04-06 17:36:29 +0500", diff --git a/testsuite/test_util.py b/testsuite/test_util.py index 11395cca..8eaba7ef 100644 --- a/testsuite/test_util.py +++ b/testsuite/test_util.py @@ -3,21 +3,21 @@ import os import unittest -import pep8 +from pycodestyle import normalize_paths class UtilTestCase(unittest.TestCase): def test_normalize_paths(self): cwd = os.getcwd() - self.assertEqual(pep8.normalize_paths(''), []) - self.assertEqual(pep8.normalize_paths([]), []) - self.assertEqual(pep8.normalize_paths(None), []) - self.assertEqual(pep8.normalize_paths(['foo']), ['foo']) - self.assertEqual(pep8.normalize_paths('foo'), ['foo']) - self.assertEqual(pep8.normalize_paths('foo,bar'), ['foo', 'bar']) - self.assertEqual(pep8.normalize_paths('foo, bar '), ['foo', 'bar']) - self.assertEqual(pep8.normalize_paths('/foo/bar,baz/../bat'), + self.assertEqual(normalize_paths(''), []) + self.assertEqual(normalize_paths([]), []) + self.assertEqual(normalize_paths(None), []) + self.assertEqual(normalize_paths(['foo']), ['foo']) + self.assertEqual(normalize_paths('foo'), ['foo']) + self.assertEqual(normalize_paths('foo,bar'), ['foo', 'bar']) + self.assertEqual(normalize_paths('foo, bar '), ['foo', 'bar']) + self.assertEqual(normalize_paths('/foo/bar,baz/../bat'), ['/foo/bar', cwd + '/bat']) - self.assertEqual(pep8.normalize_paths(".pyc,\n build/*"), + self.assertEqual(normalize_paths(".pyc,\n build/*"), ['.pyc', cwd + '/build/*']) From f39501c475338b23021245fa9df7b4e8cea3a875 Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Sun, 29 May 2016 14:39:21 +0100 Subject: [PATCH 035/412] Convert readthedocs link for their .org -> .io migration for hosted projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per their email ‘Changes to project subdomains’: > Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard. Test Plan: Manually visited all the links I’ve modified. --- CHANGES.txt | 2 +- CONTRIBUTING.rst | 2 +- README.rst | 2 +- docs/index.rst | 2 +- docs/intro.rst | 2 +- setup.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d22214f7..ebb00e81 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -406,7 +406,7 @@ Bug fixes: (Issue #93 and #141) * Add the Sphinx-based documentation, and publish it - on http://pep8.readthedocs.org/. (Issue #105) + on https://pep8.readthedocs.io/. (Issue #105) 1.3.4 (2012-12-18) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 158dc6ff..0500f0bb 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ Contributing to ``pep8`` ======================== -Please see the `developer notes `_ +Please see the `developer notes `_ diff --git a/README.rst b/README.rst index 3ba16199..f82589d9 100644 --- a/README.rst +++ b/README.rst @@ -91,6 +91,6 @@ Links :target: https://pypi.python.org/pypi/pycodestyle :alt: Wheel Status -* `Read the documentation `_ +* `Read the documentation `_ * `Fork me on GitHub `_ diff --git a/docs/index.rst b/docs/index.rst index d9770222..6ae38155 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ Contents: API developer -* Online documentation: http://pep8.readthedocs.org/ +* Online documentation: https://pep8.readthedocs.io/ * Source code and issue tracker: https://github.com/pycqa/pycodestyle diff --git a/docs/intro.rst b/docs/intro.rst index 5f7e9cd1..91cfbfea 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -429,7 +429,7 @@ Note: most errors can be listed with such one-liner:: Related tools ------------- -The `flake8 checker `_ is a wrapper around +The `flake8 checker `_ is a wrapper around ``pep8`` and similar tools. It supports plugins. Other tools which use ``pep8`` are referenced in the Wiki: `list of related diff --git a/setup.py b/setup.py index c0afd373..7c0cf84e 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def get_long_description(): keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', author='Johann C. Rocholl', author_email='johann@rocholl.net', - url='http://pep8.readthedocs.org/', + url='https://pep8.readthedocs.io/', license='Expat license', py_modules=['pycodestyle'], namespace_packages=[], From 6fd9e6f29303ca150f407c2b24e99c9336954346 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 29 May 2016 10:58:51 -0700 Subject: [PATCH 036/412] Finished updating rtd links Updated references to pep8.readthedocs.io -> pycodestyle.readthedocs.io --- CHANGES.txt | 2 +- CONTRIBUTING.rst | 2 +- docs/index.rst | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ebb00e81..4f8e28dc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -406,7 +406,7 @@ Bug fixes: (Issue #93 and #141) * Add the Sphinx-based documentation, and publish it - on https://pep8.readthedocs.io/. (Issue #105) + on https://pycodestyle.readthedocs.io/. (Issue #105) 1.3.4 (2012-12-18) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0500f0bb..61c2822d 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ Contributing to ``pep8`` ======================== -Please see the `developer notes `_ +Please see the `developer notes `_ diff --git a/docs/index.rst b/docs/index.rst index 6ae38155..d6ece440 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ Contents: API developer -* Online documentation: https://pep8.readthedocs.io/ +* Online documentation: https://pycodestyle.readthedocs.io/ * Source code and issue tracker: https://github.com/pycqa/pycodestyle diff --git a/setup.py b/setup.py index 7c0cf84e..9e6dd681 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def get_long_description(): keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', author='Johann C. Rocholl', author_email='johann@rocholl.net', - url='https://pep8.readthedocs.io/', + url='https://pycodestyle.readthedocs.io/', license='Expat license', py_modules=['pycodestyle'], namespace_packages=[], From dde7b56ba8cf1a721bbff43f39d428235807c74a Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 29 May 2016 16:36:37 -0700 Subject: [PATCH 037/412] Added maintainer metadata to package --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 9e6dd681..93a1337e 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,8 @@ def get_long_description(): keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', author='Johann C. Rocholl', author_email='johann@rocholl.net', + maintainer='Ian Lee', + maintainer_email='IanLee1521@gmail.com', url='https://pycodestyle.readthedocs.io/', license='Expat license', py_modules=['pycodestyle'], From 23d95cd4246bbf71e5ce2df1e70bd915de43496d Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 29 May 2016 17:04:49 -0700 Subject: [PATCH 038/412] Updated project name in docs Updated from references to pep8 -> pycodestyle, this fixes the sphinx build errors caught by readthedocs. --- docs/Makefile | 8 ++++---- docs/advanced.rst | 26 +++++++++++++------------- docs/api.rst | 10 +++++----- docs/conf.py | 20 ++++++++++---------- docs/developer.rst | 18 +++++++++--------- docs/index.rst | 12 ++++++------ docs/intro.rst | 45 ++++++++++++++++++++------------------------- docs/make.bat | 4 ++-- 8 files changed, 69 insertions(+), 74 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 1952db44..96c920f3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -77,17 +77,17 @@ qthelp: @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pep8.qhcp" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pycodestyle.qhcp" @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pep8.qhc" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pycodestyle.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/pep8" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pep8" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pycodestyle" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pycodestyle" @echo "# devhelp" epub: diff --git a/docs/advanced.rst b/docs/advanced.rst index 4f72e6e3..6adb2cbb 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -1,4 +1,4 @@ -.. currentmodule:: pep8 +.. currentmodule:: pycodestyle ============== Advanced usage @@ -8,7 +8,7 @@ Advanced usage Automated tests --------------- -You can also execute ``pep8`` tests from Python code. For example, this +You can also execute ``pycodestyle`` tests from Python code. For example, this can be highly useful for automated testing of coding style conformance in your project:: @@ -18,10 +18,10 @@ in your project:: class TestCodeFormat(unittest.TestCase): - def test_pep8_conformance(self): - """Test that we conform to PEP8.""" - pep8style = pycodestyle.StyleGuide(quiet=True) - result = pep8style.check_files(['file1.py', 'file2.py']) + def test_conformance(self): + """Test that we conform to PEP-8.""" + style = pycodestyle.StyleGuide(quiet=True) + result = style.check_files(['file1.py', 'file2.py']) self.assertEqual(result.total_errors, 0, "Found code style errors (and warnings).") @@ -41,18 +41,18 @@ There's also a shortcut for checking a single file:: Configuring tests ----------------- -You can configure automated ``pep8`` tests in a variety of ways. +You can configure automated ``pycodestyle`` tests in a variety of ways. -For example, you can pass in a path to a configuration file that ``pep8`` +For example, you can pass in a path to a configuration file that ``pycodestyle`` should use:: import pycodestyle - pep8style = pycodestyle.StyleGuide(config_file='/path/to/tox.ini') + style = pycodestyle.StyleGuide(config_file='/path/to/tox.ini') You can also set specific options explicitly:: - pep8style = pycodestyle.StyleGuide(ignore=['E501']) + style = pycodestyle.StyleGuide(ignore=['E501']) Skip file header @@ -81,8 +81,8 @@ through a custom wrapper for the PEP 8 library:: filename, lines=lines, expected=expected, line_offset=line_offset) if __name__ == '__main__': - pep8style = PEP8(parse_argv=True, config_file=True) - report = pep8style.check_files() + style = PEP8(parse_argv=True, config_file=True) + report = style.check_files() if report.total_errors: raise SystemExit(1) @@ -91,4 +91,4 @@ and 20 lines at the end. If there's no line to skip at the end, it could be changed with ``LINES_SLICE = slice(14, None)`` for example. You can save it in a file and use it with the same options as the -original ``pep8``. +original ``pycodestyle``. diff --git a/docs/api.rst b/docs/api.rst index b346abab..f9c70668 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,8 +1,8 @@ -======== -pep8 API -======== +=============== +pycodestyle API +=============== -.. module:: pep8 +.. module:: pycodestyle The library provides classes which are usable by third party tools. @@ -84,5 +84,5 @@ Utilities .. autofunction:: stdin_get_value() .. autofunction:: parse_udiff(diff, patterns=None, parent='.') .. autofunction:: filename_match(filename, patterns, default=True) - .. autofunction:: get_parser(prog='pep8', version=pep8.__version__) + .. autofunction:: get_parser(prog='pycodestyle', version=pycodestyle.__version__) .. autofunction:: init_checks_registry() diff --git a/docs/conf.py b/docs/conf.py index 5f990f31..7935b0b2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# pep8 documentation build configuration file, created by +# pycodestyle documentation build configuration file, created by # sphinx-quickstart on Tue Aug 21 09:47:49 2012. # # This file is execfile()d with the current directory set to its @@ -44,7 +44,7 @@ master_doc = 'index' # General information about the project. -project = u'pep8' +project = u'pycodestyle' authors = u'Johann C. Rocholl, Florent Xicluna, Ian Lee' copyright = u'2006-2016, %s' % (authors) @@ -53,11 +53,11 @@ # built documents. # -pep8_version = __import__('pep8').__version__.split('.') +pkg_version = __import__('pycodestyle').__version__.split('.') # The short X.Y version. -version = '.'.join(pep8_version[:2]) +version = '.'.join(pkg_version[:2]) # The full version, including alpha/beta/rc tags. -release = '.'.join(pep8_version) +release = '.'.join(pkg_version) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -176,7 +176,7 @@ #html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'pep8doc' +htmlhelp_basename = 'pycodestyledoc' # -- Options for LaTeX output ------------------------------------------------- @@ -196,7 +196,7 @@ # (source start file, target name, title, # author, documentclass [howto/manual]). latex_documents = [ - ('index', 'pep8.tex', u'pep8 documentation', + ('index', 'pycodestyle.tex', u'pycodestyle documentation', authors, 'manual'), ] @@ -226,7 +226,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'pep8', u'pep8 documentation', + ('index', 'pycodestyle', u'pycodestyle documentation', [authors], 1) ] @@ -240,8 +240,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'pep8', u'pep8 documentation', authors, - 'pep8', 'One line description of project.', + ('index', 'pycodestyle', u'pycodestyle documentation', authors, + 'pycodestyle', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/developer.rst b/docs/developer.rst index bbafb801..0aefbedf 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -1,4 +1,4 @@ -.. currentmodule:: pep8 +.. currentmodule:: pycodestyle ================= Developer's notes @@ -13,7 +13,7 @@ conditions of the :ref:`Expat license `. Fork away! * `Source code `_ and `issue tracker `_ on GitHub. -* `Continuous tests `_ against Python +* `Continuous tests `_ against Python 2.6 through 3.4 and PyPy, on `Travis-CI platform `_. @@ -25,14 +25,14 @@ Direction Some high-level aims and directions to bear in mind for contributions: -* ``pep8`` is intended to be as fast as possible. +* ``pycodestyle`` is intended to be as fast as possible. Using the ``ast`` module defeats that purpose. The `pep8-naming `_ plugin exists for this sort of functionality. * If you want to provide extensibility / plugins, please see `flake8 `_ - - ``pep8`` doesn't want or need a plugin architecture. + ``pycodestyle`` doesn't want or need a plugin architecture. * Python 2.6 support is still deemed important. -* ``pep8`` aims to have no external dependencies. +* ``pycodestyle`` aims to have no external dependencies. Contribute @@ -89,7 +89,7 @@ Several docstrings contain examples directly from the `PEP 8`_ document. Okay: spam(ham[1], {eggs: 2}) E201: spam( ham[1], {eggs: 2}) -These examples are verified automatically when pep8.py is run with the +These examples are verified automatically when pycodestyle.py is run with the ``--doctest`` option. You can add examples for your own check functions. The format is simple: ``"Okay"`` or error/warning code followed by colon and space, the rest of the line is example source code. If you put ``'r'`` @@ -97,9 +97,9 @@ before the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: - $ python pep8.py --testsuite testsuite - $ python pep8.py --doctest - $ python pep8.py --verbose pep8.py + $ python pycodestyle.py --testsuite testsuite + $ python pycodestyle.py --doctest + $ python pycodestyle.py --verbose pycodestyle.py When contributing to pycodestyle, please observe our `Code of Conduct`_. diff --git a/docs/index.rst b/docs/index.rst index d6ece440..0764e9f7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,12 +1,12 @@ -.. pep8 documentation master file +.. pycodestyle documentation master file -pep8's documentation -==================== +pycodestyle's documentation +=========================== *Python style guide checker* -pep8 is a tool to check your Python code against some of the style -conventions in `PEP 8`_. +pycodestyle (formerly pep8) is a tool to check your Python code against some of +the style conventions in `PEP 8`_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ @@ -45,7 +45,7 @@ Maintained by Florent Xicluna and Ian Lee. License ======= -The ``pep8`` library is provided under the terms and conditions of the +The ``pycodestyle`` library is provided under the terms and conditions of the Expat license:: # Permission is hereby granted, free of charge, to any person diff --git a/docs/intro.rst b/docs/intro.rst index 91cfbfea..d3cd755d 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -1,9 +1,9 @@ -.. currentmodule:: pep8 +.. currentmodule:: pycodestyle Introduction ============ -pep8 is a tool to check your Python code against some of the style +pycodestyle is a tool to check your Python code against some of the style conventions in `PEP 8`_. .. contents:: @@ -18,7 +18,7 @@ Features * Parseable output: Jump to error location in your editor. * Small: Just one Python file, requires only stdlib. You can use just - the pep8.py file for this purpose. + the pycodestyle.py file for this purpose. * Comes with a comprehensive test suite. @@ -40,7 +40,7 @@ Always remember this statement from `PEP 8`_: Among other things, these features are currently not in the scope of -the ``pep8`` library: +the ``pycodestyle`` library: * **naming conventions**: this kind of feature is supported through plugins. Install `flake8 `_ and the @@ -55,16 +55,11 @@ the ``pep8`` library: Installation ------------ -You can install, upgrade, uninstall pep8.py with these commands:: +You can install, upgrade, uninstall pycodestyle.py with these commands:: - $ pip install pep8 - $ pip install --upgrade pep8 - $ pip uninstall pep8 - -There's also a package for Debian/Ubuntu, but it's not always the -latest version:: - - $ sudo apt-get install pep8 + $ pip install pycodestyle + $ pip install --upgrade pycodestyle + $ pip uninstall pycodestyle Example usage and output @@ -72,7 +67,7 @@ Example usage and output :: - $ pep8 --first optparse.py + $ pycodestyle --first optparse.py optparse.py:69:11: E401 multiple imports on one line optparse.py:77:1: E302 expected 2 blank lines, found 1 optparse.py:88:5: E301 expected 1 blank line, found 0 @@ -82,10 +77,10 @@ Example usage and output optparse.py:472:29: E221 multiple spaces before operator optparse.py:544:21: W601 .has_key() is deprecated, use 'in' -You can also make pep8.py show the source code for each error, and +You can also make pycodestyle.py show the source code for each error, and even the relevant text from PEP 8:: - $ pep8 --show-source --show-pep8 testsuite/E40.py + $ pycodestyle --show-source --show-pep8 testsuite/E40.py testsuite/E40.py:2:10: E401 multiple imports on one line import os, sys ^ @@ -97,7 +92,7 @@ even the relevant text from PEP 8:: Or you can display how often each error was found:: - $ pep8 --statistics -qq Python-2.5/Lib + $ pycodestyle --statistics -qq Python-2.5/Lib 232 E201 whitespace after '[' 599 E202 whitespace before ')' 631 E203 whitespace before ',' @@ -113,13 +108,13 @@ Or you can display how often each error was found:: You can also make pep8.py show the error text in different formats by using --format having options default/pylint/custom:: - $ pep8 testsuite/E40.py --format=default + $ pycodestyle testsuite/E40.py --format=default testsuite/E40.py:2:10: E401 multiple imports on one line - $ pep8 testsuite/E40.py --format=pylint + $ pycodestyle testsuite/E40.py --format=pylint testsuite/E40.py:2: [E401] multiple imports on one line - $ pep8 testsuite/E40.py --format='%(path)s|%(row)d|%(col)d| %(code)s %(text)s' + $ pycodestyle testsuite/E40.py --format='%(path)s|%(row)d|%(col)d| %(code)s %(text)s' testsuite/E40.py|2|10| E401 multiple imports on one line Variables in the ``custom`` format option @@ -140,8 +135,8 @@ Variables in the ``custom`` format option Quick help is available on the command line:: - $ pep8 -h - Usage: pep8 [options] input ... + $ pycodestyle -h + Usage: pycodestyle [options] input ... Options: --version show program's version number and exit @@ -421,7 +416,7 @@ special comment. This possibility should be reserved for special cases. Note: most errors can be listed with such one-liner:: - $ python pep8.py --first --select E,W testsuite/ --format '%(code)s: %(text)s' + $ python pycodestyle.py --first --select E,W testsuite/ --format '%(code)s: %(text)s' .. _related-tools: @@ -430,9 +425,9 @@ Related tools ------------- The `flake8 checker `_ is a wrapper around -``pep8`` and similar tools. It supports plugins. +``pycodestyle`` and similar tools. It supports plugins. -Other tools which use ``pep8`` are referenced in the Wiki: `list of related +Other tools which use ``pycodestyle`` are referenced in the Wiki: `list of related tools `_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ diff --git a/docs/make.bat b/docs/make.bat index efa0e94d..1ecae62e 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -99,9 +99,9 @@ if "%1" == "qthelp" ( echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pep8.qhcp + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pycodestyle.qhcp echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pep8.ghc + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pycodestyle.ghc goto end ) From eee59ff17de5fb341b3c05de51df6c8377ef88d0 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 29 May 2016 17:13:21 -0700 Subject: [PATCH 039/412] Updated two more references to old name --- CONTRIBUTING.rst | 4 ++-- docs/intro.rst | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 61c2822d..9771176b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ -Contributing to ``pep8`` -======================== +Contributing to ``pycodestyle`` +=============================== Please see the `developer notes `_ diff --git a/docs/intro.rst b/docs/intro.rst index d3cd755d..2281ef15 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -106,7 +106,8 @@ Or you can display how often each error was found:: 612 W601 .has_key() is deprecated, use 'in' 1188 W602 deprecated form of raising exception -You can also make pep8.py show the error text in different formats by using --format having options default/pylint/custom:: +You can also make pycodestyle.py show the error text in different formats by +using --format having options default/pylint/custom:: $ pycodestyle testsuite/E40.py --format=default testsuite/E40.py:2:10: E401 multiple imports on one line From c40150a7d06cbe1047502b836cc0d769e26364d6 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 30 May 2016 15:17:43 -0700 Subject: [PATCH 040/412] Updated deprecation warning to actually emit a warning --- pycodestyle.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 5e90dcf5..2ed42cfe 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -55,6 +55,7 @@ import inspect import keyword import tokenize +import warnings from optparse import OptionParser from fnmatch import fnmatch try: @@ -2190,10 +2191,11 @@ def _main_pep8(): """ print( 'Deprecation Warning:\n' - 'pep8 has been renamed to pycodestyle and the use of the pep8 ' - 'executable will be removed in a future release. Please use ' - '`pycodestyle` instead.\n' + 'Use of the pep8 tool will be removed in a future release.\n' + 'Please install and use `pycodestyle` instead.\n' ) + warnings.warn('pep8 has been renamed to pycodestyle (GitHub issue #466)') + _main() From b86fa3d847bed1e7fe1e03cd3b7fba2f1b6b93d6 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 30 May 2016 16:42:08 -0700 Subject: [PATCH 041/412] Updated version string to PEP-440 format --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 2ed42cfe..e3dc05f2 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -64,7 +64,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.8.0-dev' +__version__ = '1.8.0.dev0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From 3f00bed57ccc1b33f6872021e25f60d4ddba4760 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 09:47:49 -0700 Subject: [PATCH 042/412] Fixed typo in changelog --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4f8e28dc..fe2665cb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,7 +7,7 @@ Changelog Announcements: -* Repository renamed to `pycodestyle`; Issue #466 / $481. +* Repository renamed to `pycodestyle`; Issue #466 / #481. * Added joint Code of Conduct as member of PyCQA; #483 Changes: From a7362de1af81a2f1916f2d7e52f6af80e00c7aa1 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 10:14:36 -0700 Subject: [PATCH 043/412] Updated changelog with changes made since 1.7.0 --- CHANGES.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index fe2665cb..5c042166 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,11 +13,13 @@ Announcements: Changes: * Added tox test support for Python 3.5 and pypy3 +* Added check E275 for whitespace on `from ... import ...` lines; #489 / #491 +* Added W503 to the list of codes ignored by default ignore list; #498 Bugs: -* Fixed bug with treating `~` operator as binary; #384 -* Identify binary operators as unary; #484 +* Fixed bug with treating `~` operator as binary; #383 / #384 +* Identify binary operators as unary; #484 / #485 1.7.0 (2016-01-12) ------------------ From 53da847c91b277491ef542ef53586e54cf7f4942 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 10:20:47 -0700 Subject: [PATCH 044/412] Update next version plan to 2.0.0 In order to better high-light the name change, and backwards incompatibility. This also allows dependent tools (like flake8) to pin to `pycodestyle < 2.0` as a dependency while transitioning to the new package. --- CHANGES.txt | 2 +- pycodestyle.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5c042166..40582065 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,7 +2,7 @@ Changelog ========= -1.8.0 (unreleased) +2.0.0 (unreleased) ------------------ Announcements: diff --git a/pycodestyle.py b/pycodestyle.py index e3dc05f2..de40b078 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -64,7 +64,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.8.0.dev0' +__version__ = '2.0.0.dev0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From f8bd6939d42037287f17ac3b048bdbdf0f2dd1b5 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 11:10:52 -0700 Subject: [PATCH 045/412] Updated docs to remove another pep8 based reference --- docs/advanced.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 6adb2cbb..fd3cf3e5 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -68,7 +68,7 @@ through a custom wrapper for the PEP 8 library:: LINES_SLICE = slice(14, -20) - class PEP8(pycodestyle.StyleGuide): + class StyleGuide(pycodestyle.StyleGuide): """This subclass of pycodestyle.StyleGuide will skip the first and last lines of each file.""" @@ -77,11 +77,11 @@ through a custom wrapper for the PEP 8 library:: assert line_offset == 0 line_offset = LINES_SLICE.start or 0 lines = pycodestyle.readlines(filename)[LINES_SLICE] - return super(PEP8, self).input_file( + return super(StyleGuide, self).input_file( filename, lines=lines, expected=expected, line_offset=line_offset) if __name__ == '__main__': - style = PEP8(parse_argv=True, config_file=True) + style = StyleGuide(parse_argv=True, config_file=True) report = style.check_files() if report.total_errors: raise SystemExit(1) From 56a9882ec8bea2fe3dc10ab5477187e57468f970 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 11:16:32 -0700 Subject: [PATCH 046/412] Improved formatting of filename referencing in docs --- README.rst | 6 +++--- docs/developer.rst | 10 +++++----- docs/intro.rst | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index f82589d9..466509b1 100644 --- a/README.rst +++ b/README.rst @@ -20,14 +20,14 @@ Features * Parseable output: Jump to error location in your editor. * Small: Just one Python file, requires only stdlib. You can use just - the pycodestyle.py file for this purpose. + the ``pycodestyle.py`` file for this purpose. * Comes with a comprehensive test suite. Installation ------------ -You can install, upgrade, uninstall pycodestyle.py with these commands:: +You can install, upgrade, uninstall ``pycodestyle.py`` with these commands:: $ pip install pycodestyle $ pip install --upgrade pycodestyle @@ -51,7 +51,7 @@ Example usage and output optparse.py:472:29: E221 multiple spaces before operator optparse.py:544:21: W601 .has_key() is deprecated, use 'in' -You can also make pycodestyle.py show the source code for each error, and +You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: $ pycodestyle --show-source --show-pep8 testsuite/E40.py diff --git a/docs/developer.rst b/docs/developer.rst index 0aefbedf..3acf2a60 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -89,11 +89,11 @@ Several docstrings contain examples directly from the `PEP 8`_ document. Okay: spam(ham[1], {eggs: 2}) E201: spam( ham[1], {eggs: 2}) -These examples are verified automatically when pycodestyle.py is run with the -``--doctest`` option. You can add examples for your own check functions. -The format is simple: ``"Okay"`` or error/warning code followed by colon -and space, the rest of the line is example source code. If you put ``'r'`` -before the docstring, you can use ``\n`` for newline and ``\t`` for tab. +These examples are verified automatically when ``pycodestyle.py`` is run with +the ``--doctest`` option. You can add examples for your own check functions. +The format is simple: ``"Okay"`` or error/warning code followed by colon and +space, the rest of the line is example source code. If you put ``'r'`` before +the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: diff --git a/docs/intro.rst b/docs/intro.rst index 2281ef15..5a46a026 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -18,7 +18,7 @@ Features * Parseable output: Jump to error location in your editor. * Small: Just one Python file, requires only stdlib. You can use just - the pycodestyle.py file for this purpose. + the ``pycodestyle.py`` file for this purpose. * Comes with a comprehensive test suite. @@ -55,7 +55,7 @@ the ``pycodestyle`` library: Installation ------------ -You can install, upgrade, uninstall pycodestyle.py with these commands:: +You can install, upgrade, uninstall ``pycodestyle.py`` with these commands:: $ pip install pycodestyle $ pip install --upgrade pycodestyle @@ -77,7 +77,7 @@ Example usage and output optparse.py:472:29: E221 multiple spaces before operator optparse.py:544:21: W601 .has_key() is deprecated, use 'in' -You can also make pycodestyle.py show the source code for each error, and +You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: $ pycodestyle --show-source --show-pep8 testsuite/E40.py @@ -106,8 +106,8 @@ Or you can display how often each error was found:: 612 W601 .has_key() is deprecated, use 'in' 1188 W602 deprecated form of raising exception -You can also make pycodestyle.py show the error text in different formats by -using --format having options default/pylint/custom:: +You can also make ``pycodestyle.py`` show the error text in different formats by +using ``--format`` having options default/pylint/custom:: $ pycodestyle testsuite/E40.py --format=default testsuite/E40.py:2:10: E401 multiple imports on one line From f2ade60c2fd96dd7731d8ec62b3cbe6338823954 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 11:57:01 -0700 Subject: [PATCH 047/412] Removed project level `.pep8` config file Closes #364 --- CHANGES.txt | 1 + docs/intro.rst | 5 ++--- pycodestyle.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 40582065..d4b63d0a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,6 +15,7 @@ Changes: * Added tox test support for Python 3.5 and pypy3 * Added check E275 for whitespace on `from ... import ...` lines; #489 / #491 * Added W503 to the list of codes ignored by default ignore list; #498 +* Removed use of project level `.pep8` configuration file; #364 Bugs: diff --git a/docs/intro.rst b/docs/intro.rst index 5a46a026..12f67d7e 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -199,9 +199,8 @@ Example:: max-line-length = 160 At the project level, a ``setup.cfg`` file or a ``tox.ini`` file is read if -present (``.pep8`` file is also supported, but it is deprecated). If none of -these files have a ``[pep8]`` section, no project specific configuration is -loaded. +present. If none of these files have a ``[pep8]`` section, no project specific +configuration is loaded. Error codes diff --git a/pycodestyle.py b/pycodestyle.py index de40b078..dea67cd1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -79,7 +79,7 @@ except ImportError: USER_CONFIG = None -PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8') +PROJECT_CONFIG = ('setup.cfg', 'tox.ini') TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') MAX_LINE_LENGTH = 79 REPORT_FORMAT = { From c7490b0f3ff1d18fae8c7f12b835885dc3bd19fc Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 11:59:34 -0700 Subject: [PATCH 048/412] Tagging 2.0.0 alpha 1 pre-release --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index dea67cd1..b764f89d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -64,7 +64,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.0.0.dev0' +__version__ = '2.0.0a1' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From fd4f1c286cdc4e0bc25822e10742adbc9062af15 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 16:56:02 -0700 Subject: [PATCH 049/412] Tagged release 2.0.0 --- CHANGES.txt | 2 +- pycodestyle.py | 17 +---------------- setup.py | 1 - 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d4b63d0a..e129dc87 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,7 +2,7 @@ Changelog ========= -2.0.0 (unreleased) +2.0.0 (2016-05-31) ------------------ Announcements: diff --git a/pycodestyle.py b/pycodestyle.py index b764f89d..70263968 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -64,7 +64,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.0.0a1' +__version__ = '2.0.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' @@ -2184,20 +2184,5 @@ def _main(): sys.exit(1) -def _main_pep8(): - """Entrypoint for pep8 commandline tool. - - Warn of deprecation and advise users to switch to pycodestyle. - """ - print( - 'Deprecation Warning:\n' - 'Use of the pep8 tool will be removed in a future release.\n' - 'Please install and use `pycodestyle` instead.\n' - ) - warnings.warn('pep8 has been renamed to pycodestyle (GitHub issue #466)') - - _main() - - if __name__ == '__main__': _main() diff --git a/setup.py b/setup.py index 93a1337e..b77770e5 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,6 @@ def get_long_description(): entry_points={ 'console_scripts': [ 'pycodestyle = pycodestyle:_main', - 'pep8 = pycodestyle:_main_pep8', ], }, classifiers=[ From 6fe11c44ec25a29a0ecc397acef06f42db910cad Mon Sep 17 00:00:00 2001 From: Kevin Cole Date: Thu, 2 Jun 2016 09:49:39 -0700 Subject: [PATCH 050/412] - Added venv/ (virtualenv) to list of git ignored filename patterns --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7eac23f2..7cd7441f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist docs/_build build/ +venv/ From 594908607f98225884515bf225fb5e7a735e6722 Mon Sep 17 00:00:00 2001 From: Art Hall Date: Thu, 2 Jun 2016 11:03:14 -0700 Subject: [PATCH 051/412] Modified patch for issue #314 so tests in E73.py pass --- pycodestyle.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index e0335479..e52cfbdf 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -965,8 +965,12 @@ def compound_statements(logical_line): if ((counts['{'] <= counts['}'] and # {'a': 1} (dict) counts['['] <= counts[']'] and # [1:2] (slice) counts['('] <= counts[')'])): # (annotation) - if LAMBDA_REGEX.search(line, 0, found): - yield 0, "E731 do not assign a lambda expression, use a def" + lambda_kw = LAMBDA_REGEX.search(line, 0, found) + if lambda_kw: + before = line[:lambda_kw.start()].rstrip() + if before[-1:] == '=' and isidentifier(before[:-1].strip()): + yield 0, ("E731 do not assign a lambda expression, use a " + "def") break if line.startswith('def '): yield 0, "E704 multiple statements on one line (def)" From a99bcecd4bb690271b5f0c92b22d3b2c8c872ec7 Mon Sep 17 00:00:00 2001 From: Mike Miller Date: Thu, 2 Jun 2016 12:41:11 -0700 Subject: [PATCH 052/412] Never use 'l' (lowercase letter el) as a variable name --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 70263968..92fe12a0 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1219,7 +1219,7 @@ def readlines(filename): with open(filename, 'rb') as f: (coding, lines) = tokenize.detect_encoding(f.readline) f = TextIOWrapper(f, coding, line_buffering=True) - return [l.decode(coding) for l in lines] + f.readlines() + return [line.decode(coding) for line in lines] + f.readlines() except (LookupError, SyntaxError, UnicodeError): # Fall back if file encoding is improperly declared with open(filename, encoding='latin-1') as f: From 26893dafe4562caefeb200c990972bc6262842d9 Mon Sep 17 00:00:00 2001 From: Mike Miller Date: Thu, 2 Jun 2016 12:44:23 -0700 Subject: [PATCH 053/412] Report E741 for prohibited single-letter variables Check for prohibited identifiers occuring to the lhs of an assignment operator or after the 'as' keyword. Closes #341 --- CHANGES.txt | 1 + docs/intro.rst | 2 ++ pycodestyle.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index e129dc87..f2ac1de6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,7 @@ Changes: * Added check E275 for whitespace on `from ... import ...` lines; #489 / #491 * Added W503 to the list of codes ignored by default ignore list; #498 * Removed use of project level `.pep8` configuration file; #364 +* Added check E741 for using variables named 'l', 'O', or 'I'; #341 Bugs: diff --git a/docs/intro.rst b/docs/intro.rst index 12f67d7e..bfa67860 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -358,6 +358,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E731 | do not assign a lambda expression, use a def | +------------+----------------------------------------------------------------------+ +| E741 | do not use variables named 'l', 'O', or 'I' | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **E9** | *Runtime* | +------------+----------------------------------------------------------------------+ diff --git a/pycodestyle.py b/pycodestyle.py index 92fe12a0..ad1b463d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1150,6 +1150,37 @@ def comparison_type(logical_line, noqa): yield match.start(), "E721 do not compare types, use 'isinstance()'" +def ambiguous_identifier(logical_line, tokens): + r"""Never use the characters 'l', 'O', or 'I' as variable names. + + In some fonts, these characters are indistinguishable from the numerals + one and zero. When tempted to use 'l', use 'L' instead. + + Okay: L = 0 + E741: l = 0 + """ + idents_to_avoid = ('l', 'O', 'I') + prev_type, prev_text, prev_start, prev_end, __ = tokens[0] + for token_type, text, start, end, line in tokens[1:]: + ident = pos = None + # identifiers on the lhs of an assignment operator + if token_type == tokenize.OP and '=' in text: + if prev_text in idents_to_avoid: + ident = prev_text + pos = prev_start + # identifiers after 'as' + if prev_text == 'as': + if text in idents_to_avoid: + ident = text + pos = start + if ident: + yield pos, "E741 ambiguous variable name '%s'" % ident + prev_type = token_type + prev_text = text + prev_start = start + prev_end = end + + def python_3000_has_key(logical_line, noqa): r"""The {}.has_key() method is removed in Python 3: use the 'in' operator. From 4c8152c7da74d7f7e20d3ae98b52581ef1b0f16b Mon Sep 17 00:00:00 2001 From: Mike Miller Date: Thu, 2 Jun 2016 16:27:33 -0700 Subject: [PATCH 054/412] Add more doctests for E741 --- pycodestyle.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index ad1b463d..38648a36 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1157,7 +1157,19 @@ def ambiguous_identifier(logical_line, tokens): one and zero. When tempted to use 'l', use 'L' instead. Okay: L = 0 + Okay: o = 123 + Okay: i = 42 E741: l = 0 + E741: O = 123 + E741: I = 42 + + Variables can be bound in other contexts, so identifiers appearing after + the 'as' keyword are also reported: + + Okay: except AttributeError as o: + Okay: with lock as L: + E741: except AttributeError as O: + E741: with lock as l: """ idents_to_avoid = ('l', 'O', 'I') prev_type, prev_text, prev_start, prev_end, __ = tokens[0] From 017d3b5da0c37bd48df3778752bfa98b4eed6d5e Mon Sep 17 00:00:00 2001 From: Mike Miller Date: Fri, 3 Jun 2016 10:02:08 -0700 Subject: [PATCH 055/412] Also report E741 on 'global' and 'nonlocal' statements --- pycodestyle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 38648a36..f92d8606 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1180,8 +1180,8 @@ def ambiguous_identifier(logical_line, tokens): if prev_text in idents_to_avoid: ident = prev_text pos = prev_start - # identifiers after 'as' - if prev_text == 'as': + # identifiers bound to a value with 'as', 'global', or 'nonlocal' + if prev_text in ('as', 'global', 'nonlocal'): if text in idents_to_avoid: ident = text pos = start From fdf3e6a2655c8ddd465781e1c2693886a8aa762f Mon Sep 17 00:00:00 2001 From: Mike Miller Date: Fri, 3 Jun 2016 10:03:24 -0700 Subject: [PATCH 056/412] Report E742 and E743 for badly named functions and classes --- docs/intro.rst | 4 ++++ pycodestyle.py | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index bfa67860..11b0e0c8 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -360,6 +360,10 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E741 | do not use variables named 'l', 'O', or 'I' | +------------+----------------------------------------------------------------------+ +| E742 | do not define classes named 'l', 'O', or 'I' | ++------------+----------------------------------------------------------------------+ +| E743 | do not define functions named 'l', 'O', or 'I' | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **E9** | *Runtime* | +------------+----------------------------------------------------------------------+ diff --git a/pycodestyle.py b/pycodestyle.py index f92d8606..11cb8795 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1163,13 +1163,18 @@ def ambiguous_identifier(logical_line, tokens): E741: O = 123 E741: I = 42 - Variables can be bound in other contexts, so identifiers appearing after - the 'as' keyword are also reported: + Variables can be bound in several other contexts, including class and + function definitions, 'global' and 'nonlocal' statements, exception + handlers, and 'with' statements. Okay: except AttributeError as o: Okay: with lock as L: E741: except AttributeError as O: E741: with lock as l: + E741: global I + E741: nonlocal l + E742: class I(object): + E743: def l(x): """ idents_to_avoid = ('l', 'O', 'I') prev_type, prev_text, prev_start, prev_end, __ = tokens[0] @@ -1185,6 +1190,12 @@ def ambiguous_identifier(logical_line, tokens): if text in idents_to_avoid: ident = text pos = start + if prev_text == 'class': + if text in idents_to_avoid: + yield start, "E742 ambiguous class definition '%s'" % text + if prev_text == 'def': + if text in idents_to_avoid: + yield start, "E743 ambiguous function definition '%s'" % text if ident: yield pos, "E741 ambiguous variable name '%s'" % ident prev_type = token_type From b98ed7b90c32e1348fa9d6ecb0ac988bce7d73b9 Mon Sep 17 00:00:00 2001 From: Mike Miller Date: Fri, 3 Jun 2016 10:31:56 -0700 Subject: [PATCH 057/412] Update Python version list in docs --- docs/developer.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer.rst b/docs/developer.rst index 3acf2a60..f18fe62b 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -14,7 +14,7 @@ conditions of the :ref:`Expat license `. Fork away! * `Source code `_ and `issue tracker `_ on GitHub. * `Continuous tests `_ against Python - 2.6 through 3.4 and PyPy, on `Travis-CI platform + 2.6 through 3.5 and PyPy, on `Travis-CI platform `_. .. _available on GitHub: https://github.com/pycqa/pycodestyle From 26c64badd9ea27cbfbff311f7b3b09b475e80ead Mon Sep 17 00:00:00 2001 From: MaxPayton Date: Thu, 2 Jun 2016 16:29:56 -0700 Subject: [PATCH 058/412] Change all references to the pep8 project to say pycodestyle This fixes issue #518, pep8 still referenced in the cli help command As a side effect, `[pep8]` in setup.cfg now becomes `[pycodestyle]` Also, changed the path for the config file from ~/.config/pep8 to ~/.config/pycodestyle These feel like changes that should have come with the jump to version 2.0.0, as they are breaking, but support for as a name can still be added if it's desired enough --- docs/intro.rst | 21 +++++++++--------- pycodestyle.py | 10 ++++----- setup.cfg | 2 +- testsuite/test_api.py | 2 +- testsuite/test_parser.py | 8 +++---- testsuite/test_shell.py | 48 ++++++++++++++++++++++------------------ 6 files changed, 48 insertions(+), 43 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 12f67d7e..ce716920 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -167,13 +167,14 @@ Quick help is available on the command line:: --benchmark measure processing speed Configuration: - The project options are read from the [pep8] section of the tox.ini - file or the setup.cfg file located in any parent folder of the path(s) - being processed. Allowed options are: exclude, filename, select, + The project options are read from the [pycodestyle] section of the + tox.ini file or the setup.cfg file located in any parent folder of the + path(s) being processed. Allowed options are: exclude, filename, select, ignore, max-line-length, hang-closing, count, format, quiet, show-pep8, show-source, statistics, verbose. - --config=path user config file location (default: ~/.config/pep8) + --config=path user config file location + (default: ~/.config/pycodestyle) Configuration @@ -184,23 +185,23 @@ The behaviour may be configured at two levels, the user and project levels. At the user level, settings are read from the following locations: If on Windows: - ``~\.pep8`` + ``~\.pycodestyle`` Otherwise, if the :envvar:`XDG_CONFIG_HOME` environment variable is defined: - ``XDG_CONFIG_HOME/pep8`` + ``XDG_CONFIG_HOME/pycodestyle`` Else if :envvar:`XDG_CONFIG_HOME` is not defined: - ``~/.config/pep8`` + ``~/.config/pycodestyle`` Example:: - [pep8] + [pycodestyle] ignore = E226,E302,E41 max-line-length = 160 At the project level, a ``setup.cfg`` file or a ``tox.ini`` file is read if -present. If none of these files have a ``[pep8]`` section, no project specific -configuration is loaded. +present. If none of these files have a ``[pycodestyle]`` section, no project +specific configuration is loaded. Error codes diff --git a/pycodestyle.py b/pycodestyle.py index e52cfbdf..df249119 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -70,11 +70,11 @@ DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' try: if sys.platform == 'win32': - USER_CONFIG = os.path.expanduser(r'~\.pep8') + USER_CONFIG = os.path.expanduser(r'~\.pycodestyle') else: USER_CONFIG = os.path.join( os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), - 'pep8' + 'pycodestyle' ) except ImportError: USER_CONFIG = None @@ -1965,7 +1965,7 @@ def get_checks(self, argument_name): return sorted(checks) -def get_parser(prog='pep8', version=__version__): +def get_parser(prog='pycodestyle', version=__version__): """Create the parser for the program.""" parser = OptionParser(prog=prog, version=version, usage="%prog [options] input ...") @@ -2033,7 +2033,7 @@ def read_config(options, args, arglist, parser): If a config file is specified on the command line with the "--config" option, then only it is used for configuration. - Otherwise, the user configuration (~/.config/pep8) and any local + Otherwise, the user configuration (~/.config/pycodestyle) and any local configurations in the current directory or above will be merged together (in that order) using the read method of ConfigParser. """ @@ -2101,7 +2101,7 @@ def process_options(arglist=None, parse_argv=False, config_file=None, """Process options passed either via arglist or via command line args. Passing in the ``config_file`` parameter allows other tools, such as flake8 - to specify their own options to be processed in pep8. + to specify their own options to be processed in pycodestyle. """ if not parser: parser = get_parser() diff --git a/setup.cfg b/setup.cfg index 65ca96de..803bc10f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [wheel] universal = 1 -[pep8] +[pycodestyle] select = ignore = E226,E24 max_line_length = 79 diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 6549a46c..7d381ade 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -172,7 +172,7 @@ def test_styleguide_options(self): def test_styleguide_ignore_code(self): def parse_argv(argstring): _saved_argv = sys.argv - sys.argv = shlex.split('pep8 %s /dev/null' % argstring) + sys.argv = shlex.split('pycodestyle %s /dev/null' % argstring) try: return pycodestyle.StyleGuide(parse_argv=True) finally: diff --git a/testsuite/test_parser.py b/testsuite/test_parser.py index 26a45fc6..0e546234 100644 --- a/testsuite/test_parser.py +++ b/testsuite/test_parser.py @@ -19,7 +19,7 @@ class ParserTestCase(unittest.TestCase): def test_vanilla_ignore_parsing(self): contents = b""" -[pep8] +[pycodestyle] ignore = E226,E24 """ options, args = _process_file(contents) @@ -28,7 +28,7 @@ def test_vanilla_ignore_parsing(self): def test_multiline_ignore_parsing(self): contents = b""" -[pep8] +[pycodestyle] ignore = E226, E24 @@ -40,7 +40,7 @@ def test_multiline_ignore_parsing(self): def test_trailing_comma_ignore_parsing(self): contents = b""" -[pep8] +[pycodestyle] ignore = E226, """ @@ -50,7 +50,7 @@ def test_trailing_comma_ignore_parsing(self): def test_multiline_trailing_comma_ignore_parsing(self): contents = b""" -[pep8] +[pycodestyle] ignore = E226, E24, diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index 760c2284..a80c8757 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -19,7 +19,7 @@ def setUp(self): self._saved_stdin_get_value = pycodestyle.stdin_get_value self._config_filenames = [] self.stdin = '' - sys.argv = ['pep8'] + sys.argv = ['pycodestyle'] sys.stdout = PseudoFile() sys.stderr = PseudoFile() @@ -39,7 +39,7 @@ def tearDown(self): def stdin_get_value(self): return self.stdin - def pep8(self, *args): + def pycodestyle(self, *args): del sys.stdout[:], sys.stderr[:] sys.argv[1:] = args try: @@ -50,28 +50,30 @@ def pep8(self, *args): return sys.stdout.getvalue(), sys.stderr.getvalue(), errorcode def test_print_usage(self): - stdout, stderr, errcode = self.pep8('--help') + stdout, stderr, errcode = self.pycodestyle('--help') self.assertFalse(errcode) self.assertFalse(stderr) - self.assertTrue(stdout.startswith("Usage: pep8 [options] input")) + self.assertTrue(stdout.startswith( + "Usage: pycodestyle [options] input" + )) - stdout, stderr, errcode = self.pep8('--version') + stdout, stderr, errcode = self.pycodestyle('--version') self.assertFalse(errcode) self.assertFalse(stderr) self.assertEqual(stdout.count('\n'), 1) - stdout, stderr, errcode = self.pep8('--obfuscated') + stdout, stderr, errcode = self.pycodestyle('--obfuscated') self.assertEqual(errcode, 2) self.assertEqual(stderr.splitlines(), - ["Usage: pep8 [options] input ...", "", - "pep8: error: no such option: --obfuscated"]) + ["Usage: pycodestyle [options] input ...", "", + "pycodestyle: error: no such option: --obfuscated"]) self.assertFalse(stdout) self.assertFalse(self._config_filenames) def test_check_simple(self): E11 = os.path.join(ROOT_DIR, 'testsuite', 'E11.py') - stdout, stderr, errcode = self.pep8(E11) + stdout, stderr, errcode = self.pycodestyle(E11) stdout = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) @@ -82,20 +84,20 @@ def test_check_simple(self): self.assertEqual(x, str(num)) self.assertEqual(y, str(col)) self.assertTrue(msg.startswith(' E11')) - # Config file read from the pep8's setup.cfg + # Config file read from the pycodestyle's setup.cfg config_filenames = [os.path.basename(p) for p in self._config_filenames] self.assertTrue('setup.cfg' in config_filenames) def test_check_stdin(self): pycodestyle.PROJECT_CONFIG = () - stdout, stderr, errcode = self.pep8('-') + stdout, stderr, errcode = self.pycodestyle('-') self.assertFalse(errcode) self.assertFalse(stderr) self.assertFalse(stdout) self.stdin = 'import os, sys\n' - stdout, stderr, errcode = self.pep8('-') + stdout, stderr, errcode = self.pycodestyle('-') stdout = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) @@ -104,7 +106,7 @@ def test_check_stdin(self): def test_check_non_existent(self): self.stdin = 'import os, sys\n' - stdout, stderr, errcode = self.pep8('fictitious.py') + stdout, stderr, errcode = self.pycodestyle('fictitious.py') self.assertEqual(errcode, 1) self.assertFalse(stderr) self.assertTrue(stdout.startswith('fictitious.py:1:1: E902 ')) @@ -112,11 +114,11 @@ def test_check_non_existent(self): def test_check_noarg(self): # issue #170: do not read stdin by default pycodestyle.PROJECT_CONFIG = () - stdout, stderr, errcode = self.pep8() + stdout, stderr, errcode = self.pycodestyle() self.assertEqual(errcode, 2) self.assertEqual(stderr.splitlines(), - ["Usage: pep8 [options] input ...", "", - "pep8: error: input not specified"]) + ["Usage: pycodestyle [options] input ...", "", + "pycodestyle: error: input not specified"]) self.assertFalse(self._config_filenames) def test_check_diff(self): @@ -136,7 +138,7 @@ def test_check_diff(self): ] self.stdin = '\n'.join(diff_lines) - stdout, stderr, errcode = self.pep8('--diff') + stdout, stderr, errcode = self.pycodestyle('--diff') stdout = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) @@ -149,7 +151,7 @@ def test_check_diff(self): diff_lines[:2] = ["--- a/testsuite/E11.py 2006-06-01 08:49 +0400", "+++ b/testsuite/E11.py 2008-04-06 17:36 +0400"] self.stdin = '\n'.join(diff_lines) - stdout, stderr, errcode = self.pep8('--diff') + stdout, stderr, errcode = self.pycodestyle('--diff') stdout = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) @@ -167,7 +169,7 @@ def test_check_diff(self): "@@ -5,0 +6 @@ if True:", "+ print"] self.stdin = '\n'.join(diff_lines) - stdout, stderr, errcode = self.pep8('--diff') + stdout, stderr, errcode = self.pycodestyle('--diff') (stdout,) = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) @@ -175,15 +177,17 @@ def test_check_diff(self): # missing '--diff' self.stdin = '\n'.join(diff_lines) - stdout, stderr, errcode = self.pep8() + stdout, stderr, errcode = self.pycodestyle() self.assertEqual(errcode, 2) self.assertFalse(stdout) - self.assertTrue(stderr.startswith('Usage: pep8 [options] input ...')) + self.assertTrue(stderr.startswith( + 'Usage: pycodestyle [options] input ...' + )) # no matching file in the diff diff_lines[3] = "+++ b/testsuite/lost/E11.py" self.stdin = '\n'.join(diff_lines) - stdout, stderr, errcode = self.pep8('--diff') + stdout, stderr, errcode = self.pycodestyle('--diff') self.assertFalse(errcode) self.assertFalse(stdout) self.assertFalse(stderr) From 64edd89c9e487bc202b63ed7e6fc1ff530687f84 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 22:19:13 -0700 Subject: [PATCH 059/412] Bumped version number post release --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index df249119..f5796c7f 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -64,7 +64,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.0.0' +__version__ = '2.0.1.dev0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From e8d358eb27f43544480396ef15fed6b4563537b2 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 1 Jun 2016 14:08:17 -0700 Subject: [PATCH 060/412] Re-ordered package imports alphabetically --- pycodestyle.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index f5796c7f..d0a97c6c 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -48,16 +48,18 @@ """ from __future__ import with_statement +import inspect +import keyword import os -import sys import re +import sys import time -import inspect -import keyword import tokenize import warnings -from optparse import OptionParser + from fnmatch import fnmatch +from optparse import OptionParser + try: from configparser import RawConfigParser from io import TextIOWrapper From adcb3a295893b9f260766594cc8c36573ee70aa2 Mon Sep 17 00:00:00 2001 From: Susan Tan Date: Fri, 3 Jun 2016 12:17:41 -0700 Subject: [PATCH 061/412] Fix typos --- pycodestyle.py | 4 ++-- testsuite/test_api.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index d0a97c6c..16cf8e21 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -122,7 +122,7 @@ LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') -# Work around Python < 2.6 behaviour, which does not generate NL after +# Work around Python < 2.6 behavior, which does not generate NL after # a comment which is on a line by itself. COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n' @@ -1339,7 +1339,7 @@ def filename_match(filename, patterns, default=True): def update_counts(s, counts): - r"""Adds one to the counts of each appearence of characters in s, + r"""Adds one to the counts of each appearance of characters in s, for characters in counts""" for char in s: if char in counts: diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 7d381ade..2eb5cf3d 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -138,7 +138,7 @@ def test_styleguide(self): self.reset() def test_styleguide_options(self): - # Instanciate a simple checker + # Instanstiate a simple checker pep8style = pycodestyle.StyleGuide(paths=[E11]) # Check style's attributes From 7ffd648b73d289598779e6d81c2d346a36b99eb1 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 6 Jun 2016 15:31:33 -0700 Subject: [PATCH 062/412] Reverted a few spelling changes Based on feedback on pull request #534 on GitHub Closes #534 --- pycodestyle.py | 2 +- testsuite/test_api.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 16cf8e21..1de3c2b0 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -122,7 +122,7 @@ LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') -# Work around Python < 2.6 behavior, which does not generate NL after +# Work around Python < 2.6 behaviour, which does not generate NL after # a comment which is on a line by itself. COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n' diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 2eb5cf3d..4d8b7b26 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -138,7 +138,7 @@ def test_styleguide(self): self.reset() def test_styleguide_options(self): - # Instanstiate a simple checker + # Instantiate a simple checker pep8style = pycodestyle.StyleGuide(paths=[E11]) # Check style's attributes From 5be92ad0bf9c1ea6276a6e8614e2ef47e912d687 Mon Sep 17 00:00:00 2001 From: Susan Tan Date: Fri, 3 Jun 2016 11:38:04 -0700 Subject: [PATCH 063/412] Add test-requirements.txt with basic test packages --- docs/developer.rst | 7 +++++++ test-requirements.txt | 1 + 2 files changed, 8 insertions(+) create mode 100644 test-requirements.txt diff --git a/docs/developer.rst b/docs/developer.rst index f18fe62b..054c011c 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -103,6 +103,13 @@ Then be sure to pass the tests:: When contributing to pycodestyle, please observe our `Code of Conduct`_. +To run the Travis CI tests, you'll have to create a build the pycodestyle package using tox: + + $ pip install -r test-requirements.txt + $ tox + +All the tests should pass. + .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ .. _Code of Conduct: http://meta.pycqa.org/en/latest/code-of-conduct.html diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..5ef8c7be --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +tox>=2.3.1 \ No newline at end of file From 45503fc4f123b41d6bffd19fe224cf6f386f1495 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 6 Jun 2016 15:43:13 -0700 Subject: [PATCH 064/412] Updated developer documentation for testing This builds upon and closes #533 --- dev-requirements.txt | 1 + docs/developer.rst | 11 +++++++---- test-requirements.txt | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 dev-requirements.txt delete mode 100644 test-requirements.txt diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 00000000..053148f8 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1 @@ +tox diff --git a/docs/developer.rst b/docs/developer.rst index 054c011c..4779c788 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -103,12 +103,15 @@ Then be sure to pass the tests:: When contributing to pycodestyle, please observe our `Code of Conduct`_. -To run the Travis CI tests, you'll have to create a build the pycodestyle package using tox: +To run the Travis CI tests, you'll have to create a build the pycodestyle +package using tox:: - $ pip install -r test-requirements.txt - $ tox + $ pip install -r dev-requirements.txt + $ tox -All the tests should pass. +All the tests should pass for all available interpreters, with the summary of:: + + congratulations :) .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ .. _Code of Conduct: http://meta.pycqa.org/en/latest/code-of-conduct.html diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 5ef8c7be..00000000 --- a/test-requirements.txt +++ /dev/null @@ -1 +0,0 @@ -tox>=2.3.1 \ No newline at end of file From fc8c7995248c9a134ab7627838a81a2ad5a3c606 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 6 Jun 2016 15:43:44 -0700 Subject: [PATCH 065/412] Added skip missing interpreters flag to tox config --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index a4992e99..7218f177 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ [tox] envlist = py26, py27, py32, py33, py34, py35, pypy, pypy3, jython +skip_missing_interpreters=True [testenv] commands = From ca7e43b493d682589f918c5bc02aaf29b57e2f2a Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 6 Jun 2016 16:04:18 -0700 Subject: [PATCH 066/412] Fixed wrapping of text in developer.rst --- docs/developer.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/developer.rst b/docs/developer.rst index 4779c788..c921ae73 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -27,7 +27,8 @@ Some high-level aims and directions to bear in mind for contributions: * ``pycodestyle`` is intended to be as fast as possible. Using the ``ast`` module defeats that purpose. - The `pep8-naming `_ plugin exists for this sort of functionality. + The `pep8-naming `_ plugin exists + for this sort of functionality. * If you want to provide extensibility / plugins, please see `flake8 `_ - ``pycodestyle`` doesn't want or need a plugin architecture. From 42c898716cb071cc0f8c74db4a40e6781b3b2ef2 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 2 Jun 2016 18:07:50 -0700 Subject: [PATCH 067/412] Added passing test case related to #376 This test passes currently, but would need to be updated if the functionality changes. --- testsuite/E10.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/testsuite/E10.py b/testsuite/E10.py index cd142e39..7b425945 100644 --- a/testsuite/E10.py +++ b/testsuite/E10.py @@ -38,4 +38,8 @@ def test_keys(self): u'Norrbotten', u'V\xe4sterbotten', ]) -#: +#: E101 W191 +if True: + print(""" + tab at start of this line +""") From d2171cd3b6664b8afe8b51071b835de2c29c3c30 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 6 Jun 2016 16:19:06 -0700 Subject: [PATCH 068/412] Updated docs related to tested Python versions --- docs/developer.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developer.rst b/docs/developer.rst index c921ae73..2c8ce573 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -14,8 +14,8 @@ conditions of the :ref:`Expat license `. Fork away! * `Source code `_ and `issue tracker `_ on GitHub. * `Continuous tests `_ against Python - 2.6 through 3.5 and PyPy, on `Travis-CI platform - `_. + 2.6 through 3.5 as well as the nightly Python build and PyPy, on `Travis-CI + platform `_. .. _available on GitHub: https://github.com/pycqa/pycodestyle From e15075a4fba34030bf3cfcd5cabd7fa6d78f7c10 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 6 Jun 2016 16:19:47 -0700 Subject: [PATCH 069/412] Updated Travis CI doc link --- docs/developer.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer.rst b/docs/developer.rst index 2c8ce573..8be3fe3c 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -15,7 +15,7 @@ conditions of the :ref:`Expat license `. Fork away! `issue tracker `_ on GitHub. * `Continuous tests `_ against Python 2.6 through 3.5 as well as the nightly Python build and PyPy, on `Travis-CI - platform `_. + platform `_. .. _available on GitHub: https://github.com/pycqa/pycodestyle From 60859d6408d1479ec40d80f73f37d34d914704f1 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 6 Jun 2016 17:17:10 -0700 Subject: [PATCH 070/412] Updated version number of next release to 2.1.0 --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 1de3c2b0..f2f4284f 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -66,7 +66,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.0.1.dev0' +__version__ = '2.1.0.dev0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From 6cb59d685e635a72db2230d75b61aed99ac86b29 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 6 Jun 2016 17:27:19 -0700 Subject: [PATCH 071/412] Updated changelog to specify changes in 2.1.0 --- CHANGES.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index f2ac1de6..aa080851 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Changelog ========= +2.1.0 (unreleased) +------------------ + +Changes: + +* Added check E74x for using variables named 'l', 'O', or 'I'; #341 + 2.0.0 (2016-05-31) ------------------ @@ -16,7 +23,6 @@ Changes: * Added check E275 for whitespace on `from ... import ...` lines; #489 / #491 * Added W503 to the list of codes ignored by default ignore list; #498 * Removed use of project level `.pep8` configuration file; #364 -* Added check E741 for using variables named 'l', 'O', or 'I'; #341 Bugs: From 7fa71afceea5cb045bb1d8508c2341d19396c09d Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 7 Jun 2016 15:18:59 -0500 Subject: [PATCH 072/412] Update maximum_line_length to use Checker.noqa This also updates the Checker to update it's state for physical lines as well as logical lines. This allows maximum_line_length to rely on Checker state for its noqa check. Closes #538 --- pycodestyle.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index a5141f2f..1ef837c1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -199,7 +199,7 @@ def trailing_blank_lines(physical_line, lines, line_number, total_lines): return len(physical_line), "W292 no newline at end of file" -def maximum_line_length(physical_line, max_line_length, multiline): +def maximum_line_length(physical_line, max_line_length, multiline, noqa): r"""Limit all lines to a maximum of 79 characters. There are still many devices around that are limited to 80 character @@ -213,7 +213,7 @@ def maximum_line_length(physical_line, max_line_length, multiline): """ line = physical_line.rstrip() length = len(line) - if length > max_line_length and not noqa(line): + if length > max_line_length and not noqa: # Special case for long URLs in multi-line docstrings or comments, # but still report the error when the 72 first chars are whitespaces. chunks = line.split() @@ -1500,6 +1500,7 @@ def __init__(self, filename=None, lines=None, self.lines[0] = self.lines[0][3:] self.report = report or options.report self.report_error = self.report.error + self.noqa = False def report_invalid_syntax(self): """Check if the syntax is valid.""" @@ -1634,6 +1635,7 @@ def generate_tokens(self): for token in tokengen: if token[2][0] > self.total_lines: return + self.noqa = token[4] and noqa(token[4]) self.maybe_check_physical(token) yield token except (SyntaxError, tokenize.TokenError): From 95ed8490093efaaa119f3227894481c4a0c05599 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 7 Jun 2016 15:27:32 -0500 Subject: [PATCH 073/412] Remove unused imports and assignments Neither the warnings modules, nor the two local assignments in pycodestyle's newest check were being used. This removes them and fixes pyflakes errors raised as a result. --- pycodestyle.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index a5141f2f..b5f6ef13 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -55,7 +55,6 @@ import sys import time import tokenize -import warnings from fnmatch import fnmatch from optparse import OptionParser @@ -1203,10 +1202,8 @@ def ambiguous_identifier(logical_line, tokens): yield start, "E743 ambiguous function definition '%s'" % text if ident: yield pos, "E741 ambiguous variable name '%s'" % ident - prev_type = token_type prev_text = text prev_start = start - prev_end = end def python_3000_has_key(logical_line, noqa): From 15b1b422a0b3be5d870a01d5fbae1af6265c583f Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 12:56:11 -0700 Subject: [PATCH 074/412] Updated text about running tests with tox; Closes #552 --- docs/developer.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/developer.rst b/docs/developer.rst index 8be3fe3c..db36538d 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -104,8 +104,7 @@ Then be sure to pass the tests:: When contributing to pycodestyle, please observe our `Code of Conduct`_. -To run the Travis CI tests, you'll have to create a build the pycodestyle -package using tox:: +To run the tests, the core developer team and Travis-CI use tox:: $ pip install -r dev-requirements.txt $ tox From e01ca2f9de13e570d567eaad785fa78e096e34ef Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 13:01:19 -0700 Subject: [PATCH 075/412] Updated to use sheilds.io badges --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 466509b1..1f0faaaf 100644 --- a/README.rst +++ b/README.rst @@ -83,11 +83,11 @@ Or you can display how often each error was found:: Links ----- -.. image:: https://api.travis-ci.org/PyCQA/pycodestyle.png?branch=master +.. image:: https://img.shields.io/travis/PyCQA/pycodestyle.svg :target: https://travis-ci.org/PyCQA/pycodestyle :alt: Build status -.. image:: https://pypip.in/wheel/pycodestyle/badge.png?branch=master +.. image:: https://img.shields.io/pypi/wheel/pycodestyle.svg :target: https://pypi.python.org/pypi/pycodestyle :alt: Wheel Status From 5a6ffd5fc0eaf599b3e3f6b97092391adb0592aa Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 13:36:56 -0700 Subject: [PATCH 076/412] Updated rename note in README --- README.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 1f0faaaf..d861c633 100644 --- a/README.rst +++ b/README.rst @@ -9,8 +9,12 @@ conventions in `PEP 8`_. .. note:: This package used to be called ``pep8`` but was renamed to ``pycodestyle`` - to reduce confusion. Further discussion `here - `_. + to reduce confusion. Further discussion can be found `in the issue where + Guido requested this + change `_, or in the + lightning talk at PyCon 2016 by @IanLee1521: + `slides `_ + `video `_. Features -------- From dc08dadd84369185f3c19782fd727dbf5029a056 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 14:13:41 -0700 Subject: [PATCH 077/412] Updated changelog with 2.1 milestone changes --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index aa080851..84fcb6c6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,9 @@ Changelog Changes: * Added check E74x for using variables named 'l', 'O', or 'I'; #341 +* Improved performance of `compound_statements` check; #314 / #522 +* Fixed remaining references to `pep8`; #518 / #530 +* Added `noqa` support for `maximum_line_length` check; #538 2.0.0 (2016-05-31) From 6b75c4de380c5e786d277851a7da8a581da53bcb Mon Sep 17 00:00:00 2001 From: Nikhil Benesch Date: Tue, 19 May 2015 20:40:33 -0400 Subject: [PATCH 078/412] Fix issue #400: Require two blank lines after toplevel def, class --- CHANGES.txt | 1 + docs/intro.rst | 2 ++ pycodestyle.py | 13 ++++++++++++- testsuite/E12not.py | 3 +++ testsuite/E22.py | 1 + testsuite/E30.py | 29 +++++++++++++++++++++++++++++ testsuite/support.py | 1 + testsuite/test_all.py | 1 + 8 files changed, 50 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 84fcb6c6..dc0b9708 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -26,6 +26,7 @@ Changes: * Added check E275 for whitespace on `from ... import ...` lines; #489 / #491 * Added W503 to the list of codes ignored by default ignore list; #498 * Removed use of project level `.pep8` configuration file; #364 +* Added check E305 for two blank lines after toplevel method and toplevel class; #400 Bugs: diff --git a/docs/intro.rst b/docs/intro.rst index 982b7866..0f5132bf 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -322,6 +322,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E304 | blank lines found after function decorator | +------------+----------------------------------------------------------------------+ +| E305 | expected 2 blank lines after end of function or class | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **E4** | *Import* | +------------+----------------------------------------------------------------------+ diff --git a/pycodestyle.py b/pycodestyle.py index b32f16ea..fbaae4da 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -237,7 +237,8 @@ def maximum_line_length(physical_line, max_line_length, multiline, noqa): def blank_lines(logical_line, blank_lines, indent_level, line_number, - blank_before, previous_logical, previous_indent_level): + blank_before, previous_logical, previous_logical_toplevel, + previous_indent_level): r"""Separate top-level function and class definitions with two blank lines. Method definitions inside a class are separated by a single blank line. @@ -256,6 +257,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, E303: def a():\n pass\n\n\n\ndef b(n):\n pass E303: def a():\n\n\n\n pass E304: @decorator\n\ndef a():\n pass + E305: def a():\n pass\na() """ if line_number < 3 and not previous_logical: return # Don't expect blank lines before the first line @@ -271,6 +273,10 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, yield 0, "E301 expected 1 blank line, found 0" elif blank_before != 2: yield 0, "E302 expected 2 blank lines, found %d" % blank_before + elif (logical_line and not indent_level and blank_before != 2 and + previous_logical_toplevel.startswith(('def', 'class'))): + yield 0, "E305 expected 2 blank lines after " \ + "class or function definition, found %d" % blank_before def extraneous_whitespace(logical_line): @@ -1450,6 +1456,8 @@ def init_checks_registry(): mod = inspect.getmodule(register_check) for (name, function) in inspect.getmembers(mod, inspect.isfunction): register_check(function) + + init_checks_registry() @@ -1608,6 +1616,8 @@ def check_logical(self): if self.logical_line: self.previous_indent_level = self.indent_level self.previous_logical = self.logical_line + if not self.indent_level: + self.previous_logical_toplevel = self.logical_line self.blank_lines = 0 self.tokens = [] @@ -1678,6 +1688,7 @@ def check_all(self, expected=None, line_offset=0): self.indent_char = None self.indent_level = self.previous_indent_level = 0 self.previous_logical = '' + self.previous_logical_toplevel = '' self.tokens = [] self.blank_lines = self.blank_before = 0 parens = 0 diff --git a/testsuite/E12not.py b/testsuite/E12not.py index e76ef134..18c6a646 100644 --- a/testsuite/E12not.py +++ b/testsuite/E12not.py @@ -139,6 +139,7 @@ def long_function_name( var_four): print(var_one) + if ((row < 0 or self.moduleCount <= row or col < 0 or self.moduleCount <= col)): raise Exception("%s,%s - %s" % (row, col, self.moduleCount)) @@ -400,6 +401,7 @@ def unicode2html(s): .replace('"', '"') .replace('\n', '
\n')) + # parser.add_option('--count', action='store_true', help="print total number of errors and warnings " @@ -616,6 +618,7 @@ def other_example(): for key, val in node.items() ))] + foo([ 'bug' ]) diff --git a/testsuite/E22.py b/testsuite/E22.py index 9d8efda5..ee9dc74a 100644 --- a/testsuite/E22.py +++ b/testsuite/E22.py @@ -150,6 +150,7 @@ def halves(n): def squares(n): return (i**2 for i in range(n)) + ENG_PREFIXES = { -6: "\u03bc", # Greek letter mu -3: "m", diff --git a/testsuite/E30.py b/testsuite/E30.py index d2d7bf35..1aa11379 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -88,3 +88,32 @@ def function(): It gives error E303: too many blank lines (3) """ #: + +#: E305:7:1 +def a(): + print + + # comment + + # another comment +a() +#: E305:8:1 +def a(): + print + + # comment + + # another comment + +try: + a() +except: + pass +#: E305:5:1 +def a(): + print + +# Two spaces before comments, too. +if a(): + a() +#: diff --git a/testsuite/support.py b/testsuite/support.py index 003f181b..cf9abc53 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -196,5 +196,6 @@ def run_tests(style): init_tests(style) return style.check_files() + # nose should not collect these functions init_tests.__test__ = run_tests.__test__ = False diff --git a/testsuite/test_all.py b/testsuite/test_all.py index bd18943f..08f9ea91 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -62,5 +62,6 @@ def suite(): def _main(): return unittest.TextTestRunner(verbosity=2).run(suite()) + if __name__ == '__main__': sys.exit(not _main()) From f55b1c5c73bce47882e6bdec8cd9cc7b17d1a199 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 14:15:19 -0700 Subject: [PATCH 079/412] Fixed placement of changelog entry --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index dc0b9708..3ed1ccb6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -10,6 +10,7 @@ Changes: * Improved performance of `compound_statements` check; #314 / #522 * Fixed remaining references to `pep8`; #518 / #530 * Added `noqa` support for `maximum_line_length` check; #538 +* Added check E305 for two blank lines after toplevel methods and classes; #400 2.0.0 (2016-05-31) @@ -26,7 +27,6 @@ Changes: * Added check E275 for whitespace on `from ... import ...` lines; #489 / #491 * Added W503 to the list of codes ignored by default ignore list; #498 * Removed use of project level `.pep8` configuration file; #364 -* Added check E305 for two blank lines after toplevel method and toplevel class; #400 Bugs: From 10f4e51c18a25ba147fc13721c07295dd1a35675 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 14:31:36 -0700 Subject: [PATCH 080/412] Added extra Okay example to test against --- testsuite/E30not.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/testsuite/E30not.py b/testsuite/E30not.py index 0fd8fb0c..ea75057e 100644 --- a/testsuite/E30not.py +++ b/testsuite/E30not.py @@ -132,3 +132,22 @@ def a(): def b(): pass +#: Okay +def foo(): + pass + + +def bar(): + pass + + +class Foo(object): + pass + + +class Bar(object): + pass + + +if __name__ == '__main__': + foo() From 2a5e575611674b0d202c5ea0322fce23718d6e02 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 14:32:28 -0700 Subject: [PATCH 081/412] Updated checker variable based on conversation in #536 --- pycodestyle.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index fbaae4da..a5f55346 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -237,8 +237,8 @@ def maximum_line_length(physical_line, max_line_length, multiline, noqa): def blank_lines(logical_line, blank_lines, indent_level, line_number, - blank_before, previous_logical, previous_logical_toplevel, - previous_indent_level): + blank_before, previous_logical, + previous_unindented_logical_line, previous_indent_level): r"""Separate top-level function and class definitions with two blank lines. Method definitions inside a class are separated by a single blank line. @@ -274,7 +274,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, elif blank_before != 2: yield 0, "E302 expected 2 blank lines, found %d" % blank_before elif (logical_line and not indent_level and blank_before != 2 and - previous_logical_toplevel.startswith(('def', 'class'))): + previous_unindented_logical_line.startswith(('def', 'class'))): yield 0, "E305 expected 2 blank lines after " \ "class or function definition, found %d" % blank_before @@ -1617,7 +1617,7 @@ def check_logical(self): self.previous_indent_level = self.indent_level self.previous_logical = self.logical_line if not self.indent_level: - self.previous_logical_toplevel = self.logical_line + self.previous_unindented_logical_line = self.logical_line self.blank_lines = 0 self.tokens = [] @@ -1688,7 +1688,7 @@ def check_all(self, expected=None, line_offset=0): self.indent_char = None self.indent_level = self.previous_indent_level = 0 self.previous_logical = '' - self.previous_logical_toplevel = '' + self.previous_unindented_logical_line = '' self.tokens = [] self.blank_lines = self.blank_before = 0 parens = 0 From a0d5d5d0bb2fc5e3cd75439298abce1f14323f1b Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 14:39:51 -0700 Subject: [PATCH 082/412] Added test case based on @gvanrossum comment https://github.com/PyCQA/pycodestyle/issues/400\#issue-66790880 --- testsuite/E30.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/testsuite/E30.py b/testsuite/E30.py index 1aa11379..1471079e 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -117,3 +117,14 @@ def a(): if a(): a() #: + +#: E305:8:1 +# Example from https://github.com/PyCQA/pycodestyle/issues/400 +import stuff + + +def main(): + blah, blah + +if __name__ == '__main__': + main() From 5c4c081f6f5509f746ab4c0b508a4caa4e8ade98 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 15:13:50 -0700 Subject: [PATCH 083/412] Removed test code unrelated to utf-8 checks --- testsuite/utf-8.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/testsuite/utf-8.py b/testsuite/utf-8.py index 2cee5794..e417cbc0 100644 --- a/testsuite/utf-8.py +++ b/testsuite/utf-8.py @@ -1,22 +1,5 @@ # -*- coding: utf-8 -*- - -class Rectangle(Blob): - - def __init__(self, width, height, - color='black', emphasis=None, highlight=0): - if width == 0 and height == 0 and \ - color == 'red' and emphasis == 'strong' or \ - highlight > 100: - raise ValueError("sorry, you lose") - if width == 0 and height == 0 and (color == 'red' or - emphasis is None): - raise ValueError("I don't think so -- values are %s, %s" % - (width, height)) - Blob.__init__(self, width, height, - color, emphasis, highlight) - - # Some random text with multi-byte characters (utf-8 encoded) # # Εδώ μάτσο κειμένων τη, τρόπο πιθανό διευθυντές ώρα μη. Νέων απλό παράγει ροή From 4438622d0b62df53a1999301d1bdc9fa119ae763 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 25 Jun 2016 20:36:44 -0700 Subject: [PATCH 084/412] Updated tox to do same tests as Travis CI --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7218f177..0c16e340 100644 --- a/tox.ini +++ b/tox.ini @@ -13,4 +13,4 @@ commands = {envpython} pycodestyle.py --testsuite testsuite {envpython} pycodestyle.py --statistics pycodestyle.py {envpython} pycodestyle.py --doctest - {envpython} -m testsuite.test_all + {envpython} setup.py test From 36e3400f65268d1ee76845a74aae5b0112182ef8 Mon Sep 17 00:00:00 2001 From: memeplex Date: Tue, 28 Jun 2016 21:16:36 -0300 Subject: [PATCH 085/412] Special case for nested functions an classes (fixes #28) --- pycodestyle.py | 17 +++++++++++++++-- testsuite/E30.py | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index a5f55346..a043f040 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -238,7 +238,8 @@ def maximum_line_length(physical_line, max_line_length, multiline, noqa): def blank_lines(logical_line, blank_lines, indent_level, line_number, blank_before, previous_logical, - previous_unindented_logical_line, previous_indent_level): + previous_unindented_logical_line, previous_indent_level, + lines): r"""Separate top-level function and class definitions with two blank lines. Method definitions inside a class are separated by a single blank line. @@ -270,7 +271,19 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, if indent_level: if not (blank_before or previous_indent_level < indent_level or DOCSTRING_REGEX.match(previous_logical)): - yield 0, "E301 expected 1 blank line, found 0" + ancestor_level = indent_level + nested = False + for line in lines[line_number - 2::-1]: + if line.strip() and expand_indent(line) < ancestor_level: + ancestor_level = expand_indent(line) + nested = line.lstrip().startswith('def ') + if nested or ancestor_level == 0: + break + if nested: + yield 0, "E306 expected 1 blank line, found 0" \ + " (nested definition)" + else: + yield 0, "E301 expected 1 blank line, found 0" elif blank_before != 2: yield 0, "E302 expected 2 blank lines, found %d" % blank_before elif (logical_line and not indent_level and blank_before != 2 and diff --git a/testsuite/E30.py b/testsuite/E30.py index 1471079e..e5a35044 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -118,6 +118,28 @@ def a(): a() #: +#: E306:3:5 +def a(): + x = 1 + def b(): + pass +#: E306:3:5 E306:5:9 +def a(): + x = 2 + def b(): + x = 1 + def c(): + pass +#: E306:3:5 E306:6:5 +def a(): + x = 1 + class C: + pass + x = 2 + def b(): + pass +#: + #: E305:8:1 # Example from https://github.com/PyCQA/pycodestyle/issues/400 import stuff From 98dee57c033626e34fe96bef7f86e48dbb71c121 Mon Sep 17 00:00:00 2001 From: memeplex Date: Tue, 28 Jun 2016 22:43:34 -0300 Subject: [PATCH 086/412] Improve error message and add some comments --- pycodestyle.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index a043f040..708b2866 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -273,6 +273,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, DOCSTRING_REGEX.match(previous_logical)): ancestor_level = indent_level nested = False + # Search backwards for a def ancestor or tree root (top level). for line in lines[line_number - 2::-1]: if line.strip() and expand_indent(line) < ancestor_level: ancestor_level = expand_indent(line) @@ -280,8 +281,8 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, if nested or ancestor_level == 0: break if nested: - yield 0, "E306 expected 1 blank line, found 0" \ - " (nested definition)" + yield 0, "E306 expected 1 blank line before a " \ + "nested definition, found 0" else: yield 0, "E301 expected 1 blank line, found 0" elif blank_before != 2: From 72acd6f9471f8a1c3a79b0e3785f77e84ac6244d Mon Sep 17 00:00:00 2001 From: Mark Peek Date: Fri, 1 Jul 2016 09:03:18 -0700 Subject: [PATCH 087/412] Report E302 for blank lines before an "async def" --- pycodestyle.py | 4 +++- testsuite/E30.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index a5f55346..3ee688bd 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -250,10 +250,12 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, Use blank lines in functions, sparingly, to indicate logical sections. Okay: def a():\n pass\n\n\ndef b():\n pass + Okay: def a():\n pass\n\n\nasync def b():\n pass Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass E301: class Foo:\n b = 0\n def bar():\n pass E302: def a():\n pass\n\ndef b(n):\n pass + E302: def a():\n pass\n\nasync def b(n):\n pass E303: def a():\n pass\n\n\n\ndef b(n):\n pass E303: def a():\n\n\n\n pass E304: @decorator\n\ndef a():\n pass @@ -266,7 +268,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, yield 0, "E304 blank lines found after function decorator" elif blank_lines > 2 or (indent_level and blank_lines == 2): yield 0, "E303 too many blank lines (%d)" % blank_lines - elif logical_line.startswith(('def ', 'class ', '@')): + elif logical_line.startswith(('def ', 'async def', 'class ', '@')): if indent_level: if not (blank_before or previous_indent_level < indent_level or DOCSTRING_REGEX.match(previous_logical)): diff --git a/testsuite/E30.py b/testsuite/E30.py index 1471079e..9397541b 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -44,7 +44,13 @@ def a(): def b(): pass #: +#: E302:4:1 +def a(): + pass +async def b(): + pass +#: #: E303:5:1 print From df8a6c0d37ad45f053842654767fce8ea1851f28 Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Sun, 3 Jul 2016 00:24:57 +0000 Subject: [PATCH 088/412] Add Gitter badge --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index d861c633..a0335e8b 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,10 @@ pycodestyle (formerly called pep8) - Python style guide checker =============================================================== +.. image:: https://badges.gitter.im/PyCQA/pycodestyle.svg + :alt: Join the chat at https://gitter.im/PyCQA/pycodestyle + :target: https://gitter.im/PyCQA/pycodestyle?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + pycodestyle is a tool to check your Python code against some of the style conventions in `PEP 8`_. From d4b8f49b66ca725ce7f32c438c98d17c4e6f2316 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 24 Jul 2016 20:07:26 -0500 Subject: [PATCH 089/412] Stop checking for string option type There's no need to explicitly check for a string type when parsing the configuration file. When it's neither an integer or a boolean, the only value it can logically be is string-like. Closes gh-561 --- pycodestyle.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3ee688bd..ea367f7c 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -2149,13 +2149,12 @@ def read_config(options, args, arglist, parser): opt_type = option_list[normalized_opt] if opt_type in ('int', 'count'): value = config.getint(pep8_section, opt) - elif opt_type == 'string': + elif opt_type in ('store_true', 'store_false'): + value = config.getboolean(pep8_section, opt) + else: value = config.get(pep8_section, opt) if normalized_opt == 'exclude': value = normalize_paths(value, local_dir) - else: - assert opt_type in ('store_true', 'store_false') - value = config.getboolean(pep8_section, opt) setattr(new_options, normalized_opt, value) # Third, overwrite with the command-line options From 0962a10c43b7b89734f7ce0a709064ebb312f985 Mon Sep 17 00:00:00 2001 From: Thomas Schultz Date: Tue, 2 Aug 2016 10:10:01 -0400 Subject: [PATCH 090/412] Add contributing instructions. --- CONTRIBUTING.rst | 72 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 9771176b..f5a54b5b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,72 @@ -Contributing to ``pycodestyle`` +Contributing to pycodestyle =============================== -Please see the `developer notes `_ +When contributing to pycodestyle, please observe our `Code of Conduct`_. + +Step 1: Forking pycodestyle for editing +--------------------------------------- + +Fork the pycodestyle repository on github. This will add +pycodestyle to your github account. You will push your changes to your +fork and then make pull requests into the official pycodestyle repository. + +Github has an excellent `guide`_ that has screenshots on how to do this. + +Next, clone your fork of the pycodestyle repository to your system for +editing:: + + $ git clone https://www.github.com//pycodestyle + +Now you have a copy of the pycodestyle codebase that is almost ready for +edits. Next we will setup `virtualenv`_ which will help create an isolated +environment to manage dependancies. + + +Step 3: Use virtualenv when developing +-------------------------------------- + +`virtualenv`_ is a tool to create isolated python environments. +First, install virtualenv with:: + + $ pip install virtualenv + +Next, ``cd`` to the pycodestyle repository that you cloned earlier and +create, then activate a virtualenv:: + + $ cd pycodestyle + $ virtualenv pycodestyle-venv + $ source pycodestyle-venv/bin/activate + +Now you can install the pycodestyle requirements:: + + $ pip install -r dev-requirements.txt + +To deactivate the virtualenv you can type:: + + $ deactivate + +For more information see `virtualenv`_'s documentation. + + +Step 4: Run tests +----------------- + +Before creating a pull request you should run the tests to make sure that the +changes that have been made haven't caused any regressions in functionality. +To run the tests, the core developer team and Travis-CI use `tox`_:: + + $ pip install -r dev-requirements.txt + $ tox + +All the tests should pass for all available interpreters, with the summary of:: + + congratulations :) + +At this point you can create a pull request back to the official pycodestyles +repository for review! For more information on how to make a pull request, +github has an excellent `guide`_. + +.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ +.. _guide: https://guides.github.com/activities/forking/ +.. _tox: https://tox.readthedocs.io/en/latest/ +.. _Code of Conduct: http://meta.pycqa.org/en/latest/code-of-conduct.html From a090bb2a72dfd64b0492307e9bdb608314fc1ed6 Mon Sep 17 00:00:00 2001 From: Thomas Schultz Date: Tue, 2 Aug 2016 15:16:05 -0400 Subject: [PATCH 091/412] Feedback changes. Typos and capitalization. Fix GitHub capitalization. --- CONTRIBUTING.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f5a54b5b..caab1931 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,16 +1,16 @@ Contributing to pycodestyle -=============================== +=========================== When contributing to pycodestyle, please observe our `Code of Conduct`_. Step 1: Forking pycodestyle for editing --------------------------------------- -Fork the pycodestyle repository on github. This will add -pycodestyle to your github account. You will push your changes to your +Fork the pycodestyle repository on GitHub. This will add +pycodestyle to your GitHub account. You will push your changes to your fork and then make pull requests into the official pycodestyle repository. -Github has an excellent `guide`_ that has screenshots on how to do this. +GitHub has an excellent `guide`_ that has screenshots on how to do this. Next, clone your fork of the pycodestyle repository to your system for editing:: @@ -22,7 +22,7 @@ edits. Next we will setup `virtualenv`_ which will help create an isolated environment to manage dependancies. -Step 3: Use virtualenv when developing +Step 2: Use virtualenv when developing -------------------------------------- `virtualenv`_ is a tool to create isolated python environments. @@ -48,7 +48,7 @@ To deactivate the virtualenv you can type:: For more information see `virtualenv`_'s documentation. -Step 4: Run tests +Step 3: Run tests ----------------- Before creating a pull request you should run the tests to make sure that the @@ -64,7 +64,7 @@ All the tests should pass for all available interpreters, with the summary of:: At this point you can create a pull request back to the official pycodestyles repository for review! For more information on how to make a pull request, -github has an excellent `guide`_. +GitHub has an excellent `guide`_. .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ .. _guide: https://guides.github.com/activities/forking/ From 1391c7e732fb8c1616a5b77243482c71d2644104 Mon Sep 17 00:00:00 2001 From: Thomas Schultz Date: Sun, 18 Sep 2016 16:14:10 -0400 Subject: [PATCH 092/412] Update CHANGES.txt for 2.1.0 release. --- CHANGES.txt | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3ed1ccb6..c514e47a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,14 +4,33 @@ Changelog 2.1.0 (unreleased) ------------------ +Announcements: + +* Change all references to the pep8 project to say pycodestyle; #530 + Changes: -* Added check E74x for using variables named 'l', 'O', or 'I'; #341 -* Improved performance of `compound_statements` check; #314 / #522 -* Fixed remaining references to `pep8`; #518 / #530 -* Added `noqa` support for `maximum_line_length` check; #538 -* Added check E305 for two blank lines after toplevel methods and classes; #400 +* Add Gitter badge; #557 +* Report E302 for blank lines before an "async def"; #556 +* Updated tox to do same tests as Travis CI (`setup.py test`). +* Updated to use sheilds.io badges. +* Updated text about running tests with tox; #552 +* Update maximum_line_length to use Checker.noqa; 538 +* Updated Travis CI doc link. +* Updated docs related to tested Python versions. +* Added skip missing interpreters flag to tox config. +* Add test-requirements.txt with basic test packages. +* Report E742 and E743 for badly named functions and classes. +* Also report E741 on 'global' and 'nonlocal' statements. +* Report E741 for prohibited single-letter variables. +* Added venv/ (virtualenv) to list of git ignored filename patterns. + +Bugs: +* Stop checking for string option type; #561 +* Require two blank lines after toplevel def, class; #536 +* Badly named single-letter variable names are not detected; #341 +* Fixing compound_statement not to be quadratic in # of :s; #314 2.0.0 (2016-05-31) ------------------ From e4244feabf24696ac00e74f1fb634f6e0d23277d Mon Sep 17 00:00:00 2001 From: Thomas Schultz Date: Wed, 21 Sep 2016 10:27:58 -0400 Subject: [PATCH 093/412] Updated changelog from feedback. --- CHANGES.txt | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c514e47a..953ece91 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -10,27 +10,16 @@ Announcements: Changes: -* Add Gitter badge; #557 * Report E302 for blank lines before an "async def"; #556 -* Updated tox to do same tests as Travis CI (`setup.py test`). -* Updated to use sheilds.io badges. -* Updated text about running tests with tox; #552 -* Update maximum_line_length to use Checker.noqa; 538 -* Updated Travis CI doc link. -* Updated docs related to tested Python versions. -* Added skip missing interpreters flag to tox config. -* Add test-requirements.txt with basic test packages. -* Report E742 and E743 for badly named functions and classes. -* Also report E741 on 'global' and 'nonlocal' statements. -* Report E741 for prohibited single-letter variables. -* Added venv/ (virtualenv) to list of git ignored filename patterns. +* Update our list of tested and supported Python versions which are 2.6 through 3.5 as well as the nightly Python build and PyPy. +* Report E742 and E743 for functions and classes badly named 'l', 'O', or 'I'. +* Report E741 on 'global' and 'nonlocal' statements, as well as prohibited single-letter variables. Bugs: -* Stop checking for string option type; #561 +* Fix opt_type AssertionError when using Flake8 2.6.2 and pycodestyle 2.0.0; #561 * Require two blank lines after toplevel def, class; #536 -* Badly named single-letter variable names are not detected; #341 -* Fixing compound_statement not to be quadratic in # of :s; #314 +* Remove accidentally quadratic computation based on the number of colons. This will make pycodestyle faster in some cases; #314 2.0.0 (2016-05-31) ------------------ From 61f2e4e27df4710eae645a814f8c64f56fa6c280 Mon Sep 17 00:00:00 2001 From: Thomas Schultz Date: Wed, 21 Sep 2016 11:58:06 -0400 Subject: [PATCH 094/412] Update tested python version notes. --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 953ece91..bedca15f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,7 +11,7 @@ Announcements: Changes: * Report E302 for blank lines before an "async def"; #556 -* Update our list of tested and supported Python versions which are 2.6 through 3.5 as well as the nightly Python build and PyPy. +* Update our list of tested and supported Python versions which are 2.6, 2.7, 3.2, 3.3, 3.4 and 3.5 as well as the nightly Python build and PyPy. * Report E742 and E743 for functions and classes badly named 'l', 'O', or 'I'. * Report E741 on 'global' and 'nonlocal' statements, as well as prohibited single-letter variables. From 5c4ca67b970258cd4c5ab0e36967b81255911fdb Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 29 Sep 2016 08:47:20 -0700 Subject: [PATCH 095/412] Fixed line wrapping in CHANGELOG --- CHANGES.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index bedca15f..1ade3619 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,15 +11,18 @@ Announcements: Changes: * Report E302 for blank lines before an "async def"; #556 -* Update our list of tested and supported Python versions which are 2.6, 2.7, 3.2, 3.3, 3.4 and 3.5 as well as the nightly Python build and PyPy. +* Update our list of tested and supported Python versions which are 2.6, 2.7, + 3.2, 3.3, 3.4 and 3.5 as well as the nightly Python build and PyPy. * Report E742 and E743 for functions and classes badly named 'l', 'O', or 'I'. -* Report E741 on 'global' and 'nonlocal' statements, as well as prohibited single-letter variables. +* Report E741 on 'global' and 'nonlocal' statements, as well as prohibited + single-letter variables. Bugs: -* Fix opt_type AssertionError when using Flake8 2.6.2 and pycodestyle 2.0.0; #561 +* Fix opt_type AssertionError when using Flake8 2.6.2 and pycodestyle; #561 * Require two blank lines after toplevel def, class; #536 -* Remove accidentally quadratic computation based on the number of colons. This will make pycodestyle faster in some cases; #314 +* Remove accidentally quadratic computation based on the number of colons. This + will make pycodestyle faster in some cases; #314 2.0.0 (2016-05-31) ------------------ From 2cc7a8abce1f5442dc0d23da546752dcb611bc23 Mon Sep 17 00:00:00 2001 From: Thomas Schultz Date: Thu, 3 Nov 2016 13:39:10 -0400 Subject: [PATCH 096/412] Check for both [pep8] and [pycodestyle] config sections. --- pycodestyle.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index a14369eb..64a57ec3 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -55,6 +55,7 @@ import sys import time import tokenize +import warnings from fnmatch import fnmatch from optparse import OptionParser @@ -2144,8 +2145,14 @@ def read_config(options, args, arglist, parser): print('cli configuration: %s' % cli_conf) config.read(cli_conf) - pep8_section = parser.prog - if config.has_section(pep8_section): + pycodestyle_section = None + if config.has_section(parser.prog): + pycodestyle_section = parser.prog + elif config.has_section('pep8'): + pycodestyle_section = 'pep8' # Deprecated + warnings.warn('[pep8] section is deprecated. Use [pycodestyle].') + + if pycodestyle_section: option_list = dict([(o.dest, o.type or o.action) for o in parser.option_list]) @@ -2153,20 +2160,21 @@ def read_config(options, args, arglist, parser): (new_options, __) = parser.parse_args([]) # Second, parse the configuration - for opt in config.options(pep8_section): + for opt in config.options(pycodestyle_section): if opt.replace('_', '-') not in parser.config_options: print(" unknown option '%s' ignored" % opt) continue if options.verbose > 1: - print(" %s = %s" % (opt, config.get(pep8_section, opt))) + print(" %s = %s" % (opt, + config.get(pycodestyle_section, opt))) normalized_opt = opt.replace('-', '_') opt_type = option_list[normalized_opt] if opt_type in ('int', 'count'): - value = config.getint(pep8_section, opt) + value = config.getint(pycodestyle_section, opt) elif opt_type in ('store_true', 'store_false'): - value = config.getboolean(pep8_section, opt) + value = config.getboolean(pycodestyle_section, opt) else: - value = config.get(pep8_section, opt) + value = config.get(pycodestyle_section, opt) if normalized_opt == 'exclude': value = normalize_paths(value, local_dir) setattr(new_options, normalized_opt, value) From 2d0b5d95df7f31d5ee7955ff0fceccbd8cad7944 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 4 Nov 2016 08:00:51 -0700 Subject: [PATCH 097/412] Updated changelog for #591 --- CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.txt b/CHANGES.txt index 1ade3619..3eab5480 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,7 @@ Changes: * Report E742 and E743 for functions and classes badly named 'l', 'O', or 'I'. * Report E741 on 'global' and 'nonlocal' statements, as well as prohibited single-letter variables. +* Deprecated use of `[pep8]` section name in favor of `[pycodestyle]`; #591 Bugs: From 088adfc7f7a4ab52e0bc464aa54ec351e7994eb4 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 4 Nov 2016 08:01:14 -0700 Subject: [PATCH 098/412] Moved build badges to top of README --- README.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index a0335e8b..1791f264 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,14 @@ pycodestyle (formerly called pep8) - Python style guide checker =============================================================== +.. image:: https://img.shields.io/travis/PyCQA/pycodestyle.svg + :target: https://travis-ci.org/PyCQA/pycodestyle + :alt: Build status + +.. image:: https://img.shields.io/pypi/wheel/pycodestyle.svg + :target: https://pypi.python.org/pypi/pycodestyle + :alt: Wheel Status + .. image:: https://badges.gitter.im/PyCQA/pycodestyle.svg :alt: Join the chat at https://gitter.im/PyCQA/pycodestyle :target: https://gitter.im/PyCQA/pycodestyle?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge @@ -91,14 +99,6 @@ Or you can display how often each error was found:: Links ----- -.. image:: https://img.shields.io/travis/PyCQA/pycodestyle.svg - :target: https://travis-ci.org/PyCQA/pycodestyle - :alt: Build status - -.. image:: https://img.shields.io/pypi/wheel/pycodestyle.svg - :target: https://pypi.python.org/pypi/pycodestyle - :alt: Wheel Status - * `Read the documentation `_ * `Fork me on GitHub `_ From ae2271bbcb0070d65bebc98bb4a07f2441da8a95 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 4 Nov 2016 08:12:40 -0700 Subject: [PATCH 099/412] Added RTD Badge to README --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 1791f264..e63b6923 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,10 @@ pycodestyle (formerly called pep8) - Python style guide checker :target: https://travis-ci.org/PyCQA/pycodestyle :alt: Build status +.. image:: https://readthedocs.org/projects/pycodestyle/badge/?version=latest + :target: http://pycodestyle.readthedocs.io/en/latest/ + :alt: Documentation Status + .. image:: https://img.shields.io/pypi/wheel/pycodestyle.svg :target: https://pypi.python.org/pypi/pycodestyle :alt: Wheel Status From 4f909cd834075167bb3c8562b55acb79a4650acb Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 4 Nov 2016 08:15:24 -0700 Subject: [PATCH 100/412] Made RTD badge point to base, and use SSL --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e63b6923..3a4b742d 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ pycodestyle (formerly called pep8) - Python style guide checker :alt: Build status .. image:: https://readthedocs.org/projects/pycodestyle/badge/?version=latest - :target: http://pycodestyle.readthedocs.io/en/latest/ + :target: https://pycodestyle.readthedocs.io :alt: Documentation Status .. image:: https://img.shields.io/pypi/wheel/pycodestyle.svg From 0be4e225b5c9e820d08dc0727855fa3a5b04af4e Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 4 Nov 2016 08:15:53 -0700 Subject: [PATCH 101/412] Added missing word to instructions --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 3a4b742d..abd51e47 100644 --- a/README.rst +++ b/README.rst @@ -47,7 +47,7 @@ Features Installation ------------ -You can install, upgrade, uninstall ``pycodestyle.py`` with these commands:: +You can install, upgrade, and uninstall ``pycodestyle.py`` with these commands:: $ pip install pycodestyle $ pip install --upgrade pycodestyle From 510c690873e740f4b23289ab47dc58e13685ccf1 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 4 Nov 2016 08:42:22 -0700 Subject: [PATCH 102/412] Tagged release of 2.1.0 Signed-off-by: Ian Lee --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 64a57ec3..df79877b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -66,7 +66,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.1.0.dev0' +__version__ = '2.1.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From d12e2ea39745bd3bb9e819d7342e4c1d73dbb008 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 4 Nov 2016 08:59:22 -0700 Subject: [PATCH 103/412] Bump versin number post release --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index df79877b..966e7232 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -66,7 +66,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.1.0' +__version__ = '2.2.0.dev0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From ce71d1ed68550cd1240ba948ecf2d0ae760bc2ff Mon Sep 17 00:00:00 2001 From: Mark Peek Date: Sun, 6 Nov 2016 11:49:57 -0800 Subject: [PATCH 104/412] Fix E305 regression in 2.1.0 due to fix for #400 --- pycodestyle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 966e7232..3d1125e5 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -254,6 +254,8 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, Okay: def a():\n pass\n\n\ndef b():\n pass Okay: def a():\n pass\n\n\nasync def b():\n pass Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass + Okay: default = 1\nfoo = 1 + Okay: classify = 1\nfoo = 1 E301: class Foo:\n b = 0\n def bar():\n pass E302: def a():\n pass\n\ndef b(n):\n pass @@ -291,7 +293,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, elif blank_before != 2: yield 0, "E302 expected 2 blank lines, found %d" % blank_before elif (logical_line and not indent_level and blank_before != 2 and - previous_unindented_logical_line.startswith(('def', 'class'))): + previous_unindented_logical_line.startswith(('def ', 'class '))): yield 0, "E305 expected 2 blank lines after " \ "class or function definition, found %d" % blank_before From 349deeae7f03a77954189553f5eace8fe0e980ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=81=A3=20Florian=20Best?= Date: Mon, 7 Nov 2016 04:04:52 +0100 Subject: [PATCH 105/412] Add E306 to docs --- docs/intro.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/intro.rst b/docs/intro.rst index 0f5132bf..b1dc6894 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -324,6 +324,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E305 | expected 2 blank lines after end of function or class | +------------+----------------------------------------------------------------------+ +| E306 | expected 1 blank line before a nested definition | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **E4** | *Import* | +------------+----------------------------------------------------------------------+ From 020d6ff6a85c237b0bdaa4c50625e6385dcddc16 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 14 Nov 2016 08:34:28 -0800 Subject: [PATCH 106/412] Fixed CHANGELOG release date for v2.1.0 --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3eab5480..b48d4825 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,7 @@ Changelog ========= -2.1.0 (unreleased) +2.1.0 (2016-11-04) ------------------ Announcements: From 659ef94726c5c18385e7f35a5dce792f91d3601b Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 14 Nov 2016 10:36:03 -0800 Subject: [PATCH 107/412] Added Makefile to handle proper permissions in releases --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 366f5807..d24a595d 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +release: + umask 022 && chmod -R a+rX . && python setup.py sdist bdist_wheel + test : python pycodestyle.py --testsuite testsuite From 8053c7c1d5597b062c58c5991231b62e11b435e9 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 14 Nov 2016 10:41:42 -0800 Subject: [PATCH 108/412] Tagged release of 2.2.0 Signed-off-by: Ian Lee --- CHANGES.txt | 11 +++++++++++ pycodestyle.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index b48d4825..aeb5d633 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,17 @@ Changelog ========= +2.2.0 (2016-11-14) +------------------ + +Announcements: + +* Added Make target to obtain proper tarball file permissions; #599 + +Bugs: + +* Fixed E305 regression caused by #400; #593 + 2.1.0 (2016-11-04) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index 3d1125e5..e3081775 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -66,7 +66,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.2.0.dev0' +__version__ = '2.2.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From fcc8f58f3d805e7529cfef1005285fdef1107917 Mon Sep 17 00:00:00 2001 From: Parth Laxmikant Kolekar Date: Tue, 22 Nov 2016 07:55:04 +0530 Subject: [PATCH 109/412] Prevent E305 on variable names `class*` and `def*` Prevent lines like classification_error = 0 From becoming treated as a class. --- testsuite/E30not.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testsuite/E30not.py b/testsuite/E30not.py index ea75057e..63ff733e 100644 --- a/testsuite/E30not.py +++ b/testsuite/E30not.py @@ -151,3 +151,7 @@ class Bar(object): if __name__ == '__main__': foo() +#: Okay +classification_errors = None +#: Okay +defined_properly = True From 543f12b06592c53e2e60edc4846ee02ab9550e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Rogalski?= Date: Wed, 28 Dec 2016 19:31:49 +0100 Subject: [PATCH 110/412] warn about bare except clause --- CHANGES.txt | 1 + docs/intro.rst | 2 ++ pycodestyle.py | 22 ++++++++++++++++++++-- testsuite/E30.py | 2 +- testsuite/E40.py | 2 +- testsuite/E72.py | 28 ++++++++++++++++++++++++++++ 6 files changed, 53 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index aeb5d633..9c43531c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -28,6 +28,7 @@ Changes: * Report E741 on 'global' and 'nonlocal' statements, as well as prohibited single-letter variables. * Deprecated use of `[pep8]` section name in favor of `[pycodestyle]`; #591 +* Report E722 when bare except clause is used; #579 Bugs: diff --git a/docs/intro.rst b/docs/intro.rst index b1dc6894..fcdcf724 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -361,6 +361,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E721 (^) | do not compare types, use 'isinstance()' | +------------+----------------------------------------------------------------------+ +| E722 | do not use bare except, specify exception instead | ++------------+----------------------------------------------------------------------+ | E731 | do not assign a lambda expression, use a def | +------------+----------------------------------------------------------------------+ | E741 | do not use variables named 'l', 'O', or 'I' | diff --git a/pycodestyle.py b/pycodestyle.py index e3081775..559048ef 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -912,8 +912,10 @@ def module_imports_on_top_of_file( Okay: # this is a comment\nimport os Okay: '''this is a module docstring'''\nimport os Okay: r'''this is a module docstring'''\nimport os - Okay: try:\n import x\nexcept:\n pass\nelse:\n pass\nimport y - Okay: try:\n import x\nexcept:\n pass\nfinally:\n pass\nimport y + Okay: + try:\n\timport x\nexcept ImportError:\n\tpass\nelse:\n\tpass\nimport y + Okay: + try:\n\timport x\nexcept ImportError:\n\tpass\nfinally:\n\tpass\nimport y E402: a=1\nimport os E402: 'One string'\n"Two string"\nimport os E402: a=1\nfrom sys import x @@ -1179,6 +1181,22 @@ def comparison_type(logical_line, noqa): yield match.start(), "E721 do not compare types, use 'isinstance()'" +def bare_except(logical_line, noqa): + r"""When catching exceptions, mention specific exceptions whenever possible. + + Okay: except Exception: + Okay: except BaseException: + E722: except: + """ + if noqa: + return + + regex = re.compile(r"except\s*:") + match = regex.match(logical_line) + if match: + yield match.start(), "E722 do not use bare except'" + + def ambiguous_identifier(logical_line, tokens): r"""Never use the characters 'l', 'O', or 'I' as variable names. diff --git a/testsuite/E30.py b/testsuite/E30.py index 105d3681..aaa77b73 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -113,7 +113,7 @@ def a(): try: a() -except: +except Exception: pass #: E305:5:1 def a(): diff --git a/testsuite/E40.py b/testsuite/E40.py index 1051e32c..18ac73ac 100644 --- a/testsuite/E40.py +++ b/testsuite/E40.py @@ -18,7 +18,7 @@ #: Okay try: import foo -except: +except ImportError: pass else: print('imported foo') diff --git a/testsuite/E72.py b/testsuite/E72.py index 8eb34cb3..c18527f9 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -49,3 +49,31 @@ pass if type(a) != type(b) or type(a) == type(ccc): pass +#: E722 +try: + pass +except: + pass +#: E722 +try: + pass +except Exception: + pass +except: + pass +#: E722 E203 E271 +try: + pass +except : + pass +#: Okay +fake_code = """" +try: + do_something() +except: + pass +""" +try: + pass +except Exception: + pass From bae1d1de34067c2632ef754d8015f8f384b54683 Mon Sep 17 00:00:00 2001 From: Khairi Hafsham Date: Mon, 16 Jan 2017 18:26:54 +0800 Subject: [PATCH 111/412] modified pycodestyle.py added function doc to blank_lines() for E306 --- pycodestyle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pycodestyle.py b/pycodestyle.py index 559048ef..edeb494b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -264,6 +264,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, E303: def a():\n\n\n\n pass E304: @decorator\n\ndef a():\n pass E305: def a():\n pass\na() + E306: def a():\n def b():\n pass\n def c():\n pass """ if line_number < 3 and not previous_logical: return # Don't expect blank lines before the first line From 30c7c4822db6db1a68672b1d6c6d951eacae4ae6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 24 Jan 2017 05:07:29 -0800 Subject: [PATCH 112/412] Report E704 for async def as well (#611) Fix detection of 'async def' (with multiple spaces) so that it also reports E704 --- pycodestyle.py | 8 +++++--- testsuite/E25.py | 4 ++++ testsuite/E30.py | 7 +++++++ testsuite/E70.py | 4 ++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 559048ef..0f14209d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -121,6 +121,8 @@ OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') +STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)') +STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def|def|class|@)') # Work around Python < 2.6 behaviour, which does not generate NL after # a comment which is on a line by itself. @@ -272,7 +274,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, yield 0, "E304 blank lines found after function decorator" elif blank_lines > 2 or (indent_level and blank_lines == 2): yield 0, "E303 too many blank lines (%d)" % blank_lines - elif logical_line.startswith(('def ', 'async def', 'class ', '@')): + elif STARTSWITH_TOP_LEVEL_REGEX.match(logical_line): if indent_level: if not (blank_before or previous_indent_level < indent_level or DOCSTRING_REGEX.match(previous_logical)): @@ -813,7 +815,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): no_space = False prev_end = None annotated_func_arg = False - in_def = logical_line.startswith(('def', 'async def')) + in_def = bool(STARTSWITH_DEF_REGEX.match(logical_line)) message = "E251 unexpected spaces around keyword / parameter equals" for token_type, text, start, end, line in tokens: if token_type == tokenize.NL: @@ -1000,7 +1002,7 @@ def compound_statements(logical_line): yield 0, ("E731 do not assign a lambda expression, use a " "def") break - if line.startswith('def '): + if STARTSWITH_DEF_REGEX.match(line): yield 0, "E704 multiple statements on one line (def)" else: yield found, "E701 multiple statements on one line (colon)" diff --git a/testsuite/E25.py b/testsuite/E25.py index 7a536b57..dde95b8e 100644 --- a/testsuite/E25.py +++ b/testsuite/E25.py @@ -38,3 +38,7 @@ def munge(input: AnyStr, sep: AnyStr = None, limit=1000, #: Okay async def add(a: int = 0, b: int = 0) -> int: return a + b +# Previously E251 four times +#: E272:1:6 +async def add(a: int = 0, b: int = 0) -> int: + return a + b diff --git a/testsuite/E30.py b/testsuite/E30.py index aaa77b73..bd74b803 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -156,3 +156,10 @@ def main(): if __name__ == '__main__': main() +# Previously just E272:1:6 E272:4:6 +#: E302:4:1 E272:1:6 E272:4:6 +async def x(): + pass + +async def x(y: int = 1): + pass diff --git a/testsuite/E70.py b/testsuite/E70.py index 85ca6668..caafe455 100644 --- a/testsuite/E70.py +++ b/testsuite/E70.py @@ -12,6 +12,10 @@ del a[:]; a.append(42); #: E704:1:1 def f(x): return 2 +#: E704:1:1 +async def f(x): return 2 +#: E704:1:1 E272:1:6 +async def f(x): return 2 #: E704:1:1 E226:1:19 def f(x): return 2*x #: E704:2:5 E226:2:23 From e4312f75d8e589836e080aeeae28aae0a5c7b087 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 24 Jan 2017 14:36:04 -0800 Subject: [PATCH 113/412] Don't report E701 for variable annotations (#612) --- pycodestyle.py | 13 ++++++++++++- testsuite/E90.py | 2 +- testsuite/python3.py | 12 ++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0f14209d..a5e376ca 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -123,6 +123,17 @@ HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def|def|class|@)') +STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( + r'^\s*({0})'.format('|'.join(s.replace(' ', '\s+') for s in ( + 'def', 'async def', + 'for', 'async for', + 'if', 'elif', 'else', + 'try', 'except', 'finally', + 'with', 'async with', + 'class', + 'while', + ))) +) # Work around Python < 2.6 behaviour, which does not generate NL after # a comment which is on a line by itself. @@ -1004,7 +1015,7 @@ def compound_statements(logical_line): break if STARTSWITH_DEF_REGEX.match(line): yield 0, "E704 multiple statements on one line (def)" - else: + elif STARTSWITH_INDENT_STATEMENT_REGEX.match(line): yield found, "E701 multiple statements on one line (colon)" prev_found = found found = line.find(':', found + 1) diff --git a/testsuite/E90.py b/testsuite/E90.py index 1db3d0e6..2c18e9af 100644 --- a/testsuite/E90.py +++ b/testsuite/E90.py @@ -8,7 +8,7 @@ pass except: print 'Whoops' -#: E122 E225 E251 E251 E701 +#: E122 E225 E251 E251 # Do not crash if code is invalid if msg: diff --git a/testsuite/python3.py b/testsuite/python3.py index 88818800..fde4281c 100644 --- a/testsuite/python3.py +++ b/testsuite/python3.py @@ -1,6 +1,18 @@ #!/usr/bin/env python3 +from typing import ClassVar, List # Annotated function (Issue #29) def foo(x: int) -> int: return x + 1 + + +# Annotated variables #575 +CONST: int = 42 + + +class Class: + cls_var: ClassVar[str] + + def m(self): + xs: List[int] = [] From 5a14978a3f6c1c8867b396d31491aa9cf073433c Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 7 Jun 2016 15:58:44 -0500 Subject: [PATCH 114/412] Switch Travis to use tox This reduces confusion about how travis runs tests versus a local developer. Unfortunately, pypy3 on Travis is stuck to a version that only implements Python 3.2. There is no convenient work around for that version so we remove it from our matrix until Travis updates their images. --- .travis.yml | 39 ++++++++++++++++++++++----------------- tox.ini | 8 +++++++- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index ee069e88..3847bdeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,28 @@ language: python sudo: false -python: - - 2.6 - - 2.7 - - 3.2 - - 3.3 - - 3.4 - - 3.5 - - nightly - - pypy - - pypy3 install: - - pip install -e . - - pip list -script: - - python pycodestyle.py --testsuite testsuite - - python pycodestyle.py --statistics pycodestyle.py - - python pycodestyle.py --doctest - - python setup.py test + - pip install tox +script: tox +matrix: + include: + - python: 2.6 + env: TOXENV=py26 + - python: 2.7 + env: TOXENV=py27 + - python: 3.3 + env: TOXENV=py33 + - python: 3.4 + env: TOXENV=py34 + - python: 3.5 + env: TOXENV=py35 + - python: 3.6 + env: TOXENV=py36 + - python: nightly + env: TOXENV=py37 + - python: pypy + env: TOXENV=pypy + - python: 3.5 + env: TOXENV=flake8 notifications: email: diff --git a/tox.ini b/tox.ini index 0c16e340..797bbfab 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, py35, pypy, pypy3, jython +envlist = py26, py27, py32, py33, py34, py35, py36, pypy, pypy3, jython skip_missing_interpreters=True [testenv] @@ -14,3 +14,9 @@ commands = {envpython} pycodestyle.py --statistics pycodestyle.py {envpython} pycodestyle.py --doctest {envpython} setup.py test + +[testenv:flake8] +deps = + flake8 +commands = + flake8 pycodestyle.py From 08a6d8a3c69ebef67172a70db6581469285a295b Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Thu, 2 Jun 2016 11:41:48 -0700 Subject: [PATCH 115/412] Allow all dunder variables above imports Closes #394 --- pycodestyle.py | 3 +++ testsuite/E40.py | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index e0df43f8..96d2da3b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -134,6 +134,7 @@ 'while', ))) ) +DUNDER_REGEX = re.compile(r'^__([^\s]+)__ = ') # Work around Python < 2.6 behaviour, which does not generate NL after # a comment which is on a line by itself. @@ -955,6 +956,8 @@ def is_string_literal(line): if line.startswith('import ') or line.startswith('from '): if checker_state.get('seen_non_imports', False): 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 diff --git a/testsuite/E40.py b/testsuite/E40.py index 18ac73ac..f9a18fc9 100644 --- a/testsuite/E40.py +++ b/testsuite/E40.py @@ -11,9 +11,17 @@ import myclass import foo.bar.yourclass -#: E402 +#: Okay __all__ = ['abc'] +import foo +#: Okay +__version__ = "42" + +import foo +#: Okay +__author__ = "Simon Gomizelj" + import foo #: Okay try: From 1d633b2c2d5056ddcd0c811352dc46208739054b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 27 Jan 2017 14:58:50 -0600 Subject: [PATCH 116/412] Prepare 2.3.0 release --- CHANGES.txt | 16 ++++++++++++++++ pycodestyle.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9c43531c..572caec8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,22 @@ Changelog ========= +2.3.0 (2017-01-30) +------------------ + +New Checks: + +* Add E722 warning for bare ``except`` clauses +* Report E704 for async function definitions (``async def``) + +Bugs: + +* Fix another E305 false positive for variables beginning with "class" or + "def" +* Fix detection of multiple spaces betwen ``async`` and ``def`` +* Fix handling of variable annotations. Stop reporting E701 on Python 3.6 for + variable annotations. + 2.2.0 (2016-11-14) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index 96d2da3b..2e4ebed5 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -66,7 +66,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.2.0' +__version__ = '2.3.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From a574081fd55c15f509a8c0724d40c79ac74dd2f6 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 30 Jan 2017 09:48:41 -0600 Subject: [PATCH 117/412] Fix regression in E302 detection This was similar to an issue we had with E306 in which we were only checking that a line started with "def" or "class" which catches global vars like "defaults" or "classification". Closes gh-617 --- pycodestyle.py | 2 +- testsuite/E30not.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 2e4ebed5..9f14444b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -122,7 +122,7 @@ LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)') -STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def|def|class|@)') +STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def |def |class |@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( r'^\s*({0})'.format('|'.join(s.replace(' ', '\s+') for s in ( 'def', 'async def', diff --git a/testsuite/E30not.py b/testsuite/E30not.py index 63ff733e..00bee957 100644 --- a/testsuite/E30not.py +++ b/testsuite/E30not.py @@ -155,3 +155,6 @@ class Bar(object): classification_errors = None #: Okay defined_properly = True +#: Okay +defaults = {} +defaults.update({}) From b10ea40cab108bb0cf742872c6cfa6499411f1dd Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 30 Jan 2017 10:00:19 -0600 Subject: [PATCH 118/412] Allow for multiple spaces in top level regex --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 9f14444b..05e8e5d8 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -122,7 +122,7 @@ LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)') -STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def |def |class |@)') +STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( r'^\s*({0})'.format('|'.join(s.replace(' ', '\s+') for s in ( 'def', 'async def', From e18679274bdfce281cff60191793862bedf307b4 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 30 Jan 2017 11:08:30 -0600 Subject: [PATCH 119/412] Add regression test for nested definitions The bug was already fixed since it also affected E302 but this adds a test to ensure it doesn't regress for nested definitions either. Closes gh-619 --- testsuite/E30not.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testsuite/E30not.py b/testsuite/E30not.py index 00bee957..6303b3b2 100644 --- a/testsuite/E30not.py +++ b/testsuite/E30not.py @@ -158,3 +158,7 @@ class Bar(object): #: Okay defaults = {} defaults.update({}) +#: Okay +def foo(x): + classification = x + definitely = not classification From ca5350195031c8e29c50292404eaec848bbb073b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 30 Jan 2017 17:58:37 -0600 Subject: [PATCH 120/412] Bump version for 2.3.1 Add release notes for 2.3.1 as well --- CHANGES.txt | 7 +++++++ pycodestyle.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 572caec8..230e2d8a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Changelog ========= +2.3.1 (2017-01-31) +------------------ + +Bugs: + +* Fix regression in detection of E302 and E306; #618, #620 + 2.3.0 (2017-01-30) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index 05e8e5d8..5d8c2ac1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -66,7 +66,7 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.3.0' +__version__ = '2.3.1' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' From 4f429c7a121ecac2f357fa9df7c0af462ce32edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 24 Feb 2017 11:29:06 +0200 Subject: [PATCH 121/412] Spelling fixes --- CHANGES.txt | 2 +- CONTRIBUTING.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 230e2d8a..09774327 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -20,7 +20,7 @@ Bugs: * Fix another E305 false positive for variables beginning with "class" or "def" -* Fix detection of multiple spaces betwen ``async`` and ``def`` +* Fix detection of multiple spaces between ``async`` and ``def`` * Fix handling of variable annotations. Stop reporting E701 on Python 3.6 for variable annotations. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index caab1931..aad6ad67 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -19,7 +19,7 @@ editing:: Now you have a copy of the pycodestyle codebase that is almost ready for edits. Next we will setup `virtualenv`_ which will help create an isolated -environment to manage dependancies. +environment to manage dependencies. Step 2: Use virtualenv when developing From 110a5c3bcd4ac407a471410f23f82cb6e706e6bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 24 Feb 2017 11:27:57 +0200 Subject: [PATCH 122/412] Python 3.6 invalid escape sequence deprecation fix https://docs.python.org/3/whatsnew/3.6.html#deprecated-python-behavior "backslash-character pair that is not a valid escape sequence now generates a DeprecationWarning. Although this will eventually become a SyntaxError, that will not be for several Python releases." --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 5d8c2ac1..76fce9ba 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -124,7 +124,7 @@ STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( - r'^\s*({0})'.format('|'.join(s.replace(' ', '\s+') for s in ( + r'^\s*({0})'.format('|'.join(s.replace(' ', r'\s+') for s in ( 'def', 'async def', 'for', 'async for', 'if', 'elif', 'else', From cb17d2739696df66c5b1eed2de559b4d778836e2 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 15 Mar 2017 08:37:40 -0700 Subject: [PATCH 123/412] Correctly report E501 when the first line of a docstring is too long Resolves #622 --- pycodestyle.py | 6 ++++-- testsuite/E50.py | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 5d8c2ac1..556467b5 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1199,7 +1199,7 @@ def comparison_type(logical_line, noqa): def bare_except(logical_line, noqa): - r"""When catching exceptions, mention specific exceptions whenever possible. + r"""When catching exceptions, mention specific exceptions when possible. Okay: except Exception: Okay: except BaseException: @@ -1727,7 +1727,9 @@ def maybe_check_physical(self, token): return self.multiline = True self.line_number = token[2][0] - for line in token[1].split('\n')[:-1]: + _, src, (_, offset), _, _ = token + src = self.lines[self.line_number - 1][:offset] + src + for line in src.split('\n')[:-1]: self.check_physical(line + '\n') self.line_number += 1 self.multiline = False diff --git a/testsuite/E50.py b/testsuite/E50.py index f60f3890..189f416a 100644 --- a/testsuite/E50.py +++ b/testsuite/E50.py @@ -82,6 +82,11 @@ #: E501 """ longnospaceslongnospaceslongnospaceslongnospaceslongnospaceslongnospaceslongnospaceslongnospaces""" +#: E501 +# Regression test for #622 +def foo(): + """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis pulvinar vitae + """ #: Okay """ This From 27ed964c2fc3340d145e9f602400af2f6d039350 Mon Sep 17 00:00:00 2001 From: Rach Belaid Date: Wed, 22 Mar 2017 22:55:40 -0700 Subject: [PATCH 124/412] Support variable annotation when variable start by a keyword Support case when the variable name start by a keyword, eg: ``` class Test: formula: str = None ``` --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 5d8c2ac1..ff6f0cc1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -124,7 +124,7 @@ STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( - r'^\s*({0})'.format('|'.join(s.replace(' ', '\s+') for s in ( + r'^\s*({0})\s'.format('|'.join(s.replace(' ', '\s+') for s in ( 'def', 'async def', 'for', 'async for', 'if', 'elif', 'else', From 0d70cc8fb27240390e252881615f740103535c93 Mon Sep 17 00:00:00 2001 From: Rach Belaid Date: Wed, 22 Mar 2017 23:05:17 -0700 Subject: [PATCH 125/412] Add test when variable with annotation start with a keyword --- testsuite/python3.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/testsuite/python3.py b/testsuite/python3.py index fde4281c..fce0abcd 100644 --- a/testsuite/python3.py +++ b/testsuite/python3.py @@ -13,6 +13,16 @@ def foo(x: int) -> int: class Class: cls_var: ClassVar[str] - + for_var: ClassVar[str] + while_var: ClassVar[str] + def_var: ClassVar[str] + if_var: ClassVar[str] + elif_var: ClassVar[str] + else_var: ClassVar[str] + try_var: ClassVar[str] + except_var: ClassVar[str] + finally_var: ClassVar[str] + with_var: ClassVar[str] + def m(self): xs: List[int] = [] From a988a671860eedca581868a0a40fcfbba484730c Mon Sep 17 00:00:00 2001 From: Rach Belaid Date: Thu, 6 Apr 2017 19:57:19 -0700 Subject: [PATCH 126/412] update patch --- pycodestyle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index ff6f0cc1..70c77ccf 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -121,10 +121,10 @@ OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') -STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)') +STARTSWITH_DEF_REGEX = re.compile(r'^(async\b+def|def)') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( - r'^\s*({0})\s'.format('|'.join(s.replace(' ', '\s+') for s in ( + r'^\s*({0})\b'.format('|'.join(s.replace(' ', '\s+') for s in ( 'def', 'async def', 'for', 'async for', 'if', 'elif', 'else', From e617167d529de15b8784308cdfc7ef1caeed1136 Mon Sep 17 00:00:00 2001 From: Rach Belaid Date: Thu, 6 Apr 2017 19:58:22 -0700 Subject: [PATCH 127/412] update tests --- testsuite/python3.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/testsuite/python3.py b/testsuite/python3.py index fce0abcd..4d38e139 100644 --- a/testsuite/python3.py +++ b/testsuite/python3.py @@ -23,6 +23,16 @@ class Class: except_var: ClassVar[str] finally_var: ClassVar[str] with_var: ClassVar[str] + For_var: ClassVar[str] + While_var: ClassVar[str] + Def_var: ClassVar[str] + If_var: ClassVar[str] + Elif_var: ClassVar[str] + Else_var: ClassVar[str] + Try_var: ClassVar[str] + Except_var: ClassVar[str] + Finally_var: ClassVar[str] + With_var: ClassVar[str] def m(self): xs: List[int] = [] From b3ac41fac8443529788be11224c4b017123d3226 Mon Sep 17 00:00:00 2001 From: Eddie Antonio Santos Date: Sat, 15 Apr 2017 12:16:41 -0600 Subject: [PATCH 128/412] Fix erroneous regex repetition. --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 70c77ccf..03cdd62e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -121,7 +121,7 @@ OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') -STARTSWITH_DEF_REGEX = re.compile(r'^(async\b+def|def)') +STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( r'^\s*({0})\b'.format('|'.join(s.replace(' ', '\s+') for s in ( From d87a44367c5542ab8052c212e6d51f1532086dd1 Mon Sep 17 00:00:00 2001 From: Eddie Antonio Santos Date: Sat, 15 Apr 2017 17:03:49 -0600 Subject: [PATCH 129/412] Make identifiers camel-case; remove redundant space. --- testsuite/python3.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/testsuite/python3.py b/testsuite/python3.py index 4d38e139..be7c58f4 100644 --- a/testsuite/python3.py +++ b/testsuite/python3.py @@ -12,6 +12,7 @@ def foo(x: int) -> int: class Class: + # Camel-caes cls_var: ClassVar[str] for_var: ClassVar[str] while_var: ClassVar[str] @@ -23,16 +24,16 @@ class Class: except_var: ClassVar[str] finally_var: ClassVar[str] with_var: ClassVar[str] - For_var: ClassVar[str] - While_var: ClassVar[str] - Def_var: ClassVar[str] - If_var: ClassVar[str] - Elif_var: ClassVar[str] - Else_var: ClassVar[str] - Try_var: ClassVar[str] - Except_var: ClassVar[str] - Finally_var: ClassVar[str] - With_var: ClassVar[str] - + forVar: ClassVar[str] + whileVar: ClassVar[str] + defVar: ClassVar[str] + ifVar: ClassVar[str] + elifVar: ClassVar[str] + elseVar: ClassVar[str] + tryVar: ClassVar[str] + exceptVar: ClassVar[str] + finallyVar: ClassVar[str] + withVar: ClassVar[str] + def m(self): xs: List[int] = [] From 7b98ff3a9471c6b111f477ea06e8fc207568172a Mon Sep 17 00:00:00 2001 From: Julien Palard Date: Sat, 6 May 2017 09:57:02 +0200 Subject: [PATCH 130/412] Avoid relying on inspect to list functions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allow to run python3 -m cProfile ./pycodestyle.py …. Otherwise, as cProfile looks to wrap every functions (in a way I did not studied in depth), the inspection of test function found no function, and no test were run while running under cProfile, meaning that: $ time python3 ./pycodestyle.py coefficients-148K.py [loads and loads of errors properly reported] real 0m4.712s user 0m4.684s sys 0m0.024s $ time python3 -m cProfile ./pycodestyle.py /home/mdk/Downloads/pystyle/stats/git-clones/PmagPy/PmagPy/coefficients-148K.py [no error reported as no check were found by inspection] real 0m0.447s user 0m0.436s sys 0m0.008s With this patch: $ time python3 -m cProfile ./pycodestyle.py /home/mdk/Downloads/pystyle/stats/git-clones/PmagPy/PmagPy/coefficients-148K.py [loads and loads of errors properly reported] real 0m4.889s user 0m4.852s sys 0m0.032s --- pycodestyle.py | 109 +++++++++++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 45 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 556467b5..2e033013 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -141,11 +141,43 @@ COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n' +_checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} + + +def _get_parameters(function): + if sys.version_info >= (3, 3): + return [parameter.name + for parameter + in inspect.signature(function).parameters.values() + if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] + else: + return inspect.getargspec(function)[0] + + +def register_check(check, codes=None): + """Register a new check object.""" + def _add_check(check, kind, codes, args): + if check in _checks[kind]: + _checks[kind][check][0].extend(codes or []) + else: + _checks[kind][check] = (codes or [''], args) + if inspect.isfunction(check): + args = _get_parameters(check) + if args and args[0] in ('physical_line', 'logical_line'): + if codes is None: + codes = ERRORCODE_REGEX.findall(check.__doc__ or '') + _add_check(check, args[0], codes, args) + elif inspect.isclass(check): + if _get_parameters(check.__init__)[:2] == ['self', 'tree']: + _add_check(check, 'tree', codes, None) + return check + + ############################################################################## # Plugins (check functions) for physical lines ############################################################################## - +@register_check def tabs_or_spaces(physical_line, indent_char): r"""Never mix tabs and spaces. @@ -165,6 +197,7 @@ def tabs_or_spaces(physical_line, indent_char): return offset, "E101 indentation contains mixed spaces and tabs" +@register_check def tabs_obsolete(physical_line): r"""For new projects, spaces-only are strongly recommended over tabs. @@ -176,6 +209,7 @@ def tabs_obsolete(physical_line): return indent.index('\t'), "W191 indentation contains tabs" +@register_check def trailing_whitespace(physical_line): r"""Trailing whitespace is superfluous. @@ -197,6 +231,7 @@ def trailing_whitespace(physical_line): return 0, "W293 blank line contains whitespace" +@register_check def trailing_blank_lines(physical_line, lines, line_number, total_lines): r"""Trailing blank lines are superfluous. @@ -213,6 +248,7 @@ def trailing_blank_lines(physical_line, lines, line_number, total_lines): return len(physical_line), "W292 no newline at end of file" +@register_check def maximum_line_length(physical_line, max_line_length, multiline, noqa): r"""Limit all lines to a maximum of 79 characters. @@ -251,6 +287,7 @@ def maximum_line_length(physical_line, max_line_length, multiline, noqa): ############################################################################## +@register_check def blank_lines(logical_line, blank_lines, indent_level, line_number, blank_before, previous_logical, previous_unindented_logical_line, previous_indent_level, @@ -313,6 +350,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, "class or function definition, found %d" % blank_before +@register_check def extraneous_whitespace(logical_line): r"""Avoid extraneous whitespace. @@ -345,6 +383,7 @@ def extraneous_whitespace(logical_line): yield found, "%s whitespace before '%s'" % (code, char) +@register_check def whitespace_around_keywords(logical_line): r"""Avoid extraneous whitespace around keywords. @@ -368,6 +407,7 @@ def whitespace_around_keywords(logical_line): yield match.start(2), "E271 multiple spaces after keyword" +@register_check def missing_whitespace_after_import_keyword(logical_line): r"""Multiple imports in form from x import (a, b, c) should have space between import statement and parenthesised name list. @@ -385,6 +425,7 @@ def missing_whitespace_after_import_keyword(logical_line): yield pos, "E275 missing whitespace after keyword" +@register_check def missing_whitespace(logical_line): r"""Each comma, semicolon or colon should be followed by whitespace. @@ -411,6 +452,7 @@ def missing_whitespace(logical_line): yield index, "E231 missing whitespace after '%s'" % char +@register_check def indentation(logical_line, previous_logical, indent_char, indent_level, previous_indent_level): r"""Use 4 spaces per indentation level. @@ -442,6 +484,7 @@ def indentation(logical_line, previous_logical, indent_char, yield 0, tmpl % (3 + c, "unexpected indentation") +@register_check def continued_indentation(logical_line, tokens, indent_level, hang_closing, indent_char, noqa, verbose): r"""Continuation lines indentation. @@ -641,6 +684,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, yield pos, "%s with same indent as next logical line" % code +@register_check def whitespace_before_parameters(logical_line, tokens): r"""Avoid extraneous whitespace. @@ -673,6 +717,7 @@ def whitespace_before_parameters(logical_line, tokens): prev_end = end +@register_check def whitespace_around_operator(logical_line): r"""Avoid extraneous whitespace around an operator. @@ -696,6 +741,7 @@ def whitespace_around_operator(logical_line): yield match.start(2), "E222 multiple spaces after operator" +@register_check def missing_whitespace_around_operator(logical_line, tokens): r"""Surround operators with a single space on either side. @@ -788,6 +834,7 @@ def missing_whitespace_around_operator(logical_line, tokens): prev_end = end +@register_check def whitespace_around_comma(logical_line): r"""Avoid extraneous whitespace after a comma or a colon. @@ -806,6 +853,7 @@ def whitespace_around_comma(logical_line): yield found, "E241 multiple spaces after '%s'" % m.group()[0] +@register_check def whitespace_around_named_parameter_equals(logical_line, tokens): r"""Don't use spaces around the '=' sign in function arguments. @@ -856,6 +904,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): prev_end = end +@register_check def whitespace_before_comment(logical_line, tokens): r"""Separate inline comments by at least two spaces. @@ -897,6 +946,7 @@ def whitespace_before_comment(logical_line, tokens): prev_end = end +@register_check def imports_on_separate_lines(logical_line): r"""Place imports on separate lines. @@ -916,6 +966,7 @@ def imports_on_separate_lines(logical_line): yield found, "E401 multiple imports on one line" +@register_check def module_imports_on_top_of_file( logical_line, indent_level, checker_state, noqa): r"""Place imports at the top of the file. @@ -972,6 +1023,7 @@ def is_string_literal(line): checker_state['seen_non_imports'] = True +@register_check def compound_statements(logical_line): r"""Compound statements (on the same line) are generally discouraged. @@ -1032,6 +1084,7 @@ def compound_statements(logical_line): found = line.find(';', found + 1) +@register_check def explicit_line_join(logical_line, tokens): r"""Avoid explicit line join between brackets. @@ -1071,6 +1124,7 @@ def explicit_line_join(logical_line, tokens): parens -= 1 +@register_check def break_around_binary_operator(logical_line, tokens): r""" Avoid breaks before binary operators. @@ -1120,6 +1174,7 @@ def is_binary_operator(token_type, text): previous_text = text +@register_check def comparison_to_singleton(logical_line, noqa): r"""Comparison to singletons should use "is" or "is not". @@ -1154,6 +1209,7 @@ def comparison_to_singleton(logical_line, noqa): (code, singleton, msg)) +@register_check def comparison_negative(logical_line): r"""Negative comparison should be done using "not in" and "is not". @@ -1175,6 +1231,7 @@ def comparison_negative(logical_line): yield pos, "E714 test for object identity should be 'is not'" +@register_check def comparison_type(logical_line, noqa): r"""Object type comparisons should always use isinstance(). @@ -1198,6 +1255,7 @@ def comparison_type(logical_line, noqa): yield match.start(), "E721 do not compare types, use 'isinstance()'" +@register_check def bare_except(logical_line, noqa): r"""When catching exceptions, mention specific exceptions when possible. @@ -1214,6 +1272,7 @@ def bare_except(logical_line, noqa): yield match.start(), "E722 do not use bare except'" +@register_check def ambiguous_identifier(logical_line, tokens): r"""Never use the characters 'l', 'O', or 'I' as variable names. @@ -1266,6 +1325,7 @@ def ambiguous_identifier(logical_line, tokens): prev_start = start +@register_check def python_3000_has_key(logical_line, noqa): r"""The {}.has_key() method is removed in Python 3: use the 'in' operator. @@ -1277,6 +1337,7 @@ def python_3000_has_key(logical_line, noqa): yield pos, "W601 .has_key() is deprecated, use 'in'" +@register_check def python_3000_raise_comma(logical_line): r"""When raising an exception, use "raise ValueError('message')". @@ -1290,6 +1351,7 @@ def python_3000_raise_comma(logical_line): yield match.end() - 1, "W602 deprecated form of raising exception" +@register_check def python_3000_not_equal(logical_line): r"""New code should always use != instead of <>. @@ -1303,6 +1365,7 @@ def python_3000_not_equal(logical_line): yield pos, "W603 '<>' is deprecated, use '!='" +@register_check def python_3000_backticks(logical_line): r"""Use repr() instead of backticks in Python 3. @@ -1471,50 +1534,6 @@ def _is_eol_token(token, _eol_token=_is_eol_token): ############################################################################## -_checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} - - -def _get_parameters(function): - if sys.version_info >= (3, 3): - return [parameter.name - for parameter - in inspect.signature(function).parameters.values() - if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] - else: - return inspect.getargspec(function)[0] - - -def register_check(check, codes=None): - """Register a new check object.""" - def _add_check(check, kind, codes, args): - if check in _checks[kind]: - _checks[kind][check][0].extend(codes or []) - else: - _checks[kind][check] = (codes or [''], args) - if inspect.isfunction(check): - args = _get_parameters(check) - if args and args[0] in ('physical_line', 'logical_line'): - if codes is None: - codes = ERRORCODE_REGEX.findall(check.__doc__ or '') - _add_check(check, args[0], codes, args) - elif inspect.isclass(check): - if _get_parameters(check.__init__)[:2] == ['self', 'tree']: - _add_check(check, 'tree', codes, None) - - -def init_checks_registry(): - """Register all globally visible functions. - - The first argument name is either 'physical_line' or 'logical_line'. - """ - mod = inspect.getmodule(register_check) - for (name, function) in inspect.getmembers(mod, inspect.isfunction): - register_check(function) - - -init_checks_registry() - - class Checker(object): """Load a Python source file, tokenize it, check coding style.""" From 83782f6cb617ef10186af270c05bfc87a4b4d0bb Mon Sep 17 00:00:00 2001 From: Julien Palard Date: Sat, 6 May 2017 16:39:34 +0200 Subject: [PATCH 131/412] Use bisect instead of iterating over every offsets. As the offsets looks already sorted (which looks logical so I assumed they always are), using a bisection if faster than iterating over all of them. On a specific test I encontered, I got nice enhancement with this patch: $ time python3 ./pycodestyle.py ~/Downloads/PmagPy/PmagPy/coefficients-552K.py > /dev/null real 1m16.405s $ time python3 ./pycodestyle.py ~/Downloads/PmagPy/PmagPy/coefficients-552K.py > /dev/null real 0m3.318s --- pycodestyle.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 2e033013..763747eb 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -56,6 +56,7 @@ import time import tokenize import warnings +import bisect from fnmatch import fnmatch from optparse import OptionParser @@ -1664,10 +1665,10 @@ def check_logical(self): """Build a line from tokens and run all logical checks on it.""" self.report.increment_logical_line() mapping = self.build_tokens_line() - if not mapping: return + mapping_offsets = [offset for offset, _ in mapping] (start_row, start_col) = mapping[0][1] start_line = self.lines[start_row - 1] self.indent_level = expand_indent(start_line[:start_col]) @@ -1681,9 +1682,10 @@ def check_logical(self): self.init_checker_state(name, argument_names) for offset, text in self.run_check(check, argument_names) or (): if not isinstance(offset, tuple): - for token_offset, pos in mapping: - if offset <= token_offset: - break + # As mappings are ordered, bisecting is a fast way + # to find a given offset in them. + token_offset, pos = mapping[bisect.bisect_left( + mapping_offsets, offset)] offset = (pos[0], pos[1] + offset - token_offset) self.report_error(offset[0], offset[1], text, check) if self.logical_line: From 4538f43989a49aef163355cae44ebd686d1c780c Mon Sep 17 00:00:00 2001 From: Kevin Cole Date: Tue, 30 May 2017 15:33:14 -0400 Subject: [PATCH 132/412] Added .gitattributes to prevent auto-munging intentional line endings Users' global .gitattributes files may attempt to auto-fix certain files. --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..0aadd30b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +testsuite/E90.py -text From f5be67cc842a3e0e02db130cde086f18929f656b Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 3 Jun 2017 09:25:17 -0700 Subject: [PATCH 133/412] Document all supported Python versions as trove classifiers I frequently use PyPI trove classifiers to check if a 3rd party package is usable by my projects. Documenting the supported versions makes this much easier for potential library users. --- setup.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/setup.py b/setup.py index b77770e5..e68df569 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,15 @@ def get_long_description(): 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', ], test_suite='testsuite.test_all.suite', From f913dfc041bab577cdea34e261153545b98d3e4c Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 3 Jun 2017 17:14:19 -0700 Subject: [PATCH 134/412] Rename [wheel] section to [bdist_wheel] as the former is legacy (#653) See: https://bitbucket.org/pypa/wheel/src/54ddbcc9cec25e1f4d111a142b8bfaa163130a61/wheel/bdist_wheel.py?fileviewer=file-view-default#bdist_wheel.py-119:125 http://pythonwheels.com/ --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 803bc10f..fcc079a2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ -[wheel] +[bdist_wheel] universal = 1 [pycodestyle] From 454e035fd969ee5d6da76fcc169ee028933f391e Mon Sep 17 00:00:00 2001 From: Julien Date: Sun, 4 Jun 2017 02:20:30 +0200 Subject: [PATCH 135/412] Fix issue #643: Optimize noqa() with an lru_cache for Python 3.2+. (#644) Had to catch a "No signature found for builtin " in 3.4: In python3.4 the search was not detected as a function, now that it's wrapped in an lru_cache it is, yet it still has no signature (it has in 3.5+). --- pycodestyle.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index bb96195a..8afa2868 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -58,6 +58,16 @@ import warnings import bisect +try: + from functools import lru_cache +except ImportError: + def lru_cache(maxsize=128): # noqa as it's a fake implementation. + """Does not really need a real a lru_cache, it's just optimization, so + let's just do nothing here. Python 3.2+ will just get better + performances, time to upgrade? + """ + return lambda function: function + from fnmatch import fnmatch from optparse import OptionParser @@ -1410,7 +1420,7 @@ def stdin_get_value(): """Read the value from stdin.""" return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() -noqa = re.compile(r'# no(?:qa|pep8)\b', re.I).search +noqa = lru_cache(512)(re.compile(r'# no(?:qa|pep8)\b', re.I).search) def expand_indent(line): From c556daab719a702859b648219006f9e0dee6faae Mon Sep 17 00:00:00 2001 From: Marco J Sirabella Date: Sun, 9 Jul 2017 18:58:37 -0400 Subject: [PATCH 136/412] - Forward down verbosity level --- pycodestyle.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 5b7a39c1..f402bc2a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1995,8 +1995,9 @@ def __init__(self, *args, **kwargs): # build options from dict options_dict = dict(*args, **kwargs) arglist = None if parse_argv else options_dict.get('paths', None) + verbose = options_dict.get('verbose', False) options, self.paths = process_options( - arglist, parse_argv, config_file, parser) + arglist, parse_argv, config_file, parser, verbose) if options_dict: options.__dict__.update(options_dict) if 'paths' in options_dict: @@ -2256,7 +2257,7 @@ def read_config(options, args, arglist, parser): def process_options(arglist=None, parse_argv=False, config_file=None, - parser=None): + parser=None, verbose=False): """Process options passed either via arglist or via command line args. Passing in the ``config_file`` parameter allows other tools, such as flake8 @@ -2280,6 +2281,9 @@ def process_options(arglist=None, parse_argv=False, config_file=None, (options, args) = parser.parse_args(arglist) options.reporter = None + if verbose: # If specified verbose, continue on verbosity + options.verbose = verbose + if options.ensure_value('testsuite', False): args.append(options.testsuite) elif not options.ensure_value('doctest', False): From 13d2bd200911850dacde31665235122d85290265 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 19 Aug 2017 10:19:59 -0700 Subject: [PATCH 137/412] Add W605 warning for invalid escape sequences in string literals Starting with Python 3.6, invalid escape sequences in string literals are now deprecated. In a future version of Python, invalid escape sequences will be a syntax error. While this deprecation produces a runtime warning, it only appears if warnings are enabled and the first time the Python source is compiled to byte code. By adding a check to pycodestyle, projects can take advantage of static analysis to catch and fix these future syntax errors. For more information on the deprecation, see the Python release notes, https://docs.python.org/3/whatsnew/3.6.html#deprecated-python-behavior > A backslash-character pair that is not a valid escape sequence now > generates a DeprecationWarning. Although this will eventually become a > SyntaxError, that will not be for several Python releases. Fixes #633 --- CHANGES.txt | 7 +++++++ docs/intro.rst | 2 ++ pycodestyle.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ testsuite/E12not.py | 8 +++---- testsuite/W60.py | 16 ++++++++++++++ 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 09774327..0957be83 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Changelog ========= +UNRELEASED +---------- + +New checks: + +* Add W605 warning for invalid escape sequences in string literals + 2.3.1 (2017-01-31) ------------------ diff --git a/docs/intro.rst b/docs/intro.rst index fcdcf724..3035a2f2 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -413,6 +413,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | W604 | backticks are deprecated, use 'repr()' | +------------+----------------------------------------------------------------------+ +| W605 | invalid escape sequence '\x' | ++------------+----------------------------------------------------------------------+ **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, diff --git a/pycodestyle.py b/pycodestyle.py index 5b7a39c1..d31ac9e8 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1388,6 +1388,57 @@ def python_3000_backticks(logical_line): yield pos, "W604 backticks are deprecated, use 'repr()'" +@register_check +def python_3000_invalid_escape_sequence(logical_line, tokens): + r"""Invalid escape sequences are deprecated in Python 3.6. + + Okay: regex = r'\.png$' + W605: regex = '\.png$' + """ + # https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals + valid = [ + '\n', + '\\', + '\'', + '"', + 'a', + 'b', + 'f', + 'n', + 'r', + 't', + 'v', + '0', '1', '2', '3', '4', '5', '6', '7', + 'x', + + # Escape sequences only recognized in string literals + 'N', + 'u', + 'U', + ] + + for token_type, text, start, end, line in tokens: + if token_type == tokenize.STRING: + quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1] + # Extract string modifiers (e.g. u or r) + quote_pos = text.index(quote) + prefix = text[:quote_pos].lower() + start = quote_pos + len(quote) + string = text[start:-len(quote)] + + if 'r' not in prefix: + pos = string.find('\\') + while pos >= 0: + pos += 1 + if string[pos] not in valid: + yield ( + pos, + "W605 invalid escape sequence '\\%s'" % + string[pos], + ) + pos = string.find('\\', pos + 1) + + ############################################################################## # Helper functions ############################################################################## diff --git a/testsuite/E12not.py b/testsuite/E12not.py index 18c6a646..65281075 100644 --- a/testsuite/E12not.py +++ b/testsuite/E12not.py @@ -358,10 +358,10 @@ def qualify_by_address(self, cr, uid, ids, context=None, """ This gets called by the web server """ -_ipv4_re = re.compile('^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' - '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' - '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' - '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$') +_ipv4_re = re.compile(r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$') fct(""" diff --git a/testsuite/W60.py b/testsuite/W60.py index 973d22ff..cbe267d5 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -13,3 +13,19 @@ x = 0 #: W604 val = `1 + 2` +#: W605 +regex = '\.png$' +#: W605 +regex = ''' +\.png$ +''' +#: Okay +regex = r'\.png$' +regex = '\\.png$' +regex = r''' +\.png$ +''' +regex = r''' +\\.png$ +''' +s = '\\' From 1711fb429ae4815faa55bc9ccceca59f27f88706 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 29 Sep 2017 16:34:44 -0700 Subject: [PATCH 138/412] Add W606 warning for async and await keywords in Python 3.7 From https://docs.python.org/3/whatsnew/3.6.html#new-keywords > async and await are not recommended to be used as variable, class, > function or module names. Introduced by PEP 492 in Python 3.5, they > will become proper keywords in Python 3.7. Starting in Python 3.6, the > use of async or await as names will generate a DeprecationWarning. By adding a warning to pycodestyle.py these future warnings and syntax errors can be caught during static code analysis. The await expression tests were taken from PEP-492. https://www.python.org/dev/peps/pep-0492/#id58 --- CHANGES.txt | 2 ++ docs/intro.rst | 2 ++ pycodestyle.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ testsuite/W60.py | 45 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 0957be83..8fedbaa3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,8 @@ UNRELEASED New checks: * Add W605 warning for invalid escape sequences in string literals +* Add W606 warning for 'async' and 'await' reserved keywords being introduced + in Python 3.7 2.3.1 (2017-01-31) ------------------ diff --git a/docs/intro.rst b/docs/intro.rst index 3035a2f2..4b064d05 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -415,6 +415,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | W605 | invalid escape sequence '\x' | +------------+----------------------------------------------------------------------+ +| W606 | 'async' and 'await' are reserved keywords starting with Python 3.7 | ++------------+----------------------------------------------------------------------+ **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, diff --git a/pycodestyle.py b/pycodestyle.py index d31ac9e8..ad67fb08 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1439,6 +1439,60 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): pos = string.find('\\', pos + 1) +@register_check +def python_3000_async_await_keywords(logical_line, tokens): + """'async' and 'await' are reserved keywords starting with Python 3.7 + + W606: async = 42 + W606: await = 42 + Okay: async def read_data(db):\n data = await db.fetch('SELECT ...') + """ + # The Python tokenize library before Python 3.5 recognizes async/await as a + # NAME token. Therefore, use a state machine to look for the possible + # async/await constructs as defined by the Python grammar: + # https://docs.python.org/3/reference/grammar.html + + state = None + for token_type, text, start, end, line in tokens: + error = False + + if state is None: + if token_type == tokenize.NAME: + if text == 'async': + state = ('async_stmt', start) + elif text == 'await': + state = ('await', start) + elif state[0] == 'async_stmt': + if token_type == tokenize.NAME and text in ('def', 'with', 'for'): + # One of funcdef, with_stmt, or for_stmt. Return to looking + # for async/await names. + state = None + else: + error = True + elif state[0] == 'await': + if token_type in (tokenize.NAME, tokenize.NUMBER, tokenize.STRING): + # An await expression. Return to looking for async/await names. + state = None + else: + error = True + + if error: + yield ( + state[1], + "W606 'async' and 'await' are reserved keywords starting with " + "Python 3.7", + ) + state = None + + # Last token + if state is not None: + yield ( + state[1], + "W606 'async' and 'await' are reserved keywords starting with " + "Python 3.7", + ) + + ############################################################################## # Helper functions ############################################################################## diff --git a/testsuite/W60.py b/testsuite/W60.py index cbe267d5..030bec59 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -29,3 +29,48 @@ \\.png$ ''' s = '\\' +#: W606 +async = 42 +#: W606 +await = 42 +#: W606 +def async(): + pass +#: W606 +def await(): + pass +#: W606 +class async: + pass +#: W606 +class await: + pass +#: Okay +async def read_data(db): + data = await db.fetch('SELECT ...') +#: Okay +if await fut: + pass +if (await fut): + pass +if await fut + 1: + pass +if (await fut) + 1: + pass +pair = await fut, 'spam' +pair = (await fut), 'spam' +with await fut, open(): + pass +with (await fut), open(): + pass +await foo()['spam'].baz()() +return await coro() +return (await coro()) +res = await coro() ** 2 +res = (await coro()) ** 2 +func(a1=await coro(), a2=0) +func(a1=(await coro()), a2=0) +await foo() + await bar() +(await foo()) + (await bar()) +-await foo() +-(await foo()) From 373179b1d2bac50204fc2a125b2d743ae07d0296 Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Sat, 14 Oct 2017 18:03:06 -0700 Subject: [PATCH 139/412] Config file example for boolean flags --- docs/intro.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/intro.rst b/docs/intro.rst index 3035a2f2..51ec758b 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -198,6 +198,8 @@ Example:: [pycodestyle] ignore = E226,E302,E41 max-line-length = 160 + statistics = True + count = False At the project level, a ``setup.cfg`` file or a ``tox.ini`` file is read if present. If none of these files have a ``[pycodestyle]`` section, no project From 6853b3f76a10add4b04a689348fbe6ce72ef0e62 Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Sat, 14 Oct 2017 18:10:25 -0700 Subject: [PATCH 140/412] Maintain alphabetical ordering --- docs/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.rst b/docs/intro.rst index 51ec758b..9af82da3 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -196,10 +196,10 @@ Else if :envvar:`XDG_CONFIG_HOME` is not defined: Example:: [pycodestyle] + count = False ignore = E226,E302,E41 max-line-length = 160 statistics = True - count = False At the project level, a ``setup.cfg`` file or a ``tox.ini`` file is read if present. If none of these files have a ``[pycodestyle]`` section, no project From 5f3f9a1198011f6bede70b907829cb61e05d18ec Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 15 Oct 2017 17:06:42 -0700 Subject: [PATCH 141/412] Include license file in the generated packages Use MANIFEST.in to include LICENSE in the source distribution. The wheel package format supports including the license file. This is done using the [metadata] section in the setup.cfg file. For additional information on this feature, see: https://wheel.readthedocs.io/en/stable/index.html#including-the-license-in-the-generated-wheel-file --- MANIFEST.in | 1 + setup.cfg | 3 +++ 2 files changed, 4 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 4532c06d..fb8bc97d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include *.txt include *.rst +include LICENSE recursive-include docs * recursive-include testsuite * recursive-exclude docs *.pyc diff --git a/setup.cfg b/setup.cfg index fcc079a2..91ea6742 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,9 @@ [bdist_wheel] universal = 1 +[metadata] +license_file = LICENSE + [pycodestyle] select = ignore = E226,E24 From aa9a92b057205ea98d3e397c2fc34d86112df4b4 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 17 Oct 2017 20:11:27 -0700 Subject: [PATCH 142/412] Enable pip cache in Travis CI Can speed up builds and reduce load on PyPI servers. For more information, see: https://docs.travis-ci.com/user/caching/#pip-cache --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3847bdeb..1ea5a73b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +cache: pip sudo: false install: - pip install tox From b0cdd4343fbcabbb1e56fd3e5082096e0345343f Mon Sep 17 00:00:00 2001 From: Marco Sirabella Date: Sun, 22 Oct 2017 09:59:29 -0400 Subject: [PATCH 143/412] Transform booleans to numbers to go with previous code --- pycodestyle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index f402bc2a..3bc7976b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1995,7 +1995,7 @@ def __init__(self, *args, **kwargs): # build options from dict options_dict = dict(*args, **kwargs) arglist = None if parse_argv else options_dict.get('paths', None) - verbose = options_dict.get('verbose', False) + verbose = options_dict.get('verbose', 0) options, self.paths = process_options( arglist, parse_argv, config_file, parser, verbose) if options_dict: @@ -2257,7 +2257,7 @@ def read_config(options, args, arglist, parser): def process_options(arglist=None, parse_argv=False, config_file=None, - parser=None, verbose=False): + parser=None, verbose=0): """Process options passed either via arglist or via command line args. Passing in the ``config_file`` parameter allows other tools, such as flake8 From 80fcdae5d59167ad55478d7b076799249a79991d Mon Sep 17 00:00:00 2001 From: Marco Sirabella Date: Sun, 22 Oct 2017 10:02:02 -0400 Subject: [PATCH 144/412] Handle conflicting CLI and verbose=# better --- pycodestyle.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3bc7976b..5e9796c6 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -2257,7 +2257,7 @@ def read_config(options, args, arglist, parser): def process_options(arglist=None, parse_argv=False, config_file=None, - parser=None, verbose=0): + parser=None, verbose=None): """Process options passed either via arglist or via command line args. Passing in the ``config_file`` parameter allows other tools, such as flake8 @@ -2281,7 +2281,8 @@ def process_options(arglist=None, parse_argv=False, config_file=None, (options, args) = parser.parse_args(arglist) options.reporter = None - if verbose: # If specified verbose, continue on verbosity + # If explicity specified verbosity, override any `-v` CLI flag + if verbose is not None: options.verbose = verbose if options.ensure_value('testsuite', False): From d436d955549ebfc0755e5144230de9e85891c232 Mon Sep 17 00:00:00 2001 From: Marco Sirabella Date: Sun, 22 Oct 2017 11:26:46 -0400 Subject: [PATCH 145/412] Default `verbose` to `None` instead of `0` --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 5e9796c6..bb04941a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1995,7 +1995,7 @@ def __init__(self, *args, **kwargs): # build options from dict options_dict = dict(*args, **kwargs) arglist = None if parse_argv else options_dict.get('paths', None) - verbose = options_dict.get('verbose', 0) + verbose = options_dict.get('verbose', None) options, self.paths = process_options( arglist, parse_argv, config_file, parser, verbose) if options_dict: From f92a2e36d5dcaf5acec58931f42695230131ea58 Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Tue, 24 Oct 2017 14:27:41 +0200 Subject: [PATCH 146/412] Fix missed quoting in E722 check The warning looks fairly odd: E722 do not use bare except' change this to E722 do not use bare 'except' --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index d31ac9e8..dc486c8d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1280,7 +1280,7 @@ def bare_except(logical_line, noqa): regex = re.compile(r"except\s*:") match = regex.match(logical_line) if match: - yield match.start(), "E722 do not use bare except'" + yield match.start(), "E722 do not use bare 'except'" @register_check From 331fc8512e77732b9bac1e9ed5dc92bef3065cc4 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Wed, 25 Oct 2017 16:03:03 +0100 Subject: [PATCH 147/412] Fix space in flag name Previously, the line break in the source would result in `--hang- closing` (note the space) being rendered in the HTML. --- docs/intro.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 9af82da3..4ddf91f4 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -422,8 +422,8 @@ This is the current list of error and warning codes: **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, **E133**, **E226**, **E241**, **E242**, **E704** and **W503** are ignored because they are not rules unanimously accepted, and `PEP 8`_ does not enforce them. The -check **E133** is mutually exclusive with check **E123**. Use switch ``--hang- -closing`` to report **E133** instead of **E123**. +check **E133** is mutually exclusive with check **E123**. Use switch +``--hang-closing`` to report **E133** instead of **E123**. **(^)** These checks can be disabled at the line level using the ``# noqa`` special comment. This possibility should be reserved for special cases. From b556d30f6426c854140fb13521159071ee529056 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 11 Dec 2017 12:13:37 +0200 Subject: [PATCH 148/412] Add Python 3.6 Plus Travis CI typo --- docs/developer.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developer.rst b/docs/developer.rst index db36538d..8630edc2 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -14,7 +14,7 @@ conditions of the :ref:`Expat license `. Fork away! * `Source code `_ and `issue tracker `_ on GitHub. * `Continuous tests `_ against Python - 2.6 through 3.5 as well as the nightly Python build and PyPy, on `Travis-CI + 2.6 through 3.6 as well as the nightly Python build and PyPy, on `Travis CI platform `_. .. _available on GitHub: https://github.com/pycqa/pycodestyle @@ -104,7 +104,7 @@ Then be sure to pass the tests:: When contributing to pycodestyle, please observe our `Code of Conduct`_. -To run the tests, the core developer team and Travis-CI use tox:: +To run the tests, the core developer team and Travis CI use tox:: $ pip install -r dev-requirements.txt $ tox From d9efb8f46220dc24f74cb8e0f963786d47deecdc Mon Sep 17 00:00:00 2001 From: Jimmy Jia Date: Sat, 16 Dec 2017 13:29:23 -0500 Subject: [PATCH 149/412] Add E252 on missing whitespace for annotated parameter defaults --- pycodestyle.py | 25 ++++++++++++++++++++----- testsuite/E25.py | 3 +++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 1b066911..3fec26c0 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -869,7 +869,8 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): r"""Don't use spaces around the '=' sign in function arguments. Don't use spaces around the '=' sign when used to indicate a - keyword argument or a default parameter value. + keyword argument or a default parameter value, except when using a type + annotation. Okay: def complex(real, imag=0.0): Okay: return magic(r=real, i=imag) @@ -882,13 +883,18 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): E251: def complex(real, imag = 0.0): E251: return magic(r = real, i = imag) + E252: def complex(real, image: float=0.0): """ parens = 0 no_space = False + require_space = False prev_end = None annotated_func_arg = False in_def = bool(STARTSWITH_DEF_REGEX.match(logical_line)) + message = "E251 unexpected spaces around keyword / parameter equals" + missing_message = "E252 missing whitespace around parameter equals" + for token_type, text, start, end, line in tokens: if token_type == tokenize.NL: continue @@ -896,6 +902,10 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): no_space = False if start != prev_end: yield (prev_end, message) + if require_space: + require_space = False + if start == prev_end: + yield (prev_end, missing_message) if token_type == tokenize.OP: if text in '([': parens += 1 @@ -905,10 +915,15 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): annotated_func_arg = True elif parens and text == ',' and parens == 1: annotated_func_arg = False - elif parens and text == '=' and not annotated_func_arg: - no_space = True - if start != prev_end: - yield (prev_end, message) + elif parens and text == '=': + if not annotated_func_arg: + no_space = True + if start != prev_end: + yield (prev_end, message) + else: + require_space = True + if start == prev_end: + yield (prev_end, missing_message) if not parens: annotated_func_arg = False diff --git a/testsuite/E25.py b/testsuite/E25.py index dde95b8e..71d3f800 100644 --- a/testsuite/E25.py +++ b/testsuite/E25.py @@ -42,3 +42,6 @@ async def add(a: int = 0, b: int = 0) -> int: #: E272:1:6 async def add(a: int = 0, b: int = 0) -> int: return a + b +#: E252:1:15 E252:1:16 E252:1:27 E252:1:36 +def add(a: int=0, b: int =0, c: int= 0) -> int: + return a + b + c From 766c78a01d9ef8908f8254d9e8a19cac28c8d048 Mon Sep 17 00:00:00 2001 From: Michal Kolodziejski Date: Wed, 6 Dec 2017 21:34:46 +0100 Subject: [PATCH 150/412] Fix handling of diffs with mnemonic prefixes --- pycodestyle.py | 4 +++- testsuite/test_shell.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 1b066911..eb9ff634 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1538,7 +1538,9 @@ def parse_udiff(diff, patterns=None, parent='.'): rv[path].update(range(row, row + nrows)) elif line[:3] == '+++': path = line[4:].split('\t', 1)[0] - if path[:2] == 'b/': + # Git diff will use (i)ndex, (w)ork tree, (c)ommit and (o)bject + # instead of a/b/c/d as prefixes for patches + if path[:2] in ('b/', 'w/', 'i/'): path = path[2:] rv[path] = set() return dict([(os.path.join(parent, path), rows) diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index a80c8757..7ada1a41 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -191,3 +191,13 @@ def test_check_diff(self): self.assertFalse(errcode) self.assertFalse(stdout) self.assertFalse(stderr) + + for index, diff_line in enumerate(diff_lines, 0): + diff_line = diff_line.replace('a/', 'i/') + diff_lines[index] = diff_line.replace('b/', 'w/') + + self.stdin = '\n'.join(diff_lines) + stdout, stderr, errcode = self.pycodestyle('--diff') + self.assertFalse(errcode) + self.assertFalse(stdout) + self.assertFalse(stderr) From 8f3aebdd2bbe08111e7bc76f2722bab965ab571c Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 30 Apr 2016 07:52:29 -0500 Subject: [PATCH 151/412] Add W504 for line breaks before binary operators This flips the W503 rule to enforce line breaks before binary operators. Related #498 --- pycodestyle.py | 109 ++++++++++++++++++++++++++++++++------------ testsuite/E12.py | 29 ++++++------ testsuite/E12not.py | 11 ++--- 3 files changed, 100 insertions(+), 49 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index eb9ff634..ad173829 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -80,7 +80,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. __version__ = '2.3.1' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' -DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' +DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504' try: if sys.platform == 'win32': USER_CONFIG = os.path.expanduser(r'~\.pycodestyle') @@ -1135,8 +1135,47 @@ def explicit_line_join(logical_line, tokens): parens -= 1 +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 "()[]{},:.;@=%~" + # 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. + return ((is_op_token or is_conjunction) and not_a_symbol) + + +def _break_around_binary_operators(tokens): + """Private function to reduce duplication. + + This factors out the shared details between + :func:`break_before_binary_operator` and + :func:`break_after_binary_operator`. + """ + line_break = False + unary_context = True + # Previous non-newline token types and text + previous_token_type = None + previous_text = None + for token_type, text, start, end, line in tokens: + if token_type == tokenize.COMMENT: + continue + if ('\n' in text or '\r' in text) and token_type != tokenize.STRING: + line_break = True + else: + yield (token_type, text, previous_token_type, previous_text, + line_break, unary_context, start) + unary_context = text in '([{,;' + line_break = False + previous_token_type = token_type + previous_text = text + + @register_check -def break_around_binary_operator(logical_line, tokens): +def break_before_binary_operator(logical_line, tokens): r""" Avoid breaks before binary operators. @@ -1156,33 +1195,47 @@ def break_around_binary_operator(logical_line, tokens): Okay: var = (1 /\n -2) Okay: var = (1 +\n -1 +\n -2) """ - def is_binary_operator(token_type, text): - # 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. - return ((token_type == tokenize.OP or text in ['and', 'or']) and - text not in "()[]{},:.;@=%~") + for context in _break_around_binary_operators(tokens): + (token_type, text, previous_token_type, previous_text, + line_break, unary_context, start) = context + if (_is_binary_operator(token_type, text) and line_break and + not unary_context and + not _is_binary_operator(previous_token_type, + previous_text)): + yield start, "W503 line break before binary operator" - line_break = False - unary_context = True - # Previous non-newline token types and text - previous_token_type = None - previous_text = None - for token_type, text, start, end, line in tokens: - if token_type == tokenize.COMMENT: - continue - if ('\n' in text or '\r' in text) and token_type != tokenize.STRING: - line_break = True - else: - if (is_binary_operator(token_type, text) and line_break and - not unary_context and - not is_binary_operator(previous_token_type, - previous_text)): - yield start, "W503 line break before binary operator" - unary_context = text in '([{,;' - line_break = False - previous_token_type = token_type - previous_text = text + +@register_check +def break_after_binary_operator(logical_line, tokens): + r""" + Avoid breaks after binary operators. + + The preferred place to break around a binary operator is after the + operator, not before it. + + W504: (width == 0 +\n height == 0) + W504: (width == 0 and\n height == 0) + + Okay: (width == 0\n + height == 0) + Okay: foo(\n -x) + Okay: foo(x\n []) + Okay: x = '''\n''' + '' + Okay: x = '' + '''\n''' + Okay: foo(x,\n -y) + Okay: foo(x, # comment\n -y) + Okay: var = (1\n & ~2) + Okay: var = (1\n / -2) + Okay: var = (1\n + -1\n + -2) + """ + for context in _break_around_binary_operators(tokens): + (token_type, text, previous_token_type, previous_text, + line_break, unary_context, start) = context + if (_is_binary_operator(previous_token_type, previous_text) + and line_break + and not unary_context + and not _is_binary_operator(token_type, text)): + error_pos = (start[0] - 1, start[1]) + yield error_pos, "W504 line break after binary operator" @register_check diff --git a/testsuite/E12.py b/testsuite/E12.py index a995c955..acdd81f6 100644 --- a/testsuite/E12.py +++ b/testsuite/E12.py @@ -20,9 +20,9 @@ #: E124 a = (123, ) -#: E129 -if (row < 0 or self.moduleCount <= row or - col < 0 or self.moduleCount <= col): +#: E129 W503 +if (row < 0 or self.moduleCount <= row + or col < 0 or self.moduleCount <= col): raise Exception("%s,%s - %s" % (row, col, self.moduleCount)) #: E126 print "E126", ( @@ -195,9 +195,9 @@ def qualify_by_address( self, cr, uid, ids, context=None, params_to_check=frozenset(QUALIF_BY_ADDRESS_PARAM)): """ This gets called by the web server """ -#: E129 -if (a == 2 or - b == "abc def ghi" +#: E129 W503 +if (a == 2 + or b == "abc def ghi" "jkl mno"): return True #: @@ -225,22 +225,21 @@ def qualify_by_address( eat_a_dict_a_day({ "foo": "bar", }) -#: E126 +#: E126 W503 if ( x == ( 3 - ) or - y == 4): + ) + or y == 4): pass -#: E126 +#: E126 W503 W503 if ( x == ( 3 - ) or - x == ( - 3 - ) or - y == 4): + ) + or x == ( + 3) + or y == 4): pass #: E131 troublesome_hash = { diff --git a/testsuite/E12not.py b/testsuite/E12not.py index 65281075..34d6efe7 100644 --- a/testsuite/E12not.py +++ b/testsuite/E12not.py @@ -1,8 +1,7 @@ if ( x == ( 3 - ) or - y == 4): + ) or y == 4): pass y = x == 2 \ @@ -19,13 +18,13 @@ pass -if (foo == bar and - baz == frop): +if (foo == bar + and baz == frop): pass if ( - foo == bar and - baz == frop + foo == bar + and baz == frop ): pass From a3f7585d9135c5bef13b5a91f3358f4fabb83e0c Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Wed, 17 Jan 2018 22:42:22 -0600 Subject: [PATCH 152/412] Fix up testsuite for W504 --- pycodestyle.py | 24 ++++++++++++------------ setup.cfg | 2 +- testsuite/E12not.py | 29 ++++++++++++----------------- testsuite/W19.py | 18 +++++++++--------- testsuite/test_all.py | 2 +- testsuite/test_api.py | 4 ++-- 6 files changed, 37 insertions(+), 42 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index ad173829..f70a1a6e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1184,16 +1184,15 @@ def break_before_binary_operator(logical_line, tokens): W503: (width == 0\n + height == 0) W503: (width == 0\n and height == 0) + W503: var = (1\n & ~2) + W503: var = (1\n / -2) + W503: var = (1\n + -1\n + -2) - Okay: (width == 0 +\n height == 0) Okay: foo(\n -x) Okay: foo(x\n []) Okay: x = '''\n''' + '' Okay: foo(x,\n -y) Okay: foo(x, # comment\n -y) - Okay: var = (1 &\n ~2) - Okay: var = (1 /\n -2) - Okay: var = (1 +\n -1 +\n -2) """ for context in _break_around_binary_operators(tokens): (token_type, text, previous_token_type, previous_text, @@ -1215,25 +1214,26 @@ def break_after_binary_operator(logical_line, tokens): W504: (width == 0 +\n height == 0) W504: (width == 0 and\n height == 0) + W504: var = (1 &\n ~2) - Okay: (width == 0\n + height == 0) Okay: foo(\n -x) Okay: foo(x\n []) Okay: x = '''\n''' + '' Okay: x = '' + '''\n''' Okay: foo(x,\n -y) Okay: foo(x, # comment\n -y) - Okay: var = (1\n & ~2) - Okay: var = (1\n / -2) - Okay: var = (1\n + -1\n + -2) + + The following should be W504 but unary_context is tricky with these + Okay: var = (1 /\n -2) + Okay: var = (1 +\n -1 +\n -2) """ for context in _break_around_binary_operators(tokens): (token_type, text, previous_token_type, previous_text, line_break, unary_context, start) = context - if (_is_binary_operator(previous_token_type, previous_text) - and line_break - and not unary_context - and not _is_binary_operator(token_type, text)): + if (_is_binary_operator(previous_token_type, previous_text) and + line_break and + not unary_context and + not _is_binary_operator(token_type, text)): error_pos = (start[0] - 1, start[1]) yield error_pos, "W504 line break after binary operator" diff --git a/setup.cfg b/setup.cfg index 91ea6742..2cd4b2ec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,5 +6,5 @@ license_file = LICENSE [pycodestyle] select = -ignore = E226,E24 +ignore = E226,E24,W504 max_line_length = 79 diff --git a/testsuite/E12not.py b/testsuite/E12not.py index 34d6efe7..ebaa078f 100644 --- a/testsuite/E12not.py +++ b/testsuite/E12not.py @@ -16,12 +16,11 @@ or y > 1 \ or x == 3: pass - - +#: W503 if (foo == bar and baz == frop): pass - +#: W503 if ( foo == bar and baz == frop @@ -108,7 +107,7 @@ 'BBB' \ 'iii' \ 'CCC' - +#: W504 W504 abricot = (3 + 4 + 5 + 6) @@ -137,8 +136,7 @@ def long_function_name( var_one, var_two, var_three, var_four): print(var_one) - - +#: W504 if ((row < 0 or self.moduleCount <= row or col < 0 or self.moduleCount <= col)): raise Exception("%s,%s - %s" % (row, col, self.moduleCount)) @@ -183,23 +181,23 @@ def long_function_name( "to match that of the opening " "bracket's line" ) -# +#: W504 # you want vertical alignment, so use a parens if ((foo.bar("baz") and foo.bar("frop") )): print "yes" - +#: W504 # also ok, but starting to look like LISP if ((foo.bar("baz") and foo.bar("frop"))): print "yes" - +#: W504 if (a == 2 or b == "abc def ghi" "jkl mno"): return True - +#: W504 if (a == 2 or b == """abc def ghi jkl mno"""): @@ -223,22 +221,19 @@ def long_function_name( print('%-7d %s per second (%d total)' % ( options.counters[key] / elapsed, key, options.counters[key])) - - +#: W504 if os.path.exists(os.path.join(path, PEP8_BIN)): cmd = ([os.path.join(path, PEP8_BIN)] + self._pep8_options(targetfile)) - - +#: W504 fixed = (re.sub(r'\t+', ' ', target[c::-1], 1)[::-1] + target[c + 1:]) - +#: W504 fixed = ( re.sub(r'\t+', ' ', target[c::-1], 1)[::-1] + target[c + 1:] ) - - +#: W504 if foo is None and bar is "frop" and \ blah == 'yeah': blah = 'yeahnah' diff --git a/testsuite/W19.py b/testsuite/W19.py index afdfb767..ed69e2b5 100644 --- a/testsuite/W19.py +++ b/testsuite/W19.py @@ -7,7 +7,7 @@ #: W191 y = x == 2 \ or x == 3 -#: E101 W191 +#: E101 W191 W504 if ( x == ( 3 @@ -26,11 +26,11 @@ pass #: -#: E101 W191 +#: E101 W191 W504 if (foo == bar and baz == frop): pass -#: E101 W191 +#: E101 W191 W504 if ( foo == bar and baz == frop @@ -52,7 +52,7 @@ def long_function_name( var_one, var_two, var_three, var_four): print(var_one) -#: E101 W191 +#: E101 W191 W504 if ((row < 0 or self.moduleCount <= row or col < 0 or self.moduleCount <= col)): raise Exception("%s,%s - %s" % (row, col, self.moduleCount)) @@ -65,23 +65,23 @@ def long_function_name( "bracket's line" ) # -#: E101 W191 +#: E101 W191 W504 # you want vertical alignment, so use a parens if ((foo.bar("baz") and foo.bar("frop") )): print "yes" -#: E101 W191 +#: E101 W191 W504 # also ok, but starting to look like LISP if ((foo.bar("baz") and foo.bar("frop"))): print "yes" -#: E101 W191 +#: E101 W191 W504 if (a == 2 or b == "abc def ghi" "jkl mno"): return True -#: E101 W191 +#: E101 W191 W504 if (a == 2 or b == """abc def ghi jkl mno"""): @@ -93,7 +93,7 @@ def long_function_name( # -#: E101 W191 W191 +#: E101 W191 W191 W504 if os.path.exists(os.path.join(path, PEP8_BIN)): cmd = ([os.path.join(path, PEP8_BIN)] + self._pep8_options(targetfile)) diff --git a/testsuite/test_all.py b/testsuite/test_all.py index 08f9ea91..f571a7b8 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -43,7 +43,7 @@ def test_own_dog_food(self): os.path.join(ROOT_DIR, 'setup.py')] report = self._style.init_report(pycodestyle.StandardReport) report = self._style.check_files(files) - self.assertFalse(report.total_errors, + self.assertEqual(list(report.messages.keys()), ['W504'], msg='Failures: %s' % report.messages) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 4d8b7b26..6eb9f041 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -166,7 +166,7 @@ def test_styleguide_options(self): self.assertEqual(pep8style.options.filename, ['*.py']) self.assertEqual(pep8style.options.format, 'default') self.assertEqual(pep8style.options.select, ()) - self.assertEqual(pep8style.options.ignore, ('E226', 'E24')) + self.assertEqual(pep8style.options.ignore, ('E226', 'E24', 'W504')) self.assertEqual(pep8style.options.max_line_length, 79) def test_styleguide_ignore_code(self): @@ -182,7 +182,7 @@ def parse_argv(argstring): self.assertEqual(options.select, ()) self.assertEqual( options.ignore, - ('E121', 'E123', 'E126', 'E226', 'E24', 'E704', 'W503') + ('E121', 'E123', 'E126', 'E226', 'E24', 'E704', 'W503', 'W504') ) options = parse_argv('--doctest').options From 660afc4234c477e1fec0eac66ee8a5ce18fb262e Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Wed, 17 Jan 2018 22:43:23 -0600 Subject: [PATCH 153/412] Add a changelog entry for W504 --- CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 0957be83..3ab8c791 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,6 +6,8 @@ UNRELEASED New checks: +* Add W504 warning for checking that a break doesn't happen after a binar + operator. This check is ignored by default * Add W605 warning for invalid escape sequences in string literals 2.3.1 (2017-01-31) From c5b9c147d0a06f3e2ec1468c3047f1955e3190f8 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 19 Jan 2018 08:06:07 -0800 Subject: [PATCH 154/412] Fixed typo --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3ab8c791..af06645c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,7 +6,7 @@ UNRELEASED New checks: -* Add W504 warning for checking that a break doesn't happen after a binar +* Add W504 warning for checking that a break doesn't happen after a binary operator. This check is ignored by default * Add W605 warning for invalid escape sequences in string literals From 147c399c357a08fa0af982161024af66710e35e5 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 19 Jan 2018 08:07:14 -0800 Subject: [PATCH 155/412] Fixed wording in docstring --- pycodestyle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index f70a1a6e..d8dfcf49 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1209,8 +1209,8 @@ def break_after_binary_operator(logical_line, tokens): r""" Avoid breaks after binary operators. - The preferred place to break around a binary operator is after the - operator, not before it. + The preferred place to break around a binary operator is before the + operator, not after it. W504: (width == 0 +\n height == 0) W504: (width == 0 and\n height == 0) From 80684c4c48716837b06a2b0d6cd421851b1a0ae6 Mon Sep 17 00:00:00 2001 From: Peter Cock Date: Tue, 23 Jan 2018 10:37:54 +0000 Subject: [PATCH 156/412] Describe code W504 line break after binary operator Cross reference changes in pull request #502 which added this. --- docs/intro.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 4ddf91f4..854dc543 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -402,7 +402,9 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | **W5** | *Line break warning* | +------------+----------------------------------------------------------------------+ -| W503 (*) | line break occurred before a binary operator | +| W503 (*)   | line break before binary operator                         | ++------------+----------------------------------------------------------------------+ +| W504 (*)   | line break after binary operator                         | +------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **W6** | *Deprecation warning* | @@ -420,9 +422,10 @@ This is the current list of error and warning codes: **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, -**E133**, **E226**, **E241**, **E242**, **E704** and **W503** are ignored because -they are not rules unanimously accepted, and `PEP 8`_ does not enforce them. The -check **E133** is mutually exclusive with check **E123**. Use switch +**E133**, **E226**, **E241**, **E242**, **E704**, **W503** and **W504** are ignored +because they are not rules unanimously accepted, and `PEP 8`_ does not enforce them. +The check **W503** is mutually exclusive with check **W504**. +The check **E133** is mutually exclusive with check **E123**. Use switch ``--hang-closing`` to report **E133** instead of **E123**. **(^)** These checks can be disabled at the line level using the ``# noqa`` From 01ecae5a87d2bdf7df6528722e0c2ae36930d90f Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 8 Feb 2018 11:07:03 -0800 Subject: [PATCH 157/412] Updated License / copyright date --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 302779d7..30ea0572 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Copyright © 2006-2009 Johann C. Rocholl Copyright © 2009-2014 Florent Xicluna -Copyright © 2014-2016 Ian Lee +Copyright © 2014-2018 Ian Lee Licensed under the terms of the Expat License From 368e62cb6c57ff386b5a08659a5a9d2866b80a2f Mon Sep 17 00:00:00 2001 From: Adi Roiban Date: Sat, 24 Mar 2018 13:06:24 +0000 Subject: [PATCH 158/412] Finalize support for Python 3.7 Python 3.7 added a warning for a future feature of nested regular expressions. To avoid this warning we escape what is not a nested regex. This also keeps track of the `async` keyword and handles it appropriately. Closes gh-728 --- pycodestyle.py | 4 ++-- testsuite/E25.py | 2 +- testsuite/E30.py | 2 +- testsuite/E70.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 5f021870..3e574c81 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -102,7 +102,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. PyCF_ONLY_AST = 1024 SINGLETONS = frozenset(['False', 'None', 'True']) -KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS +KEYWORDS = frozenset(keyword.kwlist + ['print', 'async']) - SINGLETONS UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-']) WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) @@ -121,7 +121,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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') diff --git a/testsuite/E25.py b/testsuite/E25.py index 71d3f800..55ed4389 100644 --- a/testsuite/E25.py +++ b/testsuite/E25.py @@ -39,7 +39,7 @@ def munge(input: AnyStr, sep: AnyStr = None, limit=1000, async def add(a: int = 0, b: int = 0) -> int: return a + b # Previously E251 four times -#: E272:1:6 +#: E271:1:6 async def add(a: int = 0, b: int = 0) -> int: return a + b #: E252:1:15 E252:1:16 E252:1:27 E252:1:36 diff --git a/testsuite/E30.py b/testsuite/E30.py index bd74b803..ad5518bb 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -157,7 +157,7 @@ def main(): if __name__ == '__main__': main() # Previously just E272:1:6 E272:4:6 -#: E302:4:1 E272:1:6 E272:4:6 +#: E302:4:1 E271:1:6 E271:4:6 async def x(): pass diff --git a/testsuite/E70.py b/testsuite/E70.py index caafe455..7c01edbb 100644 --- a/testsuite/E70.py +++ b/testsuite/E70.py @@ -14,7 +14,7 @@ def f(x): return 2 #: E704:1:1 async def f(x): return 2 -#: E704:1:1 E272:1:6 +#: E704:1:1 E271:1:6 async def f(x): return 2 #: E704:1:1 E226:1:19 def f(x): return 2*x From 9f225ac876b6340825770c9c665060dfc28300c6 Mon Sep 17 00:00:00 2001 From: Jimi Cullen Date: Tue, 3 Apr 2018 12:24:38 +0100 Subject: [PATCH 159/412] Ignore length of shebang line (#736) * Add special case to maximum_line_length to ignore long shebang lines. * Add test for ignoring long shebang lines. * Clean up shebang line check. --- pycodestyle.py | 6 +++++- testsuite/E50.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3e574c81..5371a67e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -260,7 +260,8 @@ def trailing_blank_lines(physical_line, lines, line_number, total_lines): @register_check -def maximum_line_length(physical_line, max_line_length, multiline, noqa): +def maximum_line_length(physical_line, max_line_length, multiline, + line_number, noqa): r"""Limit all lines to a maximum of 79 characters. There are still many devices around that are limited to 80 character @@ -275,6 +276,9 @@ def maximum_line_length(physical_line, max_line_length, multiline, noqa): line = physical_line.rstrip() length = len(line) if length > max_line_length and not noqa: + # Special case: ignore long shebang lines. + if line_number == 1 and line.startswith('#!'): + return # Special case for long URLs in multi-line docstrings or comments, # but still report the error when the 72 first chars are whitespaces. chunks = line.split() diff --git a/testsuite/E50.py b/testsuite/E50.py index 189f416a..6bca09bf 100644 --- a/testsuite/E50.py +++ b/testsuite/E50.py @@ -121,3 +121,7 @@ def foo(): #: E501 # This # almost_empty_line + +# +#: Okay +#!/reallylongpath/toexecutable --maybe --with --some ARGUMENTS TO DO WITH WHAT EXECUTABLE TO RUN From 5d31e7ee2e5996be10a0452d4b01b799a6698ad5 Mon Sep 17 00:00:00 2001 From: Adi Roiban Date: Sun, 8 Apr 2018 16:19:11 +0100 Subject: [PATCH 160/412] Add variables so blank lines may be configures This adds some module level configuration points for users to define how many blank lines they want in their code. It paves the way for someone to develop a flake8 plugin to configure this in pycodestyle. Fixes #732 --- CONTRIBUTING.rst | 38 +++ pycodestyle.py | 48 ++- testsuite/support.py | 20 ++ testsuite/test_all.py | 10 +- testsuite/test_blank_lines.py | 552 ++++++++++++++++++++++++++++++++++ 5 files changed, 653 insertions(+), 15 deletions(-) create mode 100644 testsuite/test_blank_lines.py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index aad6ad67..9f558590 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -66,6 +66,44 @@ At this point you can create a pull request back to the official pycodestyles repository for review! For more information on how to make a pull request, GitHub has an excellent `guide`_. +The current tests are written in 2 styles: + +* standard xUnit based only on stdlib unittest + (can be executed with nose) +* functional test using a custom framework and executed by the + pycodestyle itself when installed in dev mode. + + +Running unittest +~~~~~~~~~~~~~~~~ + +While the tests are writted using stdlib `unittest` module, the existing +test include unit, integration and functional tests. + +There are a couple of ways to run the tests:: + + $ python setup.py test + $ # Use nose to run specific test + $ nosetests \ + > testsuite.test_blank_lines:TestBlankLinesDefault.test_initial_no_blank + $ # Use nose to run a subset and check coverage, and check the resulting + $ $ cover/pycodestyle_py.html in your browser + $ nosetests --with-coverage --cover-html -s testsuite.test_blank_lines + + +Running functional +~~~~~~~~~~~~~~~~~~ + +When installed in dev mode, pycodestyle will have the `--testsuite` +option which can be used to run the tests:: + + $ pip install -e . + $ # Run all tests. + $ pycodestyle --testsuite testsuite + $ # Run a subset of the tests. + $ pycodestyle --testsuite testsuite/E30.py + + .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ .. _guide: https://guides.github.com/activities/forking/ .. _tox: https://tox.readthedocs.io/en/latest/ diff --git a/pycodestyle.py b/pycodestyle.py index 5371a67e..96779ab9 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -95,6 +95,13 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. PROJECT_CONFIG = ('setup.cfg', 'tox.ini') TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') MAX_LINE_LENGTH = 79 +# Number of blank lines between various code parts. +BLANK_LINES_CONFIG = { + # Top level class and function. + 'top_level': 2, + # Methods and nested class and function. + 'method': 1, +} REPORT_FORMAT = { 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s', 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s', @@ -332,37 +339,50 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, E305: def a():\n pass\na() E306: def a():\n def b():\n pass\n def c():\n pass """ - if line_number < 3 and not previous_logical: + top_level_lines = BLANK_LINES_CONFIG['top_level'] + method_lines = BLANK_LINES_CONFIG['method'] + + if line_number < top_level_lines + 1 and not previous_logical: return # Don't expect blank lines before the first line if previous_logical.startswith('@'): if blank_lines: yield 0, "E304 blank lines found after function decorator" - elif blank_lines > 2 or (indent_level and blank_lines == 2): + elif (blank_lines > top_level_lines or + (indent_level and blank_lines == method_lines + 1) + ): yield 0, "E303 too many blank lines (%d)" % blank_lines elif STARTSWITH_TOP_LEVEL_REGEX.match(logical_line): if indent_level: - if not (blank_before or previous_indent_level < indent_level or - DOCSTRING_REGEX.match(previous_logical)): + if not (blank_before == method_lines or + previous_indent_level < indent_level or + DOCSTRING_REGEX.match(previous_logical) + ): ancestor_level = indent_level nested = False # Search backwards for a def ancestor or tree root (top level). - for line in lines[line_number - 2::-1]: + for line in lines[line_number - top_level_lines::-1]: if line.strip() and expand_indent(line) < ancestor_level: ancestor_level = expand_indent(line) nested = line.lstrip().startswith('def ') if nested or ancestor_level == 0: break if nested: - yield 0, "E306 expected 1 blank line before a " \ - "nested definition, found 0" + yield 0, "E306 expected %s blank line before a " \ + "nested definition, found 0" % (method_lines,) else: - yield 0, "E301 expected 1 blank line, found 0" - elif blank_before != 2: - yield 0, "E302 expected 2 blank lines, found %d" % blank_before - elif (logical_line and not indent_level and blank_before != 2 and - previous_unindented_logical_line.startswith(('def ', 'class '))): - yield 0, "E305 expected 2 blank lines after " \ - "class or function definition, found %d" % blank_before + yield 0, "E301 expected %s blank line, found 0" % ( + method_lines,) + elif blank_before != top_level_lines: + yield 0, "E302 expected %s blank lines, found %d" % ( + top_level_lines, blank_before) + elif (logical_line and + not indent_level and + blank_before != top_level_lines and + previous_unindented_logical_line.startswith(('def ', 'class ')) + ): + yield 0, "E305 expected %s blank lines after " \ + "class or function definition, found %d" % ( + top_level_lines, blank_before) @register_check diff --git a/testsuite/support.py b/testsuite/support.py index cf9abc53..825def14 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -83,6 +83,26 @@ def print_results(self): print("Test failed." if self.total_errors else "Test passed.") +class InMemoryReport(BaseReport): + """ + Collect the results in memory, without printing anything. + """ + + def __init__(self, options): + super(InMemoryReport, self).__init__(options) + self.in_memory_errors = [] + + def error(self, line_number, offset, text, check): + """ + Report an error, according to options. + """ + code = text[:4] + self.in_memory_errors.append('%s:%s:%s' % ( + code, line_number, offset + 1)) + return super(InMemoryReport, self).error( + line_number, offset, text, check) + + def selftest(options): """ Test all check functions with test cases in docstrings. diff --git a/testsuite/test_all.py b/testsuite/test_all.py index f571a7b8..0e4bc7d1 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -48,11 +48,19 @@ def test_own_dog_food(self): def suite(): - from testsuite import test_api, test_parser, test_shell, test_util + from testsuite import ( + test_api, + test_blank_lines, + test_parser, + test_shell, + test_util, + ) suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(PycodestyleTestCase)) suite.addTest(unittest.makeSuite(test_api.APITestCase)) + suite.addTest(unittest.makeSuite(test_blank_lines.TestBlankLinesDefault)) + suite.addTest(unittest.makeSuite(test_blank_lines.TestBlankLinesTwisted)) suite.addTest(unittest.makeSuite(test_parser.ParserTestCase)) suite.addTest(unittest.makeSuite(test_shell.ShellTestCase)) suite.addTest(unittest.makeSuite(test_util.UtilTestCase)) diff --git a/testsuite/test_blank_lines.py b/testsuite/test_blank_lines.py new file mode 100644 index 00000000..870403a4 --- /dev/null +++ b/testsuite/test_blank_lines.py @@ -0,0 +1,552 @@ +""" +Tests for the blank_lines checker. + +It uses dedicated assertions which work with TestReport. +""" +import unittest + +import pycodestyle +from testsuite.support import InMemoryReport + + +class BlankLinesTestCase(unittest.TestCase): + """ + Common code for running blank_lines tests. + """ + + def check(self, content): + """ + Run checks on `content` and return the the list of errors. + """ + sut = pycodestyle.StyleGuide() + reporter = sut.init_report(InMemoryReport) + sut.input_file( + filename='in-memory-test-file.py', + lines=content.splitlines(True), + ) + return reporter.in_memory_errors + + def assertNoErrors(self, actual): + """ + Check that the actual result from the checker has no errors. + """ + self.assertEqual([], actual) + + +class TestBlankLinesDefault(BlankLinesTestCase): + """ + Tests for default blank with 2 blank lines for top level and 1 blank line + for methods. + """ + + def test_initial_no_blank(self): + """ + It will accept no blank lines at the start of the file. + """ + result = self.check("""def some_function(): + pass +""") + + self.assertNoErrors(result) + + def test_initial_lines_one_blank(self): + """ + It will accept 1 blank lines before the first line of actual code, + even if in other places it asks for 2 + """ + result = self.check(""" +def some_function(): + pass +""") + + self.assertNoErrors(result) + + def test_initial_lines_two_blanks(self): + """ + It will accept 2 blank lines before the first line of actual code, + as normal. + """ + result = self.check(""" + +def some_function(): + pass +""") + + self.assertNoErrors(result) + + def test_method_less_blank_lines(self): + """ + It will trigger an error when less than 1 blank lin is found before + method definitions. + """ + result = self.check("""# First comment line. +class X: + + def a(): + pass + def b(): + pass +""") + self.assertEqual([ + 'E301:6:5', # b() call + ], result) + + def test_method_less_blank_lines_comment(self): + """ + It will trigger an error when less than 1 blank lin is found before + method definition, ignoring comments. + """ + result = self.check("""# First comment line. +class X: + + def a(): + pass + # A comment will not make it better. + def b(): + pass +""") + self.assertEqual([ + 'E301:7:5', # b() call + ], result) + + def test_top_level_fewer_blank_lines(self): + """ + It will trigger an error when less 2 blank lines are found before top + level definitions. + """ + result = self.check("""# First comment line. +# Second line of comment. + +def some_function(): + pass + +async def another_function(): + pass + + +def this_one_is_good(): + pass + +class SomeCloseClass(object): + pass + + +async def this_async_is_good(): + pass + + +class AFarEnoughClass(object): + pass +""") + self.assertEqual([ + 'E302:4:1', # some_function + 'E302:7:1', # another_function + 'E302:14:1', # SomeCloseClass + ], result) + + def test_top_level_more_blank_lines(self): + """ + It will trigger an error when more 2 blank lines are found before top + level definitions. + """ + result = self.check("""# First comment line. +# Second line of comment. + + + +def some_function(): + pass + + +def this_one_is_good(): + pass + + + +class SomeFarClass(object): + pass + + +class AFarEnoughClass(object): + pass +""") + self.assertEqual([ + 'E303:6:1', # some_function + 'E303:15:1', # SomeFarClass + ], result) + + def test_method_more_blank_lines(self): + """ + It will trigger an error when more than 1 blank line is found before + method definition + """ + result = self.check("""# First comment line. + + +class SomeCloseClass(object): + + + def oneMethod(self): + pass + + + def anotherMethod(self): + pass + + def methodOK(self): + pass + + + + def veryFar(self): + pass +""") + self.assertEqual([ + 'E303:7:5', # oneMethod + 'E303:11:5', # anotherMethod + 'E303:19:5', # veryFar + ], result) + + def test_initial_lines_more_blank(self): + """ + It will trigger an error for more than 2 blank lines before the first + line of actual code. + """ + result = self.check(""" + + +def some_function(): + pass +""") + self.assertEqual(['E303:4:1'], result) + + def test_blank_line_between_decorator(self): + """ + It will trigger an error when the decorator is followed by a blank + line. + """ + result = self.check("""# First line. + + +@some_decorator + +def some_function(): + pass + + +class SomeClass(object): + + @method_decorator + + def some_method(self): + pass +""") + self.assertEqual(['E304:6:1', 'E304:14:5'], result) + + def test_blank_line_decorator(self): + """ + It will accept the decorators which are adjacent to the function and + method definition. + """ + result = self.check("""# First line. + + +@another_decorator +@some_decorator +def some_function(): + pass + + +class SomeClass(object): + + @method_decorator + def some_method(self): + pass +""") + self.assertNoErrors(result) + + def test_top_level_fewer_follow_lines(self): + """ + It will trigger an error when less than 2 blank lines are + found between a top level definitions and other top level code. + """ + result = self.check(""" +def a(): + print('Something') + +a() +""") + self.assertEqual([ + 'E305:5:1', # a call + ], result) + + def test_top_level_fewer_follow_lines_comments(self): + """ + It will trigger an error when less than 2 blank lines are + found between a top level definitions and other top level code, + even if we have comments before + """ + result = self.check(""" +def a(): + print('Something') + + # comment + + # another comment + +# With comment still needs 2 spaces before, +# as comments are ignored. +a() +""") + self.assertEqual([ + 'E305:11:1', # a call + ], result) + + def test_top_level_good_follow_lines(self): + """ + It not trigger an error when 2 blank lines are + found between a top level definitions and other top level code. + """ + result = self.check(""" +def a(): + print('Something') + + # Some comments in other parts. + + # More comments. + + +# With the right spaces, +# It will work, even when we have comments. +a() +""") + self.assertNoErrors(result) + + def test_method_fewer_follow_lines(self): + """ + It will trigger an error when less than 1 blank line is + found between a method and previous definitions. + """ + result = self.check(""" +def a(): + x = 1 + def b(): + pass +""") + self.assertEqual([ + 'E306:4:5', # b() call + ], result) + + def test_method_nested_fewer_follow_lines(self): + """ + It will trigger an error when less than 1 blank line is + found between a method and previous definitions, even when nested. + """ + result = self.check(""" +def a(): + x = 2 + + def b(): + x = 1 + def c(): + pass +""") + self.assertEqual([ + 'E306:7:9', # c() call + ], result) + + def test_method_nested_less_class(self): + """ + It will trigger an error when less than 1 blank line is found + between a method and previous definitions, even when used to + define a class. + """ + result = self.check(""" +def a(): + x = 1 + class C: + pass +""") + self.assertEqual([ + 'E306:4:5', # class C definition. + ], result) + + def test_method_nested_ok(self): + """ + Will not trigger an error when 1 blank line is found + found between a method and previous definitions, even when nested. + """ + result = self.check(""" +def a(): + x = 2 + + def b(): + x = 1 + + def c(): + pass + + class C: + pass +""") + self.assertNoErrors(result) + + +class TestBlankLinesTwisted(BlankLinesTestCase): + """ + Tests for blank_lines with 3 blank lines for top level and 2 blank line + for methods as used by the Twisted coding style. + """ + + def setUp(self): + self._original_lines_config = pycodestyle.BLANK_LINES_CONFIG.copy() + pycodestyle.BLANK_LINES_CONFIG['top_level'] = 3 + pycodestyle.BLANK_LINES_CONFIG['method'] = 2 + + def tearDown(self): + pycodestyle.BLANK_LINES_CONFIG = self._original_lines_config + + def test_initial_lines_one_blanks(self): + """ + It will accept less than 3 blank lines before the first line of actual + code. + """ + result = self.check(""" + + +def some_function(): + pass +""") + + self.assertNoErrors(result) + + def test_initial_lines_tree_blanks(self): + """ + It will accept 3 blank lines before the first line of actual code, + as normal. + """ + result = self.check(""" + + +def some_function(): + pass +""") + + self.assertNoErrors(result) + + def test_top_level_fewer_blank_lines(self): + """ + It will trigger an error when less 2 blank lines are found before top + level definitions. + """ + result = self.check("""# First comment line. +# Second line of comment. + + +def some_function(): + pass + + +async def another_function(): + pass + + + +def this_one_is_good(): + pass + +class SomeCloseClass(object): + pass + + + +async def this_async_is_good(): + pass + + + +class AFarEnoughClass(object): + pass +""") + self.assertEqual([ + 'E302:5:1', # some_function + 'E302:9:1', # another_function + 'E302:17:1', # SomeCloseClass + ], result) + + def test_top_level_more_blank_lines(self): + """ + It will trigger an error when more 2 blank lines are found before top + level definitions. + """ + result = self.check("""# First comment line. +# Second line of comment. + + + + +def some_function(): + pass + + + +def this_one_is_good(): + pass + + + + +class SomeVeryFarClass(object): + pass + + + +class AFarEnoughClass(object): + pass +""") + self.assertEqual([ + 'E303:7:1', # some_function + 'E303:18:1', # SomeVeryFarClass + ], result) + + def test_the_right_blanks(self): + """ + It will accept 3 blank for top level and 2 for nested. + """ + result = self.check(""" + + +def some_function(): + pass + + + +# With comments. +some_other = code_here + + + +class SomeClass: + ''' + Docstring here. + ''' + + def some_method(): + pass + + + def another_method(): + pass + + + # More methods. + def another_method_with_comment(): + pass + + + @decorator + def another_method_with_comment(): + pass +""") + + self.assertNoErrors(result) From 41438bf9a8ccebf392bd005237e845b6610e7e9b Mon Sep 17 00:00:00 2001 From: Jakob Gerhard Martinussen Date: Mon, 9 Apr 2018 20:22:19 +0200 Subject: [PATCH 161/412] Add changelog items for 2.3.1...master --- CHANGES.txt | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 25b8558a..e86aa99a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,10 +7,46 @@ UNRELEASED New checks: * Add W504 warning for checking that a break doesn't happen after a binary - operator. This check is ignored by default -* Add W605 warning for invalid escape sequences in string literals + operator. This check is ignored by default. +* Add W605 warning for invalid escape sequences in string literals. * Add W606 warning for 'async' and 'await' reserved keywords being introduced - in Python 3.7 + in Python 3.7. +* Add E252 error for missing whitespace around equal sign in type annotated + function arguments with defaults values. + +Changes: + +* An internal bisect search has replaced a linear search in order to improve + efficiency. +* pycodestyle now uses PyPI trove classifiers in order to document supported + python versions on PyPI. +* 'setup.cfg' '[wheel]' section has been renamed to '[bdist_wheel]', as + the former is legacy. +* pycodestyle now handles very long lines much more efficiently for python + 3.2+. Fixes #643. +* You can now write 'pycodestyle.StyleGuide(verbose=True)' instead of + 'pycodestyle.StyleGuide(verbose=True, paths=['-v'])' in order to achieve + verbosity. +* The distribution of pycodestyle now includes the licence text in order to + comply with open source licenses which require this. +* 'maximum_line_length' now ignores shebang ('#!') lines. +* Add configuration option for the allowed number of blank lines. It is + implemented as a top level dictionary which can be easily overwritten. Fixes + #732. + +Bugs: + +* Prevent a 'DeprecationWarning', and a 'SyntaxError' in future python, caused + by an invalid escape sequence. +* Correctly report E501 when the first line of a docstring is too long. + Resolves #622. +* Support variable annotation when variable start by a keyword, such as class + variable type annotations in python 3.6. +* pycodestyle internals have been changed in order to allow 'python3 -m + cProfile' to report correct metrics. +* Fix a spelling mistake in the description of E722. +* 'pycodestyle --diff' now does not break if your 'gitconfig' enables + 'mnemonicprefix'. 2.3.1 (2017-01-31) ------------------ From 6d10bc73906144d27928d1be306032393f658a80 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 9 Apr 2018 12:01:37 -0700 Subject: [PATCH 162/412] Fixed typo --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index e86aa99a..daa77fd7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -27,7 +27,7 @@ Changes: * You can now write 'pycodestyle.StyleGuide(verbose=True)' instead of 'pycodestyle.StyleGuide(verbose=True, paths=['-v'])' in order to achieve verbosity. -* The distribution of pycodestyle now includes the licence text in order to +* The distribution of pycodestyle now includes the license text in order to comply with open source licenses which require this. * 'maximum_line_length' now ignores shebang ('#!') lines. * Add configuration option for the allowed number of blank lines. It is From 51cf479586cf7f4f47e66f30739f93714b45136a Mon Sep 17 00:00:00 2001 From: Jakob Gerhard Martinussen Date: Mon, 9 Apr 2018 21:22:19 +0200 Subject: [PATCH 163/412] Add PR references from all changelog items --- CHANGES.txt | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index daa77fd7..48905f9a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,46 +7,46 @@ UNRELEASED New checks: * Add W504 warning for checking that a break doesn't happen after a binary - operator. This check is ignored by default. -* Add W605 warning for invalid escape sequences in string literals. + operator. This check is ignored by default. PR #502. +* Add W605 warning for invalid escape sequences in string literals. PR #676. * Add W606 warning for 'async' and 'await' reserved keywords being introduced - in Python 3.7. + in Python 3.7. PR #684. * Add E252 error for missing whitespace around equal sign in type annotated - function arguments with defaults values. + function arguments with defaults values. PR #717. Changes: * An internal bisect search has replaced a linear search in order to improve - efficiency. + efficiency. PR #648. * pycodestyle now uses PyPI trove classifiers in order to document supported - python versions on PyPI. + python versions on PyPI. PR #654. * 'setup.cfg' '[wheel]' section has been renamed to '[bdist_wheel]', as - the former is legacy. + the former is legacy. PR #653. * pycodestyle now handles very long lines much more efficiently for python - 3.2+. Fixes #643. + 3.2+. Fixes #643. PR #644. * You can now write 'pycodestyle.StyleGuide(verbose=True)' instead of 'pycodestyle.StyleGuide(verbose=True, paths=['-v'])' in order to achieve - verbosity. + verbosity. PR #663. * The distribution of pycodestyle now includes the license text in order to - comply with open source licenses which require this. -* 'maximum_line_length' now ignores shebang ('#!') lines. + comply with open source licenses which require this. PR #694. +* 'maximum_line_length' now ignores shebang ('#!') lines. PR #736. * Add configuration option for the allowed number of blank lines. It is implemented as a top level dictionary which can be easily overwritten. Fixes - #732. + #732. PR #733. Bugs: * Prevent a 'DeprecationWarning', and a 'SyntaxError' in future python, caused - by an invalid escape sequence. + by an invalid escape sequence. PR #625. * Correctly report E501 when the first line of a docstring is too long. - Resolves #622. + Resolves #622. PR #630. * Support variable annotation when variable start by a keyword, such as class - variable type annotations in python 3.6. + variable type annotations in python 3.6. PR #640. * pycodestyle internals have been changed in order to allow 'python3 -m - cProfile' to report correct metrics. -* Fix a spelling mistake in the description of E722. + cProfile' to report correct metrics. PR #647. +* Fix a spelling mistake in the description of E722. PR #697. * 'pycodestyle --diff' now does not break if your 'gitconfig' enables - 'mnemonicprefix'. + 'mnemonicprefix'. PR #706. 2.3.1 (2017-01-31) ------------------ From f73c4bb211986cd8d340de2f018c3ff6a3d73c5f Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Tue, 10 Apr 2018 06:26:30 -0500 Subject: [PATCH 164/412] Release v2.4.0 --- CHANGES.txt | 4 ++-- pycodestyle.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 48905f9a..ecfa609a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,8 @@ Changelog ========= -UNRELEASED ----------- +2.4.0 (2018-04-10) +------------------ New checks: diff --git a/pycodestyle.py b/pycodestyle.py index 96779ab9..1c8c5d27 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -77,7 +77,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.3.1' +__version__ = '2.4.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504' From 25221c95fd180fbc30fb17de2c1930386348ac51 Mon Sep 17 00:00:00 2001 From: hhatto Date: Thu, 12 Apr 2018 23:28:51 +0900 Subject: [PATCH 165/412] Change the position pointed out by W605 --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 1c8c5d27..da61e196 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1524,7 +1524,7 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): pos += 1 if string[pos] not in valid: yield ( - pos, + line.lstrip().find(text), "W605 invalid escape sequence '\\%s'" % string[pos], ) From fac287120f1672b06bc63065feed5f20a34f95f2 Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Thu, 12 Apr 2018 16:56:03 -0500 Subject: [PATCH 166/412] Fix flake8 test environment --- pycodestyle.py | 6 +++--- tox.ini | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 1c8c5d27..b69ce33d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1689,9 +1689,9 @@ def parse_udiff(diff, patterns=None, parent='.'): if path[:2] in ('b/', 'w/', 'i/'): path = path[2:] rv[path] = set() - return dict([(os.path.join(parent, path), rows) - for (path, rows) in rv.items() - if rows and filename_match(path, patterns)]) + return dict([(os.path.join(parent, filepath), rows) + for (filepath, rows) in rv.items() + if rows and filename_match(filepath, patterns)]) def normalize_paths(value, parent=os.curdir): diff --git a/tox.ini b/tox.ini index 797bbfab..41ba1789 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,8 @@ [tox] envlist = py26, py27, py32, py33, py34, py35, py36, pypy, pypy3, jython -skip_missing_interpreters=True +skipsdist = True +skip_missing_interpreters = True [testenv] commands = From a925fe4c1aee5e13a9846162b46852ea471cc497 Mon Sep 17 00:00:00 2001 From: hhatto Date: Sat, 14 Apr 2018 23:17:27 +0900 Subject: [PATCH 167/412] Change the position pointed out by W504 --- pycodestyle.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index dc86d30d..261856bb 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1266,6 +1266,7 @@ def break_after_binary_operator(logical_line, tokens): Okay: var = (1 /\n -2) Okay: var = (1 +\n -1 +\n -2) """ + old_start = None for context in _break_around_binary_operators(tokens): (token_type, text, previous_token_type, previous_text, line_break, unary_context, start) = context @@ -1273,8 +1274,8 @@ def break_after_binary_operator(logical_line, tokens): line_break and not unary_context and not _is_binary_operator(token_type, text)): - error_pos = (start[0] - 1, start[1]) - yield error_pos, "W504 line break after binary operator" + yield old_start, "W504 line break after binary operator" + old_start = start @register_check From 7ca10fe5788f88bfa78d93cfcfc3d0df352a187e Mon Sep 17 00:00:00 2001 From: hhatto Date: Sat, 14 Apr 2018 23:23:28 +0900 Subject: [PATCH 168/412] rename variable --- pycodestyle.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 261856bb..0e0d6a0e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1266,7 +1266,7 @@ def break_after_binary_operator(logical_line, tokens): Okay: var = (1 /\n -2) Okay: var = (1 +\n -1 +\n -2) """ - old_start = None + prev_start = None for context in _break_around_binary_operators(tokens): (token_type, text, previous_token_type, previous_text, line_break, unary_context, start) = context @@ -1274,8 +1274,8 @@ def break_after_binary_operator(logical_line, tokens): line_break and not unary_context and not _is_binary_operator(token_type, text)): - yield old_start, "W504 line break after binary operator" - old_start = start + yield prev_start, "W504 line break after binary operator" + prev_start = start @register_check From 17baca469a221da6b8c1531d13cfd33cb6eceed3 Mon Sep 17 00:00:00 2001 From: Jimmy Jia Date: Wed, 18 Apr 2018 23:28:35 -0400 Subject: [PATCH 169/412] Fix detection of annotated argument defaults This improves E252 to allow default arguments like `_default(f=1)`. Closes gh-753 --- pycodestyle.py | 12 ++++++------ testsuite/E25.py | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index dc86d30d..f545cc0e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -937,17 +937,17 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): parens -= 1 elif in_def and text == ':' and parens == 1: annotated_func_arg = True - elif parens and text == ',' and parens == 1: + elif parens == 1 and text == ',': annotated_func_arg = False elif parens and text == '=': - if not annotated_func_arg: - no_space = True - if start != prev_end: - yield (prev_end, message) - else: + if annotated_func_arg and parens == 1: require_space = True if start == prev_end: yield (prev_end, missing_message) + else: + no_space = True + if start != prev_end: + yield (prev_end, message) if not parens: annotated_func_arg = False diff --git a/testsuite/E25.py b/testsuite/E25.py index 55ed4389..88981576 100644 --- a/testsuite/E25.py +++ b/testsuite/E25.py @@ -45,3 +45,6 @@ async def add(a: int = 0, b: int = 0) -> int: #: E252:1:15 E252:1:16 E252:1:27 E252:1:36 def add(a: int=0, b: int =0, c: int= 0) -> int: return a + b + c +#: Okay +def add(a: int = _default(name='f')): + return a From 11ba4cc825e3753a5b662efe41942b6b3021d7e5 Mon Sep 17 00:00:00 2001 From: Toshiki Hirao Date: Sun, 22 Apr 2018 16:00:41 -0400 Subject: [PATCH 170/412] Add explanation about the default configuration Closes gh-685 --- docs/intro.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/intro.rst b/docs/intro.rst index 80ef08f4..70e7e81d 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -426,6 +426,8 @@ This is the current list of error and warning codes: **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, **E133**, **E226**, **E241**, **E242**, **E704**, **W503** and **W504** are ignored because they are not rules unanimously accepted, and `PEP 8`_ does not enforce them. +Please note that if the option **--ignore=errors** is used, +the default configuration will be overridden and ignore only the check(s) you skip. The check **W503** is mutually exclusive with check **W504**. The check **E133** is mutually exclusive with check **E123**. Use switch ``--hang-closing`` to report **E133** instead of **E123**. From f6139a8e47dba691046ef82b6ab9b29e3bf38690 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Fri, 11 May 2018 09:35:21 -0700 Subject: [PATCH 171/412] Implement W505 - Max Doc Length check --- docs/intro.rst | 17 +++++++---- pycodestyle.py | 69 ++++++++++++++++++++++++++++++++++++++++--- testsuite/E26.py | 12 ++++---- testsuite/E50.py | 21 ++++++------- testsuite/test_api.py | 6 ++++ testsuite/utf-8.py | 20 ++++++------- 6 files changed, 109 insertions(+), 36 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 70e7e81d..10e2aef6 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -157,6 +157,8 @@ Quick help is available on the command line:: --count print total number of errors and warnings to standard error and set exit code to 1 if total is not null --max-line-length=n set maximum allowed line length (default: 79) + --max-doc-length=n set maximum allowed doc line length and perform these + checks (unchecked if not set) --hang-closing hang closing bracket instead of matching indentation of opening bracket's line --format=format set the error format [default|pylint|] @@ -169,9 +171,9 @@ Quick help is available on the command line:: Configuration: The project options are read from the [pycodestyle] section of the tox.ini file or the setup.cfg file located in any parent folder of the - path(s) being processed. Allowed options are: exclude, filename, select, - ignore, max-line-length, hang-closing, count, format, quiet, show-pep8, - show-source, statistics, verbose. + path(s) being processed. Allowed options are: exclude, filename, + select, ignore, max-line-length, max-doc-length, hang-closing, count, + format, quiet, show-pep8, show-source, statistics, verbose. --config=path user config file location (default: ~/.config/pycodestyle) @@ -406,6 +408,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | W504 (*)   | line break after binary operator                         | +------------+----------------------------------------------------------------------+ +| W505 (\*^) | doc line too long (82 > 79 characters) | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **W6** | *Deprecation warning* | +------------+----------------------------------------------------------------------+ @@ -423,14 +427,15 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ -**(*)** In the default configuration, the checks **E121**, **E123**, **E126**, -**E133**, **E226**, **E241**, **E242**, **E704**, **W503** and **W504** are ignored +**(*)** In the default configuration, the checks **E121**, **E123**, **E126**, **E133**, +**E226**, **E241**, **E242**, **E704**, **W503**, **W504** and **W505** are ignored because they are not rules unanimously accepted, and `PEP 8`_ does not enforce them. Please note that if the option **--ignore=errors** is used, the default configuration will be overridden and ignore only the check(s) you skip. The check **W503** is mutually exclusive with check **W504**. The check **E133** is mutually exclusive with check **E123**. Use switch -``--hang-closing`` to report **E133** instead of **E123**. +``--hang-closing`` to report **E133** instead of **E123**. Use switch +``--max-doc-length=n`` to report **W505**. **(^)** These checks can be disabled at the line level using the ``# noqa`` special comment. This possibility should be reserved for special cases. diff --git a/pycodestyle.py b/pycodestyle.py index f545cc0e..bedd5a52 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -102,6 +102,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. # Methods and nested class and function. 'method': 1, } +MAX_DOC_LENGTH = 72 REPORT_FORMAT = { 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s', 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s', @@ -1585,9 +1586,64 @@ def python_3000_async_await_keywords(logical_line, tokens): ) -############################################################################## +######################################################################## +@register_check +def maximum_doc_length(logical_line, max_doc_length, noqa, tokens): + r"""Limit all doc lines to a maximum of 72 characters. + + For flowing long blocks of text (docstrings or comments), limiting + the length to 72 characters is recommended. + + Reports warning W505 + """ + if max_doc_length is None or noqa: + return + + prev_token = None + skip_lines = set() + # Skip lines that + for token_type, text, start, end, line in tokens: + if token_type not in SKIP_COMMENTS.union([tokenize.STRING]): + skip_lines.add(line) + + for token_type, text, start, end, line in tokens: + # Skip lines that aren't pure strings + if token_type == tokenize.STRING and skip_lines: + continue + if token_type in (tokenize.STRING, tokenize.COMMENT): + # Only check comment-only lines + if prev_token is None or prev_token in SKIP_TOKENS: + lines = line.splitlines() + for line_num, physical_line in enumerate(lines): + if hasattr(physical_line, 'decode'): # Python 2 + # The line could contain multi-byte characters + try: + physical_line = physical_line.decode('utf-8') + except UnicodeError: + pass + if start[0] + line_num == 1 and line.startswith('#!'): + return + length = len(physical_line) + chunks = physical_line.split() + if token_type == tokenize.COMMENT: + if (len(chunks) == 2 and + length - len(chunks[-1]) < MAX_DOC_LENGTH): + continue + if len(chunks) == 1 and line_num + 1 < len(lines): + if (len(chunks) == 1 and + length - len(chunks[-1]) < MAX_DOC_LENGTH): + continue + if length > max_doc_length: + doc_error = (start[0] + line_num, max_doc_length) + yield (doc_error, "W505 doc line too long " + "(%d > %d characters)" + % (length, max_doc_length)) + prev_token = token_type + + +######################################################################## # Helper functions -############################################################################## +######################################################################## if sys.version_info < (3,): @@ -1758,6 +1814,7 @@ def __init__(self, filename=None, lines=None, self._logical_checks = options.logical_checks self._ast_checks = options.ast_checks self.max_line_length = options.max_line_length + self.max_doc_length = options.max_doc_length self.multiline = False # in a multiline string? self.hang_closing = options.hang_closing self.verbose = options.verbose @@ -2324,8 +2381,8 @@ def get_parser(prog='pycodestyle', version=__version__): usage="%prog [options] input ...") parser.config_options = [ 'exclude', 'filename', 'select', 'ignore', 'max-line-length', - 'hang-closing', 'count', 'format', 'quiet', 'show-pep8', - 'show-source', 'statistics', 'verbose'] + 'max-doc-length', 'hang-closing', 'count', 'format', 'quiet', + 'show-pep8', 'show-source', 'statistics', 'verbose'] parser.add_option('-v', '--verbose', default=0, action='count', help="print status messages, or debug with -vv") parser.add_option('-q', '--quiet', default=0, action='count', @@ -2361,6 +2418,10 @@ def get_parser(prog='pycodestyle', version=__version__): default=MAX_LINE_LENGTH, help="set maximum allowed line length " "(default: %default)") + parser.add_option('--max-doc-length', type='int', metavar='n', + default=None, + help="set maximum allowed doc line length and perform " + "these checks (unchecked if not set)") parser.add_option('--hang-closing', action='store_true', help="hang closing bracket instead of matching " "indentation of opening bracket's line") diff --git a/testsuite/E26.py b/testsuite/E26.py index 509babf9..c3537ff5 100644 --- a/testsuite/E26.py +++ b/testsuite/E26.py @@ -50,10 +50,10 @@ def oof(): #foo not parsed """ - ########################################################################### - # A SEPARATOR # - ########################################################################### + #################################################################### + # A SEPARATOR # + #################################################################### - # ####################################################################### # - # ########################## another separator ########################## # - # ####################################################################### # + # ################################################################ # + # ####################### another separator ###################### # + # ################################################################ # diff --git a/testsuite/E50.py b/testsuite/E50.py index 6bca09bf..bcf3bdce 100644 --- a/testsuite/E50.py +++ b/testsuite/E50.py @@ -62,11 +62,11 @@ #: E501 E225 E226 very_long_identifiers=and_terrible_whitespace_habits(are_no_excuse+for_long_lines) # -#: E501 +#: E501 W505 '''multiline string with a long long long long long long long long long long long long long long long long line ''' -#: E501 +#: E501 W505 '''same thing, but this time without a terminal newline in the string long long long long long long long long long long long long long long long long line''' # @@ -74,15 +74,15 @@ #: Okay """ I'm some great documentation. Because I'm some great documentation, I'm -going to give you a reference to some valuable information about some API -that I'm calling: +going to give you a reference to some valuable information about some +API that I'm calling: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx """ -#: E501 +#: E501 W505 """ longnospaceslongnospaceslongnospaceslongnospaceslongnospaceslongnospaceslongnospaceslongnospaces""" -#: E501 +#: E501 W505 # Regression test for #622 def foo(): """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis pulvinar vitae @@ -92,19 +92,20 @@ def foo(): This almost_empty_line """ -#: E501 +#: E501 W505 """ This almost_empty_line """ -#: E501 +#: E501 W505 # A basic comment # with a long long long long long long long long long long long long long long long long line # #: Okay # I'm some great comment. Because I'm so great, I'm going to give you a -# reference to some valuable information about some API that I'm calling: +# reference to some valuable information about some API that I'm +# calling: # # http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx @@ -118,7 +119,7 @@ def foo(): # almost_empty_line # -#: E501 +#: E501 W505 # This # almost_empty_line diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 6eb9f041..ae837540 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -209,6 +209,12 @@ def parse_argv(argstring): self.assertEqual(options.select, ('E24',)) self.assertEqual(options.ignore, ('',)) + options = parse_argv('--max-doc-length=72').options + self.assertEqual(options.max_doc_length, 72) + + options = parse_argv('').options + self.assertEqual(options.max_doc_length, None) + pep8style = pycodestyle.StyleGuide(paths=[E11]) self.assertFalse(pep8style.ignore_code('E112')) self.assertFalse(pep8style.ignore_code('W191')) diff --git a/testsuite/utf-8.py b/testsuite/utf-8.py index e417cbc0..6cc1ac84 100644 --- a/testsuite/utf-8.py +++ b/testsuite/utf-8.py @@ -2,34 +2,34 @@ # Some random text with multi-byte characters (utf-8 encoded) # -# Εδώ μάτσο κειμένων τη, τρόπο πιθανό διευθυντές ώρα μη. Νέων απλό παράγει ροή -# κι, το επί δεδομένη καθορίζουν. Πάντως ζητήσεις περιβάλλοντος ένα με, τη -# ξέχασε αρπάζεις φαινόμενο όλη. Τρέξει εσφαλμένη χρησιμοποίησέ νέα τι. Θα όρο +# Εδώ μάτσο κειμένων τη, τρόπο πιθανό διευθυντές ώρα μη. Νέων απλό π ροή +# κι, το επί δεδομένη καθορίζουν. Πάντως ζητήσεις περιβάλλοντος ένα με, +# ξέχασε αρπάζεις φαινόμενο όλη. Τρέξει εσφαλμένη χρησιμοποίησέ νέα τι. # πετάνε φακέλους, άρα με διακοπής λαμβάνουν εφαμοργής. Λες κι μειώσει # καθυστερεί. # 79 narrow chars -# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 [79] +# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3[79] # 78 narrow chars (Na) + 1 wide char (W) -# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8情 +# 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 情 # 3 narrow chars (Na) + 40 wide chars (W) # 情 情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情 -# 3 narrow chars (Na) + 76 wide chars (W) -# 情 情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情 +# 3 narrow chars (Na) + 69 wide chars (W) +# 情 情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情 # -#: E501 +#: E501 W505 # 80 narrow chars (Na) # 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 [80] # -#: E501 +#: E501 W505 # 78 narrow chars (Na) + 2 wide char (W) # 01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8情情 # -#: E501 +#: E501 W505 # 3 narrow chars (Na) + 77 wide chars (W) # 情 情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情 # From 6ef3190960039fc9749ebf7d92bb1290dad47de3 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Fri, 11 May 2018 09:40:22 -0700 Subject: [PATCH 172/412] Pydoc fixes to support W505 in self-checks --- pycodestyle.py | 238 ++++++++++++++++++---------------- setup.cfg | 1 + testsuite/support.py | 8 +- testsuite/test_blank_lines.py | 70 +++++----- tox.ini | 4 +- 5 files changed, 171 insertions(+), 150 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index bedd5a52..e738f228 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1,5 +1,6 @@ #!/usr/bin/env python -# pycodestyle.py - Check Python source code formatting, according to PEP 8 +# pycodestyle.py - Check Python source code formatting, according to +# PEP 8 # # Copyright (C) 2006-2009 Johann C. Rocholl # Copyright (C) 2009-2014 Florent Xicluna @@ -62,9 +63,9 @@ from functools import lru_cache except ImportError: def lru_cache(maxsize=128): # noqa as it's a fake implementation. - """Does not really need a real a lru_cache, it's just optimization, so - let's just do nothing here. Python 3.2+ will just get better - performances, time to upgrade? + """Does not really need a real a lru_cache, it's just + optimization, so let's just do nothing here. Python 3.2+ will + just get better performances, time to upgrade? """ return lambda function: function @@ -192,20 +193,21 @@ def _add_check(check, kind, codes, args): return check -############################################################################## +######################################################################## # Plugins (check functions) for physical lines -############################################################################## +######################################################################## @register_check def tabs_or_spaces(physical_line, indent_char): r"""Never mix tabs and spaces. The most popular way of indenting Python is with spaces only. The - second-most popular way is with tabs only. Code indented with a mixture - of tabs and spaces should be converted to using spaces exclusively. When - invoking the Python command line interpreter with the -t option, it issues - warnings about code that illegally mixes tabs and spaces. When using -tt - these warnings become errors. These options are highly recommended! + second-most popular way is with tabs only. Code indented with a + mixture of tabs and spaces should be converted to using spaces + exclusively. When invoking the Python command line interpreter with + the -t option, it issues warnings about code that illegally mixes + tabs and spaces. When using -tt these warnings become errors. + These options are highly recommended! Okay: if a == 0:\n a = 1\n b = 1 E101: if a == 0:\n a = 1\n\tb = 1 @@ -218,7 +220,7 @@ def tabs_or_spaces(physical_line, indent_char): @register_check def tabs_obsolete(physical_line): - r"""For new projects, spaces-only are strongly recommended over tabs. + r"""On new projects, spaces-only are strongly recommended over tabs. Okay: if True:\n return W191: if True:\n\treturn @@ -232,8 +234,8 @@ def tabs_obsolete(physical_line): def trailing_whitespace(physical_line): r"""Trailing whitespace is superfluous. - The warning returned varies on whether the line itself is blank, for easier - filtering for those who want to indent their blank lines. + The warning returned varies on whether the line itself is blank, + for easier filtering for those who want to indent their blank lines. Okay: spam(1)\n# W291: spam(1) \n# @@ -273,11 +275,11 @@ def maximum_line_length(physical_line, max_line_length, multiline, r"""Limit all lines to a maximum of 79 characters. There are still many devices around that are limited to 80 character - lines; plus, limiting windows to 80 characters makes it possible to have - several windows side-by-side. The default wrapping on such devices looks - ugly. Therefore, please limit all lines to a maximum of 79 characters. - For flowing long blocks of text (docstrings or comments), limiting the - length to 72 characters is recommended. + lines; plus, limiting windows to 80 characters makes it possible to + have several windows side-by-side. The default wrapping on such + devices looks ugly. Therefore, please limit all lines to a maximum + of 79 characters. For flowing long blocks of text (docstrings or + comments), limiting the length to 72 characters is recommended. Reports error E501. """ @@ -287,8 +289,9 @@ def maximum_line_length(physical_line, max_line_length, multiline, # Special case: ignore long shebang lines. if line_number == 1 and line.startswith('#!'): return - # Special case for long URLs in multi-line docstrings or comments, - # but still report the error when the 72 first chars are whitespaces. + # Special case for long URLs in multi-line docstrings or + # comments, but still report the error when the 72 first chars + # are whitespaces. chunks = line.split() if ((len(chunks) == 1 and multiline) or (len(chunks) == 2 and chunks[0] == '#')) and \ @@ -305,9 +308,9 @@ def maximum_line_length(physical_line, max_line_length, multiline, "(%d > %d characters)" % (length, max_line_length)) -############################################################################## +######################################################################## # Plugins (check functions) for logical lines -############################################################################## +######################################################################## @register_check @@ -315,15 +318,18 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, blank_before, previous_logical, previous_unindented_logical_line, previous_indent_level, lines): - r"""Separate top-level function and class definitions with two blank lines. + r"""Separate top-level function and class definitions with two blank + lines. - Method definitions inside a class are separated by a single blank line. + Method definitions inside a class are separated by a single blank + line. - Extra blank lines may be used (sparingly) to separate groups of related - functions. Blank lines may be omitted between a bunch of related - one-liners (e.g. a set of dummy implementations). + Extra blank lines may be used (sparingly) to separate groups of + related functions. Blank lines may be omitted between a bunch of + related one-liners (e.g. a set of dummy implementations). - Use blank lines in functions, sparingly, to indicate logical sections. + Use blank lines in functions, sparingly, to indicate logical + sections. Okay: def a():\n pass\n\n\ndef b():\n pass Okay: def a():\n pass\n\n\nasync def b():\n pass @@ -339,7 +345,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, E304: @decorator\n\ndef a():\n pass E305: def a():\n pass\na() E306: def a():\n def b():\n pass\n def c():\n pass - """ + """ # noqa top_level_lines = BLANK_LINES_CONFIG['top_level'] method_lines = BLANK_LINES_CONFIG['method'] @@ -360,7 +366,8 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, ): ancestor_level = indent_level nested = False - # Search backwards for a def ancestor or tree root (top level). + # Search backwards for a def ancestor or tree root + # (top level). for line in lines[line_number - top_level_lines::-1]: if line.strip() and expand_indent(line) < ancestor_level: ancestor_level = expand_indent(line) @@ -445,8 +452,8 @@ def whitespace_around_keywords(logical_line): @register_check def missing_whitespace_after_import_keyword(logical_line): - r"""Multiple imports in form from x import (a, b, c) should have space - between import statement and parenthesised name list. + r"""Multiple imports in form from x import (a, b, c) should have + space between import statement and parenthesised name list. Okay: from foo import (bar, baz) E275: from foo import(bar, baz) @@ -493,8 +500,8 @@ def indentation(logical_line, previous_logical, indent_char, indent_level, previous_indent_level): r"""Use 4 spaces per indentation level. - For really old code that you don't want to mess up, you can continue to - use 8-space tabs. + For really old code that you don't want to mess up, you can continue + to use 8-space tabs. Okay: a = 1 Okay: if a == 0:\n a = 1 @@ -531,8 +538,8 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, When using a hanging indent these considerations should be applied: - there should be no arguments on the first line, and - - further indentation should be used to clearly distinguish itself as a - continuation line. + - further indentation should be used to clearly distinguish itself + as a continuation line. Okay: a = (\n) E123: a = (\n ) @@ -617,7 +624,8 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, yield (start, "E124 closing bracket does not match " "visual indentation") elif close_bracket and not hang: - # closing bracket matches indentation of opening bracket's line + # closing bracket matches indentation of opening + # bracket's line if hang_closing: yield start, "E133 closing bracket is missing indentation" elif indent[depth] and start[1] < indent[depth]: @@ -635,7 +643,8 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, # visual indent is verified indent[depth] = start[1] elif visual_indent in (text, str): - # ignore token lined up with matching one from a previous line + # ignore token lined up with matching one from a + # previous line pass else: # indent is broken @@ -848,7 +857,7 @@ def missing_whitespace_around_operator(logical_line, tokens): elif text in WS_NEEDED_OPERATORS: need_space = True elif text in UNARY_OPERATORS: - # Check if the operator is being used as a binary operator + # Check if the operator is used as a binary operator # Allow unary operators: -123, -x, +1. # Allow argument unpacking: foo(*args, **kwargs). if (prev_text in '}])' if prev_type == tokenize.OP @@ -894,8 +903,8 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): r"""Don't use spaces around the '=' sign in function arguments. Don't use spaces around the '=' sign when used to indicate a - keyword argument or a default parameter value, except when using a type - annotation. + keyword argument or a default parameter value, except when + using a type annotation. Okay: def complex(real, imag=0.0): Okay: return magic(r=real, i=imag) @@ -959,9 +968,9 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): def whitespace_before_comment(logical_line, tokens): r"""Separate inline comments by at least two spaces. - An inline comment is a comment on the same line as a statement. Inline - comments should be separated by at least two spaces from the statement. - They should start with a # and a single space. + An inline comment is a comment on the same line as a statement. + Inline comments should be separated by at least two spaces from the + statement. They should start with a # and a single space. Each line of a block comment starts with a # and a single space (unless it is indented text inside the comment). @@ -1022,8 +1031,8 @@ def module_imports_on_top_of_file( logical_line, indent_level, checker_state, noqa): r"""Place imports at the top of the file. - Always put imports at the top of the file, just after any module comments - and docstrings, and before module globals and constants. + Always put imports at the top of the file, just after any module + comments and docstrings, and before module globals and constants. Okay: import os Okay: # this is a comment\nimport os @@ -1038,7 +1047,7 @@ def module_imports_on_top_of_file( E402: a=1\nfrom sys import x Okay: if x:\n import os - """ + """ # noqa def is_string_literal(line): if line[0] in 'uUbB': line = line[1:] @@ -1048,7 +1057,7 @@ def is_string_literal(line): allowed_try_keywords = ('try', 'except', 'else', 'finally') - if indent_level: # Allow imports in conditional statements or functions + if indent_level: # Allow imports in conditional statement/function return if not logical_line: # Allow empty lines or comments return @@ -1061,11 +1070,12 @@ def is_string_literal(line): 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 + # Allow try, except, else, finally keywords intermixed with + # imports in order to support conditional importing return elif is_string_literal(line): - # The first literal is a docstring, allow it. Otherwise, report error. + # The first literal is a docstring, allow it. Otherwise, report + # error. if checker_state.get('seen_docstring', False): checker_state['seen_non_imports'] = True else: @@ -1076,7 +1086,8 @@ def is_string_literal(line): @register_check def compound_statements(logical_line): - r"""Compound statements (on the same line) are generally discouraged. + r"""Compound statements (on the same line) are generally + discouraged. While sometimes it's okay to put an if/for/while with a small body on the same line, never do this for multi-clause statements. @@ -1139,10 +1150,11 @@ def compound_statements(logical_line): def explicit_line_join(logical_line, tokens): r"""Avoid explicit line join between brackets. - The preferred way of wrapping long lines is by using Python's implied line - continuation inside parentheses, brackets and braces. Long lines can be - broken over multiple lines by wrapping expressions in parentheses. These - should be used in preference to using a backslash for line continuation. + The preferred way of wrapping long lines is by using Python's + implied line continuation inside parentheses, brackets and braces. + Long lines can be broken over multiple lines by wrapping expressions + in parentheses. These should be used in preference to using a + backslash for line continuation. E502: aaa = [123, \\n 123] E502: aaa = ("bbb " \\n "ccc") @@ -1179,8 +1191,8 @@ 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`. + # 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 "()[]{},:.;@=%~" # The % character is strictly speaking a binary operator, but the # common usage seems to be to put it next to the format parameters, @@ -1291,10 +1303,10 @@ def comparison_to_singleton(logical_line, noqa): E712: if arg == True: E712: if False == arg: - Also, beware of writing if x when you really mean if x is not None -- - e.g. when testing whether a variable or argument that defaults to None was - set to some other value. The other value might have a type (such as a - container) that could be false in a boolean context! + Also, beware of writing if x when you really mean if x is not None + -- e.g. when testing whether a variable or argument that defaults to + None was set to some other value. The other value might have a type + (such as a container) that could be false in a boolean context! """ match = not noqa and COMPARE_SINGLETON_REGEX.search(logical_line) if match: @@ -1344,9 +1356,9 @@ def comparison_type(logical_line, noqa): Okay: if isinstance(obj, int): E721: if type(obj) is type(1): - When checking if an object is a string, keep in mind that it might be a - unicode string too! In Python 2.3, str and unicode have a common base - class, basestring, so you can do: + When checking if an object is a string, keep in mind that it might + be a unicode string too! In Python 2.3, str and unicode have a + common base class, basestring, so you can do: Okay: if isinstance(obj, basestring): Okay: if type(a1) is type(b1): @@ -1361,7 +1373,8 @@ def comparison_type(logical_line, noqa): @register_check def bare_except(logical_line, noqa): - r"""When catching exceptions, mention specific exceptions when possible. + r"""When catching exceptions, mention specific exceptions when + possible. Okay: except Exception: Okay: except BaseException: @@ -1380,8 +1393,8 @@ def bare_except(logical_line, noqa): def ambiguous_identifier(logical_line, tokens): r"""Never use the characters 'l', 'O', or 'I' as variable names. - In some fonts, these characters are indistinguishable from the numerals - one and zero. When tempted to use 'l', use 'L' instead. + In some fonts, these characters are indistinguishable from the + numerals one and zero. When tempted to use 'l', use 'L' instead. Okay: L = 0 Okay: o = 123 @@ -1390,9 +1403,9 @@ def ambiguous_identifier(logical_line, tokens): E741: O = 123 E741: I = 42 - Variables can be bound in several other contexts, including class and - function definitions, 'global' and 'nonlocal' statements, exception - handlers, and 'with' statements. + Variables can be bound in several other contexts, including class + and function definitions, 'global' and 'nonlocal' statements, + exception handlers, and 'with' statements. Okay: except AttributeError as o: Okay: with lock as L: @@ -1412,7 +1425,7 @@ def ambiguous_identifier(logical_line, tokens): if prev_text in idents_to_avoid: ident = prev_text pos = prev_start - # identifiers bound to a value with 'as', 'global', or 'nonlocal' + # identifiers bound to values with 'as', 'global', or 'nonlocal' if prev_text in ('as', 'global', 'nonlocal'): if text in idents_to_avoid: ident = text @@ -1431,7 +1444,8 @@ def ambiguous_identifier(logical_line, tokens): @register_check def python_3000_has_key(logical_line, noqa): - r"""The {}.has_key() method is removed in Python 3: use the 'in' operator. + r"""The {}.has_key() method is removed in Python 3: use the 'in' + operator. Okay: if "alph" in d:\n print d["alph"] W601: assert d.has_key('alph') @@ -1534,15 +1548,16 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): @register_check def python_3000_async_await_keywords(logical_line, tokens): - """'async' and 'await' are reserved keywords starting with Python 3.7 + """'async' and 'await' are reserved keywords starting at Python 3.7. W606: async = 42 W606: await = 42 - Okay: async def read_data(db):\n data = await db.fetch('SELECT ...') + Okay: async def read(db):\n data = await db.fetch('SELECT ...') """ - # The Python tokenize library before Python 3.5 recognizes async/await as a - # NAME token. Therefore, use a state machine to look for the possible - # async/await constructs as defined by the Python grammar: + # The Python tokenize library before Python 3.5 recognizes + # async/await as a NAME token. Therefore, use a state machine to + # look for the possible async/await constructs as defined by the + # Python grammar: # https://docs.python.org/3/reference/grammar.html state = None @@ -1557,14 +1572,15 @@ def python_3000_async_await_keywords(logical_line, tokens): state = ('await', start) elif state[0] == 'async_stmt': if token_type == tokenize.NAME and text in ('def', 'with', 'for'): - # One of funcdef, with_stmt, or for_stmt. Return to looking - # for async/await names. + # One of funcdef, with_stmt, or for_stmt. Return to + # looking for async/await names. state = None else: error = True elif state[0] == 'await': if token_type in (tokenize.NAME, tokenize.NUMBER, tokenize.STRING): - # An await expression. Return to looking for async/await names. + # An await expression. Return to looking for async/await + # names. state = None else: error = True @@ -1740,8 +1756,8 @@ def parse_udiff(diff, patterns=None, parent='.'): rv[path].update(range(row, row + nrows)) elif line[:3] == '+++': path = line[4:].split('\t', 1)[0] - # Git diff will use (i)ndex, (w)ork tree, (c)ommit and (o)bject - # instead of a/b/c/d as prefixes for patches + # Git diff will use (i)ndex, (w)ork tree, (c)ommit and + # (o)bject instead of a/b/c/d as prefixes for patches if path[:2] in ('b/', 'w/', 'i/'): path = path[2:] rv[path] = set() @@ -1795,9 +1811,9 @@ def _is_eol_token(token, _eol_token=_is_eol_token): return _eol_token(token) or (token[0] == tokenize.COMMENT and token[1] == token[4]) -############################################################################## +######################################################################## # Framework to run all checks -############################################################################## +######################################################################## class Checker(object): @@ -1975,7 +1991,7 @@ def check_ast(self): self.report_error(lineno, offset, text, check) def generate_tokens(self): - """Tokenize the file, run physical line checks and yield tokens.""" + """Tokenize file, run physical line checks and yield tokens.""" if self._io_error: self.report_error(1, 0, 'E902 %s' % self._io_error, readlines) tokengen = tokenize.generate_tokens(self.readline) @@ -1990,7 +2006,7 @@ def generate_tokens(self): self.report_invalid_syntax() def maybe_check_physical(self, token): - """If appropriate (based on token), check current physical line(s).""" + """If appropriate for token, check current physical line(s).""" # Called after every token, but act only on end of line. if _is_eol_token(token): # Obviously, a newline token ends a single physical line. @@ -1998,15 +2014,16 @@ def maybe_check_physical(self, token): elif token[0] == tokenize.STRING and '\n' in token[1]: # Less obviously, a string that contains newlines is a # multiline string, either triple-quoted or with internal - # newlines backslash-escaped. Check every physical line in the - # string *except* for the last one: its newline is outside of - # the multiline string, so we consider it a regular physical - # line, and will check it like any other physical line. + # newlines backslash-escaped. Check every physical line in + # the string *except* for the last one: its newline is + # outside of the multiline string, so we consider it a + # regular physical line, and will check it like any other + # physical line. # # Subtleties: - # - we don't *completely* ignore the last line; if it contains - # the magical "# noqa" comment, we disable all physical - # checks for the entire multiline string + # - we don't *completely* ignore the last line; if it + # contains the magical "# noqa" comment, we disable all + # physical checks for the entire multiline string # - have to wind self.line_number back because initially it # points to the last line of the string, and we want # check_physical() to give accurate feedback @@ -2166,7 +2183,7 @@ def print_benchmark(self): class FileReport(BaseReport): - """Collect the results of the checks and print only the filenames.""" + """Collect the results of the checks and print the filenames.""" print_filename = True @@ -2198,7 +2215,7 @@ def error(self, line_number, offset, text, check): return code def get_file_results(self): - """Print the result and return the overall count for this file.""" + """Print results and return the overall count for this file.""" self._deferred_print.sort() for line_number, offset, code, text, doc in self._deferred_print: print(self._fmt % { @@ -2217,8 +2234,8 @@ def get_file_results(self): print(' ' + doc.strip()) # stdout is block buffered when not stdout.isatty(). - # line can be broken where buffer boundary since other processes - # write to same file. + # line can be broken where buffer boundary since other + # processes write to same file. # flush() after print() to avoid buffer boundary. # Typical buffer size is 8192. line written safely when # len(line) < 8192. @@ -2336,7 +2353,7 @@ def input_dir(self, dirname): def excluded(self, filename, parent=None): """Check if the file should be excluded. - Check if 'options.exclude' contains a pattern that matches filename. + Check if 'options.exclude' contains a pattern matching filename. """ if not self.options.exclude: return False @@ -2364,8 +2381,8 @@ def ignore_code(self, code): def get_checks(self, argument_name): """Get all the checks for this category. - Find all globally visible functions where the first argument name - starts with argument_name and which contain selected tests. + Find all globally visible functions where the first argument + name starts with argument_name and which contain selected tests. """ checks = [] for check, attrs in _checks[argument_name].items(): @@ -2444,12 +2461,13 @@ def get_parser(prog='pycodestyle', version=__version__): def read_config(options, args, arglist, parser): """Read and parse configurations. - If a config file is specified on the command line with the "--config" - option, then only it is used for configuration. + If a config file is specified on the command line with the + "--config" option, then only it is used for configuration. - Otherwise, the user configuration (~/.config/pycodestyle) and any local - configurations in the current directory or above will be merged together - (in that order) using the read method of ConfigParser. + Otherwise, the user configuration (~/.config/pycodestyle) and any + local configurations in the current directory or above will be + merged together (in that order) using the read method of + ConfigParser. """ config = RawConfigParser() @@ -2518,10 +2536,10 @@ def read_config(options, args, arglist, parser): def process_options(arglist=None, parse_argv=False, config_file=None, parser=None, verbose=None): - """Process options passed either via arglist or via command line args. + """Process options passed either via arglist or command line args. - Passing in the ``config_file`` parameter allows other tools, such as flake8 - to specify their own options to be processed in pycodestyle. + Passing in the ``config_file`` parameter allows other tools, such as + flake8 to specify their own options to be processed in pycodestyle. """ if not parser: parser = get_parser() diff --git a/setup.cfg b/setup.cfg index 2cd4b2ec..73ae4e79 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,3 +8,4 @@ license_file = LICENSE select = ignore = E226,E24,W504 max_line_length = 79 +max_doc_length = 72 diff --git a/testsuite/support.py b/testsuite/support.py index 825def14..bcca4e49 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -151,11 +151,11 @@ def init_tests(pep8style): A test file can provide many tests. Each test starts with a declaration. This declaration is a single line starting with '#:'. - It declares codes of expected failures, separated by spaces or 'Okay' - if no failure is expected. + It declares codes of expected failures, separated by spaces or + 'Okay' if no failure is expected. If the file does not contain such declaration, it should pass all - tests. If the declaration is empty, following lines are not checked, - until next declaration. + tests. If the declaration is empty, following lines are not + checked, until next declaration. Examples: diff --git a/testsuite/test_blank_lines.py b/testsuite/test_blank_lines.py index 870403a4..2b37ad1a 100644 --- a/testsuite/test_blank_lines.py +++ b/testsuite/test_blank_lines.py @@ -35,8 +35,8 @@ def assertNoErrors(self, actual): class TestBlankLinesDefault(BlankLinesTestCase): """ - Tests for default blank with 2 blank lines for top level and 1 blank line - for methods. + Tests for default blank with 2 blank lines for top level and 1 + blank line for methods. """ def test_initial_no_blank(self): @@ -51,8 +51,8 @@ def test_initial_no_blank(self): def test_initial_lines_one_blank(self): """ - It will accept 1 blank lines before the first line of actual code, - even if in other places it asks for 2 + It will accept 1 blank lines before the first line of actual + code, even if in other places it asks for 2 """ result = self.check(""" def some_function(): @@ -63,8 +63,8 @@ def some_function(): def test_initial_lines_two_blanks(self): """ - It will accept 2 blank lines before the first line of actual code, - as normal. + It will accept 2 blank lines before the first line of actual + code, as normal. """ result = self.check(""" @@ -76,8 +76,8 @@ def some_function(): def test_method_less_blank_lines(self): """ - It will trigger an error when less than 1 blank lin is found before - method definitions. + It will trigger an error when less than 1 blank lin is found + before method definitions. """ result = self.check("""# First comment line. class X: @@ -93,8 +93,8 @@ def b(): def test_method_less_blank_lines_comment(self): """ - It will trigger an error when less than 1 blank lin is found before - method definition, ignoring comments. + It will trigger an error when less than 1 blank lin is found + before method definition, ignoring comments. """ result = self.check("""# First comment line. class X: @@ -111,8 +111,8 @@ def b(): def test_top_level_fewer_blank_lines(self): """ - It will trigger an error when less 2 blank lines are found before top - level definitions. + It will trigger an error when less 2 blank lines are found + before top level definitions. """ result = self.check("""# First comment line. # Second line of comment. @@ -146,8 +146,8 @@ class AFarEnoughClass(object): def test_top_level_more_blank_lines(self): """ - It will trigger an error when more 2 blank lines are found before top - level definitions. + It will trigger an error when more 2 blank lines are found + before top level definitions. """ result = self.check("""# First comment line. # Second line of comment. @@ -177,8 +177,8 @@ class AFarEnoughClass(object): def test_method_more_blank_lines(self): """ - It will trigger an error when more than 1 blank line is found before - method definition + It will trigger an error when more than 1 blank line is found + before method definition """ result = self.check("""# First comment line. @@ -209,8 +209,8 @@ def veryFar(self): def test_initial_lines_more_blank(self): """ - It will trigger an error for more than 2 blank lines before the first - line of actual code. + It will trigger an error for more than 2 blank lines before the + first line of actual code. """ result = self.check(""" @@ -222,8 +222,8 @@ def some_function(): def test_blank_line_between_decorator(self): """ - It will trigger an error when the decorator is followed by a blank - line. + It will trigger an error when the decorator is followed by a + blank line. """ result = self.check("""# First line. @@ -245,8 +245,8 @@ def some_method(self): def test_blank_line_decorator(self): """ - It will accept the decorators which are adjacent to the function and - method definition. + It will accept the decorators which are adjacent to the function + and method definition. """ result = self.check("""# First line. @@ -340,7 +340,8 @@ def b(): def test_method_nested_fewer_follow_lines(self): """ It will trigger an error when less than 1 blank line is - found between a method and previous definitions, even when nested. + found between a method and previous definitions, even when + nested. """ result = self.check(""" def a(): @@ -374,7 +375,8 @@ class C: def test_method_nested_ok(self): """ Will not trigger an error when 1 blank line is found - found between a method and previous definitions, even when nested. + found between a method and previous definitions, even when + nested. """ result = self.check(""" def a(): @@ -394,8 +396,8 @@ class C: class TestBlankLinesTwisted(BlankLinesTestCase): """ - Tests for blank_lines with 3 blank lines for top level and 2 blank line - for methods as used by the Twisted coding style. + Tests for blank_lines with 3 blank lines for top level and 2 blank + line for methods as used by the Twisted coding style. """ def setUp(self): @@ -408,8 +410,8 @@ def tearDown(self): def test_initial_lines_one_blanks(self): """ - It will accept less than 3 blank lines before the first line of actual - code. + It will accept less than 3 blank lines before the first line of + actual code. """ result = self.check(""" @@ -422,8 +424,8 @@ def some_function(): def test_initial_lines_tree_blanks(self): """ - It will accept 3 blank lines before the first line of actual code, - as normal. + It will accept 3 blank lines before the first line of actual + code, as normal. """ result = self.check(""" @@ -436,8 +438,8 @@ def some_function(): def test_top_level_fewer_blank_lines(self): """ - It will trigger an error when less 2 blank lines are found before top - level definitions. + It will trigger an error when less 2 blank lines are found + before top level definitions. """ result = self.check("""# First comment line. # Second line of comment. @@ -476,8 +478,8 @@ class AFarEnoughClass(object): def test_top_level_more_blank_lines(self): """ - It will trigger an error when more 2 blank lines are found before top - level definitions. + It will trigger an error when more 2 blank lines are found + before top level definitions. """ result = self.check("""# First comment line. # Second line of comment. diff --git a/tox.ini b/tox.ini index 41ba1789..98df04f3 100644 --- a/tox.ini +++ b/tox.ini @@ -11,9 +11,9 @@ skip_missing_interpreters = True [testenv] commands = {envpython} setup.py install - {envpython} pycodestyle.py --testsuite testsuite + {envpython} pycodestyle.py --max-doc-length=72 --testsuite testsuite {envpython} pycodestyle.py --statistics pycodestyle.py - {envpython} pycodestyle.py --doctest + {envpython} pycodestyle.py --max-doc-length=72 --doctest {envpython} setup.py test [testenv:flake8] From 06900154096902ebbd8076b4ff22a4f41327626d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 16 Jan 2018 17:37:40 -0800 Subject: [PATCH 173/412] Remove support for EOL Python 2.6 and 3.3 Python 2.6 an 3.3 are end of life. They are no longer receiving bug fixes, including for security issues. Python 2.6 went EOL on 2013-10-29 and 3.3 on 2017-09-29. For additional details on support Python versions, see: https://devguide.python.org/#status-of-python-branches Removing support for EOL Pythons will reduce testing and maintenance resources. Removed all workarounds for older Pythons. Updated trove classifiers and documentation to better communicate supported Python versions. Additionally, pass python_requires argument to setuptools. Helps pip decide what version of the library to install. https://packaging.python.org/tutorials/distributing-packages/#python-requires > If your project only runs on certain Python versions, setting the > python_requires argument to the appropriate PEP 440 version specifier > string will prevent pip from installing the project on other Python > versions. https://setuptools.readthedocs.io/en/latest/setuptools.html#new-and-changed-setup-keywords > python_requires > > A string corresponding to a version specifier (as defined in PEP 440) > for the Python version, used to specify the Requires-Python defined in > PEP 345. Can now use more modern Python syntax including dictionary comprehension as well as more generators. Closes #755 --- .travis.yml | 4 ---- CHANGES.txt | 7 +++++++ docs/developer.rst | 3 +-- pycodestyle.py | 35 +++++++++-------------------------- setup.py | 3 +-- testsuite/test_api.py | 8 ++++---- tox.ini | 2 +- 7 files changed, 23 insertions(+), 39 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ea5a73b..511d6707 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,12 +6,8 @@ install: script: tox matrix: include: - - python: 2.6 - env: TOXENV=py26 - python: 2.7 env: TOXENV=py27 - - python: 3.3 - env: TOXENV=py33 - python: 3.4 env: TOXENV=py34 - python: 3.5 diff --git a/CHANGES.txt b/CHANGES.txt index ecfa609a..4075662c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Changelog ========= +UNRELEASED +---------- + +Changes: + +* Remove support for EOL Python 2.6 and 3.3. + 2.4.0 (2018-04-10) ------------------ diff --git a/docs/developer.rst b/docs/developer.rst index 8630edc2..080d0807 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -14,7 +14,7 @@ conditions of the :ref:`Expat license `. Fork away! * `Source code `_ and `issue tracker `_ on GitHub. * `Continuous tests `_ against Python - 2.6 through 3.6 as well as the nightly Python build and PyPy, on `Travis CI + 2.7 and 3.4+ as well as the nightly Python build and PyPy, on `Travis CI platform `_. .. _available on GitHub: https://github.com/pycqa/pycodestyle @@ -32,7 +32,6 @@ Some high-level aims and directions to bear in mind for contributions: * If you want to provide extensibility / plugins, please see `flake8 `_ - ``pycodestyle`` doesn't want or need a plugin architecture. -* Python 2.6 support is still deemed important. * ``pycodestyle`` aims to have no external dependencies. diff --git a/pycodestyle.py b/pycodestyle.py index 50149f10..bcf99330 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -156,11 +156,6 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. ) DUNDER_REGEX = re.compile(r'^__([^\s]+)__ = ') -# Work around Python < 2.6 behaviour, which does not generate NL after -# a comment which is on a line by itself. -COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n' - - _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -1118,7 +1113,7 @@ def compound_statements(logical_line): last_char = len(line) - 1 found = line.find(':') prev_found = 0 - counts = dict((char, 0) for char in '{}[]()') + counts = {char: 0 for char in '{}[]()'} while -1 < found < last_char: update_counts(line[prev_found:found], counts) if ((counts['{'] <= counts['}'] and # {'a': 1} (dict) @@ -1762,9 +1757,11 @@ def parse_udiff(diff, patterns=None, parent='.'): if path[:2] in ('b/', 'w/', 'i/'): path = path[2:] rv[path] = set() - return dict([(os.path.join(parent, filepath), rows) - for (filepath, rows) in rv.items() - if rows and filename_match(filepath, patterns)]) + return { + os.path.join(parent, filepath): rows + for (filepath, rows) in rv.items() + if rows and filename_match(filepath, patterns) + } def normalize_paths(value, parent=os.curdir): @@ -1807,11 +1804,6 @@ def _is_eol_token(token): return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == '\\\n' -if COMMENT_WITH_NL: - def _is_eol_token(token, _eol_token=_is_eol_token): - return _eol_token(token) or (token[0] == tokenize.COMMENT and - token[1] == token[4]) - ######################################################################## # Framework to run all checks ######################################################################## @@ -2079,14 +2071,6 @@ def check_all(self, expected=None, line_offset=0): del self.tokens[0] else: self.check_logical() - elif COMMENT_WITH_NL and token_type == tokenize.COMMENT: - if len(self.tokens) == 1: - # The comment also ends a physical line - token = list(token) - token[1] = text.rstrip('\r\n') - token[3] = (token[2][0], token[2][1] + len(token[1])) - self.tokens = [tuple(token)] - self.check_logical() if self.tokens: self.check_physical(self.lines[-1]) self.check_logical() @@ -2154,8 +2138,8 @@ def get_file_results(self): def get_count(self, prefix=''): """Return the total count of errors and warnings.""" - return sum([self.counters[key] - for key in self.messages if key.startswith(prefix)]) + return sum(self.counters[key] + for key in self.messages if key.startswith(prefix)) def get_statistics(self, prefix=''): """Get statistics for message codes that start with the prefix. @@ -2503,8 +2487,7 @@ def read_config(options, args, arglist, parser): warnings.warn('[pep8] section is deprecated. Use [pycodestyle].') if pycodestyle_section: - option_list = dict([(o.dest, o.type or o.action) - for o in parser.option_list]) + option_list = {o.dest: o.type or o.action for o in parser.option_list} # First, read the default values (new_options, __) = parser.parse_args([]) diff --git a/setup.py b/setup.py index e68df569..83870f7a 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ def get_long_description(): namespace_packages=[], include_package_data=True, zip_safe=False, + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', install_requires=[ # Broken with Python 3: https://github.com/pypa/pip/issues/650 # 'setuptools', @@ -51,10 +52,8 @@ def get_long_description(): 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', diff --git a/testsuite/test_api.py b/testsuite/test_api.py index ae837540..63427644 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -28,10 +28,10 @@ def setUp(self): self._saved_checks = pycodestyle._checks sys.stdout = PseudoFile() sys.stderr = PseudoFile() - pycodestyle._checks = dict( - (k, dict((f, (vals[0][:], vals[1])) for (f, vals) in v.items())) - for (k, v) in self._saved_checks.items() - ) + pycodestyle._checks = { + k: {f: (vals[0][:], vals[1]) for (f, vals) in v.items()} + for k, v in self._saved_checks.items() + } def tearDown(self): sys.stdout = self._saved_stdout diff --git a/tox.ini b/tox.ini index 98df04f3..29a9e140 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, py35, py36, pypy, pypy3, jython +envlist = py27, py34, py35, py36, pypy, pypy3, jython skipsdist = True skip_missing_interpreters = True From fc1f88e42aad42e7cf4f5f6cab424db38306bf40 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 3 Jun 2018 16:25:10 -0700 Subject: [PATCH 174/412] Added PR number --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4075662c..f2e13b83 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,7 +6,7 @@ UNRELEASED Changes: -* Remove support for EOL Python 2.6 and 3.3. +* Remove support for EOL Python 2.6 and 3.3. PR #720. 2.4.0 (2018-04-10) ------------------ From bca3d8980dab8e1a46b8a39bdb1b1788cd221b37 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 3 Jun 2018 16:28:33 -0700 Subject: [PATCH 175/412] Marked changelog that this PR will be version 3.x --- CHANGES.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f2e13b83..19e2c340 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,8 @@ Changelog ========= -UNRELEASED ----------- +UNRELEASED / 3.0.0 +------------------ Changes: From 82a2408c0c097d99d6ecfe13e876cdb77f37e112 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 9 Jun 2018 18:00:22 -0700 Subject: [PATCH 176/412] Update all pypi.python.org URLs to pypi.org For details on the new PyPI, see the blog post: https://pythoninsider.blogspot.ca/2018/04/new-pypi-launched-legacy-pypi-shutting.html --- README.rst | 2 +- docs/intro.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index abd51e47..765d85bb 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ pycodestyle (formerly called pep8) - Python style guide checker :alt: Documentation Status .. image:: https://img.shields.io/pypi/wheel/pycodestyle.svg - :target: https://pypi.python.org/pypi/pycodestyle + :target: https://pypi.org/project/pycodestyle/ :alt: Wheel Status .. image:: https://badges.gitter.im/PyCQA/pycodestyle.svg diff --git a/docs/intro.rst b/docs/intro.rst index 10e2aef6..a374a874 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -43,8 +43,8 @@ Among other things, these features are currently not in the scope of the ``pycodestyle`` library: * **naming conventions**: this kind of feature is supported through plugins. - Install `flake8 `_ and the - `pep8-naming extension `_ to use + Install `flake8 `_ and the + `pep8-naming extension `_ to use this feature. * **docstring conventions**: they are not in the scope of this library; see the `pydocstyle project `_. From 95360fc179c4451321bd4c7947f020c6d701671c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 7 Jul 2018 23:25:29 +0300 Subject: [PATCH 177/412] Comment spelling fix --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index bcf99330..0d725d27 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -2543,7 +2543,7 @@ def process_options(arglist=None, parse_argv=False, config_file=None, (options, args) = parser.parse_args(arglist) options.reporter = None - # If explicity specified verbosity, override any `-v` CLI flag + # If explicitly specified verbosity, override any `-v` CLI flag if verbose is not None: options.verbose = verbose From 44541ef69160ada319a2ded7772dd7047c71ca64 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 9 Sep 2018 19:08:58 -0700 Subject: [PATCH 178/412] Add testing and document support for Python 3.7 Python 3.7 was released on June 27, 2018. https://docs.python.org/3/whatsnew/3.7.html --- .travis.yml | 4 +++- setup.py | 1 + tox.ini | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 511d6707..b9fffe7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,10 @@ matrix: env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 - - python: nightly + - python: 3.7 env: TOXENV=py37 + dist: xenial + sudo: true - python: pypy env: TOXENV=pypy - python: 3.5 diff --git a/setup.py b/setup.py index 83870f7a..f02060f3 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ def get_long_description(): 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/tox.ini b/tox.ini index 29a9e140..c2632840 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py35, py36, pypy, pypy3, jython +envlist = py27, py34, py35, py36, py37, pypy, pypy3, jython skipsdist = True skip_missing_interpreters = True From 64c2e300cd1aed0abc82e75ed1139cc989c0e1b9 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 15 Sep 2018 11:14:21 -0700 Subject: [PATCH 179/412] Remove suggestion and documentation of using unmaintained nose The nose project has ceased development. The last commit is from Mar 3, 2016. From their docs page: https://nose.readthedocs.io/ > Note to Users > > Nose has been in maintenance mode for the past several years and will > likely cease without a new person/team to take over maintainership. > New projects should consider using Nose2, py.test, or just plain > unittest/unittest2. --- CONTRIBUTING.rst | 14 +++----------- docs/advanced.rst | 3 --- testsuite/support.py | 4 ---- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 9f558590..f55c5e99 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -69,7 +69,6 @@ GitHub has an excellent `guide`_. The current tests are written in 2 styles: * standard xUnit based only on stdlib unittest - (can be executed with nose) * functional test using a custom framework and executed by the pycodestyle itself when installed in dev mode. @@ -77,19 +76,12 @@ The current tests are written in 2 styles: Running unittest ~~~~~~~~~~~~~~~~ -While the tests are writted using stdlib `unittest` module, the existing -test include unit, integration and functional tests. +The tests are writted using stdlib `unittest` module, the existing test +include unit, integration and functional tests. -There are a couple of ways to run the tests:: +To run the tests:: $ python setup.py test - $ # Use nose to run specific test - $ nosetests \ - > testsuite.test_blank_lines:TestBlankLinesDefault.test_initial_no_blank - $ # Use nose to run a subset and check coverage, and check the resulting - $ $ cover/pycodestyle_py.html in your browser - $ nosetests --with-coverage --cover-html -s testsuite.test_blank_lines - Running functional ~~~~~~~~~~~~~~~~~~ diff --git a/docs/advanced.rst b/docs/advanced.rst index fd3cf3e5..89700d56 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -25,9 +25,6 @@ in your project:: self.assertEqual(result.total_errors, 0, "Found code style errors (and warnings).") -If you are using ``nosetests`` for running tests, remove ``quiet=True`` -since Nose suppresses stdout. - There's also a shortcut for checking a single file:: import pycodestyle diff --git a/testsuite/support.py b/testsuite/support.py index bcca4e49..cbe2f465 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -215,7 +215,3 @@ def run_tests(style): if options.testsuite: init_tests(style) return style.check_files() - - -# nose should not collect these functions -init_tests.__test__ = run_tests.__test__ = False From dbffa61665e78fd1f199a8e83df759aad7d19a3d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 15 Sep 2018 11:46:26 -0700 Subject: [PATCH 180/412] Fix some spelling in CONTRIBUTING.rst --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f55c5e99..4481d508 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -76,7 +76,7 @@ The current tests are written in 2 styles: Running unittest ~~~~~~~~~~~~~~~~ -The tests are writted using stdlib `unittest` module, the existing test +The tests are written using stdlib `unittest` module, the existing tests include unit, integration and functional tests. To run the tests:: From 397463014fda3cdefe8d6c9d117ae16d878dc494 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Tue, 25 Sep 2018 14:58:57 +1200 Subject: [PATCH 181/412] Keep compability with stdlib tokenize.py changes https://github.com/python/cpython/commit/c4ef4896eac86a6759901c8546e26de4695a1389 is not yet part of any release of Python but has been backported to all versions in Git (includeing 2.7!). It causes the tokenize.py module to emit a synthetic NEWLINE token for files that do not in fact end with a newline, which confuses pycodestyle's checks for blank lines at the end of a file. Fortunately the synthetic NEWLINE tokens are easy to detect (the token text is ""). Fixes #786 --- pycodestyle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0d725d27..fbc3dca3 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -258,10 +258,10 @@ def trailing_blank_lines(physical_line, lines, line_number, total_lines): """ if line_number == total_lines: stripped_last_line = physical_line.rstrip() - if not stripped_last_line: + if physical_line and not stripped_last_line: return 0, "W391 blank line at end of file" if stripped_last_line == physical_line: - return len(physical_line), "W292 no newline at end of file" + return len(lines[-1]), "W292 no newline at end of file" @register_check From 7004708c968440f94d9583c2a6154d8bc50844a4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 20 Oct 2018 17:08:01 -0700 Subject: [PATCH 182/412] Fix line offset for 'invalid escape sequence' --- CONTRIBUTING.rst | 2 +- pycodestyle.py | 3 ++- testsuite/W60.py | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f55c5e99..18b5a883 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -62,7 +62,7 @@ All the tests should pass for all available interpreters, with the summary of:: congratulations :) -At this point you can create a pull request back to the official pycodestyles +At this point you can create a pull request back to the official pycodestyle repository for review! For more information on how to make a pull request, GitHub has an excellent `guide`_. diff --git a/pycodestyle.py b/pycodestyle.py index 0d725d27..8b608b04 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1522,6 +1522,7 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): for token_type, text, start, end, line in tokens: if token_type == tokenize.STRING: + orig_start = start quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1] # Extract string modifiers (e.g. u or r) quote_pos = text.index(quote) @@ -1535,7 +1536,7 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): pos += 1 if string[pos] not in valid: yield ( - line.lstrip().find(text), + orig_start, "W605 invalid escape sequence '\\%s'" % string[pos], ) diff --git a/testsuite/W60.py b/testsuite/W60.py index 030bec59..3f1b77c5 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -19,6 +19,10 @@ regex = ''' \.png$ ''' +#: W605:2:5 +f( + '\_' +) #: Okay regex = r'\.png$' regex = '\\.png$' From 59f7604333b695677eaf23a61a4758969a2cee1c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 21 Oct 2018 07:31:30 -0700 Subject: [PATCH 183/412] Add offsets for other W605 tests --- testsuite/W60.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/W60.py b/testsuite/W60.py index 3f1b77c5..1b03099a 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -13,9 +13,9 @@ x = 0 #: W604 val = `1 + 2` -#: W605 +#: W605:1:9 regex = '\.png$' -#: W605 +#: W605:1:9 regex = ''' \.png$ ''' From cb8402add912fa162a42eef9067c446704d919d3 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Thu, 22 Nov 2018 15:13:47 +0200 Subject: [PATCH 184/412] Make W605 point to the invalid sequence Instead of having W605 point to the beginning of the string literal, make it point to the precise line and column of the invalid escape sequence. This is more helpful when you have multiline string literals. --- pycodestyle.py | 9 +++++++-- testsuite/W60.py | 13 ++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 8b608b04..5b66302a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1522,7 +1522,7 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): for token_type, text, start, end, line in tokens: if token_type == tokenize.STRING: - orig_start = start + start_line, start_col = start quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1] # Extract string modifiers (e.g. u or r) quote_pos = text.index(quote) @@ -1535,8 +1535,13 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): while pos >= 0: pos += 1 if string[pos] not in valid: + line = start_line + string[:pos].count('\n') + if line == start_line: + col = start_col + len(prefix) + len(quote) + pos + else: + col = pos - string.rfind('\n', 0, pos) - 1 yield ( - orig_start, + (line, col - 1), "W605 invalid escape sequence '\\%s'" % string[pos], ) diff --git a/testsuite/W60.py b/testsuite/W60.py index 1b03099a..4cbaad94 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -13,16 +13,23 @@ x = 0 #: W604 val = `1 + 2` -#: W605:1:9 +#: W605:1:10 regex = '\.png$' -#: W605:1:9 +#: W605:2:1 regex = ''' \.png$ ''' -#: W605:2:5 +#: W605:2:6 f( '\_' ) +#: W605:4:6 +""" +multi-line +literal +with \_ somewhere +in the middle +""" #: Okay regex = r'\.png$' regex = '\\.png$' From e5a057982bfd68cd825eeea6bd8dce5bc0759e9f Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Thu, 22 Nov 2018 15:17:40 +0200 Subject: [PATCH 185/412] Microoptimisation: avoid creating string slices str.count() can limit the counting to a range of string positions. --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 5b66302a..f0ae6dc1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1535,7 +1535,7 @@ def python_3000_invalid_escape_sequence(logical_line, tokens): while pos >= 0: pos += 1 if string[pos] not in valid: - line = start_line + string[:pos].count('\n') + line = start_line + string.count('\n', 0, pos) if line == start_line: col = start_col + len(prefix) + len(quote) + pos else: From 65a41840dc24bdbf2975bd28c773f758ade254f4 Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Sat, 24 Nov 2018 22:51:43 +0800 Subject: [PATCH 186/412] refs W606, only NAME can follow await await NUMBER or await STRING is not valid syntax --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index c9e16671..73f40767 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1580,7 +1580,7 @@ def python_3000_async_await_keywords(logical_line, tokens): else: error = True elif state[0] == 'await': - if token_type in (tokenize.NAME, tokenize.NUMBER, tokenize.STRING): + if token_type == tokenize.NAME: # An await expression. Return to looking for async/await # names. state = None From 2a7629f18f7a27bb2ef4b70db7a7b57e730db8de Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Sat, 24 Nov 2018 22:41:17 +0800 Subject: [PATCH 187/412] fix #811, corner cases for async/await check --- pycodestyle.py | 14 ++++++++++++++ testsuite/W60.py | 3 +++ 2 files changed, 17 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index c9e16671..18a16ca3 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1566,12 +1566,19 @@ def python_3000_async_await_keywords(logical_line, tokens): for token_type, text, start, end, line in tokens: error = False + if token_type == tokenize.NL: + continue + if state is None: if token_type == tokenize.NAME: if text == 'async': state = ('async_stmt', start) elif text == 'await': state = ('await', start) + elif (token_type == tokenize.NAME and + text in ('def', 'for')): + state = ('define', start) + elif state[0] == 'async_stmt': if token_type == tokenize.NAME and text in ('def', 'with', 'for'): # One of funcdef, with_stmt, or for_stmt. Return to @@ -1584,8 +1591,15 @@ def python_3000_async_await_keywords(logical_line, tokens): # An await expression. Return to looking for async/await # names. state = None + elif token_type == tokenize.OP and text == '(': + state = None else: error = True + elif state[0] == 'define': + if token_type == tokenize.NAME and text in ('async', 'await'): + error = True + else: + state = None if error: yield ( diff --git a/testsuite/W60.py b/testsuite/W60.py index 4cbaad94..aba6d325 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -85,3 +85,6 @@ async def read_data(db): (await foo()) + (await bar()) -await foo() -(await foo()) +(await + foo()) +await(await foo()) From bab1c9c57392717fb523105655bd5fa5a179a399 Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Sun, 25 Nov 2018 00:13:55 +0800 Subject: [PATCH 188/412] add test for demonstration --- testsuite/W60.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testsuite/W60.py b/testsuite/W60.py index 4cbaad94..78b163ef 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -45,6 +45,10 @@ #: W606 await = 42 #: W606 +await 42 +#: W606 +await 'test' +#: W606 def async(): pass #: W606 From bd17ca651a57f8cdd278a8fac84d6a09a178a3eb Mon Sep 17 00:00:00 2001 From: Tomer Keren Date: Sat, 8 Dec 2018 01:37:22 +0200 Subject: [PATCH 189/412] Add whitespace around -> annotating operator (#809) * Test for whitespace around -> operator Tests will pass after fixing issue #803 * Require whitespace around -> operator Closes: #803 * Move tests to correct cases * Whitelist python3 only tests * Fix whitespace test errors Huge thanks to @asottile! * Address code review Pushing this directly to run full testsuite on travis * :bug:Change error code to space around bitwise operator E227 * Check for -> annotation only in py3.5+ * Skip tests meant for higher versions of python * Move type annotation tests to python3.5 testsuite Type annotations were first introduced in PEP 484,https://www.python.org/dev/peps/pep-0484/ implemented in python3.5 * Shorten line skipping tests by version * Replace test skipping logic As requested in code review * Run formatting to avoid long lines --- pycodestyle.py | 5 ++++- testsuite/python35.py | 6 ++++++ testsuite/support.py | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 testsuite/python35.py diff --git a/pycodestyle.py b/pycodestyle.py index 73f40767..4372e3bd 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -115,9 +115,12 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) 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 [] WS_NEEDED_OPERATORS = frozenset([ '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', - '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=']) + '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '='] + + FUNCTION_RETURN_ANNOTATION_OP) WHITESPACE = frozenset(' \t') NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT]) diff --git a/testsuite/python35.py b/testsuite/python35.py new file mode 100644 index 00000000..1bedee5d --- /dev/null +++ b/testsuite/python35.py @@ -0,0 +1,6 @@ +#: E225 +def bar(a, b)->int: + pass +#: Okay +def baz(a, b) -> int: + pass diff --git a/testsuite/support.py b/testsuite/support.py index cbe2f465..8241a923 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -168,6 +168,13 @@ def init_tests(pep8style): def run_tests(filename): """Run all the tests from a file.""" + # Skip tests meant for higher versions of python + ver_match = re.search(r'python(\d)(\d)?\.py$', filename) + if ver_match: + test_against_version = tuple(int(val or 0) + for val in ver_match.groups()) + if sys.version_info < test_against_version: + return lines = readlines(filename) + ['#:\n'] line_offset = 0 codes = ['Okay'] From 0138bb106403241564a0c5af67886c05a2f4f690 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 28 Nov 2018 00:43:17 +0100 Subject: [PATCH 190/412] Allow omitting blank lines around one-liner definitions. --- pycodestyle.py | 10 ++++++++++ testsuite/E30.py | 18 ++++++++++++++++++ testsuite/E30not.py | 11 +++++++++++ 3 files changed, 39 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index c9e16671..27009c64 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -354,6 +354,16 @@ 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. 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 (expand_indent(prev_line) <= indent_level and + expand_indent(next_line) <= indent_level): + return if indent_level: if not (blank_before == method_lines or previous_indent_level < indent_level or diff --git a/testsuite/E30.py b/testsuite/E30.py index ad5518bb..320b2a1b 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -163,3 +163,21 @@ async def x(): async def x(y: int = 1): pass +#: E704:3:1 E302:3:1 +def bar(): + pass +def baz(): pass +#: E704:1:1 E302:2:1 +def bar(): pass +def baz(): + pass +#: E704:4:5 E306:4:5 +def foo(): + def bar(): + pass + def baz(): pass +#: E704:2:5 E306:3:5 +def foo(): + def bar(): pass + def baz(): + pass diff --git a/testsuite/E30not.py b/testsuite/E30not.py index 6303b3b2..ef795a8e 100644 --- a/testsuite/E30not.py +++ b/testsuite/E30not.py @@ -162,3 +162,14 @@ class Bar(object): def foo(x): classification = x definitely = not classification +#: E704:3:1 E704:4:1 +# This emits the (ignored-by-default) E704, but here we're testing +# for no E30x being emitted. +def bar(): pass +def baz(): pass +#: E704:4:5 E704:5:5 +def foo(): + # This emits the (ignored-by-default) E704, but here we're testing + # for no E30x being emitted. + def bar(): pass + def baz(): pass From d29eade03d8e74e0a2b2c316f53f17ffba7a7e0a Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 15 Sep 2018 11:33:26 -0700 Subject: [PATCH 191/412] Correct ReStructuredText inline literal syntax in CONTRIBUTING.rst --- CONTRIBUTING.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 9c29ba9b..3361c468 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -76,7 +76,7 @@ The current tests are written in 2 styles: Running unittest ~~~~~~~~~~~~~~~~ -The tests are written using stdlib `unittest` module, the existing tests +The tests are written using stdlib ``unittest`` module, the existing tests include unit, integration and functional tests. To run the tests:: @@ -86,8 +86,8 @@ To run the tests:: Running functional ~~~~~~~~~~~~~~~~~~ -When installed in dev mode, pycodestyle will have the `--testsuite` -option which can be used to run the tests:: +When installed in dev mode, pycodestyle will have the ``--testsuite`` option +which can be used to run the tests:: $ pip install -e . $ # Run all tests. From 68335d1a6d65de7ed09af580bdb909c5f6c139c2 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 5 Nov 2017 15:46:52 -0800 Subject: [PATCH 192/412] Add check for over-indented blocks In a project with all lines indented 4 spaces, I noticed pycodestyle was not catching code that was accidentally indented two levels (8 spaces). The over indentation was unintended and can be caught during static analysis. Fixes #430 --- CHANGES.txt | 1 + docs/intro.rst | 1 + pycodestyle.py | 6 +++++- testsuite/E10.py | 4 ++-- testsuite/E11.py | 5 ++++- testsuite/E90.py | 2 +- testsuite/W19.py | 38 +++++++++++++++++++------------------- testsuite/test_api.py | 4 ++-- testsuite/test_shell.py | 9 +++++---- 9 files changed, 40 insertions(+), 30 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 19e2c340..48c985c7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,7 @@ UNRELEASED / 3.0.0 Changes: * Remove support for EOL Python 2.6 and 3.3. PR #720. +* Add E117 error for over-indented code blocks. 2.4.0 (2018-04-10) ------------------ diff --git a/docs/intro.rst b/docs/intro.rst index a374a874..991db47b 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -232,6 +232,7 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E116 | unexpected indentation (comment) | +------------+----------------------------------------------------------------------+ +| E117 | over-indented | +------------+----------------------------------------------------------------------+ | E121 (\*^) | continuation line under-indented for hanging indent | +------------+----------------------------------------------------------------------+ diff --git a/pycodestyle.py b/pycodestyle.py index 094e24f7..766e6bd7 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -207,7 +207,7 @@ def tabs_or_spaces(physical_line, indent_char): tabs and spaces. When using -tt these warnings become errors. These options are highly recommended! - Okay: if a == 0:\n a = 1\n b = 1 + Okay: if a == 0:\n a = 1\n b = 1 E101: if a == 0:\n a = 1\n\tb = 1 """ indent = INDENT_REGEX.match(physical_line).group(1) @@ -534,6 +534,10 @@ def indentation(logical_line, previous_logical, indent_char, elif not indent_expect and indent_level > previous_indent_level: yield 0, tmpl % (3 + c, "unexpected indentation") + expected_indent_level = previous_indent_level + 4 + if indent_expect and indent_level > expected_indent_level: + yield 0, tmpl % (7, 'over-indented') + @register_check def continued_indentation(logical_line, tokens, indent_level, hang_closing, diff --git a/testsuite/E10.py b/testsuite/E10.py index 7b425945..08b03a0e 100644 --- a/testsuite/E10.py +++ b/testsuite/E10.py @@ -3,7 +3,7 @@ for b in 'xyz': print a # indented with 8 spaces print b # indented with 1 tab -#: E101 E122 W191 W191 +#: E101 E117 E122 W191 W191 if True: pass @@ -27,7 +27,7 @@ def tearDown(self): pass # -#: E101 W191 W191 +#: E101 E117 W191 W191 if True: foo(1, 2) diff --git a/testsuite/E11.py b/testsuite/E11.py index 4ed10963..c7b11d14 100644 --- a/testsuite/E11.py +++ b/testsuite/E11.py @@ -1,7 +1,7 @@ #: E111 if x > 2: print x -#: E111 +#: E111 E117 if True: print #: E112 @@ -34,3 +34,6 @@ def start(self): # finally: # sys.exit() self.master.start() +#: E117 +def start(): + print diff --git a/testsuite/E90.py b/testsuite/E90.py index 2c18e9af..3fd2b328 100644 --- a/testsuite/E90.py +++ b/testsuite/E90.py @@ -2,7 +2,7 @@ } #: E901 = [x -#: E901 E101 W191 +#: E901 E101 E117 W191 while True: try: pass diff --git a/testsuite/W19.py b/testsuite/W19.py index ed69e2b5..d77bc9ec 100644 --- a/testsuite/W19.py +++ b/testsuite/W19.py @@ -1,4 +1,4 @@ -#: W191 +#: E117 W191 if False: print # indented with 1 tab #: @@ -7,30 +7,30 @@ #: W191 y = x == 2 \ or x == 3 -#: E101 W191 W504 +#: E101 E117 W191 W504 if ( x == ( 3 ) or y == 4): pass -#: E101 W191 +#: E101 E117 W191 if x == 2 \ or y > 1 \ or x == 3: pass -#: E101 W191 +#: E101 E117 W191 if x == 2 \ or y > 1 \ or x == 3: pass #: -#: E101 W191 W504 +#: E101 E117 W191 W504 if (foo == bar and baz == frop): pass -#: E101 W191 W504 +#: E101 E117 W191 W504 if ( foo == bar and baz == frop @@ -38,25 +38,25 @@ pass #: -#: E101 E101 W191 W191 +#: E101 E101 E117 W191 W191 if start[1] > end_col and not ( over_indent == 4 and indent_next): return(0, "E121 continuation line over-" "indented for visual indent") #: -#: E101 W191 +#: E101 E117 W191 def long_function_name( var_one, var_two, var_three, var_four): print(var_one) -#: E101 W191 W504 +#: E101 E117 W191 W504 if ((row < 0 or self.moduleCount <= row or col < 0 or self.moduleCount <= col)): raise Exception("%s,%s - %s" % (row, col, self.moduleCount)) -#: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191 +#: E101 E101 E101 E101 E117 W191 W191 W191 W191 W191 W191 if bar: return( start, 'E121 lines starting with a ' @@ -65,35 +65,35 @@ def long_function_name( "bracket's line" ) # -#: E101 W191 W504 +#: E101 E117 W191 W504 # you want vertical alignment, so use a parens if ((foo.bar("baz") and foo.bar("frop") )): print "yes" -#: E101 W191 W504 +#: E101 E117 W191 W504 # also ok, but starting to look like LISP if ((foo.bar("baz") and foo.bar("frop"))): print "yes" -#: E101 W191 W504 +#: E101 E117 W191 W504 if (a == 2 or b == "abc def ghi" "jkl mno"): return True -#: E101 W191 W504 +#: E101 E117 W191 W504 if (a == 2 or b == """abc def ghi jkl mno"""): return True -#: W191:2:1 W191:3:1 E101:3:2 +#: W191:2:1 W191:3:1 E101:3:2 E117 if length > options.max_line_length: return options.max_line_length, \ "E501 line too long (%d characters)" % length # -#: E101 W191 W191 W504 +#: E101 E117 W191 W191 W504 if os.path.exists(os.path.join(path, PEP8_BIN)): cmd = ([os.path.join(path, PEP8_BIN)] + self._pep8_options(targetfile)) @@ -119,19 +119,19 @@ def long_function_name( even though the noqa comment is not immediately after the string ''' + foo # noqa # -#: E101 W191 +#: E101 E117 W191 if foo is None and bar is "frop" and \ blah == 'yeah': blah = 'yeahnah' # -#: W191 W191 W191 +#: E117 W191 W191 W191 if True: foo( 1, 2) -#: W191 W191 W191 W191 W191 +#: E117 W191 W191 W191 W191 W191 def test_keys(self): """areas.json - All regions are accounted for.""" expected = set([ diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 63427644..ddc28b8e 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -125,7 +125,7 @@ def test_styleguide(self): report = pycodestyle.StyleGuide().check_files([E11]) stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) - self.assertEqual(report.total_errors, 17) + self.assertEqual(report.total_errors, 20) self.assertFalse(sys.stderr) self.reset() @@ -133,7 +133,7 @@ def test_styleguide(self): report = pycodestyle.StyleGuide(paths=[E11]).check_files() stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) - self.assertEqual(report.total_errors, 17) + self.assertEqual(report.total_errors, 20) self.assertFalse(sys.stderr) self.reset() diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index 7ada1a41..e4d08717 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -77,8 +77,8 @@ def test_check_simple(self): stdout = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) - self.assertEqual(len(stdout), 17) - for line, num, col in zip(stdout, (3, 6, 9, 12), (3, 6, 1, 5)): + self.assertEqual(len(stdout), 20) + for line, num, col in zip(stdout, (3, 6, 6, 9, 12), (3, 6, 6, 1, 5)): path, x, y, msg = line.split(':') self.assertTrue(path.endswith(E11)) self.assertEqual(x, str(num)) @@ -170,10 +170,11 @@ def test_check_diff(self): "+ print"] self.stdin = '\n'.join(diff_lines) stdout, stderr, errcode = self.pycodestyle('--diff') - (stdout,) = stdout.splitlines() + stdout = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) - self.assertTrue('testsuite/E11.py:6:6: E111 ' in stdout) + self.assertTrue('testsuite/E11.py:6:6: E111 ' in stdout[0]) + self.assertTrue('testsuite/E11.py:6:6: E117 ' in stdout[1]) # missing '--diff' self.stdin = '\n'.join(diff_lines) From cb11014f0037e6b387e13aecf6c443ece85a0942 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 24 Jan 2019 18:12:15 -0800 Subject: [PATCH 193/412] Use 'dist: xenial' in Travis to simplify configuration Allows using Python version 3.7 without sudo declarations. Travis officially added support for Xenial on 2018-11-08. https://blog.travis-ci.com/2018-11-08-xenial-release --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b9fffe7e..ce324bef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +dist: xenial language: python cache: pip sudo: false @@ -16,10 +17,10 @@ matrix: env: TOXENV=py36 - python: 3.7 env: TOXENV=py37 - dist: xenial - sudo: true - - python: pypy + - python: pypy2.7-6.0 env: TOXENV=pypy + - python: pypy3.5-6.0 + env: TOXENV=pypy3 - python: 3.5 env: TOXENV=flake8 From 78d8b45d65a7dc32565031aa10f6f263139ca2fa Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 26 Jan 2019 19:12:44 -0800 Subject: [PATCH 194/412] Allow W605 to be silenced by noqa --- pycodestyle.py | 5 ++++- testsuite/W60.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 766e6bd7..a0221651 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1509,12 +1509,15 @@ def python_3000_backticks(logical_line): @register_check -def python_3000_invalid_escape_sequence(logical_line, tokens): +def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): r"""Invalid escape sequences are deprecated in Python 3.6. Okay: regex = r'\.png$' W605: regex = '\.png$' """ + if noqa: + return + # https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals valid = [ '\n', diff --git a/testsuite/W60.py b/testsuite/W60.py index 42a8c6d9..5003677d 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -40,6 +40,10 @@ \\.png$ ''' s = '\\' +regex = '\w' # noqa +regex = ''' +\w +''' # noqa #: W606 async = 42 #: W606 From e91ef6e40f2be30f9af7a86a84255d6bdfe23f51 Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Tue, 29 Jan 2019 07:58:21 -0600 Subject: [PATCH 195/412] Add release notes for 2.5.0 Bump version to 2.5.0 for release --- CHANGES.txt | 18 +++++++++++++++++- pycodestyle.py | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 48c985c7..28d1de89 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,13 +1,29 @@ Changelog ========= -UNRELEASED / 3.0.0 +2.5.0 (2019-01-29) ------------------ +New checks: + +* E117: Over-indented code blocks +* W505: Maximum doc-string length only when configured with --max-doc-length + Changes: * Remove support for EOL Python 2.6 and 3.3. PR #720. * Add E117 error for over-indented code blocks. +* Allow W605 to be silenced by `# noqa` and fix the position reported by W605 +* Allow users to omit blank lines around one-liner definitions of classes and + functions +* Include the function return annotation (``->``) as requiring surrounding + whitespace only on Python 3 +* Verify that only names can follow ``await``. Previously we allowed numbers + and strings. +* Add support for Python 3.7 +* Fix detection of annotated argument defaults for E252 +* Cprrect the position reported by W504 + 2.4.0 (2018-04-10) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index a0221651..0ecd7fd0 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -78,7 +78,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.4.0' +__version__ = '2.5.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504' From 69bd263a5db93730f9d184dab20e15fa0af65975 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 29 Jan 2019 20:43:03 -0800 Subject: [PATCH 196/412] Tiny typo fix --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 28d1de89..9cdbfdfe 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -22,7 +22,7 @@ Changes: and strings. * Add support for Python 3.7 * Fix detection of annotated argument defaults for E252 -* Cprrect the position reported by W504 +* Correct the position reported by W504 2.4.0 (2018-04-10) From 1f8d5ca1a1ede54e6d7b67693b36b209b940573c Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Wed, 30 Jan 2019 10:27:12 -0500 Subject: [PATCH 197/412] E402: Add "with" statement to allowed keywords --- pycodestyle.py | 8 ++++---- testsuite/E40.py | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0ecd7fd0..278bf125 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1067,7 +1067,7 @@ def is_string_literal(line): 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 indent_level: # Allow imports in conditional statement/function return @@ -1081,9 +1081,9 @@ def is_string_literal(line): 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 diff --git a/testsuite/E40.py b/testsuite/E40.py index f9a18fc9..6d123b1e 100644 --- a/testsuite/E40.py +++ b/testsuite/E40.py @@ -33,6 +33,12 @@ finally: print('made attempt to import foo') +import bar +#: Okay +with warnings.catch_warnings(): + warnings.filterwarnings("ignore", DeprecationWarning) + import foo + import bar #: E402 VERSION = '1.2.3' From eba7f0f7847762bae419e366354bb30e6230daeb Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Wed, 30 Jan 2019 10:54:40 -0500 Subject: [PATCH 198/412] E402: Add "if" statement to allowed keywords --- pycodestyle.py | 3 ++- testsuite/E40.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 278bf125..68d41401 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1067,7 +1067,8 @@ def is_string_literal(line): line = line[1:] return line and (line[0] == '"' or line[0] == "'") - allowed_keywords = ('try', 'except', 'else', 'finally', 'with') + allowed_keywords = ( + 'try', 'except', 'else', 'finally', 'with', 'if') if indent_level: # Allow imports in conditional statement/function return diff --git a/testsuite/E40.py b/testsuite/E40.py index 6d123b1e..041ffb39 100644 --- a/testsuite/E40.py +++ b/testsuite/E40.py @@ -39,6 +39,11 @@ warnings.filterwarnings("ignore", DeprecationWarning) import foo +import bar +#: Okay +if False: + import foo + import bar #: E402 VERSION = '1.2.3' From 84da6f3ef563f4dcf7180a39bb8bb824fc74aac9 Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Wed, 30 Jan 2019 12:19:57 -0500 Subject: [PATCH 199/412] CONTRIBUTING: Make virtualenv dir name consistent with gitignore Update gitignore to be more flexible --- .gitignore | 2 +- CONTRIBUTING.rst | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 7cd7441f..2a40a535 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ dist docs/_build build/ -venv/ +/venv*/ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3361c468..4cdd2132 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -34,8 +34,10 @@ Next, ``cd`` to the pycodestyle repository that you cloned earlier and create, then activate a virtualenv:: $ cd pycodestyle - $ virtualenv pycodestyle-venv - $ source pycodestyle-venv/bin/activate + $ virtualenv venv-pycodestyle + $ source venv-pycodestyle/bin/activate + +Note that ``venv*/`` is ignored via ``.gitignore``. Now you can install the pycodestyle requirements:: From dd1d313152a136f80c9ac3d508d2d99d6b3dc0a8 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 31 Jan 2019 16:38:31 -0800 Subject: [PATCH 200/412] Expect lines to be indented 8 places when tabs are used Fixes #836 --- pycodestyle.py | 8 +++++--- testsuite/E10.py | 4 ++-- testsuite/E11.py | 3 +++ testsuite/E90.py | 2 +- testsuite/W19.py | 38 +++++++++++++++++++------------------- testsuite/test_api.py | 4 ++-- testsuite/test_shell.py | 2 +- 7 files changed, 33 insertions(+), 28 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0ecd7fd0..4a0d17b1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -534,9 +534,11 @@ def indentation(logical_line, previous_logical, indent_char, elif not indent_expect and indent_level > previous_indent_level: yield 0, tmpl % (3 + c, "unexpected indentation") - expected_indent_level = previous_indent_level + 4 - if indent_expect and indent_level > expected_indent_level: - yield 0, tmpl % (7, 'over-indented') + if indent_expect: + expected_indent_amount = 8 if indent_char == '\t' else 4 + expected_indent_level = previous_indent_level + expected_indent_amount + if indent_level > expected_indent_level: + yield 0, tmpl % (7, 'over-indented') @register_check diff --git a/testsuite/E10.py b/testsuite/E10.py index 08b03a0e..7b425945 100644 --- a/testsuite/E10.py +++ b/testsuite/E10.py @@ -3,7 +3,7 @@ for b in 'xyz': print a # indented with 8 spaces print b # indented with 1 tab -#: E101 E117 E122 W191 W191 +#: E101 E122 W191 W191 if True: pass @@ -27,7 +27,7 @@ def tearDown(self): pass # -#: E101 E117 W191 W191 +#: E101 W191 W191 if True: foo(1, 2) diff --git a/testsuite/E11.py b/testsuite/E11.py index c7b11d14..ef87d929 100644 --- a/testsuite/E11.py +++ b/testsuite/E11.py @@ -37,3 +37,6 @@ def start(self): #: E117 def start(): print +#: E117 W191 +def start(): + print diff --git a/testsuite/E90.py b/testsuite/E90.py index 3fd2b328..2c18e9af 100644 --- a/testsuite/E90.py +++ b/testsuite/E90.py @@ -2,7 +2,7 @@ } #: E901 = [x -#: E901 E101 E117 W191 +#: E901 E101 W191 while True: try: pass diff --git a/testsuite/W19.py b/testsuite/W19.py index d77bc9ec..ed69e2b5 100644 --- a/testsuite/W19.py +++ b/testsuite/W19.py @@ -1,4 +1,4 @@ -#: E117 W191 +#: W191 if False: print # indented with 1 tab #: @@ -7,30 +7,30 @@ #: W191 y = x == 2 \ or x == 3 -#: E101 E117 W191 W504 +#: E101 W191 W504 if ( x == ( 3 ) or y == 4): pass -#: E101 E117 W191 +#: E101 W191 if x == 2 \ or y > 1 \ or x == 3: pass -#: E101 E117 W191 +#: E101 W191 if x == 2 \ or y > 1 \ or x == 3: pass #: -#: E101 E117 W191 W504 +#: E101 W191 W504 if (foo == bar and baz == frop): pass -#: E101 E117 W191 W504 +#: E101 W191 W504 if ( foo == bar and baz == frop @@ -38,25 +38,25 @@ pass #: -#: E101 E101 E117 W191 W191 +#: E101 E101 W191 W191 if start[1] > end_col and not ( over_indent == 4 and indent_next): return(0, "E121 continuation line over-" "indented for visual indent") #: -#: E101 E117 W191 +#: E101 W191 def long_function_name( var_one, var_two, var_three, var_four): print(var_one) -#: E101 E117 W191 W504 +#: E101 W191 W504 if ((row < 0 or self.moduleCount <= row or col < 0 or self.moduleCount <= col)): raise Exception("%s,%s - %s" % (row, col, self.moduleCount)) -#: E101 E101 E101 E101 E117 W191 W191 W191 W191 W191 W191 +#: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191 if bar: return( start, 'E121 lines starting with a ' @@ -65,35 +65,35 @@ def long_function_name( "bracket's line" ) # -#: E101 E117 W191 W504 +#: E101 W191 W504 # you want vertical alignment, so use a parens if ((foo.bar("baz") and foo.bar("frop") )): print "yes" -#: E101 E117 W191 W504 +#: E101 W191 W504 # also ok, but starting to look like LISP if ((foo.bar("baz") and foo.bar("frop"))): print "yes" -#: E101 E117 W191 W504 +#: E101 W191 W504 if (a == 2 or b == "abc def ghi" "jkl mno"): return True -#: E101 E117 W191 W504 +#: E101 W191 W504 if (a == 2 or b == """abc def ghi jkl mno"""): return True -#: W191:2:1 W191:3:1 E101:3:2 E117 +#: W191:2:1 W191:3:1 E101:3:2 if length > options.max_line_length: return options.max_line_length, \ "E501 line too long (%d characters)" % length # -#: E101 E117 W191 W191 W504 +#: E101 W191 W191 W504 if os.path.exists(os.path.join(path, PEP8_BIN)): cmd = ([os.path.join(path, PEP8_BIN)] + self._pep8_options(targetfile)) @@ -119,19 +119,19 @@ def long_function_name( even though the noqa comment is not immediately after the string ''' + foo # noqa # -#: E101 E117 W191 +#: E101 W191 if foo is None and bar is "frop" and \ blah == 'yeah': blah = 'yeahnah' # -#: E117 W191 W191 W191 +#: W191 W191 W191 if True: foo( 1, 2) -#: E117 W191 W191 W191 W191 W191 +#: W191 W191 W191 W191 W191 def test_keys(self): """areas.json - All regions are accounted for.""" expected = set([ diff --git a/testsuite/test_api.py b/testsuite/test_api.py index ddc28b8e..c69e54d2 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -125,7 +125,7 @@ def test_styleguide(self): report = pycodestyle.StyleGuide().check_files([E11]) stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) - self.assertEqual(report.total_errors, 20) + self.assertEqual(report.total_errors, 24) self.assertFalse(sys.stderr) self.reset() @@ -133,7 +133,7 @@ def test_styleguide(self): report = pycodestyle.StyleGuide(paths=[E11]).check_files() stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) - self.assertEqual(report.total_errors, 20) + self.assertEqual(report.total_errors, 24) self.assertFalse(sys.stderr) self.reset() diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index e4d08717..3878194b 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -77,7 +77,7 @@ def test_check_simple(self): stdout = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) - self.assertEqual(len(stdout), 20) + self.assertEqual(len(stdout), 24) for line, num, col in zip(stdout, (3, 6, 6, 9, 12), (3, 6, 6, 1, 5)): path, x, y, msg = line.split(':') self.assertTrue(path.endswith(E11)) From 7e10d9a95e34aa7c600f5100ba64e7710c2688f1 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 27 Feb 2019 20:05:59 +0200 Subject: [PATCH 201/412] E225 Check for space around boolean operators This was documented in the docstring for `missing_whitespace_around_operator` but not implemented, allowing e.g. `1and 0` to pass. Fixed by adding test case then modifying the check to search for the 'and' and 'or' operators as well. --- pycodestyle.py | 7 +++++-- testsuite/E22.py | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 4a0d17b1..b2285bae 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -119,7 +119,8 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. FUNCTION_RETURN_ANNOTATION_OP = ['->'] if sys.version_info >= (3, 5) else [] WS_NEEDED_OPERATORS = frozenset([ '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', - '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '='] + + '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', + 'and', 'or'] + FUNCTION_RETURN_ANNOTATION_OP) WHITESPACE = frozenset(' \t') NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) @@ -824,6 +825,7 @@ def missing_whitespace_around_operator(logical_line, tokens): E225: submitted +=1 E225: x = x /2 - 1 E225: z = x **y + E225: z = 1and 1 E226: c = (a+b) * (a-b) E226: hypot2 = x*x + y*y E227: c = a|b @@ -833,6 +835,7 @@ def missing_whitespace_around_operator(logical_line, tokens): need_space = False prev_type = tokenize.OP prev_text = prev_end = None + operator_types = (tokenize.OP, tokenize.NAME) for token_type, text, start, end, line in tokens: if token_type in SKIP_COMMENTS: continue @@ -864,7 +867,7 @@ def missing_whitespace_around_operator(logical_line, tokens): yield (need_space[0], "%s missing whitespace " "around %s operator" % (code, optype)) need_space = False - elif token_type == tokenize.OP and prev_end is not None: + elif token_type in operator_types and prev_end is not None: if text == '=' and parens: # Allow keyword args or defaults: foo(bar=None). pass diff --git a/testsuite/E22.py b/testsuite/E22.py index ee9dc74a..351e976f 100644 --- a/testsuite/E22.py +++ b/testsuite/E22.py @@ -76,6 +76,10 @@ i=i+ 1 #: E225 E225 i=i +1 +#: E225 +i = 1and 1 +#: E225 +i = 1or 0 #: E225 E226 i=i+1 #: E225 E226 From 5c036dcf5f5b464259e75f7b3942885734c4f13f Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 28 Feb 2019 07:32:08 +0100 Subject: [PATCH 202/412] Check for 'is' and 'in' as well --- pycodestyle.py | 2 +- testsuite/E22.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index b2285bae..02a717aa 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -120,7 +120,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. WS_NEEDED_OPERATORS = frozenset([ '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', - 'and', 'or'] + + 'and', 'in', 'is', 'or'] + FUNCTION_RETURN_ANNOTATION_OP) WHITESPACE = frozenset(' \t') NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) diff --git a/testsuite/E22.py b/testsuite/E22.py index 351e976f..4974cb35 100644 --- a/testsuite/E22.py +++ b/testsuite/E22.py @@ -80,6 +80,10 @@ i = 1and 1 #: E225 i = 1or 0 +#: E225 +1is 1 +#: E225 +1in [] #: E225 E226 i=i+1 #: E225 E226 From 17376f3c2539ee256a4724368c0e8ca4e667efb9 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 28 Feb 2019 07:33:21 +0100 Subject: [PATCH 203/412] Update tox url in tox.ini The previous one lead to a dead-end 404. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c2632840..742888c2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ -# Tox (https://testrun.org/tox/latest/) is a tool for running tests +# Tox (https://tox.readthedocs.io/en/latest/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. From 0327e55e756e20ade468e67992f9f43da14aabb4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 1 Mar 2019 08:22:55 -0800 Subject: [PATCH 204/412] Fix E721 false positive --- pycodestyle.py | 2 +- testsuite/E72.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 02a717aa..cad68eaf 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -139,7 +139,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s*type(?:s.\w+Type' +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)) OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') diff --git a/testsuite/E72.py b/testsuite/E72.py index c18527f9..a60d892f 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -49,6 +49,9 @@ pass if type(a) != type(b) or type(a) == type(ccc): pass +#: Okay +def func_histype(a, b, c): + pass #: E722 try: pass From b8db33dfa6cd4029b75efd1e7cab7c1b64d03bc7 Mon Sep 17 00:00:00 2001 From: Sebastian Rettenberger Date: Fri, 8 Mar 2019 14:30:19 +0100 Subject: [PATCH 205/412] Fixes some issues with E741 detection --- pycodestyle.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index cad68eaf..ab99fdb6 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1423,28 +1423,55 @@ def ambiguous_identifier(logical_line, tokens): Variables can be bound in several other contexts, including class and function definitions, 'global' and 'nonlocal' statements, - exception handlers, and 'with' statements. + exception handlers, and 'with' and 'for' statements. + In addition, we have a special handling for function parameters. Okay: except AttributeError as o: Okay: with lock as L: + Okay: foo(l=12) + Okay: for a in foo(l=12): E741: except AttributeError as O: E741: with lock as l: E741: global I E741: nonlocal l + E741: def foo(l): + E741: def foo(l=12): + E741: l = foo(l=12) + E741: for l in range(10): E742: class I(object): E743: def l(x): """ + is_func_def = False # Set to true if 'def' is found + parameter_parentheses_level = 0 idents_to_avoid = ('l', 'O', 'I') prev_type, prev_text, prev_start, prev_end, __ = tokens[0] for token_type, text, start, end, line in tokens[1:]: ident = pos = None + # find function definitions + if prev_text == 'def': + is_func_def = True + # update parameter parentheses level + if parameter_parentheses_level == 0 and prev_type == tokenize.NAME and \ + token_type == tokenize.OP and text == '(': + parameter_parentheses_level = 1 + elif parameter_parentheses_level > 0 and token_type == tokenize.OP: + if text == '(': + parameter_parentheses_level += 1 + elif text == ')': + parameter_parentheses_level -= 1 # identifiers on the lhs of an assignment operator - if token_type == tokenize.OP and '=' in text: + if token_type == tokenize.OP and '=' in text and \ + parameter_parentheses_level == 0: if prev_text in idents_to_avoid: ident = prev_text pos = prev_start - # identifiers bound to values with 'as', 'global', or 'nonlocal' - if prev_text in ('as', 'global', 'nonlocal'): + # identifiers bound to values with 'as', 'for', 'global', or 'nonlocal' + if prev_text in ('as', 'for', 'global', 'nonlocal'): + if text in idents_to_avoid: + ident = text + pos = start + # function parameter definitions + if is_func_def: if text in idents_to_avoid: ident = text pos = start @@ -1456,6 +1483,7 @@ def ambiguous_identifier(logical_line, tokens): yield start, "E743 ambiguous function definition '%s'" % text if ident: yield pos, "E741 ambiguous variable name '%s'" % ident + prev_type = token_type prev_text = text prev_start = start From e5cdc22a29bf0c4abe30c152f81124e4b7a5dad7 Mon Sep 17 00:00:00 2001 From: Sebastian Rettenberger Date: Fri, 8 Mar 2019 14:38:55 +0100 Subject: [PATCH 206/412] Add lines breaks --- pycodestyle.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index ab99fdb6..c2af1306 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1451,10 +1451,12 @@ def ambiguous_identifier(logical_line, tokens): if prev_text == 'def': is_func_def = True # update parameter parentheses level - if parameter_parentheses_level == 0 and prev_type == tokenize.NAME and \ + if parameter_parentheses_level == 0 and \ + prev_type == tokenize.NAME and \ token_type == tokenize.OP and text == '(': parameter_parentheses_level = 1 - elif parameter_parentheses_level > 0 and token_type == tokenize.OP: + elif parameter_parentheses_level > 0 and \ + token_type == tokenize.OP: if text == '(': parameter_parentheses_level += 1 elif text == ')': From 3cfd527929fc5dfcdc13a0edf6a6d13f5eb76fcb Mon Sep 17 00:00:00 2001 From: Sebastian Rettenberger Date: Fri, 8 Mar 2019 14:41:51 +0100 Subject: [PATCH 207/412] Add line breaks in comment --- pycodestyle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index c2af1306..03696b7a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1467,7 +1467,8 @@ def ambiguous_identifier(logical_line, tokens): if prev_text in idents_to_avoid: ident = prev_text pos = prev_start - # identifiers bound to values with 'as', 'for', 'global', or 'nonlocal' + # identifiers bound to values with 'as', 'for', + # 'global', or 'nonlocal' if prev_text in ('as', 'for', 'global', 'nonlocal'): if text in idents_to_avoid: ident = text From 7493e0a0e76f02640b6ff0f7cb1619747101a1c6 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 26 Mar 2019 18:01:22 +0100 Subject: [PATCH 208/412] Fix E302 false negative in presence of decorators. --- pycodestyle.py | 13 +++++++------ testsuite/E30.py | 8 ++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 03696b7a..ec6b894d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -358,14 +358,15 @@ 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. 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. + # 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 (expand_indent(prev_line) <= indent_level and + if (not logical_line.startswith("@") and + expand_indent(prev_line) <= indent_level and expand_indent(next_line) <= indent_level): return if indent_level: diff --git a/testsuite/E30.py b/testsuite/E30.py index 320b2a1b..15fbdf31 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -181,3 +181,11 @@ def foo(): def bar(): pass def baz(): pass +#: E302:5:1 +def f(): + pass + +# wat +@hi +def g(): + pass From b34b79664ccec08ea419aa44037c1b0d2815611b Mon Sep 17 00:00:00 2001 From: Nikita S Date: Tue, 2 Jul 2019 01:59:10 +0300 Subject: [PATCH 209/412] Added support for PEP 570 (#867) --- pycodestyle.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index ec6b894d..0c043fcd 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -855,6 +855,10 @@ def missing_whitespace_around_operator(logical_line, tokens): # Tolerate the "<>" operator, even if running Python 3 # Deal with Python 3's annotated return value "->" pass + elif prev_text == '/' and text == ',': + # Tolerate the "/" operator in function definition + # For more info see PEP570source + pass else: if need_space is True or need_space[1]: # A needed trailing space was not found From 4fea946aa5b66d4b39ebe52bd91e84b0ed5b5d54 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 11 Jul 2019 08:15:52 -0700 Subject: [PATCH 210/412] Ellipsis is not a binary operator --- pycodestyle.py | 5 ++++- testsuite/python3.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index ec6b894d..4ade0871 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1205,13 +1205,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/python3.py b/testsuite/python3.py index be7c58f4..709695af 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 = ... +): + ... From dedd237499bc90ab61e9df52ca907e3ce4547e4b Mon Sep 17 00:00:00 2001 From: FichteFoll Date: Wed, 31 Jul 2019 15:55:42 +0200 Subject: [PATCH 211/412] Add support for assignment expressions Introduced in Python 3.8 with PEP-572. Refer to https://www.python.org/dev/peps/pep-0572. --- pycodestyle.py | 17 ++++++++++++----- testsuite/python38.py | 12 ++++++++++++ tox.ini | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 testsuite/python38.py diff --git a/pycodestyle.py b/pycodestyle.py index 4ade0871..805ebf51 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -117,11 +117,13 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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 @@ -1141,7 +1146,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() diff --git a/testsuite/python38.py b/testsuite/python38.py new file mode 100644 index 00000000..59addad4 --- /dev/null +++ b/testsuite/python38.py @@ -0,0 +1,12 @@ +#: 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 742888c2..fc42fc1a 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 From dd6268c488ec6ab23188f31f7a1d345a8e114cbe Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Wed, 25 Sep 2019 18:08:10 -0400 Subject: [PATCH 212/412] E402: Add "elif" statement to allowed keywords --- pycodestyle.py | 2 +- testsuite/E40.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 68d41401..e339d72b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1068,7 +1068,7 @@ def is_string_literal(line): return line and (line[0] == '"' or line[0] == "'") allowed_keywords = ( - 'try', 'except', 'else', 'finally', 'with', 'if') + 'try', 'except', 'else', 'finally', 'with', 'if', 'elif') if indent_level: # Allow imports in conditional statement/function return diff --git a/testsuite/E40.py b/testsuite/E40.py index 041ffb39..6c71fa2b 100644 --- a/testsuite/E40.py +++ b/testsuite/E40.py @@ -43,6 +43,10 @@ #: Okay if False: import foo +elif not True: + import bar +else: + import mwahaha import bar #: E402 From 16ec89791f05f9e6a2eb59ae2c778b6439933e86 Mon Sep 17 00:00:00 2001 From: FichteFoll Date: Wed, 31 Jul 2019 16:02:04 +0200 Subject: [PATCH 213/412] Run CI tests on Python 3.8 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index ce324bef..696c1c0e 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 From ad86a1b818eee307c9fd558c9d7948f20d6fef71 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 5 Dec 2019 12:33:18 +0300 Subject: [PATCH 214/412] Adds matmul operator support, fixes #768 --- pycodestyle.py | 2 +- testsuite/E22.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 6d3b554c..fc02c008 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -113,7 +113,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. SINGLETONS = frozenset(['False', 'None', 'True']) KEYWORDS = frozenset(keyword.kwlist + ['print', 'async']) - SINGLETONS UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) -ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-']) +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 [] diff --git a/testsuite/E22.py b/testsuite/E22.py index 4974cb35..7ea27927 100644 --- a/testsuite/E22.py +++ b/testsuite/E22.py @@ -84,6 +84,10 @@ 1is 1 #: E225 1in [] +#: E225 +i = 1 @2 +#: E225 +i = 1@ 2 #: E225 E226 i=i+1 #: E225 E226 @@ -150,6 +154,7 @@ def halves(n): print >>sys.stderr, "x is out of range." print >> sys.stdout, "x is an integer." x = x / 2 - 1 +x = 1 @ 2 if alpha[:-i]: *a, b = (1, 2, 3) From 9bf58dfc227ac98006bb22c4a570f98e8a0713e0 Mon Sep 17 00:00:00 2001 From: Nikita Serba Date: Wed, 22 Jan 2020 20:17:36 +0200 Subject: [PATCH 215/412] Create python38 --- testsuite/python38 | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 testsuite/python38 diff --git a/testsuite/python38 b/testsuite/python38 new file mode 100644 index 00000000..adf217dc --- /dev/null +++ b/testsuite/python38 @@ -0,0 +1,3 @@ +#: Okay +def f(a, /, b): + pass From ce2fcd3cd4cebdf7eb320d037695883cd046b96f Mon Sep 17 00:00:00 2001 From: Nikita Serba Date: Wed, 22 Jan 2020 20:21:49 +0200 Subject: [PATCH 216/412] Typo fix (rename python38 to python38.py) --- testsuite/{python38 => python38.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename testsuite/{python38 => python38.py} (100%) diff --git a/testsuite/python38 b/testsuite/python38.py similarity index 100% rename from testsuite/python38 rename to testsuite/python38.py From 2f0ca0b42660d7a6ee239f3a372a8783ad20c37a Mon Sep 17 00:00:00 2001 From: Nikita Serba Date: Wed, 22 Jan 2020 20:22:25 +0200 Subject: [PATCH 217/412] Update pycodestyle.py Co-Authored-By: Anthony Sottile --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0c043fcd..7b5b4f8e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -857,7 +857,7 @@ def missing_whitespace_around_operator(logical_line, tokens): pass elif prev_text == '/' and text == ',': # Tolerate the "/" operator in function definition - # For more info see PEP570source + # For more info see PEP570 pass else: if need_space is True or need_space[1]: From 9184238b5668829759babcef61a70b2a6d433162 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 26 Feb 2020 00:44:36 +0100 Subject: [PATCH 218/412] Correctly allow *two* blank lines after a block of one-liners. Previously ``` def oneliner(): pass def otheroneliner(): pass def really_long_func(): with_some_contents ``` would raise an "E302: expected 2 blank lines, found zero" at the last line of the one liner. Ultimately, this is due to `expand_indent` being passed a line whose contents are just a newline and nothing else, and `expand_indent` thinking that the line is indented by 1 character (the newline), which is wrong. Fix that by just stripping the newline, and modify a test to cover this case. --- pycodestyle.py | 1 + testsuite/E30not.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index 705ba25a..0d0789d9 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1794,6 +1794,7 @@ def expand_indent(line): >>> expand_indent(' \t') 16 """ + line = line.rstrip('\n\r') if '\t' not in line: return len(line) - len(line.lstrip()) result = 0 diff --git a/testsuite/E30not.py b/testsuite/E30not.py index ef795a8e..ba0f742f 100644 --- a/testsuite/E30not.py +++ b/testsuite/E30not.py @@ -167,6 +167,10 @@ def foo(x): # for no E30x being emitted. def bar(): pass def baz(): pass + + +def main(): + pass #: E704:4:5 E704:5:5 def foo(): # This emits the (ignored-by-default) E704, but here we're testing From b36fba61bdcbfe336afe53838eb85dde530e6f69 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 26 Feb 2020 00:33:24 +0100 Subject: [PATCH 219/412] Support visual indent of continuation lines after with/assert/raise. "with" is likely the most common case, and this indentation is explicitly given as example by PEP8 (under "maximum line length"). --- pycodestyle.py | 3 +++ testsuite/E12.py | 24 +++++++++++++++++++++++- testsuite/E12not.py | 20 ++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 705ba25a..b9a37f07 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -695,6 +695,9 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, elif (token_type in (tokenize.STRING, tokenize.COMMENT) or text in ('u', 'ur', 'b', 'br')): indent_chances[start[1]] = str + # visual indent after assert/raise/with + elif not row and not depth and text in ["assert", "raise", "with"]: + indent_chances[end[1] + 1] = True # special case for the "if" statement because len("if (") == 4 elif not indent_chances and not row and not depth and text == 'if': indent_chances[end[1] + 1] = True diff --git a/testsuite/E12.py b/testsuite/E12.py index acdd81f6..968382c3 100644 --- a/testsuite/E12.py +++ b/testsuite/E12.py @@ -372,4 +372,26 @@ def example_issue254(): print(a , end=' ') -#: +#: E127:4:12 +def foo(): + pass + raise 123 + \ + 123 +#: E127:4:13 +class Eggs: + pass + assert 123456 == \ + 123456 +#: E127:4:11 +def f1(): + print('foo') + with open('/path/to/some/file/you/want/to/read') as file_1, \ + open('/path/to/some/file/being/written', 'w') as file_2: + file_2.write(file_1.read()) +#: E127:5:11 +def f1(): + print('foo') + with open('/path/to/some/file/you/want/to/read') as file_1, \ + open('/path/to/some/file/being/written', 'w') as file_2, \ + open('later-misindent'): + file_2.write(file_1.read()) diff --git a/testsuite/E12not.py b/testsuite/E12not.py index ebaa078f..2e2366c2 100644 --- a/testsuite/E12not.py +++ b/testsuite/E12not.py @@ -639,3 +639,23 @@ def other_example(): # more stuff ) ) + + +def foo(): + pass + raise 123 + \ + 123 + + +class Eggs: + pass + assert 123456 == \ + 123456 + + +def f1(): + print('foo') + with open('/path/to/some/file/you/want/to/read') as file_1, \ + open('/path/to/some/file/being/written', 'w') as file_2, \ + open('just-making-sure-more-continuations-also-work'): + file_2.write(file_1.read()) From e7abf262b6ffb992bf53bb8e33e213d77928d64a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 23 Mar 2020 10:01:28 -0700 Subject: [PATCH 220/412] Fix E225 for PEP 570 all positional-only arguments --- pycodestyle.py | 11 ++++++++++- testsuite/python38.py | 14 +++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 235532c7..7629429a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -863,7 +863,16 @@ def missing_whitespace_around_operator(logical_line, tokens): # Tolerate the "<>" operator, even if running Python 3 # Deal with Python 3's annotated return value "->" pass - elif prev_text == '/' and text == ',': + elif ( + # def f(a, /, b): + # ^ + # def f(a, b, /): + # ^ + prev_text == '/' and text in {',', ')'} or + # def f(a, b, /): + # ^ + prev_text == ')' and text == ':' + ): # Tolerate the "/" operator in function definition # For more info see PEP570 pass diff --git a/testsuite/python38.py b/testsuite/python38.py index 5f62a3f1..57ee6139 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -1,5 +1,17 @@ #: Okay -def f(a, /, b): +def f1(a, /, b): + pass + + +def f2(a, b, /): + pass + + +def f3( + a, + /, + b, +): pass #: Okay if x := 1: From 7e3655b9655ed8a13daa65c6cd2f38a06bfa5469 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 23 Mar 2020 10:51:41 -0700 Subject: [PATCH 221/412] Allow N-and-fewer blank lines before the first top level thing --- pycodestyle.py | 2 +- testsuite/E30.py | 5 ----- testsuite/E30not.py | 10 ++++++++++ testsuite/test_blank_lines.py | 4 +--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 235532c7..d2e93370 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -350,7 +350,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, top_level_lines = BLANK_LINES_CONFIG['top_level'] method_lines = BLANK_LINES_CONFIG['method'] - if line_number < top_level_lines + 1 and not previous_logical: + if not previous_logical and blank_before < top_level_lines: return # Don't expect blank lines before the first line if previous_logical.startswith('@'): if blank_lines: diff --git a/testsuite/E30.py b/testsuite/E30.py index 15fbdf31..8d1879bc 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -16,11 +16,6 @@ def b(): #: -#: E302:3:1 -#!python -# -*- coding: utf-8 -*- -def a(): - pass #: E302:2:1 """Main module.""" def _main(): diff --git a/testsuite/E30not.py b/testsuite/E30not.py index ba0f742f..a86b99ec 100644 --- a/testsuite/E30not.py +++ b/testsuite/E30not.py @@ -177,3 +177,13 @@ def foo(): # for no E30x being emitted. def bar(): pass def baz(): pass +#: Okay +#!python +# -*- coding: utf-8 -*- +def a(): + pass +#: Okay +def f( + a, +): + pass diff --git a/testsuite/test_blank_lines.py b/testsuite/test_blank_lines.py index 2b37ad1a..e239f8b7 100644 --- a/testsuite/test_blank_lines.py +++ b/testsuite/test_blank_lines.py @@ -139,7 +139,6 @@ class AFarEnoughClass(object): pass """) self.assertEqual([ - 'E302:4:1', # some_function 'E302:7:1', # another_function 'E302:14:1', # SomeCloseClass ], result) @@ -438,7 +437,7 @@ def some_function(): def test_top_level_fewer_blank_lines(self): """ - It will trigger an error when less 2 blank lines are found + It will trigger an error when less 3 blank lines are found before top level definitions. """ result = self.check("""# First comment line. @@ -471,7 +470,6 @@ class AFarEnoughClass(object): pass """) self.assertEqual([ - 'E302:5:1', # some_function 'E302:9:1', # another_function 'E302:17:1', # SomeCloseClass ], result) From 8e7b671e34e6ed5dd2d4bf1c98459b542ee5a396 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 24 Mar 2020 11:48:32 -0700 Subject: [PATCH 222/412] Instrument code coverage --- .coveragerc | 40 +++++++++++++++++++++++++++ .gitignore | 9 +++--- testsuite/test_all.py | 28 ------------------- testsuite/test_api.py | 64 +++++++++++++++++++------------------------ tox.ini | 17 ++++++------ 5 files changed, 82 insertions(+), 76 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..57fc88ec --- /dev/null +++ b/.coveragerc @@ -0,0 +1,40 @@ +[run] +source = . +branch = true +parallel = true +omit = + */.tox/* + */__main__.py + */setup.py + */venv*/* + # TODO: separate the tests from the test data + testsuite/E*.py + testsuite/W*.py + testsuite/latin-1.py + testsuite/noqa.py + testsuite/python*.py + testsuite/utf-8-bom.py + +[report] +show_missing = True +skip_covered = True +# TODO: increase this +fail_under = 90 +exclude_lines = + # a more strict default pragma + \# pragma: no cover\b + + # allow defensive code + ^\s*raise AssertionError\b + ^\s*raise NotImplementedError\b + ^\s*return NotImplemented\b + ^\s*raise$ + + # typing-related code + ^if (False|TYPE_CHECKING): + : \.\.\.$ + ^ +\.\.\.$ + -> ['"]?NoReturn['"]?: + + # non-runnable code + if __name__ == ['"]__main__['"]:$ diff --git a/.gitignore b/.gitignore index 2a40a535..a2848173 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ *.egg *.egg-info *.pyc -.tox -dist -docs/_build -build/ +/.coverage +/.tox +/build/ +/dist /venv*/ +docs/_build diff --git a/testsuite/test_all.py b/testsuite/test_all.py index 0e4bc7d1..35086743 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -45,31 +45,3 @@ def test_own_dog_food(self): report = self._style.check_files(files) self.assertEqual(list(report.messages.keys()), ['W504'], msg='Failures: %s' % report.messages) - - -def suite(): - from testsuite import ( - test_api, - test_blank_lines, - test_parser, - test_shell, - test_util, - ) - - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(PycodestyleTestCase)) - suite.addTest(unittest.makeSuite(test_api.APITestCase)) - suite.addTest(unittest.makeSuite(test_blank_lines.TestBlankLinesDefault)) - suite.addTest(unittest.makeSuite(test_blank_lines.TestBlankLinesTwisted)) - suite.addTest(unittest.makeSuite(test_parser.ParserTestCase)) - suite.addTest(unittest.makeSuite(test_shell.ShellTestCase)) - suite.addTest(unittest.makeSuite(test_util.UtilTestCase)) - return suite - - -def _main(): - return unittest.TextTestRunner(verbosity=2).run(suite()) - - -if __name__ == '__main__': - sys.exit(not _main()) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index c69e54d2..ad960749 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -43,8 +43,7 @@ def reset(self): def test_register_physical_check(self): def check_dummy(physical_line, line_number): - if False: - yield + raise NotImplementedError pycodestyle.register_check(check_dummy, ['Z001']) self.assertTrue(check_dummy in pycodestyle._checks['physical_line']) @@ -53,13 +52,12 @@ def check_dummy(physical_line, line_number): self.assertEqual(args, ['physical_line', 'line_number']) options = pycodestyle.StyleGuide().options - self.assertTrue(any(func == check_dummy - for name, func, args in options.physical_checks)) + functions = [func for _, func, _ in options.physical_checks] + self.assertIn(check_dummy, functions) def test_register_logical_check(self): def check_dummy(logical_line, tokens): - if False: - yield + raise NotImplementedError pycodestyle.register_check(check_dummy, ['Z401']) self.assertTrue(check_dummy in pycodestyle._checks['logical_line']) @@ -74,8 +72,8 @@ def check_dummy(logical_line, tokens): self.assertEqual(args, ['logical_line', 'tokens']) options = pycodestyle.StyleGuide().options - self.assertTrue(any(func == check_dummy - for name, func, args in options.logical_checks)) + functions = [func for _, func, _ in options.logical_checks] + self.assertIn(check_dummy, functions) def test_register_ast_check(self): pycodestyle.register_check(DummyChecker, ['Z701']) @@ -86,17 +84,17 @@ def test_register_ast_check(self): self.assertTrue(args is None) options = pycodestyle.StyleGuide().options - self.assertTrue(any(cls == DummyChecker - for name, cls, args in options.ast_checks)) + classes = [cls for _, cls, _ in options.ast_checks] + self.assertIn(DummyChecker, classes) def test_register_invalid_check(self): class InvalidChecker(DummyChecker): def __init__(self, filename): - pass + raise NotImplementedError def check_dummy(logical, tokens): - if False: - yield + raise NotImplementedError + pycodestyle.register_check(InvalidChecker, ['Z741']) pycodestyle.register_check(check_dummy, ['Z441']) @@ -272,28 +270,28 @@ def test_styleguide_checks(self): # Do run E11 checks options = pycodestyle.StyleGuide().options - self.assertTrue(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + functions = [func for _, func, _ in options.logical_checks] + self.assertIn(pycodestyle.indentation, functions) options = pycodestyle.StyleGuide(select=['E']).options - self.assertTrue(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + functions = [func for _, func, _ in options.logical_checks] + self.assertIn(pycodestyle.indentation, functions) options = pycodestyle.StyleGuide(ignore=['W']).options - self.assertTrue(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + functions = [func for _, func, _ in options.logical_checks] + self.assertIn(pycodestyle.indentation, functions) options = pycodestyle.StyleGuide(ignore=['E12']).options - self.assertTrue(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + functions = [func for _, func, _ in options.logical_checks] + self.assertIn(pycodestyle.indentation, functions) # Do not run E11 checks options = pycodestyle.StyleGuide(select=['W']).options - self.assertFalse(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + functions = [func for _, func, _ in options.logical_checks] + self.assertNotIn(pycodestyle.indentation, functions) options = pycodestyle.StyleGuide(ignore=['E']).options - self.assertFalse(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + functions = [func for _, func, _ in options.logical_checks] + self.assertNotIn(pycodestyle.indentation, functions) options = pycodestyle.StyleGuide(ignore=['E11']).options - self.assertFalse(any(func == pycodestyle.indentation - for name, func, args in options.logical_checks)) + functions = [func for _, func, _ in options.logical_checks] + self.assertNotIn(pycodestyle.indentation, functions) def test_styleguide_init_report(self): style = pycodestyle.StyleGuide(paths=[E11]) @@ -327,9 +325,7 @@ def test_styleguide_check_files(self): def test_check_unicode(self): # Do not crash if lines are Unicode (Python 2.x) pycodestyle.register_check(DummyChecker, ['Z701']) - source = '#\n' - if hasattr(source, 'decode'): - source = source.decode('ascii') + source = u'#\n' pep8style = pycodestyle.StyleGuide() count_errors = pep8style.input_file('stdin', lines=[source]) @@ -345,13 +341,9 @@ def test_check_nullbytes(self): count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() - if 'SyntaxError' in stdout: - # PyPy 2.2 returns a SyntaxError - expected = "stdin:1:2: E901 SyntaxError" - elif 'ValueError' in stdout: - # Python 3.5. + if 'ValueError' in stdout: # pragma: no cover (python 3.5+) expected = "stdin:1:1: E901 ValueError" - else: + else: # pragma: no cover (< python3.5) expected = "stdin:1:1: E901 TypeError" self.assertTrue(stdout.startswith(expected), msg='Output %r does not start with %r' % diff --git a/tox.ini b/tox.ini index fc42fc1a..f4356a5d 100644 --- a/tox.ini +++ b/tox.ini @@ -5,19 +5,20 @@ [tox] envlist = py27, py34, py35, py36, py37, py38, pypy, pypy3, jython -skipsdist = True skip_missing_interpreters = True [testenv] +deps = coverage commands = - {envpython} setup.py install - {envpython} pycodestyle.py --max-doc-length=72 --testsuite testsuite - {envpython} pycodestyle.py --statistics pycodestyle.py - {envpython} pycodestyle.py --max-doc-length=72 --doctest - {envpython} setup.py test + python -m pycodestyle --statistics pycodestyle.py + coverage run -m pycodestyle --max-doc-length=72 --testsuite testsuite + coverage run -m pycodestyle --max-doc-length=72 --doctest + coverage run -m unittest discover testsuite -vv + coverage combine + coverage report [testenv:flake8] -deps = - flake8 +skip_install = true +deps = flake8 commands = flake8 pycodestyle.py From 05dc63c081fb1cd16923e01b1e4c9385f0bf87b9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 2 Apr 2020 20:55:50 -0700 Subject: [PATCH 223/412] 2.6.0a1 --- CHANGES.txt | 25 +++++++++++++++++++++++++ pycodestyle.py | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9cdbfdfe..aa2d181c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,31 @@ Changelog ========= +2.6.0a1 (2020-04-02) +-------------------- + +New checks: + +* E225: require whitespace around ``and`` ``in`` ``is`` and ``or``. PR #847. + +Changes: + +* E117: fix indentation using tabs by treating as 8-space indents. PR #837. +* E721: fix false positive with names containg ``istype``. PR #850. +* E741: allow ``l`` as a named argument in a function call. PR #853. +* E302: fix false-negative with decorated functions. PR #859. +* W504: ellipsis (``...``) is no longer treated as a binary operator. PR #875. +* E402: allow ``with``, ``if``, ``elif``, ``else`` to guard imports. PR #834. +* Add support for assignment expressions ``:=`` (PEP 572). PR #879. +* Add support for positional-only arguments ``/`` (PEP 570). PR #872, #918. +* Add support for python 3.8. +* Add support for matrix multiplication operator ``@`` (PEP 465). PR #897. +* Support visual indent for continuation lines for ``with`` / ``assert`` / + ``raise``. PR #912. +* E302: allow two blank lines after a block of one-liners. PR #913. +* E302: allow two-and-fewer newlines at the top of the file. PR #919. + + 2.5.0 (2019-01-29) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index b6a22f33..0ad37e23 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -78,7 +78,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.5.0' +__version__ = '2.6.0a1' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504' From fbe6704553bf132e4f90c2e01c01ca132cbb2fca Mon Sep 17 00:00:00 2001 From: jnozsc Date: Mon, 6 Apr 2020 22:43:53 -0700 Subject: [PATCH 224/412] setup.py: add py38 to document --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f02060f3..d7d01df8 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,7 @@ def get_long_description(): 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', From 742228c05fdd0a63acda3a26976f6d4fec49342c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 27 Apr 2020 20:55:02 -0700 Subject: [PATCH 225/412] re-allow decorated one-liners --- pycodestyle.py | 50 ++++++++++++++++++++++++++++++++++++--------- testsuite/E30not.py | 26 +++++++++++++++++++++++ 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0ad37e23..b8f12d40 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -314,6 +314,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 +395,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 a86b99ec..9c33236b 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 -*- From 0126ce47f640e131a28029e8d75f9768a547f2f4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 28 Apr 2020 16:43:57 -0700 Subject: [PATCH 226/412] E306: fix detection inside `async def` --- pycodestyle.py | 2 +- testsuite/E30.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0ad37e23..fe455488 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -383,7 +383,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, for line in lines[line_number - top_level_lines::-1]: if line.strip() and expand_indent(line) < ancestor_level: ancestor_level = expand_indent(line) - nested = line.lstrip().startswith('def ') + nested = STARTSWITH_DEF_REGEX.match(line.lstrip()) if nested or ancestor_level == 0: break if nested: diff --git a/testsuite/E30.py b/testsuite/E30.py index 8d1879bc..ebe4e9d2 100644 --- a/testsuite/E30.py +++ b/testsuite/E30.py @@ -124,6 +124,11 @@ def a(): x = 1 def b(): pass +#: E306:3:5 +async def a(): + x = 1 + def b(): + pass #: E306:3:5 E306:5:9 def a(): x = 2 From 5230110c339133b2da060ef3f5c04a55c40fc096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Susannah=20Klane=C4=8Dek?= Date: Fri, 1 May 2020 09:22:18 -0400 Subject: [PATCH 227/412] E714: fix chained is not Closes #767 --- pycodestyle.py | 3 ++- testsuite/E71.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index fe455488..b1fb8a74 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -140,7 +140,8 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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(not)\s+[^][)(}{ ]+\s+(in|is)' + r'\s+(?!not\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)) diff --git a/testsuite/E71.py b/testsuite/E71.py index 7464da9d..e7f88697 100644 --- a/testsuite/E71.py +++ b/testsuite/E71.py @@ -64,6 +64,9 @@ #: E714 if not X.B is Y: pass +#: E714 +if not X is Y is not Z: + pass # #: Okay @@ -79,6 +82,9 @@ 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 From 39895aae46a2a4b665fc85a3747f2fafd27441fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Susannah=20Klane=C4=8Dek?= Date: Fri, 1 May 2020 12:40:11 -0400 Subject: [PATCH 228/412] Handle --- pycodestyle.py | 4 ++-- testsuite/E71.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index b1fb8a74..d9682b8f 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -140,8 +140,8 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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)' - r'\s+(?!not\s)') +COMPARE_NEGATIVE_REGEX = re.compile(r'\b(? Date: Tue, 5 May 2020 18:48:08 +0300 Subject: [PATCH 229/412] Add Changes project URL Background info at https://github.com/pypa/warehouse/pull/7882#issue-412444446 --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index d7d01df8..12add3d5 100644 --- a/setup.py +++ b/setup.py @@ -64,4 +64,8 @@ def get_long_description(): 'Topic :: Software Development :: Libraries :: Python Modules', ], test_suite='testsuite.test_all.suite', + project_urls={ + 'Changes': + 'https://pycodestyle.pycqa.org/en/latest/developer.html#changes', + }, ) From 82b04165eb53f8e3320b492ddfd2b0c3ee5f70e1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 8 May 2020 07:32:59 -0700 Subject: [PATCH 230/412] 2.6.0 --- CHANGES.txt | 12 +++++++++++- pycodestyle.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index aa2d181c..86bf47be 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,17 @@ Changelog ========= -2.6.0a1 (2020-04-02) +2.6.0 (2020-05-08) +------------------ + +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/pycodestyle.py b/pycodestyle.py index 651edfb6..deb45395 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -78,7 +78,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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' From 0f42fed9996fe0fcc3c9acf3dfe95d5768bd7c34 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 11 May 2020 12:15:29 -0700 Subject: [PATCH 231/412] Bumped license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 30ea0572..72d9921c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Copyright © 2006-2009 Johann C. Rocholl Copyright © 2009-2014 Florent Xicluna -Copyright © 2014-2018 Ian Lee +Copyright © 2014-2020 Ian Lee Licensed under the terms of the Expat License From 4795a0edd451d3bffb5ecd5e0b9a073546b64068 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 11 May 2020 12:16:57 -0700 Subject: [PATCH 232/412] Added comment reminder of how to upload these days --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index d24a595d..d46e599c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ release: umask 022 && chmod -R a+rX . && python setup.py sdist bdist_wheel + # twine upload dist/* test : python pycodestyle.py --testsuite testsuite From 203041bb29f0eb987d03ceab44f047398f91f169 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 11 May 2020 12:20:15 -0700 Subject: [PATCH 233/412] Announce Anthony as core developer, tweak release date. --- CHANGES.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 86bf47be..bf90329f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,9 +1,13 @@ Changelog ========= -2.6.0 (2020-05-08) +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. From d414383860c483c57d1fafc12c630b46a5616d3c Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 11 May 2020 12:55:35 -0700 Subject: [PATCH 234/412] Simplified / cleaned up the Makefile to match Travis (just use tox) --- Makefile | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index d46e599c..263ea367 100644 --- a/Makefile +++ b/Makefile @@ -2,16 +2,5 @@ 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 From a317cd31f46ca7df910dd2a0a5162e569fbd8062 Mon Sep 17 00:00:00 2001 From: Sebastiaan Lokhorst Date: Sun, 24 May 2020 21:00:09 +0200 Subject: [PATCH 235/412] Update documentation URLs --- README.rst | 4 ++-- docs/index.rst | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 765d85bb..7f222ce0 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ pycodestyle (formerly called pep8) - Python style guide checker :alt: Build status .. image:: https://readthedocs.org/projects/pycodestyle/badge/?version=latest - :target: https://pycodestyle.readthedocs.io + :target: https://pycodestyle.pycqa.org :alt: Documentation Status .. image:: https://img.shields.io/pypi/wheel/pycodestyle.svg @@ -103,6 +103,6 @@ Or you can display how often each error was found:: Links ----- -* `Read the documentation `_ +* `Read the documentation `_ * `Fork me on GitHub `_ diff --git a/docs/index.rst b/docs/index.rst index 0764e9f7..8f68a410 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ Contents: API developer -* Online documentation: https://pycodestyle.readthedocs.io/ +* Online documentation: https://pycodestyle.pycqa.org/ * Source code and issue tracker: https://github.com/pycqa/pycodestyle diff --git a/setup.py b/setup.py index 12add3d5..1be22d32 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def get_long_description(): author_email='johann@rocholl.net', maintainer='Ian Lee', maintainer_email='IanLee1521@gmail.com', - url='https://pycodestyle.readthedocs.io/', + url='https://pycodestyle.pycqa.org/', license='Expat license', py_modules=['pycodestyle'], namespace_packages=[], From 11dd1c84879e476a4d3cb264d97bfea9bcfb30d6 Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Tue, 26 May 2020 17:58:59 +0300 Subject: [PATCH 236/412] Remove broken mention of testsuite from setup.py --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 1be22d32..1c117c27 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,6 @@ def get_long_description(): 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', ], - test_suite='testsuite.test_all.suite', project_urls={ 'Changes': 'https://pycodestyle.pycqa.org/en/latest/developer.html#changes', From 2ed2baa404d9a4711b9bcc3105683cbd8679d0f8 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 11 Sep 2020 13:07:38 -0700 Subject: [PATCH 237/412] fix skipping of physical checks when file does not end in newline --- pycodestyle.py | 19 ++++++++++++++----- testsuite/W29.py | 5 +++++ testsuite/W39.py | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index deb45395..88eb4d75 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -264,7 +264,7 @@ def trailing_blank_lines(physical_line, lines, line_number, total_lines): However the last line should end with a new line (warning W292). """ if line_number == total_lines: - stripped_last_line = physical_line.rstrip() + stripped_last_line = physical_line.rstrip('\r\n') if physical_line and not stripped_last_line: return 0, "W391 blank line at end of file" if stripped_last_line == physical_line: @@ -2125,21 +2125,30 @@ def generate_tokens(self): self.report_error(1, 0, 'E902 %s' % self._io_error, readlines) tokengen = tokenize.generate_tokens(self.readline) try: + prev_physical = '' for token in tokengen: if token[2][0] > self.total_lines: return self.noqa = token[4] and noqa(token[4]) - self.maybe_check_physical(token) + self.maybe_check_physical(token, prev_physical) yield token + prev_physical = token[4] except (SyntaxError, tokenize.TokenError): self.report_invalid_syntax() - def maybe_check_physical(self, token): + def maybe_check_physical(self, token, prev_physical): """If appropriate for token, check current physical line(s).""" # Called after every token, but act only on end of line. + + # a newline token ends a single physical line. if _is_eol_token(token): - # Obviously, a newline token ends a single physical line. - self.check_physical(token[4]) + # if the file does not end with a newline, the NEWLINE + # token is inserted by the parser, but it does not contain + # the previous physical line in `token[4]` + if token[4] == '': + self.check_physical(prev_physical) + else: + self.check_physical(token[4]) elif token[0] == tokenize.STRING and '\n' in token[1]: # Less obviously, a string that contains newlines is a # multiline string, either triple-quoted or with internal diff --git a/testsuite/W29.py b/testsuite/W29.py index 4050c2ff..e9ad5800 100644 --- a/testsuite/W29.py +++ b/testsuite/W29.py @@ -9,6 +9,11 @@ class Foo(object): #: W291:2:35 '''multiline string with trailing whitespace''' +#: W291 W292 noeol +x = 1 +#: W191 W292 noeol +if False: + pass # indented with tabs #: W292:1:36 noeol # This line doesn't have a linefeed #: W292:1:5 E225:1:2 noeol diff --git a/testsuite/W39.py b/testsuite/W39.py index 68f886be..25e05b05 100644 --- a/testsuite/W39.py +++ b/testsuite/W39.py @@ -5,7 +5,7 @@ # Two additional empty lines -#: W391:4:1 W293:3:1 W293:4:1 noeol +#: W292:4:5 W293:3:1 W293:4:1 noeol # The last lines contain space From 8dcb7573a2c3a469b2182831dc98fbb693fe0f6d Mon Sep 17 00:00:00 2001 From: JPeterMugaas Date: Wed, 4 Nov 2020 13:02:50 -0500 Subject: [PATCH 238/412] Fix tests to handle paths in Windows. NormalizePath works properly but the tests would still fail because NormalizePath would NOT work as the test results expected. --- testsuite/test_shell.py | 2 +- testsuite/test_util.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index 3878194b..059d8823 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -79,7 +79,7 @@ def test_check_simple(self): self.assertFalse(stderr) self.assertEqual(len(stdout), 24) for line, num, col in zip(stdout, (3, 6, 6, 9, 12), (3, 6, 6, 1, 5)): - path, x, y, msg = line.split(':') + path, x, y, msg = line.rsplit(':', 3) self.assertTrue(path.endswith(E11)) self.assertEqual(x, str(num)) self.assertEqual(y, str(col)) diff --git a/testsuite/test_util.py b/testsuite/test_util.py index 8eaba7ef..7eff16ae 100644 --- a/testsuite/test_util.py +++ b/testsuite/test_util.py @@ -18,6 +18,6 @@ def test_normalize_paths(self): self.assertEqual(normalize_paths('foo,bar'), ['foo', 'bar']) self.assertEqual(normalize_paths('foo, bar '), ['foo', 'bar']) self.assertEqual(normalize_paths('/foo/bar,baz/../bat'), - ['/foo/bar', cwd + '/bat']) + [os.path.realpath('/foo/bar'), cwd + '/bat']) self.assertEqual(normalize_paths(".pyc,\n build/*"), ['.pyc', cwd + '/build/*']) From a6e25e1f84220dccd984f7190a1e54806e009230 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 4 Nov 2020 16:20:42 -0800 Subject: [PATCH 239/412] use github actions instead of travis-ci --- .github/workflows/main.yml | 56 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 36 ------------------------ testsuite/test_util.py | 14 ++++++---- 3 files changed, 64 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..29f1bd35 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,56 @@ +name: main +on: + pull_request: + push: + branches: [master, 'test-me-*'] + tags: ['*'] + +jobs: + main: + strategy: + matrix: + include: + - os: windows-latest + py: 2.7 + toxenv: py + - os: windows-latest + py: 3.9 + toxenv: py + - os: ubuntu-latest + py: pypy2 + toxenv: py + - os: ubuntu-latest + py: pypy3 + toxenv: py + - os: ubuntu-latest + py: 2.7 + toxenv: py + - os: ubuntu-latest + py: 3.4 + toxenv: py + - os: ubuntu-latest + py: 3.5 + toxenv: py + - os: ubuntu-latest + py: 3.6 + toxenv: py + - os: ubuntu-latest + py: 3.7 + toxenv: py + - os: ubuntu-latest + py: 3.8 + toxenv: py + - os: ubuntu-latest + py: 3.9 + toxenv: py + - os: ubuntu-latest + py: 3.9 + toxenv: flake8 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.py }} + - run: pip install tox + - run: tox -e ${{ matrix.toxenv }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 696c1c0e..00000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -dist: xenial -language: python -cache: pip -sudo: false -install: - - pip install tox -script: tox -matrix: - include: - - python: 2.7 - env: TOXENV=py27 - - python: 3.4 - env: TOXENV=py34 - - python: 3.5 - env: TOXENV=py35 - - python: 3.6 - 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 - env: TOXENV=pypy3 - - python: 3.5 - env: TOXENV=flake8 - -notifications: - email: - - IanLee1521@gmail.com - irc: - channels: - - "irc.freenode.org##python-code-quality" - use_notice: true - skip_join: true diff --git a/testsuite/test_util.py b/testsuite/test_util.py index 7eff16ae..075b163c 100644 --- a/testsuite/test_util.py +++ b/testsuite/test_util.py @@ -8,8 +8,6 @@ class UtilTestCase(unittest.TestCase): def test_normalize_paths(self): - cwd = os.getcwd() - self.assertEqual(normalize_paths(''), []) self.assertEqual(normalize_paths([]), []) self.assertEqual(normalize_paths(None), []) @@ -17,7 +15,11 @@ def test_normalize_paths(self): self.assertEqual(normalize_paths('foo'), ['foo']) self.assertEqual(normalize_paths('foo,bar'), ['foo', 'bar']) self.assertEqual(normalize_paths('foo, bar '), ['foo', 'bar']) - self.assertEqual(normalize_paths('/foo/bar,baz/../bat'), - [os.path.realpath('/foo/bar'), cwd + '/bat']) - self.assertEqual(normalize_paths(".pyc,\n build/*"), - ['.pyc', cwd + '/build/*']) + self.assertEqual( + normalize_paths('/foo/bar,baz/../bat'), + [os.path.realpath('/foo/bar'), os.path.abspath('bat')], + ) + self.assertEqual( + normalize_paths(".pyc,\n build/*"), + ['.pyc', os.path.abspath('build/*')], + ) From c060b1c1f46b126a5f0c429701f30f2e7e800815 Mon Sep 17 00:00:00 2001 From: Jason Ansel Date: Sat, 8 Jun 2013 22:29:52 +0000 Subject: [PATCH 240/412] Support for space indents with size other than 4 This is an updated revision of pull requests #206 and #524 --- docs/intro.rst | 1 + pycodestyle.py | 40 ++++++++++++++++++++++++----------- testsuite/test_api.py | 49 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 991db47b..7945053d 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -159,6 +159,7 @@ Quick help is available on the command line:: --max-line-length=n set maximum allowed line length (default: 79) --max-doc-length=n set maximum allowed doc line length and perform these checks (unchecked if not set) + --indent-size=n set how many spaces make up an indent (default: 4) --hang-closing hang closing bracket instead of matching indentation of opening bracket's line --format=format set the error format [default|pylint|] diff --git a/pycodestyle.py b/pycodestyle.py index 88eb4d75..17fac7a8 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -104,6 +104,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 'method': 1, } MAX_DOC_LENGTH = 72 +INDENT_SIZE = 4 REPORT_FORMAT = { 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s', 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s', @@ -543,8 +544,9 @@ def missing_whitespace(logical_line): @register_check def indentation(logical_line, previous_logical, indent_char, - indent_level, previous_indent_level): - r"""Use 4 spaces per indentation level. + indent_level, previous_indent_level, + indent_size, indent_size_str): + r"""Use indent_size (PEP8 says 4) spaces per indentation level. For really old code that you don't want to mess up, you can continue to use 8-space tabs. @@ -564,8 +566,11 @@ def indentation(logical_line, previous_logical, indent_char, """ c = 0 if logical_line else 3 tmpl = "E11%d %s" if logical_line else "E11%d %s (comment)" - if indent_level % 4: - yield 0, tmpl % (1 + c, "indentation is not a multiple of four") + if indent_level % indent_size: + yield 0, tmpl % ( + 1 + c, + "indentation is not a multiple of " + indent_size_str, + ) indent_expect = previous_logical.endswith(':') if indent_expect and indent_level <= previous_indent_level: yield 0, tmpl % (2 + c, "expected an indented block") @@ -581,7 +586,8 @@ def indentation(logical_line, previous_logical, indent_char, @register_check def continued_indentation(logical_line, tokens, indent_level, hang_closing, - indent_char, noqa, verbose): + indent_char, indent_size, indent_size_str, noqa, + verbose): r"""Continuation lines indentation. Continuation lines should align wrapped elements either vertically @@ -620,7 +626,8 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, indent_next = logical_line.endswith(':') row = depth = 0 - valid_hangs = (4,) if indent_char != '\t' else (4, 8) + valid_hangs = (indent_size,) if indent_char != '\t' \ + else (indent_size, indent_size * 2) # remember how many brackets were opened on each line parens = [0] * nrows # relative indents of physical lines @@ -685,7 +692,8 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, # visual indent is broken yield (start, "E128 continuation line " "under-indented for visual indent") - elif hanging_indent or (indent_next and rel_indent[row] == 8): + elif hanging_indent or (indent_next and + rel_indent[row] == 2 * indent_size): # hanging indent is verified if close_bracket and not hang_closing: yield (start, "E123 closing bracket does not match " @@ -708,7 +716,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, error = "E131", "unaligned for hanging indent" else: hangs[depth] = hang - if hang > 4: + if hang > indent_size: error = "E126", "over-indented for hanging indent" else: error = "E121", "under-indented for hanging indent" @@ -775,8 +783,8 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if last_token_multiline: rel_indent[end[0] - first_row] = rel_indent[row] - if indent_next and expand_indent(line) == indent_level + 4: - pos = (start[0], indent[0] + 4) + if indent_next and expand_indent(line) == indent_level + indent_size: + pos = (start[0], indent[0] + indent_size) if visual_indent: code = "E129 visually indented line" else: @@ -1960,8 +1968,12 @@ def __init__(self, filename=None, lines=None, self._ast_checks = options.ast_checks self.max_line_length = options.max_line_length self.max_doc_length = options.max_doc_length + self.indent_size = options.indent_size self.multiline = False # in a multiline string? self.hang_closing = options.hang_closing + self.indent_size = options.indent_size + self.indent_size_str = ({2: 'two', 4: 'four', 8: 'eight'} + .get(self.indent_size, str(self.indent_size))) self.verbose = options.verbose self.filename = filename # Dictionary where a checker can store its custom state. @@ -2528,8 +2540,8 @@ def get_parser(prog='pycodestyle', version=__version__): usage="%prog [options] input ...") parser.config_options = [ 'exclude', 'filename', 'select', 'ignore', 'max-line-length', - 'max-doc-length', 'hang-closing', 'count', 'format', 'quiet', - 'show-pep8', 'show-source', 'statistics', 'verbose'] + 'max-doc-length', 'indent-size', 'hang-closing', 'count', 'format', + 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose'] parser.add_option('-v', '--verbose', default=0, action='count', help="print status messages, or debug with -vv") parser.add_option('-q', '--quiet', default=0, action='count', @@ -2569,6 +2581,10 @@ def get_parser(prog='pycodestyle', version=__version__): default=None, help="set maximum allowed doc line length and perform " "these checks (unchecked if not set)") + parser.add_option('--indent-size', type='int', metavar='n', + default=INDENT_SIZE, + help="set how many spaces make up an indent " + "(default: %default)") parser.add_option('--hang-closing', action='store_true', help="hang closing bracket instead of matching " "indentation of opening bracket's line") diff --git a/testsuite/test_api.py b/testsuite/test_api.py index ad960749..ce2d33a9 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -391,3 +391,52 @@ def test_styleguide_continuation_line_outdented(self): # TODO: runner # TODO: input_file + + def test_styleguides_other_indent_size(self): + pycodestyle.register_check(DummyChecker, ['Z701']) + lines = [ + 'def foo():\n', + ' pass\n', + '\n', + '\n', + 'def foo_correct():\n', + ' pass\n', + '\n', + '\n', + 'def bar():\n', + ' [1, 2, 3,\n', + ' 4, 5, 6,\n', + ' ]\n', + '\n', + '\n', + 'if (1 in [1, 2, 3]\n', + ' and bool(0) is False\n', + ' and bool(1) is True):\n', + ' pass\n' + ] + + pep8style = pycodestyle.StyleGuide() + pep8style.options.indent_size = 3 + count_errors = pep8style.input_file('stdin', lines=lines) + stdout = sys.stdout.getvalue() + self.assertEqual(count_errors, 4) + expected = ( + 'stdin:2:5: ' + 'E111 indentation is not a multiple of 3' + ) + self.assertTrue(expected in stdout) + expected = ( + 'stdin:11:6: ' + 'E127 continuation line over-indented for visual indent' + ) + self.assertTrue(expected in stdout) + expected = ( + 'stdin:12:6: ' + 'E124 closing bracket does not match visual indentation' + ) + self.assertTrue(expected in stdout) + expected = ( + 'stdin:17:6: ' + 'E127 continuation line over-indented for visual indent' + ) + self.assertTrue(expected in stdout) From dde53f69f11571161c1db7c43ebfc3cfdfcc6f96 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 7 Jan 2021 08:16:57 -0800 Subject: [PATCH 241/412] use tokenize.open to avoid incorrect line endings at beginning of file --- pycodestyle.py | 6 ++---- testsuite/crlf.py | 3 +++ 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 testsuite/crlf.py diff --git a/pycodestyle.py b/pycodestyle.py index 17fac7a8..2f4313ff 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1814,10 +1814,8 @@ def readlines(filename): def readlines(filename): """Read the source code.""" try: - with open(filename, 'rb') as f: - (coding, lines) = tokenize.detect_encoding(f.readline) - f = TextIOWrapper(f, coding, line_buffering=True) - return [line.decode(coding) for line in lines] + f.readlines() + with tokenize.open(filename) as f: + return f.readlines() except (LookupError, SyntaxError, UnicodeError): # Fall back if file encoding is improperly declared with open(filename, encoding='latin-1') as f: diff --git a/testsuite/crlf.py b/testsuite/crlf.py new file mode 100644 index 00000000..41f0d0fc --- /dev/null +++ b/testsuite/crlf.py @@ -0,0 +1,3 @@ +'''\ +test +''' From 62003f65573947f2620b97c0f8cb79c22ac68b25 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 7 Jan 2021 17:28:44 -0800 Subject: [PATCH 242/412] 2.7.0 --- CHANGES.txt | 10 ++++++++++ pycodestyle.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index bf90329f..35523b56 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,16 @@ Changelog ========= +2.7.0 (2021-03-14) +------------------ + +Changes: + +* Fix physical checks (such as W191) at end of file. PR #961. +* Add ``--indent-size`` option (defaulting to ``4``). PR #970. +* W605: fix escaped crlf false positive on windows. PR #976. + + 2.6.0 (2020-05-11) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index 2f4313ff..f5d2f896 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -78,7 +78,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.6.0' +__version__ = '2.7.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504' From 9da39388168641582ea44c177a3259a6327f8eeb Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 16 Mar 2021 07:51:22 -0700 Subject: [PATCH 243/412] drop python 3.4 github actions no longer supports python 3.4 --- .github/workflows/main.yml | 3 --- CONTRIBUTING.rst | 2 +- README.rst | 4 ++-- docs/developer.rst | 8 ++++---- setup.py | 3 +-- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 29f1bd35..4624d216 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,9 +25,6 @@ jobs: - os: ubuntu-latest py: 2.7 toxenv: py - - os: ubuntu-latest - py: 3.4 - toxenv: py - os: ubuntu-latest py: 3.5 toxenv: py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 4cdd2132..5515108e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -55,7 +55,7 @@ Step 3: Run tests Before creating a pull request you should run the tests to make sure that the changes that have been made haven't caused any regressions in functionality. -To run the tests, the core developer team and Travis-CI use `tox`_:: +To run the tests, the core developer team and GitHub Actions use `tox`_:: $ pip install -r dev-requirements.txt $ tox diff --git a/README.rst b/README.rst index 7f222ce0..c71b933e 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ pycodestyle (formerly called pep8) - Python style guide checker =============================================================== -.. image:: https://img.shields.io/travis/PyCQA/pycodestyle.svg - :target: https://travis-ci.org/PyCQA/pycodestyle +.. image:: https://github.com/PyCQA/pycodestyle/actions/workflows/main.yml/badge.svg + :target: https://github.com/PyCQA/pycodestyle/actions/workflows/main.yml :alt: Build status .. image:: https://readthedocs.org/projects/pycodestyle/badge/?version=latest diff --git a/docs/developer.rst b/docs/developer.rst index 080d0807..74e3ede7 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -13,9 +13,9 @@ conditions of the :ref:`Expat license `. Fork away! * `Source code `_ and `issue tracker `_ on GitHub. -* `Continuous tests `_ against Python - 2.7 and 3.4+ as well as the nightly Python build and PyPy, on `Travis CI - platform `_. +* `Continuous tests `_ against + Python 2.7 and 3.5+ as well as the nightly Python build and PyPy, on GitHub + Actions. .. _available on GitHub: https://github.com/pycqa/pycodestyle @@ -103,7 +103,7 @@ Then be sure to pass the tests:: When contributing to pycodestyle, please observe our `Code of Conduct`_. -To run the tests, the core developer team and Travis CI use tox:: +To run the tests, the core developer team and GitHub Actions use tox:: $ pip install -r dev-requirements.txt $ tox diff --git a/setup.py b/setup.py index 1c117c27..148751bc 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ def get_long_description(): namespace_packages=[], include_package_data=True, zip_safe=False, - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', install_requires=[ # Broken with Python 3: https://github.com/pypa/pip/issues/650 # 'setuptools', @@ -54,7 +54,6 @@ def get_long_description(): 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', From 37e96aa5355e85e60c1003b1e85f6e29bcb8e20b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 1 Apr 2021 12:30:03 -0700 Subject: [PATCH 244/412] detect multiple E712 in a line --- pycodestyle.py | 6 ++++-- testsuite/E71.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index f5d2f896..c0916395 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1393,8 +1393,10 @@ def comparison_to_singleton(logical_line, noqa): None was set to some other value. The other value might have a type (such as a container) that could be false in a boolean context! """ - match = not noqa and COMPARE_SINGLETON_REGEX.search(logical_line) - if match: + if noqa: + return + + for match in COMPARE_SINGLETON_REGEX.finditer(logical_line): singleton = match.group(1) or match.group(3) same = (match.group(2) == '==') diff --git a/testsuite/E71.py b/testsuite/E71.py index abf4e7a8..b4b53afc 100644 --- a/testsuite/E71.py +++ b/testsuite/E71.py @@ -42,6 +42,8 @@ #: E712 if res[1] != False: pass +#: E712 E712 +var = 1 if cond == True else -1 if cond == False else cond # #: E713 From e2ebb356246615e1749a5366592e4523bb8752ec Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 2 May 2021 18:41:16 +0200 Subject: [PATCH 245/412] Fix false-positive E211 with match and case --- pycodestyle.py | 10 +++++++--- testsuite/python310.py | 12 ++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 testsuite/python310.py diff --git a/pycodestyle.py b/pycodestyle.py index c0916395..8124f9dc 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -811,14 +811,18 @@ def whitespace_before_parameters(logical_line, tokens): prev_type, prev_text, __, prev_end, __ = tokens[0] for index in range(1, len(tokens)): token_type, text, start, end, __ = tokens[index] - if (token_type == tokenize.OP and + if ( + token_type == tokenize.OP and text in '([' and start != prev_end and (prev_type == tokenize.NAME or prev_text in '}])') and # Syntax "class A (B):" is allowed, but avoid it (index < 2 or tokens[index - 2][1] != 'class') and - # Allow "return (a.foo for a in range(5))" - not keyword.iskeyword(prev_text)): + # Allow "return (a.foo for a in range(5))" + not keyword.iskeyword(prev_text) and + # 'match' and 'case' are only soft keywords + prev_text not in ('match', 'case') + ): yield prev_end, "E211 whitespace before '%s'" % text prev_type = token_type prev_text = text diff --git a/testsuite/python310.py b/testsuite/python310.py new file mode 100644 index 00000000..b72bf6d8 --- /dev/null +++ b/testsuite/python310.py @@ -0,0 +1,12 @@ +var, var2 = 1, 2 +#: Okay +match (var, var2): + #: Okay + case [2, 3]: + pass + #: Okay + case (1, 2): + pass + #: Okay + case _: + print("Default") From 4cca9a535cea3fd858636120c07502ad37a46803 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 2 May 2021 21:40:08 +0200 Subject: [PATCH 246/412] Changes after review --- pycodestyle.py | 5 ++++- testsuite/python310.py | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 8124f9dc..1ceff9ea 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -821,7 +821,10 @@ def whitespace_before_parameters(logical_line, tokens): # Allow "return (a.foo for a in range(5))" not keyword.iskeyword(prev_text) and # 'match' and 'case' are only soft keywords - prev_text not in ('match', 'case') + ( + sys.version_info <= (3, 10) or + not keyword.issoftkeyword(prev_text) + ) ): yield prev_end, "E211 whitespace before '%s'" % text prev_type = token_type diff --git a/testsuite/python310.py b/testsuite/python310.py index b72bf6d8..83b7bb4b 100644 --- a/testsuite/python310.py +++ b/testsuite/python310.py @@ -1,12 +1,9 @@ -var, var2 = 1, 2 #: Okay +var, var2 = 1, 2 match (var, var2): - #: Okay case [2, 3]: pass - #: Okay case (1, 2): pass - #: Okay case _: print("Default") From b7decf78584d00412ce8a52da34a5d5133690b3d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 2 May 2021 21:42:56 +0200 Subject: [PATCH 247/412] Fix testsuite --- testsuite/support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/support.py b/testsuite/support.py index 8241a923..eb8b4436 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -169,7 +169,7 @@ def init_tests(pep8style): def run_tests(filename): """Run all the tests from a file.""" # Skip tests meant for higher versions of python - ver_match = re.search(r'python(\d)(\d)?\.py$', filename) + ver_match = re.search(r'python(\d)(\d+)?\.py$', filename) if ver_match: test_against_version = tuple(int(val or 0) for val in ver_match.groups()) From 91fc81764a347797b79dfe6fa901a77b63d87473 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 2 May 2021 21:49:53 +0200 Subject: [PATCH 248/412] Add 3.10-dev to Github CI action --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4624d216..59dfac33 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,6 +40,9 @@ jobs: - os: ubuntu-latest py: 3.9 toxenv: py + - os: ubuntu-latest + py: 3.10-dev + toxenv: py - os: ubuntu-latest py: 3.9 toxenv: flake8 From 2371965dc53f909f16d67f061fc2cb274ccfef82 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 3 May 2021 01:08:36 +0200 Subject: [PATCH 249/412] Small fix to version_info comparison --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 1ceff9ea..e864f023 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -822,7 +822,7 @@ def whitespace_before_parameters(logical_line, tokens): not keyword.iskeyword(prev_text) and # 'match' and 'case' are only soft keywords ( - sys.version_info <= (3, 10) or + sys.version_info < (3, 10) or not keyword.issoftkeyword(prev_text) ) ): From 76ed53c0dfc3b17257aca59671c5c92c39356b57 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 3 May 2021 01:16:00 +0200 Subject: [PATCH 250/412] issoftkeyword was added in py39 --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index e864f023..0f2f0786 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -822,7 +822,7 @@ def whitespace_before_parameters(logical_line, tokens): not keyword.iskeyword(prev_text) and # 'match' and 'case' are only soft keywords ( - sys.version_info < (3, 10) or + sys.version_info < (3, 9) or not keyword.issoftkeyword(prev_text) ) ): From c098297fa5154fd18f81ae97feb06e84b0f6ba15 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 3 May 2021 00:35:11 +0200 Subject: [PATCH 251/412] Add whitespace checks for match and case --- pycodestyle.py | 27 +++++++++++++++++++++++++++ testsuite/python310.py | 18 ++++++++++++++++++ testsuite/support.py | 7 +++++++ 3 files changed, 52 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index 0f2f0786..abadf6ef 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -163,6 +163,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. ))) ) DUNDER_REGEX = re.compile(r'^__([^\s]+)__ = ') +MATCH_CASE_REGEX = re.compile(r'^\s*\b(?:match|case)(\s*)(?=.*\:)') _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -512,6 +513,32 @@ def missing_whitespace_after_import_keyword(logical_line): yield pos, "E275 missing whitespace after keyword" +@register_check +def missing_whitespace_after_match_case(logical_line): + r"""Check whitespace after 'match' and 'case'. + + Python 3.10 + Okay: match status: + E271: match status: + E271: case\tstatus: + E271: case _: + E275: matchstatus: + E275: casestatus: + E275: case_: + """ + if sys.version_info < (3, 10): + return + match = MATCH_CASE_REGEX.match(logical_line) + if match: + whitespace = match.groups()[0] + if whitespace == ' ': + return + if whitespace == '': + yield match.start(1), "E275 missing whitespace after keyword" + else: + yield match.start(1), "E271 multiple spaces after keyword" + + @register_check def missing_whitespace(logical_line): r"""Each comma, semicolon or colon should be followed by whitespace. diff --git a/testsuite/python310.py b/testsuite/python310.py index 83b7bb4b..e78d3727 100644 --- a/testsuite/python310.py +++ b/testsuite/python310.py @@ -7,3 +7,21 @@ pass case _: print("Default") +#: E271:2:6 E271:3:9 E271:5:9 E271:7:9 +var = 1 +match var: + case 1: + pass + case 2: + pass + case ( + 3 + ): + pass +#: E275:2:6 E275:3:9 E275:5:9 +var = 1 +match(var): + case(1): + pass + case_: + pass diff --git a/testsuite/support.py b/testsuite/support.py index eb8b4436..e6c897f5 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -6,6 +6,7 @@ from pycodestyle import Checker, BaseReport, StandardReport, readlines SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') +SELFTEST_PY_REGEX = re.compile(r'\bPython (\d).(\d+)') ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -112,8 +113,14 @@ def selftest(options): counters = report.counters checks = options.physical_checks + options.logical_checks for name, check, argument_names in checks: + python_version = None for line in check.__doc__.splitlines(): line = line.lstrip() + match = SELFTEST_PY_REGEX.match(line) + if match: + python_version = tuple(map(int, match.groups())) + if python_version and sys.version_info < python_version: + continue match = SELFTEST_REGEX.match(line) if match is None: continue From d13134eba2f6a2f2d9fced5a95bf78fdeaabc9e8 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 3 May 2021 00:40:49 +0200 Subject: [PATCH 252/412] Add additional py environments to tox --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f4356a5d..312c20d3 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, py38, pypy, pypy3, jython +envlist = py27, py34, py35, py36, py37, py38, py39, py310, pypy, pypy3, jython skip_missing_interpreters = True [testenv] From 76abb5ddc61b6532cca1f448d7e95793b4296eac Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 3 May 2021 01:21:24 +0200 Subject: [PATCH 253/412] Moved to existing check --- pycodestyle.py | 37 +++++++++++-------------------------- testsuite/support.py | 7 ------- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index abadf6ef..47ab5cef 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -494,6 +494,17 @@ def whitespace_around_keywords(logical_line): elif len(after) > 1: yield match.start(2), "E271 multiple spaces after keyword" + if sys.version_info >= (3, 10): + match = MATCH_CASE_REGEX.match(logical_line) + if match: + whitespace = match.groups()[0] + if whitespace == ' ': + return + if whitespace == '': + yield match.start(1), "E275 missing whitespace after keyword" + else: + yield match.start(1), "E271 multiple spaces after keyword" + @register_check def missing_whitespace_after_import_keyword(logical_line): @@ -513,32 +524,6 @@ def missing_whitespace_after_import_keyword(logical_line): yield pos, "E275 missing whitespace after keyword" -@register_check -def missing_whitespace_after_match_case(logical_line): - r"""Check whitespace after 'match' and 'case'. - - Python 3.10 - Okay: match status: - E271: match status: - E271: case\tstatus: - E271: case _: - E275: matchstatus: - E275: casestatus: - E275: case_: - """ - if sys.version_info < (3, 10): - return - match = MATCH_CASE_REGEX.match(logical_line) - if match: - whitespace = match.groups()[0] - if whitespace == ' ': - return - if whitespace == '': - yield match.start(1), "E275 missing whitespace after keyword" - else: - yield match.start(1), "E271 multiple spaces after keyword" - - @register_check def missing_whitespace(logical_line): r"""Each comma, semicolon or colon should be followed by whitespace. diff --git a/testsuite/support.py b/testsuite/support.py index e6c897f5..eb8b4436 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -6,7 +6,6 @@ from pycodestyle import Checker, BaseReport, StandardReport, readlines SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') -SELFTEST_PY_REGEX = re.compile(r'\bPython (\d).(\d+)') ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -113,14 +112,8 @@ def selftest(options): counters = report.counters checks = options.physical_checks + options.logical_checks for name, check, argument_names in checks: - python_version = None for line in check.__doc__.splitlines(): line = line.lstrip() - match = SELFTEST_PY_REGEX.match(line) - if match: - python_version = tuple(map(int, match.groups())) - if python_version and sys.version_info < python_version: - continue match = SELFTEST_REGEX.match(line) if match is None: continue From 3527106c822f52b01ce516e6ccf4cb30b69e3a87 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 3 May 2021 02:15:06 +0200 Subject: [PATCH 254/412] Small improvement --- pycodestyle.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 47ab5cef..7f00739a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -497,10 +497,9 @@ def whitespace_around_keywords(logical_line): if sys.version_info >= (3, 10): match = MATCH_CASE_REGEX.match(logical_line) if match: - whitespace = match.groups()[0] - if whitespace == ' ': + if match[1] == ' ': return - if whitespace == '': + if match[1] == '': yield match.start(1), "E275 missing whitespace after keyword" else: yield match.start(1), "E271 multiple spaces after keyword" From 60dc2b0d7b75c46a41612d2d0f6a9430b4dcdc40 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 4 May 2021 16:46:40 -0700 Subject: [PATCH 255/412] improve performance of bare_except check - despite `re.compile(...)` being cached, this still amounts to 60% of the overhead of the `bare_except` check - overall, this improved pycodestyle performance by about .25% (.0025) --- pycodestyle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 7f00739a..52bcc091 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -164,6 +164,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. ) DUNDER_REGEX = re.compile(r'^__([^\s]+)__ = ') MATCH_CASE_REGEX = re.compile(r'^\s*\b(?:match|case)(\s*)(?=.*\:)') +BLANK_EXCEPT_REGEX = re.compile(r"except\s*:") _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -1488,8 +1489,7 @@ def bare_except(logical_line, noqa): if noqa: return - regex = re.compile(r"except\s*:") - match = regex.match(logical_line) + match = BLANK_EXCEPT_REGEX.match(logical_line) if match: yield match.start(), "E722 do not use bare 'except'" From 24d348a46a957a52c724f0a56da6ac4b4480f5f5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 4 May 2021 16:53:03 -0700 Subject: [PATCH 256/412] add performance hack to improve tokenize speed in profiles, this attributed somewhere between 4% and 14% of pycodestyle's execution time --- pycodestyle.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index 7f00739a..4055716b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -78,6 +78,13 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. except ImportError: from ConfigParser import RawConfigParser +# this is a performance hack. see https://bugs.python.org/issue43014 +if ( + sys.version_info < (3, 10) and + callable(getattr(tokenize, '_compile', None)) +): # pragma: no cover ( Date: Sat, 8 May 2021 12:00:33 -0700 Subject: [PATCH 257/412] remove indent_size_str --- pycodestyle.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0d8ed50f..1086840d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -564,7 +564,7 @@ def missing_whitespace(logical_line): @register_check def indentation(logical_line, previous_logical, indent_char, indent_level, previous_indent_level, - indent_size, indent_size_str): + indent_size): r"""Use indent_size (PEP8 says 4) spaces per indentation level. For really old code that you don't want to mess up, you can continue @@ -588,7 +588,7 @@ def indentation(logical_line, previous_logical, indent_char, if indent_level % indent_size: yield 0, tmpl % ( 1 + c, - "indentation is not a multiple of " + indent_size_str, + "indentation is not a multiple of " + str(indent_size), ) indent_expect = previous_logical.endswith(':') if indent_expect and indent_level <= previous_indent_level: @@ -605,8 +605,7 @@ def indentation(logical_line, previous_logical, indent_char, @register_check def continued_indentation(logical_line, tokens, indent_level, hang_closing, - indent_char, indent_size, indent_size_str, noqa, - verbose): + indent_char, indent_size, noqa, verbose): r"""Continuation lines indentation. Continuation lines should align wrapped elements either vertically @@ -1997,8 +1996,6 @@ def __init__(self, filename=None, lines=None, self.multiline = False # in a multiline string? self.hang_closing = options.hang_closing self.indent_size = options.indent_size - self.indent_size_str = ({2: 'two', 4: 'four', 8: 'eight'} - .get(self.indent_size, str(self.indent_size))) self.verbose = options.verbose self.filename = filename # Dictionary where a checker can store its custom state. From 696170927b2723ce390d011d5d1af34e914e61b8 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 9 Jul 2021 17:19:44 +0200 Subject: [PATCH 258/412] Fix false-positive with star pattern --- pycodestyle.py | 9 +++++++-- testsuite/python310.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0d8ed50f..b00b1f1b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -964,8 +964,13 @@ def missing_whitespace_around_operator(logical_line, tokens): # Check if the operator is used as a binary operator # Allow unary operators: -123, -x, +1. # Allow argument unpacking: foo(*args, **kwargs). - if (prev_text in '}])' if prev_type == tokenize.OP - else prev_text not in KEYWORDS): + if prev_type == tokenize.OP and prev_text in '}])' or ( + prev_type != tokenize.OP and + prev_text not in KEYWORDS and ( + sys.version_info < (3, 9) or + not keyword.issoftkeyword(prev_text) + ) + ): need_space = None elif text in WS_OPTIONAL_OPERATORS: need_space = None diff --git a/testsuite/python310.py b/testsuite/python310.py index e78d3727..8cd98f25 100644 --- a/testsuite/python310.py +++ b/testsuite/python310.py @@ -7,6 +7,21 @@ pass case _: print("Default") +#: Okay +var = 0, 1, 2 +match var: + case *_, 1, 2: + pass + case 0, *_, 2: + pass + case 0, 1, *_: + pass + case (*_, 1, 2): + pass + case (0, *_, 2): + pass + case (0, 1, *_): + pass #: E271:2:6 E271:3:9 E271:5:9 E271:7:9 var = 1 match var: From e458011af828bda5d8c7e573fbefdd9d865b2334 Mon Sep 17 00:00:00 2001 From: Zac-HD Date: Mon, 13 Sep 2021 18:39:55 +1000 Subject: [PATCH 259/412] Fix E225 for lambdas --- pycodestyle.py | 4 +++- testsuite/python38.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0d8ed50f..ac584cea 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -933,7 +933,9 @@ def missing_whitespace_around_operator(logical_line, tokens): # ^ # def f(a, b, /): # ^ - prev_text == '/' and text in {',', ')'} or + # f = lambda a, /: + # ^ + prev_text == '/' and text in {',', ')', ':'} or # def f(a, b, /): # ^ prev_text == ')' and text == ':' diff --git a/testsuite/python38.py b/testsuite/python38.py index 57ee6139..f9276734 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -13,6 +13,9 @@ def f3( b, ): pass + + +lambda a, /: None #: Okay if x := 1: print(x) From d19b47a307fd45f2e2d2994f105a3c69d66caf82 Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Thu, 30 Sep 2021 06:35:53 -0500 Subject: [PATCH 260/412] Allow __all__ to be typed Closes gh-1018 --- pycodestyle.py | 2 +- testsuite/python38.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 68d31d40..1d98dce6 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -169,7 +169,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 'while', ))) ) -DUNDER_REGEX = re.compile(r'^__([^\s]+)__ = ') +DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ") MATCH_CASE_REGEX = re.compile(r'^\s*\b(?:match|case)(\s*)(?=.*\:)') BLANK_EXCEPT_REGEX = re.compile(r"except\s*:") diff --git a/testsuite/python38.py b/testsuite/python38.py index f9276734..1374192d 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -28,3 +28,17 @@ def f3( #: E225:1:18 if False or (x :=1): pass +#: Okay +import typing as t + +__all__: t.List[str] = [] + +import logging + +logging.getLogger(__name__) +#: E402 +import typing as t + +all_the_things: t.List[str] = [] + +import logging From f1d28848c34d5d8a773896c531c12390a7192508 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 17 Sep 2021 00:06:05 +0200 Subject: [PATCH 261/412] Issue #588: E201: detect tabs as whitespace --- pycodestyle.py | 4 ++-- testsuite/E20.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index cc1720e5..ca6a2bd0 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -144,7 +144,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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'[\[({][ \t]|[ \t][\]}),;:](?!=)') 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') @@ -471,7 +471,7 @@ def extraneous_whitespace(logical_line): text = match.group() char = text.strip() found = match.start() - if text == char + ' ': + if text[-1].isspace(): # assert char in '([{' yield found + 1, "E201 whitespace after '%s'" % char elif line[found - 1] != ',': diff --git a/testsuite/E20.py b/testsuite/E20.py index 2f1ecc28..20c6dfd8 100644 --- a/testsuite/E20.py +++ b/testsuite/E20.py @@ -4,6 +4,12 @@ spam(ham[ 1], {eggs: 2}) #: E201:1:15 spam(ham[1], { eggs: 2}) +#: E201:1:6 +spam( ham[1], {eggs: 2}) +#: E201:1:10 +spam(ham[ 1], {eggs: 2}) +#: E201:1:15 +spam(ham[1], { eggs: 2}) #: Okay spam(ham[1], {eggs: 2}) #: @@ -15,6 +21,12 @@ spam(ham[1], {eggs: 2 }) #: E202:1:11 spam(ham[1 ], {eggs: 2}) +#: E202:1:23 +spam(ham[1], {eggs: 2} ) +#: E202:1:22 +spam(ham[1], {eggs: 2 }) +#: E202:1:11 +spam(ham[1 ], {eggs: 2}) #: Okay spam(ham[1], {eggs: 2}) @@ -39,13 +51,24 @@ if x == 4 : print x, y x, y = y, x +#: E203:1:10 +if x == 4 : + print x, y + x, y = y, x #: E203:2:15 E702:2:16 if x == 4: print x, y ; x, y = y, x +#: E203:2:15 E702:2:16 +if x == 4: + print x, y ; x, y = y, x #: E203:3:13 if x == 4: print x, y x, y = y , x +#: E203:3:13 +if x == 4: + print x, y + x, y = y , x #: Okay if x == 4: print x, y From bb57b4cf2f61c643655eef4c77683fa144b2c3cd Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 5 Oct 2021 20:19:06 -0400 Subject: [PATCH 262/412] Revert "Merge pull request #990 from cdce8p/whitespace-match-case" This reverts commit 0f079a061590217515421fc337df8dbf3563fed5, reversing changes made to 3d0ac73d8045b5fa771dbbf594ca0b9a4e581e15. --- pycodestyle.py | 11 ----------- testsuite/python310.py | 18 ------------------ 2 files changed, 29 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index ca6a2bd0..cb269047 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -170,7 +170,6 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. ))) ) DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ") -MATCH_CASE_REGEX = re.compile(r'^\s*\b(?:match|case)(\s*)(?=.*\:)') BLANK_EXCEPT_REGEX = re.compile(r"except\s*:") _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -502,16 +501,6 @@ def whitespace_around_keywords(logical_line): elif len(after) > 1: yield match.start(2), "E271 multiple spaces after keyword" - if sys.version_info >= (3, 10): - match = MATCH_CASE_REGEX.match(logical_line) - if match: - if match[1] == ' ': - return - if match[1] == '': - yield match.start(1), "E275 missing whitespace after keyword" - else: - yield match.start(1), "E271 multiple spaces after keyword" - @register_check def missing_whitespace_after_import_keyword(logical_line): diff --git a/testsuite/python310.py b/testsuite/python310.py index 8cd98f25..2355f55e 100644 --- a/testsuite/python310.py +++ b/testsuite/python310.py @@ -22,21 +22,3 @@ pass case (0, 1, *_): pass -#: E271:2:6 E271:3:9 E271:5:9 E271:7:9 -var = 1 -match var: - case 1: - pass - case 2: - pass - case ( - 3 - ): - pass -#: E275:2:6 E275:3:9 E275:5:9 -var = 1 -match(var): - case(1): - pass - case_: - pass From e06e93e0dc6193e3b4045b470fd6ec5b022e50fc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 5 Oct 2021 20:23:22 -0400 Subject: [PATCH 263/412] add example cases for incorrect E275 from reverted patch --- testsuite/E27.py | 2 ++ testsuite/python3.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/testsuite/E27.py b/testsuite/E27.py index 888b3a80..9bb53f8f 100644 --- a/testsuite/E27.py +++ b/testsuite/E27.py @@ -42,3 +42,5 @@ from importable.module import(e, f) except ImportError: pass +#: Okay +matched = {"true": True, "false": False} diff --git a/testsuite/python3.py b/testsuite/python3.py index 709695af..959956e5 100644 --- a/testsuite/python3.py +++ b/testsuite/python3.py @@ -9,6 +9,8 @@ def foo(x: int) -> int: # Annotated variables #575 CONST: int = 42 +match: int = 42 +case: int = 42 class Class: From e2eaea87bbf72c52c7db5415420b5befd0f43723 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 10 Oct 2021 17:14:20 -0700 Subject: [PATCH 264/412] add simple .pre-commit-config.yaml --- .pre-commit-config.yaml | 17 ++++++++ docs/conf.py | 87 ++++++++++++++++++++--------------------- pycodestyle.py | 3 +- setup.py | 1 - 4 files changed, 61 insertions(+), 47 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..6cbf31c5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +exclude: ^testsuite/ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/asottile/reorder_python_imports + rev: v2.6.0 + hooks: + - id: reorder-python-imports +- repo: https://github.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 diff --git a/docs/conf.py b/docs/conf.py index 7935b0b2..a89a2e07 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,20 +11,19 @@ # # All configuration values have a default; values that are commented out # serve to show the default. - -import sys import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('..')) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -38,7 +37,7 @@ source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' @@ -61,13 +60,13 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -75,24 +74,24 @@ # The reST default role (used for this markup: `text`) to use for # all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output -------------------------------------------------- @@ -108,26 +107,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -136,44 +135,44 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'pycodestyledoc' @@ -183,13 +182,13 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', + # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', + # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. - #'preamble': '', + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples @@ -202,23 +201,23 @@ # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output ------------------------------------------- @@ -231,7 +230,7 @@ ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ----------------------------------------------- @@ -246,10 +245,10 @@ ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' diff --git a/pycodestyle.py b/pycodestyle.py index cb269047..aa707913 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -25,7 +25,6 @@ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - r""" Check Python source code formatting, according to PEP 8. @@ -49,6 +48,7 @@ """ from __future__ import with_statement +import bisect import inspect import keyword import os @@ -57,7 +57,6 @@ import time import tokenize import warnings -import bisect try: from functools import lru_cache diff --git a/setup.py b/setup.py index 148751bc..3f0c363a 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import with_statement from setuptools import setup From 91c90a02f70d38ba88ab0dc01bfdf948319bfd4c Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Sun, 10 Oct 2021 19:31:01 -0500 Subject: [PATCH 265/412] Run tests on main as well This is prep to switch the master branch to main --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 59dfac33..1caefafe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: main on: pull_request: push: - branches: [master, 'test-me-*'] + branches: [master, main, 'test-me-*'] tags: ['*'] jobs: From 5c2c3543b95981440d18a1b3cc492e34401c39b1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 10 Oct 2021 17:06:28 -0700 Subject: [PATCH 266/412] Release 2.8.0 --- CHANGES.txt | 17 +++++++++++++++++ pycodestyle.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 35523b56..49db29b2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,23 @@ Changelog ========= +2.8.0 (2021-10-10) +------------------ + +Changes: + +* Drop python 3.4. PR #982. +* E712: fix false negative with multiple comparisons. PR #987. +* E211: fix false positives with ``match``. PR #989. +* E772: improve performance of bare except check. PR #992. +* Backport tokenize performance improvement from python 3.10. PR #993. +* E225: fix for lambdas containing positional-only args. PR #1012. +* Remove ``indent_size_str`` "setting". PR #995. +* E402: allow ``__all__`` to be typed. PR #1019. +* E225: fix false positives for ``*`` in ``case``. PR #1003. +* E201: detect tabs as whitespace. PR #1015. + + 2.7.0 (2021-03-14) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index aa707913..16167df2 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -84,7 +84,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. ): # pragma: no cover ( Date: Mon, 11 Oct 2021 20:25:06 +0000 Subject: [PATCH 267/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 3.9.2 → 4.0.1](https://github.com/pycqa/flake8/compare/3.9.2...4.0.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6cbf31c5..375d5728 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,6 @@ repos: hooks: - id: reorder-python-imports - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + rev: 4.0.1 hooks: - id: flake8 From 52ac6c6f97e778b4716ea28959940b97148e7ae0 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 13 Oct 2021 14:44:16 +0200 Subject: [PATCH 268/412] Emit E221-E224 for walrus op --- pycodestyle.py | 2 +- testsuite/python38.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 16167df2..e4954dc0 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -152,7 +152,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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)) -OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') +OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:(?:[-+*/|!<=>%&^]+|:=))(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') diff --git a/testsuite/python38.py b/testsuite/python38.py index 1374192d..8bf0d4d0 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -42,3 +42,14 @@ def f3( all_the_things: t.List[str] = [] import logging +#: E221:1:5 E222:1:9 E221:3:6 +if x := 1: + pass +if (x := 2): + pass +#: E223:1:5 E224:1:8 +if x := 2: + pass +#: E221:1:6 E221:1:19 +if (x := 1) == (y := 2): + pass From d17c08b507cbf5428a0ad263a6856ca85e6f205a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 13 Oct 2021 15:11:33 +0200 Subject: [PATCH 269/412] Remove unnecessary non-capture group --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index e4954dc0..72229078 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -152,7 +152,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. 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)) -OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:(?:[-+*/|!<=>%&^]+|:=))(\s*)') +OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+|:=)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') From 57802ace9f0a22fb303a2f5ba5f98b31cb8aadf8 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 8 Nov 2021 18:20:25 -0500 Subject: [PATCH 270/412] manual: drop python 2 --- .github/workflows/main.yml | 12 ------------ setup.py | 10 +--------- tox.ini | 2 +- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1caefafe..0d26707d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,24 +10,12 @@ jobs: strategy: matrix: include: - - os: windows-latest - py: 2.7 - toxenv: py - os: windows-latest py: 3.9 toxenv: py - - os: ubuntu-latest - py: pypy2 - toxenv: py - os: ubuntu-latest py: pypy3 toxenv: py - - os: ubuntu-latest - py: 2.7 - toxenv: py - - os: ubuntu-latest - py: 3.5 - toxenv: py - os: ubuntu-latest py: 3.6 toxenv: py diff --git a/setup.py b/setup.py index 3f0c363a..b0b9efd9 100644 --- a/setup.py +++ b/setup.py @@ -30,14 +30,9 @@ def get_long_description(): url='https://pycodestyle.pycqa.org/', license='Expat license', py_modules=['pycodestyle'], - namespace_packages=[], include_package_data=True, zip_safe=False, - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', - install_requires=[ - # Broken with Python 3: https://github.com/pypa/pip/issues/650 - # 'setuptools', - ], + python_requires='>=3.6', entry_points={ 'console_scripts': [ 'pycodestyle = pycodestyle:_main', @@ -50,10 +45,7 @@ def get_long_description(): 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', diff --git a/tox.ini b/tox.ini index 312c20d3..18f1a4ff 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, py38, py39, py310, pypy, pypy3, jython +envlist = py36, py37, py38, py39, py310, pypy3 skip_missing_interpreters = True [testenv] From a9b60986706c037949ef8a7d01a0085cc8122f81 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 8 Nov 2021 18:32:03 -0500 Subject: [PATCH 271/412] run pyupgrade --- .pre-commit-config.yaml | 6 +++ docs/conf.py | 13 +++-- pycodestyle.py | 108 +++++++++++++++++----------------------- setup.py | 1 - 4 files changed, 58 insertions(+), 70 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 375d5728..63eba574 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,12 @@ repos: rev: v2.6.0 hooks: - id: reorder-python-imports + args: [--py36-plus] +- repo: https://github.com/asottile/pyupgrade + rev: v2.29.0 + hooks: + - id: pyupgrade + args: [--py36-plus] - repo: https://github.com/pycqa/flake8 rev: 4.0.1 hooks: diff --git a/docs/conf.py b/docs/conf.py index a89a2e07..f0cb9888 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # pycodestyle documentation build configuration file, created by # sphinx-quickstart on Tue Aug 21 09:47:49 2012. @@ -43,9 +42,9 @@ master_doc = 'index' # General information about the project. -project = u'pycodestyle' -authors = u'Johann C. Rocholl, Florent Xicluna, Ian Lee' -copyright = u'2006-2016, %s' % (authors) +project = 'pycodestyle' +authors = 'Johann C. Rocholl, Florent Xicluna, Ian Lee' +copyright = '2006-2016, %s' % (authors) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -195,7 +194,7 @@ # (source start file, target name, title, # author, documentclass [howto/manual]). latex_documents = [ - ('index', 'pycodestyle.tex', u'pycodestyle documentation', + ('index', 'pycodestyle.tex', 'pycodestyle documentation', authors, 'manual'), ] @@ -225,7 +224,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'pycodestyle', u'pycodestyle documentation', + ('index', 'pycodestyle', 'pycodestyle documentation', [authors], 1) ] @@ -239,7 +238,7 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'pycodestyle', u'pycodestyle documentation', authors, + ('index', 'pycodestyle', 'pycodestyle documentation', authors, 'pycodestyle', 'One line description of project.', 'Miscellaneous'), ] diff --git a/pycodestyle.py b/pycodestyle.py index 72229078..d3245444 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -46,8 +46,6 @@ 700 statements 900 syntax error """ -from __future__ import with_statement - import bisect import inspect import keyword @@ -122,14 +120,11 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) 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 + + 'and', 'in', 'is', 'or', '->'] + ASSIGNMENT_EXPRESSION_OP) WHITESPACE = frozenset(' \t') NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) @@ -158,7 +153,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( - r'^\s*({0})\b'.format('|'.join(s.replace(' ', r'\s+') for s in ( + r'^\s*({})\b'.format('|'.join(s.replace(' ', r'\s+') for s in ( 'def', 'async def', 'for', 'async for', 'if', 'elif', 'else', @@ -175,13 +170,10 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. def _get_parameters(function): - if sys.version_info >= (3, 3): - return [parameter.name - for parameter - in inspect.signature(function).parameters.values() - if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] - else: - return inspect.getargspec(function)[0] + return [parameter.name + for parameter + in inspect.signature(function).parameters.values() + if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] def register_check(check, codes=None): @@ -429,8 +421,8 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, yield 0, "E306 expected %s blank line before a " \ "nested definition, found 0" % (method_lines,) else: - yield 0, "E301 expected %s blank line, found 0" % ( - method_lines,) + yield 0, "E301 expected {} blank line, found 0".format( + method_lines) elif blank_before != top_level_lines: yield 0, "E302 expected %s blank lines, found %d" % ( top_level_lines, blank_before) @@ -474,7 +466,7 @@ def extraneous_whitespace(logical_line): yield found + 1, "E201 whitespace after '%s'" % char elif line[found - 1] != ',': code = ('E202' if char in '}])' else 'E203') # if char in ',;:' - yield found, "%s whitespace before '%s'" % (code, char) + yield found, f"{code} whitespace before '{char}'" @register_check @@ -735,7 +727,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, indent[depth] = start[1] indent_chances[start[1]] = True if verbose >= 4: - print("bracket depth %s indent to %s" % (depth, start[1])) + print(f"bracket depth {depth} indent to {start[1]}") # deal with implicit string concatenation elif (token_type in (tokenize.STRING, tokenize.COMMENT) or text in ('u', 'ur', 'b', 'br')): @@ -1228,7 +1220,7 @@ def compound_statements(logical_line): lambda_kw = LAMBDA_REGEX.search(line, 0, found) if lambda_kw: before = line[:lambda_kw.start()].rstrip() - if before[-1:] == '=' and isidentifier(before[:-1].strip()): + if before[-1:] == '=' and before[:-1].strip().isidentifier(): yield 0, ("E731 do not assign a lambda expression, use a " "def") break @@ -1473,7 +1465,7 @@ def comparison_type(logical_line, noqa): match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: inst = match.group(1) - if inst and isidentifier(inst) and inst not in SINGLETONS: + if inst and inst.isidentifier() and inst not in SINGLETONS: return # Allow comparison for types which are not obvious yield match.start(), "E721 do not compare types, use 'isinstance()'" @@ -1822,30 +1814,21 @@ def maximum_doc_length(logical_line, max_doc_length, noqa, tokens): ######################################################################## -if sys.version_info < (3,): - # Python 2: implicit encoding. - def readlines(filename): - """Read the source code.""" - with open(filename, 'rU') as f: +def readlines(filename): + """Read the source code.""" + try: + with tokenize.open(filename) as f: + return f.readlines() + except (LookupError, SyntaxError, UnicodeError): + # Fall back if file encoding is improperly declared + with open(filename, encoding='latin-1') as f: return f.readlines() - isidentifier = re.compile(r'[a-zA-Z_]\w*$').match - stdin_get_value = sys.stdin.read -else: - # Python 3 - def readlines(filename): - """Read the source code.""" - try: - with tokenize.open(filename) as f: - return f.readlines() - except (LookupError, SyntaxError, UnicodeError): - # Fall back if file encoding is improperly declared - with open(filename, encoding='latin-1') as f: - return f.readlines() - isidentifier = str.isidentifier - - def stdin_get_value(): - """Read the value from stdin.""" - return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() + + +def stdin_get_value(): + """Read the value from stdin.""" + return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() + noqa = lru_cache(512)(re.compile(r'# no(?:qa|pep8)\b', re.I).search) @@ -1911,7 +1894,7 @@ def parse_udiff(diff, patterns=None, parent='.'): continue if line[:3] == '@@ ': hunk_match = HUNK_REGEX.match(line) - (row, nrows) = [int(g or '1') for g in hunk_match.groups()] + (row, nrows) = (int(g or '1') for g in hunk_match.groups()) rv[path].update(range(row, row + nrows)) elif line[:3] == '+++': path = line[4:].split('\t', 1)[0] @@ -1972,7 +1955,7 @@ def _is_eol_token(token): ######################################################################## -class Checker(object): +class Checker: """Load a Python source file, tokenize it, check coding style.""" def __init__(self, filename=None, lines=None, @@ -2004,9 +1987,9 @@ def __init__(self, filename=None, lines=None, elif lines is None: try: self.lines = readlines(filename) - except IOError: + except OSError: (exc_type, exc) = sys.exc_info()[:2] - self._io_error = '%s: %s' % (exc_type.__name__, exc) + self._io_error = f'{exc_type.__name__}: {exc}' self.lines = [] else: self.lines = lines @@ -2031,7 +2014,7 @@ def report_invalid_syntax(self): else: offset = (1, 0) self.report_error(offset[0], offset[1] or 0, - 'E901 %s: %s' % (exc_type.__name__, exc.args[0]), + f'E901 {exc_type.__name__}: {exc.args[0]}', self.report_invalid_syntax) def readline(self): @@ -2224,7 +2207,7 @@ def check_all(self, expected=None, line_offset=0): token_type, text = token[0:2] if self.verbose >= 3: if token[2][0] == token[3][0]: - pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) + pos = '[{}:{}]'.format(token[2][1] or '', token[3][1]) else: pos = 'l.%s' % token[3][0] print('l.%s\t%s\t%s\t%r' % @@ -2251,7 +2234,7 @@ def check_all(self, expected=None, line_offset=0): return self.report.get_file_results() -class BaseReport(object): +class BaseReport: """Collect the results of the checks.""" print_filename = False @@ -2333,7 +2316,7 @@ def print_statistics(self, prefix=''): def print_benchmark(self): """Print benchmark numbers.""" - print('%-7.2f %s' % (self.elapsed, 'seconds elapsed')) + print('{:<7.2f} {}'.format(self.elapsed, 'seconds elapsed')) if self.elapsed: for key in self._benchmark_keys: print('%-7d %s per second (%d total)' % @@ -2351,7 +2334,7 @@ class StandardReport(BaseReport): """Collect and print the results of the checks.""" def __init__(self, options): - super(StandardReport, self).__init__(options) + super().__init__(options) self._fmt = REPORT_FORMAT.get(options.format.lower(), options.format) self._repeat = options.repeat @@ -2361,13 +2344,12 @@ def __init__(self, options): def init_file(self, filename, lines, expected, line_offset): """Signal a new file.""" self._deferred_print = [] - return super(StandardReport, self).init_file( + return super().init_file( filename, lines, expected, line_offset) def error(self, line_number, offset, text, check): """Report an error, according to options.""" - code = super(StandardReport, self).error(line_number, offset, - text, check) + code = super().error(line_number, offset, text, check) if code and (self.counters[code] == 1 or self._repeat): self._deferred_print.append( (line_number, offset, code, text[5:], check.__doc__)) @@ -2406,16 +2388,16 @@ class DiffReport(StandardReport): """Collect and print the results for the changed lines only.""" def __init__(self, options): - super(DiffReport, self).__init__(options) + super().__init__(options) self._selected = options.selected_lines def error(self, line_number, offset, text, check): if line_number not in self._selected[self.filename]: return - return super(DiffReport, self).error(line_number, offset, text, check) + return super().error(line_number, offset, text, check) -class StyleGuide(object): +class StyleGuide: """Initialize a PEP-8 instance with few options.""" def __init__(self, *args, **kwargs): @@ -2505,8 +2487,10 @@ def input_dir(self, dirname): dirs.remove(subdir) for filename in sorted(files): # contain a pattern that matches? - if ((filename_match(filename, filepatterns) and - not self.excluded(filename, root))): + if ( + filename_match(filename, filepatterns) and + not self.excluded(filename, root) + ): runner(os.path.join(root, filename)) def excluded(self, filename, parent=None): @@ -2676,8 +2660,8 @@ def read_config(options, args, arglist, parser): print(" unknown option '%s' ignored" % opt) continue if options.verbose > 1: - print(" %s = %s" % (opt, - config.get(pycodestyle_section, opt))) + print(" {} = {}".format(opt, + config.get(pycodestyle_section, opt))) normalized_opt = opt.replace('-', '_') opt_type = option_list[normalized_opt] if opt_type in ('int', 'count'): diff --git a/setup.py b/setup.py index b0b9efd9..fe96737b 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from setuptools import setup From cc57361db5f8458930e612206f0e05d3fc882c93 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 6 Nov 2021 14:35:55 +0100 Subject: [PATCH 272/412] Remove involuntary NBSP from the documentation --- docs/intro.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 7945053d..7fbf6549 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -406,9 +406,9 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | **W5** | *Line break warning* | +------------+----------------------------------------------------------------------+ -| W503 (*)   | line break before binary operator                         | +| W503 (*) | line break before binary operator | +------------+----------------------------------------------------------------------+ -| W504 (*)   | line break after binary operator                         | +| W504 (*) | line break after binary operator | +------------+----------------------------------------------------------------------+ | W505 (\*^) | doc line too long (82 > 79 characters) | +------------+----------------------------------------------------------------------+ From faa9a4bb62b9466e5b0b98288097f1d47bf773f4 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 6 Nov 2021 15:02:36 +0100 Subject: [PATCH 273/412] Upgrade comment and documentation for block comment See https://github.com/PyCQA/pycodestyle/issues/1034\#issuecomment-962454974 --- pycodestyle.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index d3245444..e0c9e42d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1062,12 +1062,14 @@ def whitespace_before_comment(logical_line, tokens): Inline comments should be separated by at least two spaces from the statement. They should start with a # and a single space. - Each line of a block comment starts with a # and a single space - (unless it is indented text inside the comment). + Each line of a block comment starts with a # and one or multiple + spaces as there can be indented text inside the comment. Okay: x = x + 1 # Increment x Okay: x = x + 1 # Increment x - Okay: # Block comment + Okay: # Block comments: + Okay: # - Block comment list + Okay: #  - Block comment list E261: x = x + 1 # Increment x E262: x = x + 1 #Increment x E262: x = x + 1 # Increment x From 59854e1caa96d31a5e100a7a3edbe32cf8fc5209 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 6 Nov 2021 12:49:48 +0100 Subject: [PATCH 274/412] Properly warn for E262 with non breaking whitespaces Closes #1034 --- pycodestyle.py | 7 ++++--- testsuite/E26.py | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index e0c9e42d..b839e355 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -126,7 +126,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', 'and', 'in', 'is', 'or', '->'] + ASSIGNMENT_EXPRESSION_OP) -WHITESPACE = frozenset(' \t') +WHITESPACE = frozenset(' \t\xa0') NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT]) # ERRORTOKEN is triggered by backticks in Python 3 @@ -1056,7 +1056,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): @register_check def whitespace_before_comment(logical_line, tokens): - r"""Separate inline comments by at least two spaces. + """Separate inline comments by at least two spaces. An inline comment is a comment on the same line as a statement. Inline comments should be separated by at least two spaces from the @@ -1069,10 +1069,11 @@ def whitespace_before_comment(logical_line, tokens): Okay: x = x + 1 # Increment x Okay: # Block comments: Okay: # - Block comment list - Okay: #  - Block comment list + Okay: # \xa0- Block comment list E261: x = x + 1 # Increment x E262: x = x + 1 #Increment x E262: x = x + 1 # Increment x + E262: x = x + 1 # \xa0Increment x E265: #Block comment E266: ### Block comment """ diff --git a/testsuite/E26.py b/testsuite/E26.py index c3537ff5..937ef721 100644 --- a/testsuite/E26.py +++ b/testsuite/E26.py @@ -57,3 +57,10 @@ def oof(): # ################################################################ # # ####################### another separator ###################### # # ################################################################ # +#: E262:3:9 +# -*- coding: utf8 -*- +#  (One space one NBSP) Ok for block comment +a = 42 #  (One space one NBSP) +#: E262:2:9 +# (Two spaces) Ok for block comment +a = 42 # (Two spaces) From 327c3697d3a46699441238a092e500959f9b323b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Nov 2021 21:47:42 +0000 Subject: [PATCH 275/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.29.0 → v2.29.1](https://github.com/asottile/pyupgrade/compare/v2.29.0...v2.29.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 63eba574..6251d43b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.29.0 + rev: v2.29.1 hooks: - id: pyupgrade args: [--py36-plus] From b8bb460dae1935420149e92317cae55d3b3c2f99 Mon Sep 17 00:00:00 2001 From: William Andrea Date: Mon, 22 Nov 2021 23:46:25 -0500 Subject: [PATCH 276/412] docs: E203 also applies to comma and semicolon Per PEP8, "Avoid extraneous whitespace ... Immediately before a comma, semicolon, or colon". https://www.python.org/dev/peps/pep-0008/#pet-peeves --- docs/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.rst b/docs/intro.rst index 7fbf6549..ffc5fc39 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -264,7 +264,7 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E202 | whitespace before ')' | +------------+----------------------------------------------------------------------+ -| E203 | whitespace before ':' | +| E203 | whitespace before ',', ';', or ':' | +------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | E211 | whitespace before '(' | From 861c9a40fda1bf5094dc4229d0b00ec7e9ac8765 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Sun, 12 Dec 2021 08:28:59 +0000 Subject: [PATCH 277/412] Add a failing test for a custom types module case Any `types.*Type*` matches incorrectly as a `type(...)` comparison; the regex `COMPARE_TYPE_REGEX` seems a bit too complicated for what should be a simple comparison case. Ref: https://github.com/PyCQA/pycodestyle/blob/main/pycodestyle.py#L147-L148 This reproduces the case in #830 --- testsuite/E72.py | 5 +++++ testsuite/custom_types.py | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 testsuite/custom_types.py diff --git a/testsuite/E72.py b/testsuite/E72.py index a60d892f..e789d82f 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -80,3 +80,8 @@ def func_histype(a, b, c): pass except Exception: pass +#: Okay +from . import custom_types as types + +red = types.ColorTypeRED +red is types.ColorType.RED diff --git a/testsuite/custom_types.py b/testsuite/custom_types.py new file mode 100644 index 00000000..43daf026 --- /dev/null +++ b/testsuite/custom_types.py @@ -0,0 +1,7 @@ +import enum + + +class ColorType(enum.Enum): + RED = 1 + GREEN = 2 + BLUE = 3 From f3ee72b75a4df32221ab08d315e0614a59c17f26 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Sun, 12 Dec 2021 09:16:36 +0000 Subject: [PATCH 278/412] Correct regex to only match type(...) comparisons --- pycodestyle.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index b839e355..c1ec98db 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -144,8 +144,10 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation. r'\s*(?(1)|(None|False|True))\b') COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+|:=)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') From dcedb98f17514cc3174313fd0f009d460fa7babb Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Sun, 12 Dec 2021 09:24:39 +0000 Subject: [PATCH 279/412] Correct test assertions for E721 * `type(a) is type(b)` should still fail * same for `type(a) != type(b) or type(a) == type(ccc)` * We cannot assume `res == types.IntType` is wrong as the identity of the objects is not known at check time, either way it shouldn't be a E721 as it doesn't involve type(...) function as described in PEP8 --- pycodestyle.py | 1 - testsuite/E72.py | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index c1ec98db..c71071e6 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1465,7 +1465,6 @@ def comparison_type(logical_line, noqa): common base class, basestring, so you can do: Okay: if isinstance(obj, basestring): - Okay: if type(a1) is type(b1): """ match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: diff --git a/testsuite/E72.py b/testsuite/E72.py index e789d82f..bba55f50 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -4,7 +4,7 @@ #: E721 if type(res) != type(""): pass -#: E721 +#: Okay import types if res == types.IntType: @@ -47,8 +47,6 @@ pass if isinstance(res, types.MethodType): pass -if type(a) != type(b) or type(a) == type(ccc): - pass #: Okay def func_histype(a, b, c): pass From 5df259bf8d602168e5f29d4bc26ac6b34798e83b Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Mon, 13 Dec 2021 09:14:40 +0000 Subject: [PATCH 280/412] Remove unused module --- testsuite/custom_types.py | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 testsuite/custom_types.py diff --git a/testsuite/custom_types.py b/testsuite/custom_types.py deleted file mode 100644 index 43daf026..00000000 --- a/testsuite/custom_types.py +++ /dev/null @@ -1,7 +0,0 @@ -import enum - - -class ColorType(enum.Enum): - RED = 1 - GREEN = 2 - BLUE = 3 From 302d1ddb383eac42d27d18ee3d69d312c90f2569 Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Sun, 26 Dec 2021 17:56:40 -0500 Subject: [PATCH 281/412] Remove lingering py2 code --- pycodestyle.py | 29 +---------------------------- testsuite/test_all.py | 3 --- testsuite/test_api.py | 17 +---------------- 3 files changed, 2 insertions(+), 47 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index c71071e6..bc48e35c 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -56,17 +56,8 @@ import tokenize import warnings -try: - from functools import lru_cache -except ImportError: - def lru_cache(maxsize=128): # noqa as it's a fake implementation. - """Does not really need a real a lru_cache, it's just - optimization, so let's just do nothing here. Python 3.2+ will - just get better performances, time to upgrade? - """ - return lambda function: function - from fnmatch import fnmatch +from functools import lru_cache from optparse import OptionParser try: @@ -301,12 +292,6 @@ def maximum_line_length(physical_line, max_line_length, multiline, (len(chunks) == 2 and chunks[0] == '#')) and \ len(line) - len(chunks[-1]) < max_line_length - 7: return - if hasattr(line, 'decode'): # Python 2 - # The line could contain multi-byte characters - try: - length = len(line.decode('utf-8')) - except UnicodeError: - pass if length > max_line_length: return (max_line_length, "E501 line too long " "(%d > %d characters)" % (length, max_line_length)) @@ -1459,12 +1444,6 @@ def comparison_type(logical_line, noqa): Okay: if isinstance(obj, int): E721: if type(obj) is type(1): - - When checking if an object is a string, keep in mind that it might - be a unicode string too! In Python 2.3, str and unicode have a - common base class, basestring, so you can do: - - Okay: if isinstance(obj, basestring): """ match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: @@ -1787,12 +1766,6 @@ def maximum_doc_length(logical_line, max_doc_length, noqa, tokens): if prev_token is None or prev_token in SKIP_TOKENS: lines = line.splitlines() for line_num, physical_line in enumerate(lines): - if hasattr(physical_line, 'decode'): # Python 2 - # The line could contain multi-byte characters - try: - physical_line = physical_line.decode('utf-8') - except UnicodeError: - pass if start[0] + line_num == 1 and line.startswith('#!'): return length = len(physical_line) diff --git a/testsuite/test_all.py b/testsuite/test_all.py index 35086743..38b3c452 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -7,9 +7,6 @@ import pycodestyle from testsuite.support import init_tests, selftest, ROOT_DIR -# Note: please only use a subset of unittest methods which were present -# in Python 2.5: assert(True|False|Equal|NotEqual|Raises) - class PycodestyleTestCase(unittest.TestCase): """Test the standard errors and warnings (E and W).""" diff --git a/testsuite/test_api.py b/testsuite/test_api.py index ce2d33a9..8dde32ff 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -322,18 +322,6 @@ def test_styleguide_check_files(self): # < 3.3 raises TypeError; >= 3.3 raises AttributeError self.assertRaises(Exception, pep8style.check_files, [42]) - def test_check_unicode(self): - # Do not crash if lines are Unicode (Python 2.x) - pycodestyle.register_check(DummyChecker, ['Z701']) - source = u'#\n' - - pep8style = pycodestyle.StyleGuide() - count_errors = pep8style.input_file('stdin', lines=[source]) - - self.assertFalse(sys.stdout) - self.assertFalse(sys.stderr) - self.assertEqual(count_errors, 0) - def test_check_nullbytes(self): pycodestyle.register_check(DummyChecker, ['Z701']) @@ -341,10 +329,7 @@ def test_check_nullbytes(self): count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() - if 'ValueError' in stdout: # pragma: no cover (python 3.5+) - expected = "stdin:1:1: E901 ValueError" - else: # pragma: no cover (< python3.5) - expected = "stdin:1:1: E901 TypeError" + expected = "stdin:1:1: E901 ValueError" self.assertTrue(stdout.startswith(expected), msg='Output %r does not start with %r' % (stdout, expected)) From b90935608da6600e6d1deef47e7f537fab04b609 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 26 Dec 2021 23:01:43 +0000 Subject: [PATCH 282/412] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pycodestyle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index bc48e35c..83069c50 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -55,7 +55,6 @@ import time import tokenize import warnings - from fnmatch import fnmatch from functools import lru_cache from optparse import OptionParser From 0a7509a1a3300b4f6f4ae18c6e50a6dc071f9ae7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Dec 2021 22:23:10 +0000 Subject: [PATCH 283/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6251d43b..99d103dd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testsuite/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-yaml - id: debug-statements From e4be8e7fcf34d9e1872ab6c46474690a9e7e776b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 27 Dec 2021 20:15:10 -0500 Subject: [PATCH 284/412] fix regex bug for E721 --- pycodestyle.py | 2 +- testsuite/E72.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 83069c50..d2916372 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -136,7 +136,7 @@ r'(in|is)\s') COMPARE_TYPE_REGEX = re.compile( r'(?:[=!]=|is(?:\s+not)?)\s+type(?:\s*\(\s*([^)]*[^ )])\s*\))' + - r'|type(?:\s*\(\s*([^)]*[^ )])\s*\))\s+(?:[=!]=|is(?:\s+not)?)' + r'|\btype(?:\s*\(\s*([^)]*[^ )])\s*\))\s+(?:[=!]=|is(?:\s+not)?)' ) KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS)) OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+|:=)(\s*)') diff --git a/testsuite/E72.py b/testsuite/E72.py index bba55f50..61e17eb2 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -83,3 +83,8 @@ def func_histype(a, b, c): red = types.ColorTypeRED red is types.ColorType.RED +#: Okay +from . import compute_type + +if compute_type(foo) == 5: + pass From c9411a14fd4cdcf9a3c85be9ec31dfed45e688fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 22:10:59 +0000 Subject: [PATCH 285/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.29.1 → v2.31.0](https://github.com/asottile/pyupgrade/compare/v2.29.1...v2.31.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99d103dd..4e00c7bf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.29.1 + rev: v2.31.0 hooks: - id: pyupgrade args: [--py36-plus] From a6c3f2f86dab22debd1b1b8cf8842ae9d6aec7a0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 27 Jan 2022 11:33:54 -0500 Subject: [PATCH 286/412] use short-circuit evaluation in _is_binary_operator this improves performance by about 1% --- pycodestyle.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index d2916372..2cbc2cd6 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1269,20 +1269,19 @@ def explicit_line_join(logical_line, tokens): parens -= 1 +# 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. _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 _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. - return ((is_op_token or is_conjunction) and not_a_symbol) + return ( + token_type == tokenize.OP or + text in {'and', 'or'} + ) and ( + text not in _SYMBOLIC_OPS + ) def _break_around_binary_operators(tokens): From 417a00f70e1e484121cc65ddeddd6cfed8cc04a0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Jan 2022 23:33:01 +0000 Subject: [PATCH 287/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v2.6.0 → v2.7.1](https://github.com/asottile/reorder_python_imports/compare/v2.6.0...v2.7.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4e00c7bf..1352e22c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v2.6.0 + rev: v2.7.1 hooks: - id: reorder-python-imports args: [--py36-plus] From 604dc6933511d99ba14bc9a6771055a8f4ca5304 Mon Sep 17 00:00:00 2001 From: William Andrea Date: Thu, 24 Feb 2022 15:28:35 -0500 Subject: [PATCH 288/412] docs: Fix double-dash converted to en-dash The double-dash for command line option `--ignore` was being converted to an en-dash. --- docs/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.rst b/docs/intro.rst index ffc5fc39..2f108138 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -432,7 +432,7 @@ This is the current list of error and warning codes: **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, **E133**, **E226**, **E241**, **E242**, **E704**, **W503**, **W504** and **W505** are ignored because they are not rules unanimously accepted, and `PEP 8`_ does not enforce them. -Please note that if the option **--ignore=errors** is used, +Please note that if the option ``--ignore=errors`` is used, the default configuration will be overridden and ignore only the check(s) you skip. The check **W503** is mutually exclusive with check **W504**. The check **E133** is mutually exclusive with check **E123**. Use switch From 49a2f88c96af5c94f5e9aa2c02411ef44fc8a4ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Mar 2022 00:44:07 +0000 Subject: [PATCH 289/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v2.7.1 → v3.0.1](https://github.com/asottile/reorder_python_imports/compare/v2.7.1...v3.0.1) - [github.com/asottile/pyupgrade: v2.31.0 → v2.31.1](https://github.com/asottile/pyupgrade/compare/v2.31.0...v2.31.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1352e22c..03b71291 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v2.7.1 + rev: v3.0.1 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 + rev: v2.31.1 hooks: - id: pyupgrade args: [--py36-plus] From d874160c45f25c3d3fd8dab58cdc529aba05d3ea Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 22:57:56 +0000 Subject: [PATCH 290/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.1.0 → v4.2.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.1.0...v4.2.0) - [github.com/asottile/pyupgrade: v2.31.1 → v2.32.0](https://github.com/asottile/pyupgrade/compare/v2.31.1...v2.32.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 03b71291..fea42165 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testsuite/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: check-yaml - id: debug-statements @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.31.1 + rev: v2.32.0 hooks: - id: pyupgrade args: [--py36-plus] From 52513b30195fe0d985a64e972c1218489cae7042 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 16 Apr 2022 23:59:44 +0200 Subject: [PATCH 291/412] Generalize E275 to require space after all keywords, not just "import". --- pycodestyle.py | 22 +++++++++++++--------- testsuite/E12not.py | 6 +++--- testsuite/E27.py | 5 +++++ testsuite/W19.py | 6 +++--- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 2cbc2cd6..e2e4b96b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -480,20 +480,24 @@ def whitespace_around_keywords(logical_line): @register_check -def missing_whitespace_after_import_keyword(logical_line): - r"""Multiple imports in form from x import (a, b, c) should have - space between import statement and parenthesised name list. +def missing_whitespace_after_keyword(logical_line, tokens): + r"""Keywords should be followed by whitespace. Okay: from foo import (bar, baz) E275: from foo import(bar, baz) E275: from importable.module import(bar, baz) + E275: if(foo): bar """ - line = logical_line - indicator = ' import(' - if line.startswith('from '): - found = line.find(indicator) - if -1 < found: - pos = found + len(indicator) - 1 + for tok0, tok1 in zip(tokens, tokens[1:]): + # This must exclude the True/False/None singletons, which can + # appear e.g. as "if x is None:", and async/await, which were + # valid identifier names in old Python versions. + if (tok0.end == tok1.start and + keyword.iskeyword(tok0.string) and + tok0.string not in SINGLETONS and + tok0.string not in ('async', 'await') and + tok1.string not in ':\n'): + line, pos = tok0.end yield pos, "E275 missing whitespace after keyword" diff --git a/testsuite/E12not.py b/testsuite/E12not.py index 2e2366c2..76776591 100644 --- a/testsuite/E12not.py +++ b/testsuite/E12not.py @@ -40,8 +40,8 @@ if start[1] > end_col and not ( over_indent == 4 and indent_next): - return(0, "E121 continuation line over-" - "indented for visual indent") + return (0, "E121 continuation line over-" + "indented for visual indent") print "OK", ("visual", @@ -175,7 +175,7 @@ def long_function_name( # if bar: - return( + return ( start, 'E121 lines starting with a ' 'closing bracket should be indented ' "to match that of the opening " diff --git a/testsuite/E27.py b/testsuite/E27.py index 9bb53f8f..91aa0790 100644 --- a/testsuite/E27.py +++ b/testsuite/E27.py @@ -42,5 +42,10 @@ from importable.module import(e, f) except ImportError: pass +#: E275 +if(foo): + pass +else: + pass #: Okay matched = {"true": True, "false": False} diff --git a/testsuite/W19.py b/testsuite/W19.py index ed69e2b5..9d4eb4d0 100644 --- a/testsuite/W19.py +++ b/testsuite/W19.py @@ -41,8 +41,8 @@ #: E101 E101 W191 W191 if start[1] > end_col and not ( over_indent == 4 and indent_next): - return(0, "E121 continuation line over-" - "indented for visual indent") + return (0, "E121 continuation line over-" + "indented for visual indent") #: #: E101 W191 @@ -58,7 +58,7 @@ def long_function_name( raise Exception("%s,%s - %s" % (row, col, self.moduleCount)) #: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191 if bar: - return( + return ( start, 'E121 lines starting with a ' 'closing bracket should be indented ' "to match that of the opening " From a1dfd984380b0afcc0521f6869a4b3103b18058c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 22:00:50 +0000 Subject: [PATCH 292/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.0.1 → v3.1.0](https://github.com/asottile/reorder_python_imports/compare/v3.0.1...v3.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fea42165..e932206d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.1.0 hooks: - id: reorder-python-imports args: [--py36-plus] From 3de9e91bfba14fa74a4ff78ee7e2d90faeab2878 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 22:08:07 +0000 Subject: [PATCH 293/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.32.0 → v2.32.1](https://github.com/asottile/pyupgrade/compare/v2.32.0...v2.32.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e932206d..383f1b9a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.32.0 + rev: v2.32.1 hooks: - id: pyupgrade args: [--py36-plus] From e72afc551f1fa178cb753e1ec164803655ba5692 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 16 May 2022 20:42:29 -0400 Subject: [PATCH 294/412] add python3.11 support (except* and a[*b]) --- .github/workflows/main.yml | 5 ++++- pycodestyle.py | 1 + testsuite/python311.py | 23 +++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 testsuite/python311.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d26707d..00f5e75c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,10 @@ jobs: py: 3.9 toxenv: py - os: ubuntu-latest - py: 3.10-dev + py: '3.10' + toxenv: py + - os: ubuntu-latest + py: '3.11-dev' toxenv: py - os: ubuntu-latest py: 3.9 diff --git a/pycodestyle.py b/pycodestyle.py index e2e4b96b..550770b2 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -496,6 +496,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): keyword.iskeyword(tok0.string) and tok0.string not in SINGLETONS and tok0.string not in ('async', 'await') and + not (tok0.string == 'except' and tok1.string == '*') and tok1.string not in ':\n'): line, pos = tok0.end yield pos, "E275 missing whitespace after keyword" diff --git a/testsuite/python311.py b/testsuite/python311.py new file mode 100644 index 00000000..a405125a --- /dev/null +++ b/testsuite/python311.py @@ -0,0 +1,23 @@ +#: Okay +try: + ... +except* OSError as e: + pass +#: Okay +from typing import Generic +from typing import TypeVarTuple + + +Ts = TypeVarTuple('Ts') + + +class Shape(Generic[*Ts]): + pass + + +def f(*args: *Ts) -> None: + ... + + +def g(x: Shape[*Ts]) -> Shape[*Ts]: + ... From c1c9a4be729e63a2b72495f8b8d43f3a7580c4a9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 30 May 2022 14:25:21 -0400 Subject: [PATCH 295/412] fix IndexError regression with E275 --- pycodestyle.py | 3 +-- testsuite/E27.py | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index e2e4b96b..adf743ca 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -497,8 +497,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): tok0.string not in SINGLETONS and tok0.string not in ('async', 'await') and tok1.string not in ':\n'): - line, pos = tok0.end - yield pos, "E275 missing whitespace after keyword" + yield tok0.end, "E275 missing whitespace after keyword" @register_check diff --git a/testsuite/E27.py b/testsuite/E27.py index 91aa0790..5b476577 100644 --- a/testsuite/E27.py +++ b/testsuite/E27.py @@ -49,3 +49,6 @@ pass #: Okay matched = {"true": True, "false": False} +#: E275:2:11 +if True: + assert(1) From 6d9d1248117c0ab14e02163002fb39ddc736761c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jun 2022 21:59:34 +0000 Subject: [PATCH 296/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.32.1 → v2.33.0](https://github.com/asottile/pyupgrade/compare/v2.32.1...v2.33.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 383f1b9a..0135a7a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.32.1 + rev: v2.33.0 hooks: - id: pyupgrade args: [--py36-plus] From a0c4d7ded79cadf353c3391c1117c5dea2387910 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Jun 2022 00:06:33 +0000 Subject: [PATCH 297/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0) - [github.com/asottile/pyupgrade: v2.33.0 → v2.34.0](https://github.com/asottile/pyupgrade/compare/v2.33.0...v2.34.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0135a7a9..3d92a7d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testsuite/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-yaml - id: debug-statements @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.33.0 + rev: v2.34.0 hooks: - id: pyupgrade args: [--py36-plus] From 856115397248db361a430ae3d6065342288248d1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 00:17:46 +0000 Subject: [PATCH 298/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.1.0 → v3.3.0](https://github.com/asottile/reorder_python_imports/compare/v3.1.0...v3.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d92a7d5..7f1e5aeb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.1.0 + rev: v3.3.0 hooks: - id: reorder-python-imports args: [--py36-plus] From b80783672014f7de213b5dec44f2d6f1be5cc482 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Jul 2022 23:19:26 +0000 Subject: [PATCH 299/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.3.0 → v3.8.1](https://github.com/asottile/reorder_python_imports/compare/v3.3.0...v3.8.1) - [github.com/asottile/pyupgrade: v2.34.0 → v2.37.1](https://github.com/asottile/pyupgrade/compare/v2.34.0...v2.37.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7f1e5aeb..a0541126 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.3.0 + rev: v3.8.1 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v2.37.1 hooks: - id: pyupgrade args: [--py36-plus] From 3af77d1b1030fae288899ce0c6f0c192844b94b4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Jul 2022 23:43:36 +0000 Subject: [PATCH 300/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.1 → v3.8.2](https://github.com/asottile/reorder_python_imports/compare/v3.8.1...v3.8.2) - [github.com/asottile/pyupgrade: v2.37.1 → v2.37.2](https://github.com/asottile/pyupgrade/compare/v2.37.1...v2.37.2) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0541126..9b2ef3ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.1 + rev: v3.8.2 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.37.1 + rev: v2.37.2 hooks: - id: pyupgrade args: [--py36-plus] From c14bd2aac8e370bc84048a97f17a1ed906523bf9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 30 Jul 2022 15:01:02 -0400 Subject: [PATCH 301/412] Revert "Merge pull request #1041 from asfaltboy/issue-830-e721-types-regex-incorrect" This reverts commit 8b5c964321776b2fe8dfd25f4f18db0ffbdbd281, reversing changes made to 9777ac5a8ea1ae14e70bfb27063e2e7c0daa06e3. --- pycodestyle.py | 13 +++++++++---- testsuite/E72.py | 9 +++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index f866dd4a..ee0a58f2 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -134,10 +134,8 @@ r'\s*(?(1)|(None|False|True))\b') COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+|:=)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') @@ -1446,6 +1444,13 @@ def comparison_type(logical_line, noqa): Okay: if isinstance(obj, int): E721: if type(obj) is type(1): + + When checking if an object is a string, keep in mind that it might + be a unicode string too! In Python 2.3, str and unicode have a + common base class, basestring, so you can do: + + Okay: if isinstance(obj, basestring): + Okay: if type(a1) is type(b1): """ match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: diff --git a/testsuite/E72.py b/testsuite/E72.py index 61e17eb2..d127ff77 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -4,7 +4,7 @@ #: E721 if type(res) != type(""): pass -#: Okay +#: E721 import types if res == types.IntType: @@ -47,6 +47,8 @@ pass if isinstance(res, types.MethodType): pass +if type(a) != type(b) or type(a) == type(ccc): + pass #: Okay def func_histype(a, b, c): pass @@ -79,11 +81,6 @@ def func_histype(a, b, c): except Exception: pass #: Okay -from . import custom_types as types - -red = types.ColorTypeRED -red is types.ColorType.RED -#: Okay from . import compute_type if compute_type(foo) == 5: From 57e39fa4f66707c305ead679e62d7ce1b7af9362 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 30 Jul 2022 15:26:26 -0400 Subject: [PATCH 302/412] Release 2.9.0 --- CHANGES.txt | 12 ++++++++++++ pycodestyle.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 49db29b2..f24ee348 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,18 @@ Changelog ========= +2.9.0 (2022-07-30) +------------------ + +Changes: + +* E221, E222, E223, E224: add support for ``:=`` operator. PR #1032. +* Drop python 2.7 / 3.5. +* E262: consider non-breaking spaces (``\xa0``) as whitespace. PR #1035. +* Improve performance of ``_is_binary_operator``. PR #1052. +* E275: requires whitespace around keywords. PR #1063. +* Add support for python 3.11. PR #1070. + 2.8.0 (2021-10-10) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index ee0a58f2..5c4d5f99 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -72,7 +72,7 @@ ): # pragma: no cover ( Date: Tue, 2 Aug 2022 01:02:55 +0000 Subject: [PATCH 303/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.37.2 → v2.37.3](https://github.com/asottile/pyupgrade/compare/v2.37.2...v2.37.3) - [github.com/pycqa/flake8: 4.0.1 → 5.0.3](https://github.com/pycqa/flake8/compare/4.0.1...5.0.3) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9b2ef3ff..4a25298a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,11 +13,11 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.37.2 + rev: v2.37.3 hooks: - id: pyupgrade args: [--py36-plus] - repo: https://github.com/pycqa/flake8 - rev: 4.0.1 + rev: 5.0.3 hooks: - id: flake8 From 43c5afaeef44a01b512ade340030ff4d7b0ba78e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 3 Aug 2022 18:26:51 -0400 Subject: [PATCH 304/412] allow parenthesized yield (generator-coroutines) --- pycodestyle.py | 1 + testsuite/E27.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index 5c4d5f99..ad3030af 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -495,6 +495,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): tok0.string not in SINGLETONS and tok0.string not in ('async', 'await') and not (tok0.string == 'except' and tok1.string == '*') and + not (tok0.string == 'yield' and tok1.string == ')') and tok1.string not in ':\n'): yield tok0.end, "E275 missing whitespace after keyword" diff --git a/testsuite/E27.py b/testsuite/E27.py index 5b476577..ca069306 100644 --- a/testsuite/E27.py +++ b/testsuite/E27.py @@ -52,3 +52,7 @@ #: E275:2:11 if True: assert(1) +#: Okay +def f(): + print((yield)) + x = (yield) From c33e852a5938b823b04dd981260bd1664c643385 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 3 Aug 2022 19:09:49 -0400 Subject: [PATCH 305/412] Release 2.9.1 --- CHANGES.txt | 7 +++++++ pycodestyle.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index f24ee348..f7fddf7f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Changelog ========= +2.9.1 (2022-08-03) +------------------ + +Changes: + +* E275: fix false positive for yield expressions. + 2.9.0 (2022-07-30) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index ad3030af..542272c3 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -72,7 +72,7 @@ ): # pragma: no cover ( Date: Tue, 9 Aug 2022 00:39:28 +0000 Subject: [PATCH 306/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 5.0.3 → 5.0.4](https://github.com/pycqa/flake8/compare/5.0.3...5.0.4) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a25298a..d0871973 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,6 @@ repos: - id: pyupgrade args: [--py36-plus] - repo: https://github.com/pycqa/flake8 - rev: 5.0.3 + rev: 5.0.4 hooks: - id: flake8 From b88a4c348ea88fd6489cbdc2f68ca05fcd644004 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 00:55:15 +0000 Subject: [PATCH 307/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.37.3 → v2.38.0](https://github.com/asottile/pyupgrade/compare/v2.37.3...v2.38.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d0871973..e3a9341b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.37.3 + rev: v2.38.0 hooks: - id: pyupgrade args: [--py36-plus] From a5768ea128eba3badc46a8e52a4f46fae78b3017 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 01:09:46 +0000 Subject: [PATCH 308/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.2 → v3.8.3](https://github.com/asottile/reorder_python_imports/compare/v3.8.2...v3.8.3) - [github.com/asottile/pyupgrade: v2.38.0 → v2.38.2](https://github.com/asottile/pyupgrade/compare/v2.38.0...v2.38.2) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3a9341b..e0e09fd5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.2 + rev: v3.8.3 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.38.0 + rev: v2.38.2 hooks: - id: pyupgrade args: [--py36-plus] From 6d136b80963ccfe9c89e971eed98e8c5d109b594 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 28 Sep 2022 20:26:11 -0400 Subject: [PATCH 309/412] make the example ignore a valid prefix resolves #1104 --- docs/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.rst b/docs/intro.rst index 2f108138..3be9d8ed 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -200,7 +200,7 @@ Example:: [pycodestyle] count = False - ignore = E226,E302,E41 + ignore = E226,E302,E71 max-line-length = 160 statistics = True From b74e8b91e11360782223fd0da79941fc4efaec6b Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Sat, 8 Oct 2022 23:50:50 -0400 Subject: [PATCH 310/412] E231 should work with tuples in brackets --- pycodestyle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 542272c3..2119d06c 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -506,6 +506,7 @@ def missing_whitespace(logical_line): Okay: [a, b] Okay: (3,) + Okay: a[3,] = 1 Okay: a[1:4] Okay: a[:4] Okay: a[1:] @@ -523,7 +524,7 @@ def missing_whitespace(logical_line): if char == ':' and before.count('[') > before.count(']') and \ before.rfind('{') < before.rfind('['): continue # Slice syntax, no space required - if char == ',' and next_char == ')': + if char == ',' and next_char in ')]': continue # Allow tuple with only one element: (3,) if char == ':' and next_char == '=' and sys.version_info >= (3, 8): continue # Allow assignment expression From 15f6d7bdf57b908cc6e544816206e8b15aad307f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 02:28:05 +0000 Subject: [PATCH 311/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.3 → v3.8.4](https://github.com/asottile/reorder_python_imports/compare/v3.8.3...v3.8.4) - [github.com/asottile/pyupgrade: v2.38.2 → v3.1.0](https://github.com/asottile/pyupgrade/compare/v2.38.2...v3.1.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0e09fd5..32f46853 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.3 + rev: v3.8.4 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.38.2 + rev: v3.1.0 hooks: - id: pyupgrade args: [--py36-plus] From b2cd3bcc71b2f4b043129e6be5242d14d29e10db Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 01:25:58 +0000 Subject: [PATCH 312/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.4 → v3.8.5](https://github.com/asottile/reorder_python_imports/compare/v3.8.4...v3.8.5) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 32f46853..cd9567a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.4 + rev: v3.8.5 hooks: - id: reorder-python-imports args: [--py36-plus] From 80c5a50ccfb2fba29594901c02b5a9c4689f7f92 Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Tue, 18 Oct 2022 00:21:49 -0400 Subject: [PATCH 313/412] Delete W601 -> W604 warnings --- README.rst | 4 ---- docs/intro.rst | 12 ----------- pycodestyle.py | 53 ------------------------------------------------ testsuite/W60.py | 15 -------------- 4 files changed, 84 deletions(-) diff --git a/README.rst b/README.rst index c71b933e..2f6ddb87 100644 --- a/README.rst +++ b/README.rst @@ -65,11 +65,9 @@ Example usage and output optparse.py:69:11: E401 multiple imports on one line optparse.py:77:1: E302 expected 2 blank lines, found 1 optparse.py:88:5: E301 expected 1 blank line, found 0 - optparse.py:222:34: W602 deprecated form of raising exception optparse.py:347:31: E211 whitespace before '(' optparse.py:357:17: E201 whitespace after '{' optparse.py:472:29: E221 multiple spaces before operator - optparse.py:544:21: W601 .has_key() is deprecated, use 'in' You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: @@ -97,8 +95,6 @@ Or you can display how often each error was found:: 165 E303 too many blank lines (4) 325 E401 multiple imports on one line 3615 E501 line too long (82 characters) - 612 W601 .has_key() is deprecated, use 'in' - 1188 W602 deprecated form of raising exception Links ----- diff --git a/docs/intro.rst b/docs/intro.rst index 3be9d8ed..aba8a151 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -71,11 +71,9 @@ Example usage and output optparse.py:69:11: E401 multiple imports on one line optparse.py:77:1: E302 expected 2 blank lines, found 1 optparse.py:88:5: E301 expected 1 blank line, found 0 - optparse.py:222:34: W602 deprecated form of raising exception optparse.py:347:31: E211 whitespace before '(' optparse.py:357:17: E201 whitespace after '{' optparse.py:472:29: E221 multiple spaces before operator - optparse.py:544:21: W601 .has_key() is deprecated, use 'in' You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: @@ -103,8 +101,6 @@ Or you can display how often each error was found:: 165 E303 too many blank lines (4) 325 E401 multiple imports on one line 3615 E501 line too long (82 characters) - 612 W601 .has_key() is deprecated, use 'in' - 1188 W602 deprecated form of raising exception You can also make ``pycodestyle.py`` show the error text in different formats by using ``--format`` having options default/pylint/custom:: @@ -415,14 +411,6 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | **W6** | *Deprecation warning* | +------------+----------------------------------------------------------------------+ -| W601 | .has_key() is deprecated, use 'in' | -+------------+----------------------------------------------------------------------+ -| W602 | deprecated form of raising exception | -+------------+----------------------------------------------------------------------+ -| W603 | '<>' is deprecated, use '!=' | -+------------+----------------------------------------------------------------------+ -| W604 | backticks are deprecated, use 'repr()' | -+------------+----------------------------------------------------------------------+ | W605 | invalid escape sequence '\x' | +------------+----------------------------------------------------------------------+ | W606 | 'async' and 'await' are reserved keywords starting with Python 3.7 | diff --git a/pycodestyle.py b/pycodestyle.py index 2119d06c..4a02ff26 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1563,59 +1563,6 @@ def ambiguous_identifier(logical_line, tokens): prev_start = start -@register_check -def python_3000_has_key(logical_line, noqa): - r"""The {}.has_key() method is removed in Python 3: use the 'in' - operator. - - Okay: if "alph" in d:\n print d["alph"] - W601: assert d.has_key('alph') - """ - pos = logical_line.find('.has_key(') - if pos > -1 and not noqa: - yield pos, "W601 .has_key() is deprecated, use 'in'" - - -@register_check -def python_3000_raise_comma(logical_line): - r"""When raising an exception, use "raise ValueError('message')". - - The older form is removed in Python 3. - - Okay: raise DummyError("Message") - W602: raise DummyError, "Message" - """ - match = RAISE_COMMA_REGEX.match(logical_line) - if match and not RERAISE_COMMA_REGEX.match(logical_line): - yield match.end() - 1, "W602 deprecated form of raising exception" - - -@register_check -def python_3000_not_equal(logical_line): - r"""New code should always use != instead of <>. - - The older syntax is removed in Python 3. - - Okay: if a != 'no': - W603: if a <> 'no': - """ - pos = logical_line.find('<>') - if pos > -1: - yield pos, "W603 '<>' is deprecated, use '!='" - - -@register_check -def python_3000_backticks(logical_line): - r"""Use repr() instead of backticks in Python 3. - - Okay: val = repr(1 + 2) - W604: val = `1 + 2` - """ - pos = logical_line.find('`') - if pos > -1: - yield pos, "W604 backticks are deprecated, use 'repr()'" - - @register_check def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): r"""Invalid escape sequences are deprecated in Python 3.6. diff --git a/testsuite/W60.py b/testsuite/W60.py index 5003677d..f44552d9 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -1,18 +1,3 @@ -#: W601 -if a.has_key("b"): - print a -#: W602 -raise DummyError, "Message" -#: W602 -raise ValueError, "hello %s %s" % (1, 2) -#: Okay -raise type_, val, tb -raise Exception, Exception("f"), t -#: W603 -if x <> 0: - x = 0 -#: W604 -val = `1 + 2` #: W605:1:10 regex = '\.png$' #: W605:2:1 From 9a0bc6eecccdc50e725c4ca9f866d6e13fa9e298 Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Fri, 7 Oct 2022 00:21:38 -0400 Subject: [PATCH 314/412] E741 should work with lambdas --- pycodestyle.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 4a02ff26..0e5694be 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1494,14 +1494,18 @@ def ambiguous_identifier(logical_line, tokens): E741: I = 42 Variables can be bound in several other contexts, including class - and function definitions, 'global' and 'nonlocal' statements, - exception handlers, and 'with' and 'for' statements. + and function definitions, lambda functions, 'global' and 'nonlocal' + statements, exception handlers, and 'with' and 'for' statements. In addition, we have a special handling for function parameters. Okay: except AttributeError as o: Okay: with lock as L: Okay: foo(l=12) + Okay: foo(l=I) Okay: for a in foo(l=12): + Okay: lambda arg: arg * l + Okay: lambda a=l[I:5]: None + Okay: lambda x=a.I: None E741: except AttributeError as O: E741: with lock as l: E741: global I @@ -1510,17 +1514,23 @@ def ambiguous_identifier(logical_line, tokens): E741: def foo(l=12): E741: l = foo(l=12) E741: for l in range(10): + E741: [l for l in lines if l] + E741: lambda l: None + E741: lambda a=x[1:5], l: None + E741: lambda **l: + E741: def f(**l): E742: class I(object): E743: def l(x): """ - is_func_def = False # Set to true if 'def' is found + is_func_def = False # Set to true if 'def' or 'lambda' is found parameter_parentheses_level = 0 idents_to_avoid = ('l', 'O', 'I') prev_type, prev_text, prev_start, prev_end, __ = tokens[0] - for token_type, text, start, end, line in tokens[1:]: + for index in range(1, len(tokens)): + token_type, text, start, end, line = tokens[index] ident = pos = None # find function definitions - if prev_text == 'def': + if prev_text in {'def', 'lambda'}: is_func_def = True # update parameter parentheses level if parameter_parentheses_level == 0 and \ @@ -1545,11 +1555,15 @@ def ambiguous_identifier(logical_line, tokens): if text in idents_to_avoid: ident = text pos = start - # function parameter definitions - if is_func_def: - if text in idents_to_avoid: - ident = text - pos = start + # function / lambda parameter definitions + if ( + is_func_def and + index < len(tokens) - 1 and tokens[index + 1][1] in ':,=)' and + prev_text in {'lambda', ',', '*', '**', '('} and + text in idents_to_avoid + ): + ident = text + pos = start if prev_text == 'class': if text in idents_to_avoid: yield start, "E742 ambiguous class definition '%s'" % text From cc42c160abbd9d7452973d43537cf690863711b6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 30 Oct 2022 14:49:37 -0400 Subject: [PATCH 315/412] remove some unused regexes --- pycodestyle.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0e5694be..29f93ea8 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -124,8 +124,6 @@ BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines'] INDENT_REGEX = re.compile(r'([ \t]*)') -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'[\[({][ \t]|[ \t][\]}),;:](?!=)') From c09994f46a62b3f3e036bfe99ee95d38484463b6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 01:55:45 +0000 Subject: [PATCH 316/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.5 → v3.9.0](https://github.com/asottile/reorder_python_imports/compare/v3.8.5...v3.9.0) - [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cd9567a4..39acafe1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.5 + rev: v3.9.0 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.1.0 + rev: v3.2.0 hooks: - id: pyupgrade args: [--py36-plus] From a13131627c67db9e422c203f391a74b3ca7315a8 Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Mon, 31 Oct 2022 22:44:15 -0400 Subject: [PATCH 317/412] Fix false positive with E741 --- pycodestyle.py | 3 ++- testsuite/python38.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 29f93ea8..a105a0f3 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1504,6 +1504,7 @@ def ambiguous_identifier(logical_line, tokens): Okay: lambda arg: arg * l Okay: lambda a=l[I:5]: None Okay: lambda x=a.I: None + Okay: if l >= 12: E741: except AttributeError as O: E741: with lock as l: E741: global I @@ -1542,7 +1543,7 @@ def ambiguous_identifier(logical_line, tokens): elif text == ')': parameter_parentheses_level -= 1 # identifiers on the lhs of an assignment operator - if token_type == tokenize.OP and '=' in text and \ + if token_type == tokenize.OP and text in {'=', ':='} and \ parameter_parentheses_level == 0: if prev_text in idents_to_avoid: ident = prev_text diff --git a/testsuite/python38.py b/testsuite/python38.py index 8bf0d4d0..faf9aa7a 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -53,3 +53,6 @@ def f3( #: E221:1:6 E221:1:19 if (x := 1) == (y := 2): pass +#: E741 +while l := 1: + pass From 10946760efb082c6616d09abfe1d6ea553704434 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 01:56:28 +0000 Subject: [PATCH 318/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.2.0 → v3.2.2](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.2.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39acafe1..7c927561 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.2.2 hooks: - id: pyupgrade args: [--py36-plus] From 56dac1348f44d2e1f0bca085d415658cd3ee9874 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Nov 2022 13:51:42 -0500 Subject: [PATCH 319/412] fix reporting of ambiguous identifier after parameter list --- pycodestyle.py | 24 ++++++++++-------------- testsuite/E74.py | 4 ++++ testsuite/python38.py | 3 +++ 3 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 testsuite/E74.py diff --git a/pycodestyle.py b/pycodestyle.py index a105a0f3..7ee2375a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1522,7 +1522,8 @@ def ambiguous_identifier(logical_line, tokens): E743: def l(x): """ is_func_def = False # Set to true if 'def' or 'lambda' is found - parameter_parentheses_level = 0 + seen_colon = False # set to true if we're done with function parameters + brace_depth = 0 idents_to_avoid = ('l', 'O', 'I') prev_type, prev_text, prev_start, prev_end, __ = tokens[0] for index in range(1, len(tokens)): @@ -1531,20 +1532,15 @@ def ambiguous_identifier(logical_line, tokens): # find function definitions if prev_text in {'def', 'lambda'}: is_func_def = True + elif is_func_def and text == ':' and brace_depth == 0: + seen_colon = True # update parameter parentheses level - if parameter_parentheses_level == 0 and \ - prev_type == tokenize.NAME and \ - token_type == tokenize.OP and text == '(': - parameter_parentheses_level = 1 - elif parameter_parentheses_level > 0 and \ - token_type == tokenize.OP: - if text == '(': - parameter_parentheses_level += 1 - elif text == ')': - parameter_parentheses_level -= 1 + if text in '([{': + brace_depth += 1 + elif text in ')]}': + brace_depth -= 1 # identifiers on the lhs of an assignment operator - if token_type == tokenize.OP and text in {'=', ':='} and \ - parameter_parentheses_level == 0: + if text == ':=' or (text == '=' and brace_depth == 0): if prev_text in idents_to_avoid: ident = prev_text pos = prev_start @@ -1557,6 +1553,7 @@ def ambiguous_identifier(logical_line, tokens): # function / lambda parameter definitions if ( is_func_def and + not seen_colon and index < len(tokens) - 1 and tokens[index + 1][1] in ':,=)' and prev_text in {'lambda', ',', '*', '**', '('} and text in idents_to_avoid @@ -1571,7 +1568,6 @@ def ambiguous_identifier(logical_line, tokens): yield start, "E743 ambiguous function definition '%s'" % text if ident: yield pos, "E741 ambiguous variable name '%s'" % ident - prev_type = token_type prev_text = text prev_start = start diff --git a/testsuite/E74.py b/testsuite/E74.py new file mode 100644 index 00000000..93d6c131 --- /dev/null +++ b/testsuite/E74.py @@ -0,0 +1,4 @@ +#: E741:1:8 +lambda l: dict(zip(l, range(len(l)))) +#: E741:1:7 E704:1:1 +def f(l): print(l, l, l) diff --git a/testsuite/python38.py b/testsuite/python38.py index faf9aa7a..536448d8 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -56,3 +56,6 @@ def f3( #: E741 while l := 1: pass +#: E741 +if (l := 1): + pass From 956ab1fa7aeffdca4eb1c7b625f8921883e1bc0d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Nov 2022 14:06:14 -0500 Subject: [PATCH 320/412] remove some leftover python 2 compat --- pycodestyle.py | 12 ++++-------- testsuite/test_shell.py | 7 ++++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 7ee2375a..b75eed00 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -47,7 +47,9 @@ 900 syntax error """ import bisect +import configparser import inspect +import io import keyword import os import re @@ -59,12 +61,6 @@ from functools import lru_cache from optparse import OptionParser -try: - from configparser import RawConfigParser - from io import TextIOWrapper -except ImportError: - from ConfigParser import RawConfigParser - # this is a performance hack. see https://bugs.python.org/issue43014 if ( sys.version_info < (3, 10) and @@ -1769,7 +1765,7 @@ def readlines(filename): def stdin_get_value(): """Read the value from stdin.""" - return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() + return io.TextIOWrapper(sys.stdin.buffer, errors='ignore').read() noqa = lru_cache(512)(re.compile(r'# no(?:qa|pep8)\b', re.I).search) @@ -2558,7 +2554,7 @@ def read_config(options, args, arglist, parser): merged together (in that order) using the read method of ConfigParser. """ - config = RawConfigParser() + config = configparser.RawConfigParser() cli_conf = options.config diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index 059d8823..a2fa35ea 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import configparser import os.path import sys import unittest @@ -15,7 +16,7 @@ def setUp(self): self._saved_stdout = sys.stdout self._saved_stderr = sys.stderr self._saved_pconfig = pycodestyle.PROJECT_CONFIG - self._saved_cpread = pycodestyle.RawConfigParser._read + self._saved_cpread = configparser.RawConfigParser._read self._saved_stdin_get_value = pycodestyle.stdin_get_value self._config_filenames = [] self.stdin = '' @@ -25,7 +26,7 @@ def setUp(self): def fake_config_parser_read(cp, fp, filename): self._config_filenames.append(filename) - pycodestyle.RawConfigParser._read = fake_config_parser_read + configparser.RawConfigParser._read = fake_config_parser_read pycodestyle.stdin_get_value = self.stdin_get_value def tearDown(self): @@ -33,7 +34,7 @@ def tearDown(self): sys.stdout = self._saved_stdout sys.stderr = self._saved_stderr pycodestyle.PROJECT_CONFIG = self._saved_pconfig - pycodestyle.RawConfigParser._read = self._saved_cpread + configparser.RawConfigParser._read = self._saved_cpread pycodestyle.stdin_get_value = self._saved_stdin_get_value def stdin_get_value(self): From 798d620cd461a1e7abac1cbba5aab66274aa3229 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Nov 2022 14:11:37 -0500 Subject: [PATCH 321/412] fix ambiguous identifiers in lambda bodies inside braces --- pycodestyle.py | 13 +++++++++---- testsuite/E74.py | 9 +++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 7ee2375a..6dc9142b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1521,7 +1521,7 @@ def ambiguous_identifier(logical_line, tokens): E742: class I(object): E743: def l(x): """ - is_func_def = False # Set to true if 'def' or 'lambda' is found + func_depth = None # set to brace depth if 'def' or 'lambda' is found seen_colon = False # set to true if we're done with function parameters brace_depth = 0 idents_to_avoid = ('l', 'O', 'I') @@ -1531,8 +1531,13 @@ def ambiguous_identifier(logical_line, tokens): ident = pos = None # find function definitions if prev_text in {'def', 'lambda'}: - is_func_def = True - elif is_func_def and text == ':' and brace_depth == 0: + func_depth = brace_depth + seen_colon = False + elif ( + func_depth is not None and + text == ':' and + brace_depth == func_depth + ): seen_colon = True # update parameter parentheses level if text in '([{': @@ -1552,7 +1557,7 @@ def ambiguous_identifier(logical_line, tokens): pos = start # function / lambda parameter definitions if ( - is_func_def and + func_depth is not None and not seen_colon and index < len(tokens) - 1 and tokens[index + 1][1] in ':,=)' and prev_text in {'lambda', ',', '*', '**', '('} and diff --git a/testsuite/E74.py b/testsuite/E74.py index 93d6c131..9bb4c581 100644 --- a/testsuite/E74.py +++ b/testsuite/E74.py @@ -2,3 +2,12 @@ lambda l: dict(zip(l, range(len(l)))) #: E741:1:7 E704:1:1 def f(l): print(l, l, l) +#: E741:2:12 +x = ( + lambda l: dict(zip(l, range(len(l)))), +) +#: E741:2:12 E741:3:12 +x = ( + lambda l: dict(zip(l, range(len(l)))), + lambda l: dict(zip(l, range(len(l)))), +) From 806bf5c7b556017fbc759f7576d2fb19aae8c464 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 23 Nov 2022 13:15:34 -0500 Subject: [PATCH 322/412] Release 2.10.0 --- CHANGES.txt | 12 +++++++++++- pycodestyle.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f7fddf7f..9570eecf 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,12 +1,22 @@ Changelog ========= +2.10.0 (2022-11-23) +------------------- + +Changes: + +* E231: allow trailing comma inside 1-tuples in `[]`. PR #1108. +* W601, W602, W603, W604: removed (no longer relevant in python 3). PR #1111. +* E741: also apply to lambdas. PR #1106. +* E741: fix false positive for comparison operators. PR #1118. + 2.9.1 (2022-08-03) ------------------ Changes: -* E275: fix false positive for yield expressions. +* E275: fix false positive for yield expressions. PR #1091. 2.9.0 (2022-07-30) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index f06f9231..f5e05cfa 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Tue, 29 Nov 2022 04:04:49 +0000 Subject: [PATCH 323/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0) - [github.com/pycqa/flake8: 5.0.4 → 6.0.0](https://github.com/pycqa/flake8/compare/5.0.4...6.0.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c927561..39b7bfdd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testsuite/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-yaml - id: debug-statements @@ -18,6 +18,6 @@ repos: - id: pyupgrade args: [--py36-plus] - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 From 9c7d93f7cf9445ed0d8f62e29989b60fed408bec Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 02:22:42 +0000 Subject: [PATCH 324/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.2.2 → v3.3.0](https://github.com/asottile/pyupgrade/compare/v3.2.2...v3.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39b7bfdd..061253f7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.3.0 hooks: - id: pyupgrade args: [--py36-plus] From e20394e5cbd8d440c9c1868ddc25d036afcbfe45 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 13 Dec 2022 01:06:43 -0500 Subject: [PATCH 325/412] drop python3.6 github actions no longer supports it --- .github/workflows/main.yml | 14 +++++++------- .pre-commit-config.yaml | 4 ++-- setup.py | 5 +---- testsuite/test_api.py | 5 ++++- tox.ini | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 00f5e75c..b2eba4c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,10 +14,7 @@ jobs: py: 3.9 toxenv: py - os: ubuntu-latest - py: pypy3 - toxenv: py - - os: ubuntu-latest - py: 3.6 + py: pypy3.9 toxenv: py - os: ubuntu-latest py: 3.7 @@ -32,15 +29,18 @@ jobs: py: '3.10' toxenv: py - os: ubuntu-latest - py: '3.11-dev' + py: '3.11' + toxenv: py + - os: ubuntu-latest + py: '3.12-dev' toxenv: py - os: ubuntu-latest py: 3.9 toxenv: flake8 runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.py }} - run: pip install tox diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 061253f7..c477ffe3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,12 @@ repos: rev: v3.9.0 hooks: - id: reorder-python-imports - args: [--py36-plus] + args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade rev: v3.3.0 hooks: - id: pyupgrade - args: [--py36-plus] + args: [--py37-plus] - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: diff --git a/setup.py b/setup.py index fe96737b..2fe5a11b 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def get_long_description(): py_modules=['pycodestyle'], include_package_data=True, zip_safe=False, - python_requires='>=3.6', + python_requires='>=3.7', entry_points={ 'console_scripts': [ 'pycodestyle = pycodestyle:_main', @@ -45,9 +45,6 @@ def get_long_description(): 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 8dde32ff..38e34acf 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -329,7 +329,10 @@ def test_check_nullbytes(self): count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() - expected = "stdin:1:1: E901 ValueError" + if sys.version_info < (3, 12): + expected = "stdin:1:1: E901 ValueError" + else: + expected = "stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes" # noqa: E501 self.assertTrue(stdout.startswith(expected), msg='Output %r does not start with %r' % (stdout, expected)) diff --git a/tox.ini b/tox.ini index 18f1a4ff..f4a32d71 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py36, py37, py38, py39, py310, pypy3 +envlist = py, pypy3 skip_missing_interpreters = True [testenv] From dc23a7bfceead62b92f9da3f6436662e5391c0c9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 03:28:41 +0000 Subject: [PATCH 326/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.3.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.3.0...v3.3.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c477ffe3..d2aa5d19 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.0 + rev: v3.3.1 hooks: - id: pyupgrade args: [--py37-plus] From f43be155bda895d2cc0a9369c0b8c816ba123c9c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 07:14:02 +0000 Subject: [PATCH 327/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.3.1 → v3.3.2](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.3.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d2aa5d19..1a7b2b44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.3.2 hooks: - id: pyupgrade args: [--py37-plus] From 28de31778c2cdda96fdf2f0a590e302ef24c8995 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 08:01:52 +0000 Subject: [PATCH 328/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - https://github.com/asottile/reorder_python_imports → https://github.com/asottile/reorder-python-imports - [github.com/asottile/pyupgrade: v3.3.2 → v3.4.0](https://github.com/asottile/pyupgrade/compare/v3.3.2...v3.4.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a7b2b44..0a29d813 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,13 +7,13 @@ repos: - id: debug-statements - id: end-of-file-fixer - id: trailing-whitespace -- repo: https://github.com/asottile/reorder_python_imports +- repo: https://github.com/asottile/reorder-python-imports rev: v3.9.0 hooks: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.2 + rev: v3.4.0 hooks: - id: pyupgrade args: [--py37-plus] From 18cf7a27bca5af03bff67f22759867dd6f1372c8 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2023 20:16:42 -0400 Subject: [PATCH 329/412] remove 3.12 for now: it is not passing --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b2eba4c7..505a1d04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,9 +31,6 @@ jobs: - os: ubuntu-latest py: '3.11' toxenv: py - - os: ubuntu-latest - py: '3.12-dev' - toxenv: py - os: ubuntu-latest py: 3.9 toxenv: flake8 From 84839aa65cdd652aa7af6b74ac24fdae3d173fd9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2023 20:12:45 -0400 Subject: [PATCH 330/412] make testsuite helper for generating errors --- testsuite/support.py | 13 ++++++++ testsuite/test_blank_lines.py | 60 ++++++++++++++--------------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/testsuite/support.py b/testsuite/support.py index eb8b4436..a116c697 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import annotations + import os.path import re import sys from pycodestyle import Checker, BaseReport, StandardReport, readlines +from pycodestyle import StyleGuide SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -222,3 +225,13 @@ def run_tests(style): if options.testsuite: init_tests(style) return style.check_files() + + +def errors_from_src(src: str) -> list[str]: + guide = StyleGuide() + reporter = guide.init_report(InMemoryReport) + guide.input_file( + filename='in-memory-test-file.py', + lines=src.splitlines(True), + ) + return reporter.in_memory_errors diff --git a/testsuite/test_blank_lines.py b/testsuite/test_blank_lines.py index e239f8b7..fa36f236 100644 --- a/testsuite/test_blank_lines.py +++ b/testsuite/test_blank_lines.py @@ -6,7 +6,7 @@ import unittest import pycodestyle -from testsuite.support import InMemoryReport +from testsuite.support import errors_from_src class BlankLinesTestCase(unittest.TestCase): @@ -14,18 +14,6 @@ class BlankLinesTestCase(unittest.TestCase): Common code for running blank_lines tests. """ - def check(self, content): - """ - Run checks on `content` and return the the list of errors. - """ - sut = pycodestyle.StyleGuide() - reporter = sut.init_report(InMemoryReport) - sut.input_file( - filename='in-memory-test-file.py', - lines=content.splitlines(True), - ) - return reporter.in_memory_errors - def assertNoErrors(self, actual): """ Check that the actual result from the checker has no errors. @@ -43,7 +31,7 @@ def test_initial_no_blank(self): """ It will accept no blank lines at the start of the file. """ - result = self.check("""def some_function(): + result = errors_from_src("""def some_function(): pass """) @@ -54,7 +42,7 @@ def test_initial_lines_one_blank(self): It will accept 1 blank lines before the first line of actual code, even if in other places it asks for 2 """ - result = self.check(""" + result = errors_from_src(""" def some_function(): pass """) @@ -66,7 +54,7 @@ def test_initial_lines_two_blanks(self): It will accept 2 blank lines before the first line of actual code, as normal. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): pass @@ -79,7 +67,7 @@ def test_method_less_blank_lines(self): It will trigger an error when less than 1 blank lin is found before method definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. class X: def a(): @@ -96,7 +84,7 @@ def test_method_less_blank_lines_comment(self): It will trigger an error when less than 1 blank lin is found before method definition, ignoring comments. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. class X: def a(): @@ -114,7 +102,7 @@ def test_top_level_fewer_blank_lines(self): It will trigger an error when less 2 blank lines are found before top level definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. # Second line of comment. def some_function(): @@ -148,7 +136,7 @@ def test_top_level_more_blank_lines(self): It will trigger an error when more 2 blank lines are found before top level definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. # Second line of comment. @@ -179,7 +167,7 @@ def test_method_more_blank_lines(self): It will trigger an error when more than 1 blank line is found before method definition """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. class SomeCloseClass(object): @@ -211,7 +199,7 @@ def test_initial_lines_more_blank(self): It will trigger an error for more than 2 blank lines before the first line of actual code. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): @@ -224,7 +212,7 @@ def test_blank_line_between_decorator(self): It will trigger an error when the decorator is followed by a blank line. """ - result = self.check("""# First line. + result = errors_from_src("""# First line. @some_decorator @@ -247,7 +235,7 @@ def test_blank_line_decorator(self): It will accept the decorators which are adjacent to the function and method definition. """ - result = self.check("""# First line. + result = errors_from_src("""# First line. @another_decorator @@ -269,7 +257,7 @@ def test_top_level_fewer_follow_lines(self): It will trigger an error when less than 2 blank lines are found between a top level definitions and other top level code. """ - result = self.check(""" + result = errors_from_src(""" def a(): print('Something') @@ -285,7 +273,7 @@ def test_top_level_fewer_follow_lines_comments(self): found between a top level definitions and other top level code, even if we have comments before """ - result = self.check(""" + result = errors_from_src(""" def a(): print('Something') @@ -306,7 +294,7 @@ def test_top_level_good_follow_lines(self): It not trigger an error when 2 blank lines are found between a top level definitions and other top level code. """ - result = self.check(""" + result = errors_from_src(""" def a(): print('Something') @@ -326,7 +314,7 @@ def test_method_fewer_follow_lines(self): It will trigger an error when less than 1 blank line is found between a method and previous definitions. """ - result = self.check(""" + result = errors_from_src(""" def a(): x = 1 def b(): @@ -342,7 +330,7 @@ def test_method_nested_fewer_follow_lines(self): found between a method and previous definitions, even when nested. """ - result = self.check(""" + result = errors_from_src(""" def a(): x = 2 @@ -361,7 +349,7 @@ def test_method_nested_less_class(self): between a method and previous definitions, even when used to define a class. """ - result = self.check(""" + result = errors_from_src(""" def a(): x = 1 class C: @@ -377,7 +365,7 @@ def test_method_nested_ok(self): found between a method and previous definitions, even when nested. """ - result = self.check(""" + result = errors_from_src(""" def a(): x = 2 @@ -412,7 +400,7 @@ def test_initial_lines_one_blanks(self): It will accept less than 3 blank lines before the first line of actual code. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): @@ -426,7 +414,7 @@ def test_initial_lines_tree_blanks(self): It will accept 3 blank lines before the first line of actual code, as normal. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): @@ -440,7 +428,7 @@ def test_top_level_fewer_blank_lines(self): It will trigger an error when less 3 blank lines are found before top level definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. # Second line of comment. @@ -479,7 +467,7 @@ def test_top_level_more_blank_lines(self): It will trigger an error when more 2 blank lines are found before top level definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. # Second line of comment. @@ -513,7 +501,7 @@ def test_the_right_blanks(self): """ It will accept 3 blank for top level and 2 for nested. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): From 84937582b81d570b95354e33a746947f84462c55 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2023 21:28:57 -0400 Subject: [PATCH 331/412] add fix for muting FSTRING_MIDDLE in 3.12+ --- pycodestyle.py | 5 +++++ testsuite/python36.py | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 testsuite/python36.py diff --git a/pycodestyle.py b/pycodestyle.py index f5e05cfa..4a973724 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -2010,6 +2010,11 @@ def build_tokens_line(self): continue if token_type == tokenize.STRING: text = mute_string(text) + elif ( + sys.version_info >= (3, 12) and + token_type == tokenize.FSTRING_MIDDLE + ): + text = 'x' * len(text) if prev_row: (start_row, start_col) = start if prev_row != start_row: # different row diff --git a/testsuite/python36.py b/testsuite/python36.py new file mode 100644 index 00000000..94ec2dc5 --- /dev/null +++ b/testsuite/python36.py @@ -0,0 +1,2 @@ +#: Okay +f'{hello}:{world}' From 0b13fddbe352901070ed06906e5879ee22fdeee7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 08:01:43 +0000 Subject: [PATCH 332/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.4.0 → v3.6.0](https://github.com/asottile/pyupgrade/compare/v3.4.0...v3.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a29d813..ec7ddaa9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.4.0 + rev: v3.6.0 hooks: - id: pyupgrade args: [--py37-plus] From e8d84098da10d013ee686027e174814dbe4dd908 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2023 20:11:49 -0400 Subject: [PATCH 333/412] get testsuite passing on 3.12 --- .github/workflows/main.yml | 10 +++++- pycodestyle.py | 4 +-- testsuite/E10.py | 5 --- testsuite/E90.py | 10 ------ testsuite/python312.py | 9 ++++++ testsuite/test_E101.py | 18 +++++++++++ testsuite/test_E901.py | 29 ++++++++++++++++++ testsuite/test_api.py | 62 ++++++++++++++++---------------------- 8 files changed, 93 insertions(+), 54 deletions(-) create mode 100644 testsuite/python312.py create mode 100644 testsuite/test_E101.py create mode 100644 testsuite/test_E901.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 505a1d04..61c41971 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: py: 3.9 toxenv: py - os: ubuntu-latest - py: pypy3.9 + py: pypy3.10 toxenv: py - os: ubuntu-latest py: 3.7 @@ -31,6 +31,9 @@ jobs: - os: ubuntu-latest py: '3.11' toxenv: py + - os: ubuntu-latest + py: '3.12-dev' + toxenv: py - os: ubuntu-latest py: 3.9 toxenv: flake8 @@ -40,5 +43,10 @@ jobs: - uses: actions/setup-python@v4 with: python-version: ${{ matrix.py }} + if: matrix.py != '3.12-dev' + - uses: deadsnakes/action@v3.0.1 + with: + python-version: ${{ matrix.py }} + if: matrix.py == '3.12-dev' - run: pip install tox - run: tox -e ${{ matrix.toxenv }} diff --git a/pycodestyle.py b/pycodestyle.py index 4a973724..2ee3dac5 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -196,7 +196,6 @@ def tabs_or_spaces(physical_line, indent_char): These options are highly recommended! Okay: if a == 0:\n a = 1\n b = 1 - E101: if a == 0:\n a = 1\n\tb = 1 """ indent = INDENT_REGEX.match(physical_line).group(1) for offset, char in enumerate(indent): @@ -802,9 +801,10 @@ def whitespace_before_parameters(logical_line, tokens): (index < 2 or tokens[index - 2][1] != 'class') and # Allow "return (a.foo for a in range(5))" not keyword.iskeyword(prev_text) and - # 'match' and 'case' are only soft keywords ( sys.version_info < (3, 9) or + # 3.12+: type is a soft keyword but no braces after + prev_text == 'type' or not keyword.issoftkeyword(prev_text) ) ): diff --git a/testsuite/E10.py b/testsuite/E10.py index 7b425945..1a901124 100644 --- a/testsuite/E10.py +++ b/testsuite/E10.py @@ -1,8 +1,3 @@ -#: E101 W191 -for a in 'abc': - for b in 'xyz': - print a # indented with 8 spaces - print b # indented with 1 tab #: E101 E122 W191 W191 if True: pass diff --git a/testsuite/E90.py b/testsuite/E90.py index 2c18e9af..e0a10d04 100644 --- a/testsuite/E90.py +++ b/testsuite/E90.py @@ -1,6 +1,4 @@ #: E901 -} -#: E901 = [x #: E901 E101 W191 while True: @@ -8,14 +6,6 @@ pass except: print 'Whoops' -#: E122 E225 E251 E251 - -# Do not crash if code is invalid -if msg: - errmsg = msg % progress.get(cr_dbname)) - -def lasting(self, duration=300): - progress = self._progress.setdefault('foo', {} #: Okay # Issue #119 diff --git a/testsuite/python312.py b/testsuite/python312.py new file mode 100644 index 00000000..2b42833f --- /dev/null +++ b/testsuite/python312.py @@ -0,0 +1,9 @@ +#: Okay +# https://github.com/python/cpython/issues/90432: fixed in 3.12 +def foo(): + pas + +\ + +def bar(): + pass diff --git a/testsuite/test_E101.py b/testsuite/test_E101.py new file mode 100644 index 00000000..ba7d707a --- /dev/null +++ b/testsuite/test_E101.py @@ -0,0 +1,18 @@ +"""moved from testsuite files due to 3.12 making this a TokenError""" +import unittest +import sys + +from testsuite.support import errors_from_src + + +class E101Test(unittest.TestCase): + def test_E101(self): + errors = errors_from_src( + 'if True:\n' + '\tprint(1) # tabs\n' + ' print(2) # spaces\n' + ) + if sys.version_info >= (3, 12): + self.assertEqual(errors, ['W191:2:1', 'E901:3:28']) + else: + self.assertEqual(errors, ['W191:2:1', 'E101:3:1']) diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py new file mode 100644 index 00000000..3633822b --- /dev/null +++ b/testsuite/test_E901.py @@ -0,0 +1,29 @@ +"""moved from testsuite files due to 3.12 changing syntax errors""" +import unittest +import sys + +from testsuite.support import errors_from_src + + +class E901Test(unittest.TestCase): + def test_closing_brace(self): + errors = errors_from_src('}\n') + if sys.version_info < (3, 12): + self.assertEqual(errors, ['E901:2:1']) + else: + self.assertEqual(errors, []) + + def test_unclosed_brace(self): + src = '''\ +if msg: + errmsg = msg % progress.get(cr_dbname)) + +def lasting(self, duration=300): + progress = self._progress.setdefault('foo', {} +''' + errors = errors_from_src(src) + if sys.version_info < (3, 12): + expected = ['E122:4:1', 'E225:4:27', 'E251:5:13', 'E251:5:15'] + else: + expected = ['E122:4:1', 'E225:4:27', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 + self.assertEqual(errors, expected) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 38e34acf..fa4d8a03 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -329,15 +329,18 @@ def test_check_nullbytes(self): count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() - if sys.version_info < (3, 12): - expected = "stdin:1:1: E901 ValueError" + if sys.version_info < (3, 11, 4): + expected = ["stdin:1:1: E901 ValueError: source code string cannot contain null bytes"] # noqa: E501 + elif sys.version_info < (3, 12): + expected = ["stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes"] # noqa: E501 else: - expected = "stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes" # noqa: E501 - self.assertTrue(stdout.startswith(expected), - msg='Output %r does not start with %r' % - (stdout, expected)) + expected = [ + "stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes", # noqa: E501 + "stdin:1:1: E901 TokenError: source code cannot contain null bytes", # noqa: E501 + ] + self.assertEqual(stdout.splitlines(), expected) self.assertFalse(sys.stderr) - self.assertEqual(count_errors, 1) + self.assertEqual(count_errors, len(expected)) def test_styleguide_unmatched_triple_quotes(self): pycodestyle.register_check(DummyChecker, ['Z701']) @@ -350,35 +353,22 @@ def test_styleguide_unmatched_triple_quotes(self): pep8style.input_file('stdin', lines=lines) stdout = sys.stdout.getvalue() - expected = 'stdin:2:5: E901 TokenError: EOF in multi-line string' - self.assertTrue(expected in stdout) - - def test_styleguide_continuation_line_outdented(self): - pycodestyle.register_check(DummyChecker, ['Z701']) - lines = [ - 'def foo():\n', - ' pass\n', - '\n', - '\\\n', - '\n', - 'def bar():\n', - ' pass\n', - ] - - pep8style = pycodestyle.StyleGuide() - count_errors = pep8style.input_file('stdin', lines=lines) - self.assertEqual(count_errors, 2) - stdout = sys.stdout.getvalue() - expected = ( - 'stdin:6:1: ' - 'E122 continuation line missing indentation or outdented' - ) - self.assertTrue(expected in stdout) - expected = 'stdin:6:1: E302 expected 2 blank lines, found 1' - self.assertTrue(expected in stdout) - - # TODO: runner - # TODO: input_file + if sys.version_info < (3, 10): + expected = [ + 'stdin:2:5: E901 TokenError: EOF in multi-line string', + 'stdin:2:26: E901 SyntaxError: EOF while scanning triple-quoted string literal', # noqa: E501 + ] + elif sys.version_info < (3, 12): + expected = [ + 'stdin:2:5: E901 TokenError: EOF in multi-line string', + 'stdin:2:6: E901 SyntaxError: unterminated triple-quoted string literal (detected at line 2)', # noqa: E501 + ] + else: + expected = [ + 'stdin:2:6: E901 SyntaxError: unterminated triple-quoted string literal (detected at line 2)', # noqa: E501 + 'stdin:2:6: E901 TokenError: EOF in multi-line string', + ] + self.assertEqual(stdout.splitlines(), expected) def test_styleguides_other_indent_size(self): pycodestyle.register_check(DummyChecker, ['Z701']) From 0d786b43a09243d7655831d0b6c4a94a8d7c7581 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 17 Jun 2023 09:22:31 -0400 Subject: [PATCH 334/412] add test file for new 3.12 syntax --- testsuite/python312.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/testsuite/python312.py b/testsuite/python312.py index 2b42833f..d2e73bc3 100644 --- a/testsuite/python312.py +++ b/testsuite/python312.py @@ -7,3 +7,23 @@ def foo(): def bar(): pass +#: Okay +# new type aliases +type X = int | str +type Y[T] = list[T] +type Z[T: str] = list[T] +#: Okay +# new generics +def f[T](x: T) -> T: + pass + + +def g[T: str, U: int](x: T, y: U) -> dict[T, U]: + pass +#: Okay +# new nested f-strings +f'{ + thing +} {f'{other} {thing}'}' +#: E201:1:4 E202:1:17 +f'{ an_error_now }' From 6fddf7399d70627c46d1cc82bb3c02da2d708ec4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 17 Jun 2023 09:58:23 -0400 Subject: [PATCH 335/412] 3.12: format specs are not an error --- pycodestyle.py | 53 +++++++++++++++++++++++++++--------------- testsuite/E12.py | 2 +- testsuite/python312.py | 2 ++ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 2ee3dac5..f9c236a1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -150,6 +150,13 @@ DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ") BLANK_EXCEPT_REGEX = re.compile(r"except\s*:") +if sys.version_info >= (3, 12): + FSTRING_START = tokenize.FSTRING_START + FSTRING_MIDDLE = tokenize.FSTRING_MIDDLE + FSTRING_END = tokenize.FSTRING_END +else: + FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1 + _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -494,7 +501,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): @register_check -def missing_whitespace(logical_line): +def missing_whitespace(logical_line, tokens): r"""Each comma, semicolon or colon should be followed by whitespace. Okay: [a, b] @@ -508,20 +515,31 @@ def missing_whitespace(logical_line): E231: foo(bar,baz) E231: [{'a':'b'}] """ - line = logical_line - for index in range(len(line) - 1): - char = line[index] - 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 next_char in ')]': - 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 + brace_stack = [] + for tok in tokens: + if tok.type == tokenize.OP and tok.string in {'[', '(', '{'}: + brace_stack.append(tok.string) + elif tok.type == FSTRING_START: + brace_stack.append('f') + elif brace_stack: + if tok.type == tokenize.OP and tok.string in {']', ')', '}'}: + brace_stack.pop() + elif tok.type == FSTRING_END: + brace_stack.pop() + + if tok.type == tokenize.OP and tok.string in {',', ';', ':'}: + next_char = tok.line[tok.end[1]:tok.end[1] + 1] + if next_char not in WHITESPACE and next_char not in '\r\n': + # slice + if tok.string == ':' and brace_stack[-1:] == ['[']: + continue + # 3.12+ fstring format specifier + elif tok.string == ':' and brace_stack[-2:] == ['f', '{']: + continue + # tuple (and list for some reason?) + elif tok.string == ',' and next_char in ')]': + continue + yield tok.end, f'E231 missing whitespace after {tok.string!r}' @register_check @@ -2010,10 +2028,7 @@ def build_tokens_line(self): continue if token_type == tokenize.STRING: text = mute_string(text) - elif ( - sys.version_info >= (3, 12) and - token_type == tokenize.FSTRING_MIDDLE - ): + elif token_type == FSTRING_MIDDLE: text = 'x' * len(text) if prev_row: (start_row, start_col) = start diff --git a/testsuite/E12.py b/testsuite/E12.py index 968382c3..241bad0d 100644 --- a/testsuite/E12.py +++ b/testsuite/E12.py @@ -366,7 +366,7 @@ def example_issue254(): # more stuff ) ) -#: E701:1:8 E122:2:1 E203:4:8 E128:5:1 +#: E701:1:8 E231:1:9 E122:2:1 E203:4:8 E128:5:1 if True:\ print(True) diff --git a/testsuite/python312.py b/testsuite/python312.py index d2e73bc3..aabb6a42 100644 --- a/testsuite/python312.py +++ b/testsuite/python312.py @@ -27,3 +27,5 @@ def g[T: str, U: int](x: T, y: U) -> dict[T, U]: } {f'{other} {thing}'}' #: E201:1:4 E202:1:17 f'{ an_error_now }' +#: Okay +f'{x:02x}' From 2f040e85af2338afc875682ca16c68174fd405b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 08:45:55 +0000 Subject: [PATCH 336/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.9.0 → v3.10.0](https://github.com/asottile/reorder-python-imports/compare/v3.9.0...v3.10.0) - [github.com/asottile/pyupgrade: v3.6.0 → v3.7.0](https://github.com/asottile/pyupgrade/compare/v3.6.0...v3.7.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec7ddaa9..90256d6c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.9.0 + rev: v3.10.0 hooks: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.6.0 + rev: v3.7.0 hooks: - id: pyupgrade args: [--py37-plus] From f661e95ee8a0507e2575b6bb4f731f8b4f24c3b8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jul 2023 09:40:10 +0000 Subject: [PATCH 337/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.7.0 → v3.8.0](https://github.com/asottile/pyupgrade/compare/v3.7.0...v3.8.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 90256d6c..757b2c07 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.7.0 + rev: v3.8.0 hooks: - id: pyupgrade args: [--py37-plus] From 233b9002a7d27a957c0c7eaf9022d884c8a87d0a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:16:05 +0000 Subject: [PATCH 338/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.8.0 → v3.9.0](https://github.com/asottile/pyupgrade/compare/v3.8.0...v3.9.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 757b2c07..f54a2d22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.8.0 + rev: v3.9.0 hooks: - id: pyupgrade args: [--py37-plus] From 9f6a18169bb38f33a6893a32033db5ffb61c85f7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 30 Jul 2022 15:13:05 -0400 Subject: [PATCH 339/412] Revert "Merge pull request #1085 from PyCQA/revert-1041" This reverts commit ab806f3f9133ca24366b6254e499f0363f6bf5ec, reversing changes made to d3566623bb451f6c7f1b65cc4f8538d2540da9e6. --- pycodestyle.py | 13 ++++--------- testsuite/E72.py | 9 ++++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index f9c236a1..93638aa8 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -128,8 +128,10 @@ r'\s*(?(1)|(None|False|True))\b') COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+|:=)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') @@ -1458,13 +1460,6 @@ def comparison_type(logical_line, noqa): Okay: if isinstance(obj, int): E721: if type(obj) is type(1): - - When checking if an object is a string, keep in mind that it might - be a unicode string too! In Python 2.3, str and unicode have a - common base class, basestring, so you can do: - - Okay: if isinstance(obj, basestring): - Okay: if type(a1) is type(b1): """ match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: diff --git a/testsuite/E72.py b/testsuite/E72.py index d127ff77..61e17eb2 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -4,7 +4,7 @@ #: E721 if type(res) != type(""): pass -#: E721 +#: Okay import types if res == types.IntType: @@ -47,8 +47,6 @@ pass if isinstance(res, types.MethodType): pass -if type(a) != type(b) or type(a) == type(ccc): - pass #: Okay def func_histype(a, b, c): pass @@ -81,6 +79,11 @@ def func_histype(a, b, c): except Exception: pass #: Okay +from . import custom_types as types + +red = types.ColorTypeRED +red is types.ColorType.RED +#: Okay from . import compute_type if compute_type(foo) == 5: From ac223bde3970c50d679ee4dae6e2b3b8f5fad3c2 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 30 Jul 2022 14:48:12 -0400 Subject: [PATCH 340/412] allow `is` and `is not` for type compares --- pycodestyle.py | 6 +++--- testsuite/E72.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 93638aa8..876fc0db 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -129,8 +129,8 @@ COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+|:=)(\s*)') @@ -1459,7 +1459,7 @@ def comparison_type(logical_line, noqa): Do not compare types directly. Okay: if isinstance(obj, int): - E721: if type(obj) is type(1): + E721: if type(obj) == type(1): """ match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: diff --git a/testsuite/E72.py b/testsuite/E72.py index 61e17eb2..ac55a958 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -9,7 +9,7 @@ if res == types.IntType: pass -#: E721 +#: Okay import types if type(res) is not types.ListType: @@ -26,9 +26,9 @@ assert type(res) == type((0)) #: E721 assert type(res) != type((1, )) -#: E721 +#: Okay assert type(res) is type((1, )) -#: E721 +#: Okay assert type(res) is not type((1, )) #: E211 E721 assert type(res) == type ([2, ]) From 5f24f6dafb805e7ebb19abd9f1887d30d2711aac Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 13:11:50 -0400 Subject: [PATCH 341/412] 3.12: handle multiline FSTRING_MIDDLE --- pycodestyle.py | 5 ++++- testsuite/E50.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 876fc0db..9c71c573 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -2119,7 +2119,10 @@ def maybe_check_physical(self, token, prev_physical): self.check_physical(prev_physical) else: self.check_physical(token[4]) - elif token[0] == tokenize.STRING and '\n' in token[1]: + elif ( + token[0] in {tokenize.STRING, FSTRING_MIDDLE} and + '\n' in token[1] + ): # Less obviously, a string that contains newlines is a # multiline string, either triple-quoted or with internal # newlines backslash-escaped. Check every physical line in diff --git a/testsuite/E50.py b/testsuite/E50.py index bcf3bdce..ab7ddd45 100644 --- a/testsuite/E50.py +++ b/testsuite/E50.py @@ -69,6 +69,11 @@ #: E501 W505 '''same thing, but this time without a terminal newline in the string long long long long long long long long long long long long long long long long line''' +#: E501 +if True: + x = f""" + covdefaults>=1.2; python_version == '2.7' or python_version == '{py_ver}' + """ # # issue 224 (unavoidable long lines in docstrings) #: Okay From 933eed867163b3e4da2b99b6473751cc25eaba06 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 13:38:26 -0400 Subject: [PATCH 342/412] drop python 3.7 support --- .github/workflows/main.yml | 3 --- .pre-commit-config.yaml | 4 ++-- pycodestyle.py | 17 ++++++++--------- setup.py | 2 +- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 61c41971..393067b1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,6 @@ jobs: - os: ubuntu-latest py: pypy3.10 toxenv: py - - os: ubuntu-latest - py: 3.7 - toxenv: py - os: ubuntu-latest py: 3.8 toxenv: py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f54a2d22..4a68e31a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,12 @@ repos: rev: v3.10.0 hooks: - id: reorder-python-imports - args: [--py37-plus] + args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade rev: v3.9.0 hooks: - id: pyupgrade - args: [--py37-plus] + args: [--py38-plus] - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: diff --git a/pycodestyle.py b/pycodestyle.py index 9c71c573..1f2f2344 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -66,7 +66,7 @@ sys.version_info < (3, 10) and callable(getattr(tokenize, '_compile', None)) ): # pragma: no cover (>', '**', '*', '+', '-']) ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@']) WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) -ASSIGNMENT_EXPRESSION_OP = [':='] if sys.version_info >= (3, 8) else [] WS_NEEDED_OPERATORS = frozenset([ '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', - 'and', 'in', 'is', 'or', '->'] + - ASSIGNMENT_EXPRESSION_OP) + 'and', 'in', 'is', 'or', '->', ':=']) WHITESPACE = frozenset(' \t\xa0') NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT]) @@ -1219,11 +1217,12 @@ def compound_statements(logical_line): counts = {char: 0 for char in '{}[]()'} while -1 < found < last_char: update_counts(line[prev_found:found], counts) - if ((counts['{'] <= counts['}'] and # {'a': 1} (dict) - counts['['] <= counts[']'] and # [1:2] (slice) - counts['('] <= counts[')']) and # (annotation) - not (sys.version_info >= (3, 8) and - line[found + 1] == '=')): # assignment expression + if ( + counts['{'] <= counts['}'] and # {'a': 1} (dict) + counts['['] <= counts[']'] and # [1:2] (slice) + counts['('] <= counts[')'] and # (annotation) + line[found + 1] != '=' # assignment expression + ): lambda_kw = LAMBDA_REGEX.search(line, 0, found) if lambda_kw: before = line[:lambda_kw.start()].rstrip() diff --git a/setup.py b/setup.py index 2fe5a11b..ab735f9f 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def get_long_description(): py_modules=['pycodestyle'], include_package_data=True, zip_safe=False, - python_requires='>=3.7', + python_requires='>=3.8', entry_points={ 'console_scripts': [ 'pycodestyle = pycodestyle:_main', From 19b55a9d2b2fe02121e0daead468954cdbfacaee Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 14:10:16 -0400 Subject: [PATCH 343/412] remove python 2.x handling of <> and -> operators --- pycodestyle.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 1f2f2344..65c59b9e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -107,7 +107,7 @@ ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@']) WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) WS_NEEDED_OPERATORS = frozenset([ - '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', + '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<', '>', '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', 'and', 'in', 'is', 'or', '->', ':=']) WHITESPACE = frozenset(' \t\xa0') @@ -905,10 +905,6 @@ def missing_whitespace_around_operator(logical_line, tokens): yield (need_space[0], "E225 missing whitespace around operator") need_space = False - elif text == '>' and prev_text in ('<', '-'): - # Tolerate the "<>" operator, even if running Python 3 - # Deal with Python 3's annotated return value "->" - pass elif ( # def f(a, /, b): # ^ From 2c14709e7daa2633c6512901b9aeda4624fd6966 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 14:18:07 -0400 Subject: [PATCH 344/412] async/await are now always keywords pycodestyle no longer supports python 3.6 --- docs/intro.rst | 2 -- pycodestyle.py | 73 +----------------------------------------------- testsuite/W60.py | 52 ---------------------------------- 3 files changed, 1 insertion(+), 126 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index aba8a151..cdf5312b 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -413,8 +413,6 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | W605 | invalid escape sequence '\x' | +------------+----------------------------------------------------------------------+ -| W606 | 'async' and 'await' are reserved keywords starting with Python 3.7 | -+------------+----------------------------------------------------------------------+ **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, **E133**, diff --git a/pycodestyle.py b/pycodestyle.py index 1f2f2344..c8d2aa34 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -102,7 +102,7 @@ PyCF_ONLY_AST = 1024 SINGLETONS = frozenset(['False', 'None', 'True']) -KEYWORDS = frozenset(keyword.kwlist + ['print', 'async']) - SINGLETONS +KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@']) WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) @@ -493,7 +493,6 @@ def missing_whitespace_after_keyword(logical_line, tokens): if (tok0.end == tok1.start and keyword.iskeyword(tok0.string) and tok0.string not in SINGLETONS and - tok0.string not in ('async', 'await') and not (tok0.string == 'except' and tok1.string == '*') and not (tok0.string == 'yield' and tok1.string == ')') and tok1.string not in ':\n'): @@ -1645,76 +1644,6 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): pos = string.find('\\', pos + 1) -@register_check -def python_3000_async_await_keywords(logical_line, tokens): - """'async' and 'await' are reserved keywords starting at Python 3.7. - - W606: async = 42 - W606: await = 42 - Okay: async def read(db):\n data = await db.fetch('SELECT ...') - """ - # The Python tokenize library before Python 3.5 recognizes - # async/await as a NAME token. Therefore, use a state machine to - # look for the possible async/await constructs as defined by the - # Python grammar: - # https://docs.python.org/3/reference/grammar.html - - state = None - for token_type, text, start, end, line in tokens: - error = False - - if token_type == tokenize.NL: - continue - - if state is None: - if token_type == tokenize.NAME: - if text == 'async': - state = ('async_stmt', start) - elif text == 'await': - state = ('await', start) - elif (token_type == tokenize.NAME and - text in ('def', 'for')): - state = ('define', start) - - elif state[0] == 'async_stmt': - if token_type == tokenize.NAME and text in ('def', 'with', 'for'): - # One of funcdef, with_stmt, or for_stmt. Return to - # looking for async/await names. - state = None - else: - error = True - elif state[0] == 'await': - if token_type == tokenize.NAME: - # An await expression. Return to looking for async/await - # names. - state = None - elif token_type == tokenize.OP and text == '(': - state = None - else: - error = True - elif state[0] == 'define': - if token_type == tokenize.NAME and text in ('async', 'await'): - error = True - else: - state = None - - if error: - yield ( - state[1], - "W606 'async' and 'await' are reserved keywords starting with " - "Python 3.7", - ) - state = None - - # Last token - if state is not None: - yield ( - state[1], - "W606 'async' and 'await' are reserved keywords starting with " - "Python 3.7", - ) - - ######################################################################## @register_check def maximum_doc_length(logical_line, max_doc_length, noqa, tokens): diff --git a/testsuite/W60.py b/testsuite/W60.py index f44552d9..71263f94 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -29,55 +29,3 @@ regex = ''' \w ''' # noqa -#: W606 -async = 42 -#: W606 -await = 42 -#: W606 -await 42 -#: W606 -await 'test' -#: W606 -def async(): - pass -#: W606 -def await(): - pass -#: W606 -class async: - pass -#: W606 -class await: - pass -#: Okay -async def read_data(db): - data = await db.fetch('SELECT ...') -#: Okay -if await fut: - pass -if (await fut): - pass -if await fut + 1: - pass -if (await fut) + 1: - pass -pair = await fut, 'spam' -pair = (await fut), 'spam' -with await fut, open(): - pass -with (await fut), open(): - pass -await foo()['spam'].baz()() -return await coro() -return (await coro()) -res = await coro() ** 2 -res = (await coro()) ** 2 -func(a1=await coro(), a2=0) -func(a1=(await coro()), a2=0) -await foo() + await bar() -(await foo()) + (await bar()) --await foo() --(await foo()) -(await - foo()) -await(await foo()) From f04e2a1f5d37f8668519f123b97bf38165994b72 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 15:03:50 -0400 Subject: [PATCH 345/412] combine whitespace around operator checks this slightly simplifies some checking --- pycodestyle.py | 100 ++++++++++++++++++++--------------------- testsuite/E22.py | 2 + testsuite/test_E901.py | 4 +- 3 files changed, 54 insertions(+), 52 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 35258160..36acd8b0 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -499,48 +499,6 @@ def missing_whitespace_after_keyword(logical_line, tokens): yield tok0.end, "E275 missing whitespace after keyword" -@register_check -def missing_whitespace(logical_line, tokens): - r"""Each comma, semicolon or colon should be followed by whitespace. - - Okay: [a, b] - Okay: (3,) - Okay: a[3,] = 1 - Okay: a[1:4] - Okay: a[:4] - Okay: a[1:] - Okay: a[1:4:2] - E231: ['a','b'] - E231: foo(bar,baz) - E231: [{'a':'b'}] - """ - brace_stack = [] - for tok in tokens: - if tok.type == tokenize.OP and tok.string in {'[', '(', '{'}: - brace_stack.append(tok.string) - elif tok.type == FSTRING_START: - brace_stack.append('f') - elif brace_stack: - if tok.type == tokenize.OP and tok.string in {']', ')', '}'}: - brace_stack.pop() - elif tok.type == FSTRING_END: - brace_stack.pop() - - if tok.type == tokenize.OP and tok.string in {',', ';', ':'}: - next_char = tok.line[tok.end[1]:tok.end[1] + 1] - if next_char not in WHITESPACE and next_char not in '\r\n': - # slice - if tok.string == ':' and brace_stack[-1:] == ['[']: - continue - # 3.12+ fstring format specifier - elif tok.string == ':' and brace_stack[-2:] == ['f', '{']: - continue - # tuple (and list for some reason?) - elif tok.string == ',' and next_char in ')]': - continue - yield tok.end, f'E231 missing whitespace after {tok.string!r}' - - @register_check def indentation(logical_line, previous_logical, indent_char, indent_level, previous_indent_level, @@ -856,14 +814,16 @@ def whitespace_around_operator(logical_line): @register_check -def missing_whitespace_around_operator(logical_line, tokens): - r"""Surround operators with a single space on either side. +def missing_whitespace(logical_line, tokens): + r"""Surround operators with the correct amount of whitespace. - Always surround these binary operators with a single space on either side: assignment (=), augmented assignment (+=, -= etc.), comparisons (==, <, >, !=, <=, >=, in, not in, is, is not), Booleans (and, or, not). + - Each comma, semicolon or colon should be followed by whitespace. + - If operators with different priorities are used, consider adding whitespace around the operators with the lowest priorities. @@ -874,6 +834,13 @@ def missing_whitespace_around_operator(logical_line, tokens): Okay: c = (a + b) * (a - b) Okay: foo(bar, key='word', *args, **kwargs) Okay: alpha[:-i] + Okay: [a, b] + Okay: (3,) + Okay: a[3,] = 1 + Okay: a[1:4] + Okay: a[:4] + Okay: a[1:] + Okay: a[1:4:2] E225: i=i+1 E225: submitted +=1 @@ -884,19 +851,52 @@ def missing_whitespace_around_operator(logical_line, tokens): E226: hypot2 = x*x + y*y E227: c = a|b E228: msg = fmt%(errno, errmsg) + E231: ['a','b'] + E231: foo(bar,baz) + E231: [{'a':'b'}] """ - parens = 0 need_space = False prev_type = tokenize.OP prev_text = prev_end = None operator_types = (tokenize.OP, tokenize.NAME) + brace_stack = [] for token_type, text, start, end, line in tokens: + if token_type == tokenize.OP and text in {'[', '(', '{'}: + brace_stack.append(text) + elif token_type == FSTRING_START: + brace_stack.append('f') + elif token_type == tokenize.NAME and text == 'lambda': + brace_stack.append('l') + elif brace_stack: + if token_type == tokenize.OP and text in {']', ')', '}'}: + brace_stack.pop() + elif token_type == FSTRING_END: + brace_stack.pop() + elif ( + brace_stack[-1] == 'l' and + token_type == tokenize.OP and + text == ':' + ): + brace_stack.pop() + if token_type in SKIP_COMMENTS: continue - if text in ('(', 'lambda'): - parens += 1 - elif text == ')': - parens -= 1 + + if token_type == tokenize.OP and text in {',', ';', ':'}: + next_char = line[end[1]:end[1] + 1] + if next_char not in WHITESPACE and next_char not in '\r\n': + # slice + if text == ':' and brace_stack[-1:] == ['[']: + pass + # 3.12+ fstring format specifier + elif text == ':' and brace_stack[-2:] == ['f', '{']: + pass + # tuple (and list for some reason?) + elif text == ',' and next_char in ')]': + pass + else: + yield end, f'E231 missing whitespace after {text!r}' + if need_space: if start != prev_end: # Found a (probably) needed space @@ -933,7 +933,7 @@ def missing_whitespace_around_operator(logical_line, tokens): "around %s operator" % (code, optype)) need_space = False elif token_type in operator_types and prev_end is not None: - if text == '=' and parens: + if text == '=' and brace_stack and brace_stack[-1] in {'l', '('}: # Allow keyword args or defaults: foo(bar=None). pass elif text in WS_NEEDED_OPERATORS: diff --git a/testsuite/E22.py b/testsuite/E22.py index 7ea27927..ba3f3960 100644 --- a/testsuite/E22.py +++ b/testsuite/E22.py @@ -98,6 +98,8 @@ c = (a +b)*(a - b) #: E225 E226 c = (a+ b)*(a - b) +#: E225 +x[lambda: None]=1 #: #: E226 diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py index 3633822b..d94e9233 100644 --- a/testsuite/test_E901.py +++ b/testsuite/test_E901.py @@ -23,7 +23,7 @@ def lasting(self, duration=300): ''' errors = errors_from_src(src) if sys.version_info < (3, 12): - expected = ['E122:4:1', 'E225:4:27', 'E251:5:13', 'E251:5:15'] + expected = ['E122:4:1', 'E251:5:13', 'E251:5:15'] else: - expected = ['E122:4:1', 'E225:4:27', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 + expected = ['E122:4:1', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 self.assertEqual(errors, expected) From eeeca60ace678f093f3bfeda0cd4ae243a5346ae Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 15:09:34 -0400 Subject: [PATCH 346/412] E225: fix false-positive in 3.12 --- pycodestyle.py | 12 ++++++++++-- testsuite/python38.py | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 36acd8b0..24421d05 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -933,8 +933,16 @@ def missing_whitespace(logical_line, tokens): "around %s operator" % (code, optype)) need_space = False elif token_type in operator_types and prev_end is not None: - if text == '=' and brace_stack and brace_stack[-1] in {'l', '('}: - # Allow keyword args or defaults: foo(bar=None). + if ( + text == '=' and ( + # allow lambda default args: lambda x=None: None + brace_stack[-1:] == ['l'] or + # allow keyword args or defaults: foo(bar=None). + brace_stack[-1:] == ['('] or + # allow python 3.8 fstring repr specifier + brace_stack[-2:] == ['f', '{'] + ) + ): pass elif text in WS_NEEDED_OPERATORS: need_space = True diff --git a/testsuite/python38.py b/testsuite/python38.py index 536448d8..44737fed 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -59,3 +59,5 @@ def f3( #: E741 if (l := 1): pass +#: Okay +f'{x=}' From bb8ed07c4d3f4b21a4673d4b60fc6d0d81a1e4fe Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 16:03:28 -0400 Subject: [PATCH 347/412] 3.12+ handle W605 for fstrings --- pycodestyle.py | 34 +++++++++++++++++----------------- testsuite/W60.py | 2 ++ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 24421d05..3224b62b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1620,32 +1620,32 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): 'U', ] - for token_type, text, start, end, line in tokens: - if token_type == tokenize.STRING: - start_line, start_col = start - quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1] + prefixes = [] + for token_type, text, start, _, _ in tokens: + if token_type in {tokenize.STRING, FSTRING_START}: # Extract string modifiers (e.g. u or r) - quote_pos = text.index(quote) - prefix = text[:quote_pos].lower() - start = quote_pos + len(quote) - string = text[start:-len(quote)] + prefixes.append(text[:text.index(text[-1])].lower()) - if 'r' not in prefix: - pos = string.find('\\') + if token_type in {tokenize.STRING, FSTRING_MIDDLE}: + if 'r' not in prefixes[-1]: + start_line, start_col = start + pos = text.find('\\') while pos >= 0: pos += 1 - if string[pos] not in valid: - line = start_line + string.count('\n', 0, pos) + if text[pos] not in valid: + line = start_line + text.count('\n', 0, pos) if line == start_line: - col = start_col + len(prefix) + len(quote) + pos + col = start_col + pos else: - col = pos - string.rfind('\n', 0, pos) - 1 + col = pos - text.rfind('\n', 0, pos) - 1 yield ( (line, col - 1), - "W605 invalid escape sequence '\\%s'" % - string[pos], + f"W605 invalid escape sequence '\\{text[pos]}'" ) - pos = string.find('\\', pos + 1) + pos = text.find('\\', pos + 1) + + if token_type in {tokenize.STRING, FSTRING_END}: + prefixes.pop() ######################################################################## diff --git a/testsuite/W60.py b/testsuite/W60.py index 71263f94..cf719b4d 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -15,6 +15,8 @@ with \_ somewhere in the middle """ +#: W605:1:3 +f"\d" #: Okay regex = r'\.png$' regex = '\\.png$' From 5f2dc8655c9a0d6092d13e64cdbd2e8013458d72 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 18:03:48 -0400 Subject: [PATCH 348/412] make E231 offsets match pycodestyle 2.10 --- pycodestyle.py | 2 +- testsuite/E12.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3224b62b..3a2e1656 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -895,7 +895,7 @@ def missing_whitespace(logical_line, tokens): elif text == ',' and next_char in ')]': pass else: - yield end, f'E231 missing whitespace after {text!r}' + yield start, f'E231 missing whitespace after {text!r}' if need_space: if start != prev_end: diff --git a/testsuite/E12.py b/testsuite/E12.py index 241bad0d..dabac0d7 100644 --- a/testsuite/E12.py +++ b/testsuite/E12.py @@ -366,7 +366,7 @@ def example_issue254(): # more stuff ) ) -#: E701:1:8 E231:1:9 E122:2:1 E203:4:8 E128:5:1 +#: E701:1:8 E231:1:8 E122:2:1 E203:4:8 E128:5:1 if True:\ print(True) From 2f9a0b5c3a0fdb7fd3f5c6ace95a18dabeb0b51b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 18:12:46 -0400 Subject: [PATCH 349/412] E721: have the message better match the check --- pycodestyle.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3a2e1656..bcd857a5 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1456,11 +1456,12 @@ def comparison_negative(logical_line): @register_check def comparison_type(logical_line, noqa): - r"""Object type comparisons should always use isinstance(). + r"""Object type comparisons should `is` / `is not` / `isinstance()`. Do not compare types directly. Okay: if isinstance(obj, int): + Okay: if type(obj) is int: E721: if type(obj) == type(1): """ match = COMPARE_TYPE_REGEX.search(logical_line) @@ -1468,7 +1469,11 @@ def comparison_type(logical_line, noqa): inst = match.group(1) if inst and inst.isidentifier() and inst not in SINGLETONS: return # Allow comparison for types which are not obvious - yield match.start(), "E721 do not compare types, use 'isinstance()'" + yield ( + match.start(), + "E721 do not compare types, for exact checks use `is` / `is not`, " + "for instance checks use `isinstance()`", + ) @register_check From dc633e222dca50762500a3c91fdb282d40749223 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 18:31:47 -0400 Subject: [PATCH 350/412] improve coverage --- .coveragerc | 40 ---------------------------------------- pycodestyle.py | 16 ++++++++-------- setup.cfg | 15 +++++++++++++++ testsuite/support.py | 35 +++++++++++++++++------------------ testsuite/test_E101.py | 4 ++-- testsuite/test_E901.py | 8 ++++---- testsuite/test_api.py | 12 ++++++------ tox.ini | 4 +++- 8 files changed, 55 insertions(+), 79 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 57fc88ec..00000000 --- a/.coveragerc +++ /dev/null @@ -1,40 +0,0 @@ -[run] -source = . -branch = true -parallel = true -omit = - */.tox/* - */__main__.py - */setup.py - */venv*/* - # TODO: separate the tests from the test data - testsuite/E*.py - testsuite/W*.py - testsuite/latin-1.py - testsuite/noqa.py - testsuite/python*.py - testsuite/utf-8-bom.py - -[report] -show_missing = True -skip_covered = True -# TODO: increase this -fail_under = 90 -exclude_lines = - # a more strict default pragma - \# pragma: no cover\b - - # allow defensive code - ^\s*raise AssertionError\b - ^\s*raise NotImplementedError\b - ^\s*return NotImplemented\b - ^\s*raise$ - - # typing-related code - ^if (False|TYPE_CHECKING): - : \.\.\.$ - ^ +\.\.\.$ - -> ['"]?NoReturn['"]?: - - # non-runnable code - if __name__ == ['"]__main__['"]:$ diff --git a/pycodestyle.py b/pycodestyle.py index bcd857a5..6836ac1e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -73,9 +73,9 @@ DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504' try: - if sys.platform == 'win32': + if sys.platform == 'win32': # pragma: win32 cover USER_CONFIG = os.path.expanduser(r'~\.pycodestyle') - else: + else: # pragma: win32 no cover USER_CONFIG = os.path.join( os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), 'pycodestyle' @@ -150,11 +150,11 @@ DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ") BLANK_EXCEPT_REGEX = re.compile(r"except\s*:") -if sys.version_info >= (3, 12): +if sys.version_info >= (3, 12): # pragma: >=3.12 cover FSTRING_START = tokenize.FSTRING_START FSTRING_MIDDLE = tokenize.FSTRING_MIDDLE FSTRING_END = tokenize.FSTRING_END -else: +else: # pragma: <3.12 cover FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1 _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -863,14 +863,14 @@ def missing_whitespace(logical_line, tokens): for token_type, text, start, end, line in tokens: if token_type == tokenize.OP and text in {'[', '(', '{'}: brace_stack.append(text) - elif token_type == FSTRING_START: + elif token_type == FSTRING_START: # pragma: >=3.12 cover brace_stack.append('f') elif token_type == tokenize.NAME and text == 'lambda': brace_stack.append('l') elif brace_stack: if token_type == tokenize.OP and text in {']', ')', '}'}: brace_stack.pop() - elif token_type == FSTRING_END: + elif token_type == FSTRING_END: # pragma: >=3.12 cover brace_stack.pop() elif ( brace_stack[-1] == 'l' and @@ -889,7 +889,7 @@ def missing_whitespace(logical_line, tokens): if text == ':' and brace_stack[-1:] == ['[']: pass # 3.12+ fstring format specifier - elif text == ':' and brace_stack[-2:] == ['f', '{']: + elif text == ':' and brace_stack[-2:] == ['f', '{']: # pragma: >=3.12 cover # noqa: E501 pass # tuple (and list for some reason?) elif text == ',' and next_char in ')]': @@ -1960,7 +1960,7 @@ def build_tokens_line(self): continue if token_type == tokenize.STRING: text = mute_string(text) - elif token_type == FSTRING_MIDDLE: + elif token_type == FSTRING_MIDDLE: # pragma: >=3.12 cover text = 'x' * len(text) if prev_row: (start_row, start_col) = start diff --git a/setup.cfg b/setup.cfg index 73ae4e79..6ea2d8ce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,3 +9,18 @@ select = ignore = E226,E24,W504 max_line_length = 79 max_doc_length = 72 + +[coverage:run] +plugins = covdefaults +omit = + # TODO: separate the tests from the test data + testsuite/E*.py + testsuite/W*.py + testsuite/latin-1.py + testsuite/noqa.py + testsuite/python*.py + testsuite/utf-8-bom.py +parallel = true + +[coverage:report] +fail_under = 93 diff --git a/testsuite/support.py b/testsuite/support.py index a116c697..39a3380a 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -40,20 +40,19 @@ def error(self, line_number, offset, text, check): self.counters[code] = 1 detailed_code = '%s:%s:%s' % (code, line_number, offset + 1) # Don't care about expected errors or warnings - if code in self.expected or detailed_code in self.expected: - return - self._deferred_print.append( - (line_number, offset, detailed_code, text[5:], check.__doc__)) - self.file_errors += 1 - self.total_errors += 1 - return code + if code not in self.expected and detailed_code not in self.expected: # pragma: no cover # noqa: E501 + err = (line_number, offset, detailed_code, text[5:], check.__doc__) + self._deferred_print.append(err) + self.file_errors += 1 + self.total_errors += 1 + return code def get_file_results(self): # Check if the expected errors were found label = '%s:%s:1' % (self.filename, self.line_offset) for extended_code in self.expected: code = extended_code.split(':')[0] - if not self.counters.get(code): + if not self.counters.get(code): # pragma: no cover self.file_errors += 1 self.total_errors += 1 print('%s: error %s not found' % (label, extended_code)) @@ -61,25 +60,25 @@ def get_file_results(self): self.counters[code] -= 1 for code, extra in sorted(self.counters.items()): if code not in self._benchmark_keys: - if extra and code in self.expected: + if extra and code in self.expected: # pragma: no cover self.file_errors += 1 self.total_errors += 1 print('%s: error %s found too many times (+%d)' % (label, code, extra)) # Reset counters del self.counters[code] - if self._verbose and not self.file_errors: + if self._verbose and not self.file_errors: # pragma: no cover print('%s: passed (%s)' % (label, ' '.join(self.expected) or 'Okay')) self.counters['test cases'] += 1 - if self.file_errors: + if self.file_errors: # pragma: no cover self.counters['failed tests'] += 1 return super(TestReport, self).get_file_results() def print_results(self): results = ("%(physical lines)d lines tested: %(files)d files, " "%(test cases)d test cases%%s." % self.counters) - if self.total_errors: + if self.total_errors: # pragma: no cover print(results % ", %s failures" % self.total_errors) else: print(results % "") @@ -127,20 +126,20 @@ def selftest(options): checker.check_all() error = None if code == 'Okay': - if len(counters) > len(options.benchmark_keys): + if len(counters) > len(options.benchmark_keys): # pragma: no cover # noqa: E501 codes = [key for key in counters if key not in options.benchmark_keys] error = "incorrectly found %s" % ', '.join(codes) - elif not counters.get(code): + elif not counters.get(code): # pragma: no cover error = "failed to find %s" % code # Keep showing errors for multiple tests for key in set(counters) - set(options.benchmark_keys): del counters[key] count_all += 1 if not error: - if options.verbose: + if options.verbose: # pragma: no cover print("%s: %s" % (code, source)) - else: + else: # pragma: no cover count_failed += 1 print("pycodestyle.py: %s:" % error) for line in checker.lines: @@ -176,7 +175,7 @@ def run_tests(filename): if ver_match: test_against_version = tuple(int(val or 0) for val in ver_match.groups()) - if sys.version_info < test_against_version: + if sys.version_info < test_against_version: # pragma: no cover return lines = readlines(filename) + ['#:\n'] line_offset = 0 @@ -220,7 +219,7 @@ def run_tests(style): count_passed = done_d + done_s - count_failed print("%d passed and %d failed." % (count_passed, count_failed)) print("Test failed." if count_failed else "Test passed.") - if count_failed: + if count_failed: # pragma: no cover sys.exit(1) if options.testsuite: init_tests(style) diff --git a/testsuite/test_E101.py b/testsuite/test_E101.py index ba7d707a..10c43bfa 100644 --- a/testsuite/test_E101.py +++ b/testsuite/test_E101.py @@ -12,7 +12,7 @@ def test_E101(self): '\tprint(1) # tabs\n' ' print(2) # spaces\n' ) - if sys.version_info >= (3, 12): + if sys.version_info >= (3, 12): # pragma: >=3.12 cover self.assertEqual(errors, ['W191:2:1', 'E901:3:28']) - else: + else: # pragma: <3.12 cover self.assertEqual(errors, ['W191:2:1', 'E101:3:1']) diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py index d94e9233..706db8ca 100644 --- a/testsuite/test_E901.py +++ b/testsuite/test_E901.py @@ -8,9 +8,9 @@ class E901Test(unittest.TestCase): def test_closing_brace(self): errors = errors_from_src('}\n') - if sys.version_info < (3, 12): + if sys.version_info < (3, 12): # pragma: <3.12 cover self.assertEqual(errors, ['E901:2:1']) - else: + else: # pragma: >=3.12 cover self.assertEqual(errors, []) def test_unclosed_brace(self): @@ -22,8 +22,8 @@ def lasting(self, duration=300): progress = self._progress.setdefault('foo', {} ''' errors = errors_from_src(src) - if sys.version_info < (3, 12): + if sys.version_info < (3, 12): # pragma: <3.12 cover expected = ['E122:4:1', 'E251:5:13', 'E251:5:15'] - else: + else: # pragma: >=3.12 cover expected = ['E122:4:1', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 self.assertEqual(errors, expected) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index fa4d8a03..20450324 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -329,11 +329,11 @@ def test_check_nullbytes(self): count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() - if sys.version_info < (3, 11, 4): + if sys.version_info < (3, 11, 4): # pragma: <3.11 cover expected = ["stdin:1:1: E901 ValueError: source code string cannot contain null bytes"] # noqa: E501 - elif sys.version_info < (3, 12): + elif sys.version_info < (3, 12): # pragma: <3.12 cover # pragma: >=3.11 cover # noqa: E501 expected = ["stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes"] # noqa: E501 - else: + else: # pragma: >=3.12 cover expected = [ "stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes", # noqa: E501 "stdin:1:1: E901 TokenError: source code cannot contain null bytes", # noqa: E501 @@ -353,17 +353,17 @@ def test_styleguide_unmatched_triple_quotes(self): pep8style.input_file('stdin', lines=lines) stdout = sys.stdout.getvalue() - if sys.version_info < (3, 10): + if sys.version_info < (3, 10): # pragma: <3.10 cover expected = [ 'stdin:2:5: E901 TokenError: EOF in multi-line string', 'stdin:2:26: E901 SyntaxError: EOF while scanning triple-quoted string literal', # noqa: E501 ] - elif sys.version_info < (3, 12): + elif sys.version_info < (3, 12): # pragma: >=3.10 cover # pragma: <3.12 cover # noqa: E501 expected = [ 'stdin:2:5: E901 TokenError: EOF in multi-line string', 'stdin:2:6: E901 SyntaxError: unterminated triple-quoted string literal (detected at line 2)', # noqa: E501 ] - else: + else: # pragma: >=3.12 cover expected = [ 'stdin:2:6: E901 SyntaxError: unterminated triple-quoted string literal (detected at line 2)', # noqa: E501 'stdin:2:6: E901 TokenError: EOF in multi-line string', diff --git a/tox.ini b/tox.ini index f4a32d71..9a004b19 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,9 @@ envlist = py, pypy3 skip_missing_interpreters = True [testenv] -deps = coverage +deps = + covdefaults + coverage commands = python -m pycodestyle --statistics pycodestyle.py coverage run -m pycodestyle --max-doc-length=72 --testsuite testsuite From 7e94466850b3602256cf600ba30a59d1c1a78a23 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 19:34:30 -0400 Subject: [PATCH 351/412] move test data to ./testing/data --- .pre-commit-config.yaml | 2 +- setup.cfg | 9 +------ {testsuite => testing/data}/E10.py | 0 {testsuite => testing/data}/E11.py | 0 {testsuite => testing/data}/E12.py | 0 {testsuite => testing/data}/E12not.py | 0 {testsuite => testing/data}/E20.py | 0 {testsuite => testing/data}/E21.py | 0 {testsuite => testing/data}/E22.py | 0 {testsuite => testing/data}/E23.py | 0 {testsuite => testing/data}/E24.py | 0 {testsuite => testing/data}/E25.py | 0 {testsuite => testing/data}/E26.py | 0 {testsuite => testing/data}/E27.py | 0 {testsuite => testing/data}/E30.py | 0 {testsuite => testing/data}/E30not.py | 0 {testsuite => testing/data}/E40.py | 0 {testsuite => testing/data}/E50.py | 0 {testsuite => testing/data}/E70.py | 0 {testsuite => testing/data}/E71.py | 0 {testsuite => testing/data}/E72.py | 0 {testsuite => testing/data}/E73.py | 0 {testsuite => testing/data}/E74.py | 0 {testsuite => testing/data}/E90.py | 0 {testsuite => testing/data}/W19.py | 0 {testsuite => testing/data}/W29.py | 0 {testsuite => testing/data}/W39.py | 0 {testsuite => testing/data}/W60.py | 0 {testsuite => testing/data}/crlf.py | 0 {testsuite => testing/data}/latin-1.py | 0 {testsuite => testing/data}/noqa.py | 0 {testsuite => testing/data}/python3.py | 0 {testsuite => testing/data}/python310.py | 0 {testsuite => testing/data}/python311.py | 0 {testsuite => testing/data}/python312.py | 0 {testsuite => testing/data}/python35.py | 0 {testsuite => testing/data}/python36.py | 0 {testsuite => testing/data}/python38.py | 0 {testsuite => testing/data}/utf-8-bom.py | 0 {testsuite => testing/data}/utf-8.py | 0 testsuite/support.py | 24 +++++++++-------- testsuite/test_E101.py | 2 +- testsuite/test_E901.py | 2 +- testsuite/test_all.py | 6 ++--- testsuite/test_api.py | 10 +++---- testsuite/test_shell.py | 34 +++++++++++++----------- testsuite/test_util.py | 1 - tox.ini | 2 +- 48 files changed, 45 insertions(+), 47 deletions(-) rename {testsuite => testing/data}/E10.py (100%) rename {testsuite => testing/data}/E11.py (100%) rename {testsuite => testing/data}/E12.py (100%) rename {testsuite => testing/data}/E12not.py (100%) rename {testsuite => testing/data}/E20.py (100%) rename {testsuite => testing/data}/E21.py (100%) rename {testsuite => testing/data}/E22.py (100%) rename {testsuite => testing/data}/E23.py (100%) rename {testsuite => testing/data}/E24.py (100%) rename {testsuite => testing/data}/E25.py (100%) rename {testsuite => testing/data}/E26.py (100%) rename {testsuite => testing/data}/E27.py (100%) rename {testsuite => testing/data}/E30.py (100%) rename {testsuite => testing/data}/E30not.py (100%) rename {testsuite => testing/data}/E40.py (100%) rename {testsuite => testing/data}/E50.py (100%) rename {testsuite => testing/data}/E70.py (100%) rename {testsuite => testing/data}/E71.py (100%) rename {testsuite => testing/data}/E72.py (100%) rename {testsuite => testing/data}/E73.py (100%) rename {testsuite => testing/data}/E74.py (100%) rename {testsuite => testing/data}/E90.py (100%) rename {testsuite => testing/data}/W19.py (100%) rename {testsuite => testing/data}/W29.py (100%) rename {testsuite => testing/data}/W39.py (100%) rename {testsuite => testing/data}/W60.py (100%) rename {testsuite => testing/data}/crlf.py (100%) rename {testsuite => testing/data}/latin-1.py (100%) rename {testsuite => testing/data}/noqa.py (100%) rename {testsuite => testing/data}/python3.py (100%) rename {testsuite => testing/data}/python310.py (100%) rename {testsuite => testing/data}/python311.py (100%) rename {testsuite => testing/data}/python312.py (100%) rename {testsuite => testing/data}/python35.py (100%) rename {testsuite => testing/data}/python36.py (100%) rename {testsuite => testing/data}/python38.py (100%) rename {testsuite => testing/data}/utf-8-bom.py (100%) rename {testsuite => testing/data}/utf-8.py (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a68e31a..aff95394 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: ^testsuite/ +exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 diff --git a/setup.cfg b/setup.cfg index 6ea2d8ce..2116dfb6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,14 +12,7 @@ max_doc_length = 72 [coverage:run] plugins = covdefaults -omit = - # TODO: separate the tests from the test data - testsuite/E*.py - testsuite/W*.py - testsuite/latin-1.py - testsuite/noqa.py - testsuite/python*.py - testsuite/utf-8-bom.py +omit = testsuite/data parallel = true [coverage:report] diff --git a/testsuite/E10.py b/testing/data/E10.py similarity index 100% rename from testsuite/E10.py rename to testing/data/E10.py diff --git a/testsuite/E11.py b/testing/data/E11.py similarity index 100% rename from testsuite/E11.py rename to testing/data/E11.py diff --git a/testsuite/E12.py b/testing/data/E12.py similarity index 100% rename from testsuite/E12.py rename to testing/data/E12.py diff --git a/testsuite/E12not.py b/testing/data/E12not.py similarity index 100% rename from testsuite/E12not.py rename to testing/data/E12not.py diff --git a/testsuite/E20.py b/testing/data/E20.py similarity index 100% rename from testsuite/E20.py rename to testing/data/E20.py diff --git a/testsuite/E21.py b/testing/data/E21.py similarity index 100% rename from testsuite/E21.py rename to testing/data/E21.py diff --git a/testsuite/E22.py b/testing/data/E22.py similarity index 100% rename from testsuite/E22.py rename to testing/data/E22.py diff --git a/testsuite/E23.py b/testing/data/E23.py similarity index 100% rename from testsuite/E23.py rename to testing/data/E23.py diff --git a/testsuite/E24.py b/testing/data/E24.py similarity index 100% rename from testsuite/E24.py rename to testing/data/E24.py diff --git a/testsuite/E25.py b/testing/data/E25.py similarity index 100% rename from testsuite/E25.py rename to testing/data/E25.py diff --git a/testsuite/E26.py b/testing/data/E26.py similarity index 100% rename from testsuite/E26.py rename to testing/data/E26.py diff --git a/testsuite/E27.py b/testing/data/E27.py similarity index 100% rename from testsuite/E27.py rename to testing/data/E27.py diff --git a/testsuite/E30.py b/testing/data/E30.py similarity index 100% rename from testsuite/E30.py rename to testing/data/E30.py diff --git a/testsuite/E30not.py b/testing/data/E30not.py similarity index 100% rename from testsuite/E30not.py rename to testing/data/E30not.py diff --git a/testsuite/E40.py b/testing/data/E40.py similarity index 100% rename from testsuite/E40.py rename to testing/data/E40.py diff --git a/testsuite/E50.py b/testing/data/E50.py similarity index 100% rename from testsuite/E50.py rename to testing/data/E50.py diff --git a/testsuite/E70.py b/testing/data/E70.py similarity index 100% rename from testsuite/E70.py rename to testing/data/E70.py diff --git a/testsuite/E71.py b/testing/data/E71.py similarity index 100% rename from testsuite/E71.py rename to testing/data/E71.py diff --git a/testsuite/E72.py b/testing/data/E72.py similarity index 100% rename from testsuite/E72.py rename to testing/data/E72.py diff --git a/testsuite/E73.py b/testing/data/E73.py similarity index 100% rename from testsuite/E73.py rename to testing/data/E73.py diff --git a/testsuite/E74.py b/testing/data/E74.py similarity index 100% rename from testsuite/E74.py rename to testing/data/E74.py diff --git a/testsuite/E90.py b/testing/data/E90.py similarity index 100% rename from testsuite/E90.py rename to testing/data/E90.py diff --git a/testsuite/W19.py b/testing/data/W19.py similarity index 100% rename from testsuite/W19.py rename to testing/data/W19.py diff --git a/testsuite/W29.py b/testing/data/W29.py similarity index 100% rename from testsuite/W29.py rename to testing/data/W29.py diff --git a/testsuite/W39.py b/testing/data/W39.py similarity index 100% rename from testsuite/W39.py rename to testing/data/W39.py diff --git a/testsuite/W60.py b/testing/data/W60.py similarity index 100% rename from testsuite/W60.py rename to testing/data/W60.py diff --git a/testsuite/crlf.py b/testing/data/crlf.py similarity index 100% rename from testsuite/crlf.py rename to testing/data/crlf.py diff --git a/testsuite/latin-1.py b/testing/data/latin-1.py similarity index 100% rename from testsuite/latin-1.py rename to testing/data/latin-1.py diff --git a/testsuite/noqa.py b/testing/data/noqa.py similarity index 100% rename from testsuite/noqa.py rename to testing/data/noqa.py diff --git a/testsuite/python3.py b/testing/data/python3.py similarity index 100% rename from testsuite/python3.py rename to testing/data/python3.py diff --git a/testsuite/python310.py b/testing/data/python310.py similarity index 100% rename from testsuite/python310.py rename to testing/data/python310.py diff --git a/testsuite/python311.py b/testing/data/python311.py similarity index 100% rename from testsuite/python311.py rename to testing/data/python311.py diff --git a/testsuite/python312.py b/testing/data/python312.py similarity index 100% rename from testsuite/python312.py rename to testing/data/python312.py diff --git a/testsuite/python35.py b/testing/data/python35.py similarity index 100% rename from testsuite/python35.py rename to testing/data/python35.py diff --git a/testsuite/python36.py b/testing/data/python36.py similarity index 100% rename from testsuite/python36.py rename to testing/data/python36.py diff --git a/testsuite/python38.py b/testing/data/python38.py similarity index 100% rename from testsuite/python38.py rename to testing/data/python38.py diff --git a/testsuite/utf-8-bom.py b/testing/data/utf-8-bom.py similarity index 100% rename from testsuite/utf-8-bom.py rename to testing/data/utf-8-bom.py diff --git a/testsuite/utf-8.py b/testing/data/utf-8.py similarity index 100% rename from testsuite/utf-8.py rename to testing/data/utf-8.py diff --git a/testsuite/support.py b/testsuite/support.py index 39a3380a..f5399f40 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -1,11 +1,13 @@ -# -*- coding: utf-8 -*- from __future__ import annotations import os.path import re import sys -from pycodestyle import Checker, BaseReport, StandardReport, readlines +from pycodestyle import BaseReport +from pycodestyle import Checker +from pycodestyle import readlines +from pycodestyle import StandardReport from pycodestyle import StyleGuide SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') @@ -28,7 +30,7 @@ class TestReport(StandardReport): def __init__(self, options): options.benchmark_keys += ['test cases', 'failed tests'] - super(TestReport, self).__init__(options) + super().__init__(options) self._verbose = options.verbose def error(self, line_number, offset, text, check): @@ -38,7 +40,7 @@ def error(self, line_number, offset, text, check): self.counters[code] += 1 else: self.counters[code] = 1 - detailed_code = '%s:%s:%s' % (code, line_number, offset + 1) + detailed_code = f'{code}:{line_number}:{offset + 1}' # Don't care about expected errors or warnings if code not in self.expected and detailed_code not in self.expected: # pragma: no cover # noqa: E501 err = (line_number, offset, detailed_code, text[5:], check.__doc__) @@ -49,13 +51,13 @@ def error(self, line_number, offset, text, check): def get_file_results(self): # Check if the expected errors were found - label = '%s:%s:1' % (self.filename, self.line_offset) + label = f'{self.filename}:{self.line_offset}:1' for extended_code in self.expected: code = extended_code.split(':')[0] if not self.counters.get(code): # pragma: no cover self.file_errors += 1 self.total_errors += 1 - print('%s: error %s not found' % (label, extended_code)) + print(f'{label}: error {extended_code} not found') else: self.counters[code] -= 1 for code, extra in sorted(self.counters.items()): @@ -73,7 +75,7 @@ def get_file_results(self): self.counters['test cases'] += 1 if self.file_errors: # pragma: no cover self.counters['failed tests'] += 1 - return super(TestReport, self).get_file_results() + return super().get_file_results() def print_results(self): results = ("%(physical lines)d lines tested: %(files)d files, " @@ -91,7 +93,7 @@ class InMemoryReport(BaseReport): """ def __init__(self, options): - super(InMemoryReport, self).__init__(options) + super().__init__(options) self.in_memory_errors = [] def error(self, line_number, offset, text, check): @@ -99,9 +101,9 @@ def error(self, line_number, offset, text, check): Report an error, according to options. """ code = text[:4] - self.in_memory_errors.append('%s:%s:%s' % ( + self.in_memory_errors.append('{}:{}:{}'.format( code, line_number, offset + 1)) - return super(InMemoryReport, self).error( + return super().error( line_number, offset, text, check) @@ -138,7 +140,7 @@ def selftest(options): count_all += 1 if not error: if options.verbose: # pragma: no cover - print("%s: %s" % (code, source)) + print(f"{code}: {source}") else: # pragma: no cover count_failed += 1 print("pycodestyle.py: %s:" % error) diff --git a/testsuite/test_E101.py b/testsuite/test_E101.py index 10c43bfa..0f1915ff 100644 --- a/testsuite/test_E101.py +++ b/testsuite/test_E101.py @@ -1,6 +1,6 @@ """moved from testsuite files due to 3.12 making this a TokenError""" -import unittest import sys +import unittest from testsuite.support import errors_from_src diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py index 706db8ca..423c2f39 100644 --- a/testsuite/test_E901.py +++ b/testsuite/test_E901.py @@ -1,6 +1,6 @@ """moved from testsuite files due to 3.12 changing syntax errors""" -import unittest import sys +import unittest from testsuite.support import errors_from_src diff --git a/testsuite/test_all.py b/testsuite/test_all.py index 38b3c452..122966b6 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -1,11 +1,11 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import os.path -import sys import unittest import pycodestyle -from testsuite.support import init_tests, selftest, ROOT_DIR +from testsuite.support import init_tests +from testsuite.support import ROOT_DIR +from testsuite.support import selftest class PycodestyleTestCase(unittest.TestCase): diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 20450324..a9f23568 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -1,16 +1,16 @@ -# -*- coding: utf-8 -*- import os.path import shlex import sys import unittest import pycodestyle -from testsuite.support import ROOT_DIR, PseudoFile +from testsuite.support import PseudoFile +from testsuite.support import ROOT_DIR -E11 = os.path.join(ROOT_DIR, 'testsuite', 'E11.py') +E11 = os.path.join(ROOT_DIR, 'testing', 'data', 'E11.py') -class DummyChecker(object): +class DummyChecker: def __init__(self, tree, filename): pass @@ -151,7 +151,7 @@ def test_styleguide_options(self): 'doctest', 'quiet', 'show_pep8', 'show_source', 'statistics', 'testsuite', 'verbose'): oval = getattr(pep8style.options, o) - self.assertTrue(oval in (None, False), msg='%s = %r' % (o, oval)) + self.assertTrue(oval in (None, False), msg=f'{o} = {oval!r}') # Check default options self.assertTrue(pep8style.options.repeat) diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index a2fa35ea..f25864e6 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- import configparser import os.path import sys import unittest import pycodestyle -from testsuite.support import ROOT_DIR, PseudoFile +from testsuite.support import PseudoFile +from testsuite.support import ROOT_DIR class ShellTestCase(unittest.TestCase): @@ -73,7 +73,7 @@ def test_print_usage(self): self.assertFalse(self._config_filenames) def test_check_simple(self): - E11 = os.path.join(ROOT_DIR, 'testsuite', 'E11.py') + E11 = os.path.join(ROOT_DIR, 'testing', 'data', 'E11.py') stdout, stderr, errcode = self.pycodestyle(E11) stdout = stdout.splitlines() self.assertEqual(errcode, 1) @@ -125,8 +125,8 @@ def test_check_noarg(self): def test_check_diff(self): pycodestyle.PROJECT_CONFIG = () diff_lines = [ - "--- testsuite/E11.py 2006-06-01 08:49:50 +0500", - "+++ testsuite/E11.py 2008-04-06 17:36:29 +0500", + "--- testing/data/E11.py 2006-06-01 08:49:50 +0500", + "+++ testing/data/E11.py 2008-04-06 17:36:29 +0500", "@@ -2,4 +2,7 @@", " if x > 2:", " print x", @@ -149,8 +149,10 @@ def test_check_diff(self): self.assertEqual(y, str(col)) self.assertTrue(msg.startswith(' E11')) - diff_lines[:2] = ["--- a/testsuite/E11.py 2006-06-01 08:49 +0400", - "+++ b/testsuite/E11.py 2008-04-06 17:36 +0400"] + diff_lines[:2] = [ + "--- a/testing/data/E11.py 2006-06-01 08:49 +0400", + "+++ b/testing/data/E11.py 2008-04-06 17:36 +0400", + ] self.stdin = '\n'.join(diff_lines) stdout, stderr, errcode = self.pycodestyle('--diff') stdout = stdout.splitlines() @@ -163,19 +165,21 @@ def test_check_diff(self): self.assertTrue(msg.startswith(' E11')) # issue #127, #137: one-line chunks - diff_lines[:-1] = ["diff --git a/testsuite/E11.py b/testsuite/E11.py", - "index 8735e25..2ecb529 100644", - "--- a/testsuite/E11.py", - "+++ b/testsuite/E11.py", - "@@ -5,0 +6 @@ if True:", - "+ print"] + diff_lines[:-1] = [ + "diff --git a/testing/data/E11.py b/testing/data/E11.py", + "index 8735e25..2ecb529 100644", + "--- a/testing/data/E11.py", + "+++ b/testing/data/E11.py", + "@@ -5,0 +6 @@ if True:", + "+ print", + ] self.stdin = '\n'.join(diff_lines) stdout, stderr, errcode = self.pycodestyle('--diff') stdout = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) - self.assertTrue('testsuite/E11.py:6:6: E111 ' in stdout[0]) - self.assertTrue('testsuite/E11.py:6:6: E117 ' in stdout[1]) + self.assertTrue('testing/data/E11.py:6:6: E111 ' in stdout[0]) + self.assertTrue('testing/data/E11.py:6:6: E117 ' in stdout[1]) # missing '--diff' self.stdin = '\n'.join(diff_lines) diff --git a/testsuite/test_util.py b/testsuite/test_util.py index 075b163c..ec4b9d4d 100644 --- a/testsuite/test_util.py +++ b/testsuite/test_util.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import os import unittest diff --git a/tox.ini b/tox.ini index 9a004b19..a8ed0dab 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ deps = coverage commands = python -m pycodestyle --statistics pycodestyle.py - coverage run -m pycodestyle --max-doc-length=72 --testsuite testsuite + coverage run -m pycodestyle --max-doc-length=72 --testsuite testing/data coverage run -m pycodestyle --max-doc-length=72 --doctest coverage run -m unittest discover testsuite -vv coverage combine From a15e618f31c753cc03a0e3dcf9da4d7c2b31b547 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 19:48:16 -0400 Subject: [PATCH 352/412] remove some unnecessary shebangs --- testsuite/test_all.py | 1 - testsuite/test_util.py | 1 - 2 files changed, 2 deletions(-) diff --git a/testsuite/test_all.py b/testsuite/test_all.py index 122966b6..fd404c54 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python import os.path import unittest diff --git a/testsuite/test_util.py b/testsuite/test_util.py index ec4b9d4d..ce3058a9 100644 --- a/testsuite/test_util.py +++ b/testsuite/test_util.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python import os import unittest From 7f6e3688cfd822866900b523a5a7aa77747bbbd1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 20:09:45 -0400 Subject: [PATCH 353/412] run testsuite with pytest --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a8ed0dab..fb8e3903 100644 --- a/tox.ini +++ b/tox.ini @@ -11,11 +11,12 @@ skip_missing_interpreters = True deps = covdefaults coverage + pytest commands = python -m pycodestyle --statistics pycodestyle.py coverage run -m pycodestyle --max-doc-length=72 --testsuite testing/data coverage run -m pycodestyle --max-doc-length=72 --doctest - coverage run -m unittest discover testsuite -vv + coverage run -m pytest testsuite coverage combine coverage report From 3443725aff46aa02bad7d44cdc5723778cd2e918 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 20:15:14 -0400 Subject: [PATCH 354/412] use setup.cfg to handle setuptools metadata --- .pre-commit-config.yaml | 4 +++ setup.cfg | 43 ++++++++++++++++++++++++++++--- setup.py | 56 +---------------------------------------- 3 files changed, 44 insertions(+), 59 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aff95394..f57e1dd9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,6 +17,10 @@ repos: hooks: - id: pyupgrade args: [--py38-plus] +- repo: https://github.com/asottile/setup-cfg-fmt + rev: v2.4.0 + hooks: + - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: diff --git a/setup.cfg b/setup.cfg index 2116dfb6..9d735614 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,46 @@ +[metadata] +name = pycodestyle +version = attr: pycodestyle.__version__ +description = Python style guide checker +long_description = file: README.rst +long_description_content_type = text/x-rst +url = https://pycodestyle.pycqa.org/ +author = Johann C. Rocholl +author_email = johann@rocholl.net +maintainer = Ian Lee +maintainer_email = IanLee1521@gmail.com +license = MIT +license_files = LICENSE +classifiers = + Development Status :: 5 - Production/Stable + Environment :: Console + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: Implementation :: PyPy + Topic :: Software Development :: Libraries :: Python Modules +keywords = pycodestyle, pep8, PEP 8, PEP-8, PEP8 +project_urls = + Changes=https://pycodestyle.pycqa.org/en/latest/developer.html#changes + +[options] +py_modules = pycodestyle +python_requires = >=3.8 +include_package_data = True +zip_safe = False + +[options.entry_points] +console_scripts = + pycodestyle = pycodestyle:_main + [bdist_wheel] universal = 1 -[metadata] -license_file = LICENSE - [pycodestyle] -select = ignore = E226,E24,W504 max_line_length = 79 max_doc_length = 72 diff --git a/setup.py b/setup.py index ab735f9f..8bf1ba93 100644 --- a/setup.py +++ b/setup.py @@ -1,56 +1,2 @@ from setuptools import setup - - -def get_version(): - with open('pycodestyle.py') as f: - for line in f: - if line.startswith('__version__'): - return eval(line.split('=')[-1]) - - -def get_long_description(): - descr = [] - for fname in 'README.rst', 'CHANGES.txt': - with open(fname) as f: - descr.append(f.read()) - return '\n\n'.join(descr) - - -setup( - name='pycodestyle', - version=get_version(), - description="Python style guide checker", - long_description=get_long_description(), - keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', - author='Johann C. Rocholl', - author_email='johann@rocholl.net', - maintainer='Ian Lee', - maintainer_email='IanLee1521@gmail.com', - url='https://pycodestyle.pycqa.org/', - license='Expat license', - py_modules=['pycodestyle'], - include_package_data=True, - zip_safe=False, - python_requires='>=3.8', - entry_points={ - 'console_scripts': [ - 'pycodestyle = pycodestyle:_main', - ], - }, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - project_urls={ - 'Changes': - 'https://pycodestyle.pycqa.org/en/latest/developer.html#changes', - }, -) +setup() From 68f5ee1d69957ed4e38a4c6c6276a95738a79c18 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 21:08:04 -0400 Subject: [PATCH 355/412] convert --doctest to pytest tests --- .gitignore | 2 +- docs/developer.rst | 6 +-- pycodestyle.py | 33 ++---------- testing/__init__.py | 0 testing/support.py | 32 ++++++++++++ testsuite/support.py | 89 +-------------------------------- testsuite/test_E101.py | 2 +- testsuite/test_E901.py | 2 +- testsuite/test_all.py | 14 ------ testsuite/test_api.py | 6 +-- testsuite/test_blank_lines.py | 2 +- testsuite/test_pycodestyle.py | 29 +++++++++++ testsuite/test_self_doctests.py | 38 ++++++++++++++ tox.ini | 1 - 14 files changed, 113 insertions(+), 143 deletions(-) create mode 100644 testing/__init__.py create mode 100644 testing/support.py create mode 100644 testsuite/test_pycodestyle.py create mode 100644 testsuite/test_self_doctests.py diff --git a/.gitignore b/.gitignore index a2848173..ba74c72f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ *.egg *.egg-info *.pyc -/.coverage +/.coverage* /.tox /build/ /dist diff --git a/docs/developer.rst b/docs/developer.rst index 74e3ede7..59800113 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -89,8 +89,8 @@ Several docstrings contain examples directly from the `PEP 8`_ document. Okay: spam(ham[1], {eggs: 2}) E201: spam( ham[1], {eggs: 2}) -These examples are verified automatically when ``pycodestyle.py`` is run with -the ``--doctest`` option. You can add examples for your own check functions. +These examples are verified automatically by ``test_self_doctest.py``. +You can add examples for your own check functions. The format is simple: ``"Okay"`` or error/warning code followed by colon and space, the rest of the line is example source code. If you put ``'r'`` before the docstring, you can use ``\n`` for newline and ``\t`` for tab. @@ -98,7 +98,7 @@ the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: $ python pycodestyle.py --testsuite testsuite - $ python pycodestyle.py --doctest + $ pytest testsuite/test_self_doctest.py $ python pycodestyle.py --verbose pycodestyle.py When contributing to pycodestyle, please observe our `Code of Conduct`_. diff --git a/pycodestyle.py b/pycodestyle.py index 6836ac1e..0c7d15d7 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1136,10 +1136,6 @@ def module_imports_on_top_of_file( Okay: # this is a comment\nimport os Okay: '''this is a module docstring'''\nimport os Okay: r'''this is a module docstring'''\nimport os - Okay: - try:\n\timport x\nexcept ImportError:\n\tpass\nelse:\n\tpass\nimport y - Okay: - try:\n\timport x\nexcept ImportError:\n\tpass\nfinally:\n\tpass\nimport y E402: a=1\nimport os E402: 'One string'\n"Two string"\nimport os E402: a=1\nfrom sys import x @@ -1730,15 +1726,6 @@ def expand_indent(line): r"""Return the amount of indentation. Tabs are expanded to the next multiple of 8. - - >>> expand_indent(' ') - 4 - >>> expand_indent('\t') - 8 - >>> expand_indent(' \t') - 8 - >>> expand_indent(' \t') - 16 """ line = line.rstrip('\n\r') if '\t' not in line: @@ -1755,15 +1742,7 @@ def expand_indent(line): def mute_string(text): - """Replace contents with 'xxx' to prevent syntax matching. - - >>> mute_string('"abc"') - '"xxx"' - >>> mute_string("'''abc'''") - "'''xxx'''" - >>> mute_string("r'abc'") - "r'xxx'" - """ + """Replace contents with 'xxx' to prevent syntax matching.""" # String modifiers (e.g. u or r) start = text.index(text[-1]) + 1 end = len(text) - 1 @@ -2323,7 +2302,7 @@ def __init__(self, *args, **kwargs): options.select = tuple(options.select or ()) if not (options.select or options.ignore or - options.testsuite or options.doctest) and DEFAULT_IGNORE: + options.testsuite) and DEFAULT_IGNORE: # The default choice: ignore controversial checks options.ignore = tuple(DEFAULT_IGNORE.split(',')) else: @@ -2496,8 +2475,6 @@ def get_parser(prog='pycodestyle', version=__version__): if os.path.exists(TESTSUITE_PATH): group.add_option('--testsuite', metavar='dir', help="run regression tests from dir") - group.add_option('--doctest', action='store_true', - help="run doctest on myself") group.add_option('--benchmark', action='store_true', help="measure processing speed") return parser @@ -2574,7 +2551,7 @@ def read_config(options, args, arglist, parser): # Third, overwrite with the command-line options (options, __) = parser.parse_args(arglist, values=new_options) - options.doctest = options.testsuite = False + options.testsuite = False return options @@ -2609,7 +2586,7 @@ def process_options(arglist=None, parse_argv=False, config_file=None, if options.ensure_value('testsuite', False): args.append(options.testsuite) - elif not options.ensure_value('doctest', False): + else: if parse_argv and not args: if options.diff or any(os.path.exists(name) for name in PROJECT_CONFIG): @@ -2662,7 +2639,7 @@ def _main(): style_guide = StyleGuide(parse_argv=True) options = style_guide.options - if options.doctest or options.testsuite: + if options.testsuite: from testsuite.support import run_tests report = run_tests(style_guide) else: diff --git a/testing/__init__.py b/testing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testing/support.py b/testing/support.py new file mode 100644 index 00000000..ca4744be --- /dev/null +++ b/testing/support.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from pycodestyle import BaseReport +from pycodestyle import StyleGuide + + +class InMemoryReport(BaseReport): + """ + Collect the results in memory, without printing anything. + """ + + def __init__(self, options): + super().__init__(options) + self.in_memory_errors = [] + + def error(self, line_number, offset, text, check): + """ + Report an error, according to options. + """ + code = text[:4] + self.in_memory_errors.append(f'{code}:{line_number}:{offset + 1}') + return super().error(line_number, offset, text, check) + + +def errors_from_src(src: str) -> list[str]: + guide = StyleGuide(select=('E', 'W')) + reporter = guide.init_report(InMemoryReport) + guide.input_file( + filename='in-memory-test-file.py', + lines=src.splitlines(True), + ) + return reporter.in_memory_errors diff --git a/testsuite/support.py b/testsuite/support.py index f5399f40..8bea0920 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -4,11 +4,8 @@ import re import sys -from pycodestyle import BaseReport -from pycodestyle import Checker from pycodestyle import readlines from pycodestyle import StandardReport -from pycodestyle import StyleGuide SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -87,68 +84,6 @@ def print_results(self): print("Test failed." if self.total_errors else "Test passed.") -class InMemoryReport(BaseReport): - """ - Collect the results in memory, without printing anything. - """ - - def __init__(self, options): - super().__init__(options) - self.in_memory_errors = [] - - def error(self, line_number, offset, text, check): - """ - Report an error, according to options. - """ - code = text[:4] - self.in_memory_errors.append('{}:{}:{}'.format( - code, line_number, offset + 1)) - return super().error( - line_number, offset, text, check) - - -def selftest(options): - """ - Test all check functions with test cases in docstrings. - """ - count_failed = count_all = 0 - report = BaseReport(options) - counters = report.counters - checks = options.physical_checks + options.logical_checks - for name, check, argument_names in checks: - for line in check.__doc__.splitlines(): - line = line.lstrip() - match = SELFTEST_REGEX.match(line) - if match is None: - continue - code, source = match.groups() - lines = [part.replace(r'\t', '\t') + '\n' - for part in source.split(r'\n')] - checker = Checker(lines=lines, options=options, report=report) - checker.check_all() - error = None - if code == 'Okay': - if len(counters) > len(options.benchmark_keys): # pragma: no cover # noqa: E501 - codes = [key for key in counters - if key not in options.benchmark_keys] - error = "incorrectly found %s" % ', '.join(codes) - elif not counters.get(code): # pragma: no cover - error = "failed to find %s" % code - # Keep showing errors for multiple tests - for key in set(counters) - set(options.benchmark_keys): - del counters[key] - count_all += 1 - if not error: - if options.verbose: # pragma: no cover - print(f"{code}: {source}") - else: # pragma: no cover - count_failed += 1 - print("pycodestyle.py: %s:" % error) - for line in checker.lines: - print(line.rstrip()) - return count_failed, count_all - - def init_tests(pep8style): """ Initialize testing framework. @@ -211,28 +146,6 @@ def run_tests(filename): def run_tests(style): - options = style.options - if options.doctest: - import doctest - fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose) - fail_s, done_s = selftest(options) - count_failed = fail_s + fail_d - if not options.quiet: - count_passed = done_d + done_s - count_failed - print("%d passed and %d failed." % (count_passed, count_failed)) - print("Test failed." if count_failed else "Test passed.") - if count_failed: # pragma: no cover - sys.exit(1) - if options.testsuite: + if style.options.testsuite: init_tests(style) return style.check_files() - - -def errors_from_src(src: str) -> list[str]: - guide = StyleGuide() - reporter = guide.init_report(InMemoryReport) - guide.input_file( - filename='in-memory-test-file.py', - lines=src.splitlines(True), - ) - return reporter.in_memory_errors diff --git a/testsuite/test_E101.py b/testsuite/test_E101.py index 0f1915ff..c713370f 100644 --- a/testsuite/test_E101.py +++ b/testsuite/test_E101.py @@ -2,7 +2,7 @@ import sys import unittest -from testsuite.support import errors_from_src +from testing.support import errors_from_src class E101Test(unittest.TestCase): diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py index 423c2f39..402b85a4 100644 --- a/testsuite/test_E901.py +++ b/testsuite/test_E901.py @@ -2,7 +2,7 @@ import sys import unittest -from testsuite.support import errors_from_src +from testing.support import errors_from_src class E901Test(unittest.TestCase): diff --git a/testsuite/test_all.py b/testsuite/test_all.py index fd404c54..aa4a93b4 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -4,7 +4,6 @@ import pycodestyle from testsuite.support import init_tests from testsuite.support import ROOT_DIR -from testsuite.support import selftest class PycodestyleTestCase(unittest.TestCase): @@ -15,19 +14,6 @@ def setUp(self): paths=[os.path.join(ROOT_DIR, 'testsuite')], select='E,W', quiet=True) - def test_doctest(self): - import doctest - fail_d, done_d = doctest.testmod( - pycodestyle, verbose=False, report=False - ) - self.assertTrue(done_d, msg='tests not found') - self.assertFalse(fail_d, msg='%s failure(s)' % fail_d) - - def test_selftest(self): - fail_s, done_s = selftest(self._style.options) - self.assertTrue(done_s, msg='tests not found') - self.assertFalse(fail_s, msg='%s failure(s)' % fail_s) - def test_checkers_testsuite(self): init_tests(self._style) report = self._style.check_files() diff --git a/testsuite/test_api.py b/testsuite/test_api.py index a9f23568..8f467b21 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -148,7 +148,7 @@ def test_styleguide_options(self): # Check unset options for o in ('benchmark', 'config', 'count', 'diff', - 'doctest', 'quiet', 'show_pep8', 'show_source', + 'quiet', 'show_pep8', 'show_source', 'statistics', 'testsuite', 'verbose'): oval = getattr(pep8style.options, o) self.assertTrue(oval in (None, False), msg=f'{o} = {oval!r}') @@ -183,10 +183,6 @@ def parse_argv(argstring): ('E121', 'E123', 'E126', 'E226', 'E24', 'E704', 'W503', 'W504') ) - options = parse_argv('--doctest').options - self.assertEqual(options.select, ()) - self.assertEqual(options.ignore, ()) - options = parse_argv('--ignore E,W').options self.assertEqual(options.select, ()) self.assertEqual(options.ignore, ('E', 'W')) diff --git a/testsuite/test_blank_lines.py b/testsuite/test_blank_lines.py index fa36f236..d1f78eae 100644 --- a/testsuite/test_blank_lines.py +++ b/testsuite/test_blank_lines.py @@ -6,7 +6,7 @@ import unittest import pycodestyle -from testsuite.support import errors_from_src +from testing.support import errors_from_src class BlankLinesTestCase(unittest.TestCase): diff --git a/testsuite/test_pycodestyle.py b/testsuite/test_pycodestyle.py new file mode 100644 index 00000000..8885f0d8 --- /dev/null +++ b/testsuite/test_pycodestyle.py @@ -0,0 +1,29 @@ +import pytest + +from pycodestyle import expand_indent +from pycodestyle import mute_string + + +@pytest.mark.parametrize( + ('s', 'expected'), + ( + (' ', 4), + ('\t', 8), + (' \t', 8), + (' \t', 16), + ), +) +def test_expand_indent(s, expected): + assert expand_indent(s) == expected + + +@pytest.mark.parametrize( + ('s', 'expected'), + ( + ('"abc"', '"xxx"'), + ("'''abc'''", "'''xxx'''"), + ("r'abc'", "r'xxx'"), + ), +) +def test_mute_string(s, expected): + assert mute_string(s) == expected diff --git a/testsuite/test_self_doctests.py b/testsuite/test_self_doctests.py new file mode 100644 index 00000000..e2f10ba6 --- /dev/null +++ b/testsuite/test_self_doctests.py @@ -0,0 +1,38 @@ +import re + +import pytest + +import pycodestyle +from testing.support import errors_from_src + +SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}): (.*)') + + +def get_tests(): + ret = [ + pytest.param( + match[1], + match[2], + id=f'pycodestyle.py:{f.__code__.co_firstlineno}:{f.__name__}@{i}', + ) + for group in pycodestyle._checks.values() + for f in group + if f.__doc__ is not None + for i, match in enumerate(SELFTEST_REGEX.finditer(f.__doc__)) + ] + assert ret + return tuple(ret) + + +@pytest.mark.parametrize(('expected', 's'), get_tests()) +def test(expected, s): + s = '\n'.join((*s.replace(r'\t', '\t').split(r'\n'), '')) + errors = errors_from_src(s) + if expected == 'Okay': + assert errors == [] + else: + for error in errors: + if error.startswith(f'{expected}:'): + break + else: + raise AssertionError(f'expected {expected} from {s!r}') diff --git a/tox.ini b/tox.ini index fb8e3903..064a7f00 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,6 @@ deps = commands = python -m pycodestyle --statistics pycodestyle.py coverage run -m pycodestyle --max-doc-length=72 --testsuite testing/data - coverage run -m pycodestyle --max-doc-length=72 --doctest coverage run -m pytest testsuite coverage combine coverage report From 57dc8def1b5113cb53f21b6ea25e148e1d9ba6fa Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 17 Jul 2023 20:05:42 -0400 Subject: [PATCH 356/412] port --testsuite to pytest --- CONTRIBUTING.rst | 20 +++--- docs/developer.rst | 3 +- pycodestyle.py | 36 +++------- setup.cfg | 1 - testing/data/W29.py | 2 - testing/data/python39.py | 2 + testing/support.py | 6 +- testsuite/support.py | 151 --------------------------------------- testsuite/test_all.py | 31 ++------ testsuite/test_api.py | 31 ++++---- testsuite/test_data.py | 95 ++++++++++++++++++++++++ testsuite/test_shell.py | 15 ++-- tox.ini | 2 - 13 files changed, 154 insertions(+), 241 deletions(-) create mode 100644 testing/data/python39.py delete mode 100644 testsuite/support.py create mode 100644 testsuite/test_data.py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 5515108e..062b726a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -70,32 +70,28 @@ GitHub has an excellent `guide`_. The current tests are written in 2 styles: -* standard xUnit based only on stdlib unittest -* functional test using a custom framework and executed by the - pycodestyle itself when installed in dev mode. +* pytest tests +* functional test using a custom framework -Running unittest -~~~~~~~~~~~~~~~~ +Running tests +~~~~~~~~~~~~~ -The tests are written using stdlib ``unittest`` module, the existing tests +The tests are written using ``pytest``, the existing tests include unit, integration and functional tests. To run the tests:: - $ python setup.py test + $ pytest testsuite Running functional ~~~~~~~~~~~~~~~~~~ -When installed in dev mode, pycodestyle will have the ``--testsuite`` option -which can be used to run the tests:: - $ pip install -e . $ # Run all tests. - $ pycodestyle --testsuite testsuite + $ pytest testsuite $ # Run a subset of the tests. - $ pycodestyle --testsuite testsuite/E30.py + $ pytest testsuite -k testing/data/E30.py .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ diff --git a/docs/developer.rst b/docs/developer.rst index 59800113..5a529e66 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -97,8 +97,7 @@ the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: - $ python pycodestyle.py --testsuite testsuite - $ pytest testsuite/test_self_doctest.py + $ pytest testsuite $ python pycodestyle.py --verbose pycodestyle.py When contributing to pycodestyle, please observe our `Code of Conduct`_. diff --git a/pycodestyle.py b/pycodestyle.py index 0c7d15d7..9388ae52 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -84,7 +84,6 @@ USER_CONFIG = None PROJECT_CONFIG = ('setup.cfg', 'tox.ini') -TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') MAX_LINE_LENGTH = 79 # Number of blank lines between various code parts. BLANK_LINES_CONFIG = { @@ -2301,8 +2300,7 @@ def __init__(self, *args, **kwargs): options.reporter = BaseReport if options.quiet else StandardReport options.select = tuple(options.select or ()) - if not (options.select or options.ignore or - options.testsuite) and DEFAULT_IGNORE: + if not (options.select or options.ignore) and DEFAULT_IGNORE: # The default choice: ignore controversial checks options.ignore = tuple(DEFAULT_IGNORE.split(',')) else: @@ -2472,9 +2470,6 @@ def get_parser(prog='pycodestyle', version=__version__): help="report changes only within line number ranges in " "the unified diff received on STDIN") group = parser.add_option_group("Testing Options") - if os.path.exists(TESTSUITE_PATH): - group.add_option('--testsuite', metavar='dir', - help="run regression tests from dir") group.add_option('--benchmark', action='store_true', help="measure processing speed") return parser @@ -2551,7 +2546,6 @@ def read_config(options, args, arglist, parser): # Third, overwrite with the command-line options (options, __) = parser.parse_args(arglist, values=new_options) - options.testsuite = False return options @@ -2584,17 +2578,14 @@ def process_options(arglist=None, parse_argv=False, config_file=None, if verbose is not None: options.verbose = verbose - if options.ensure_value('testsuite', False): - args.append(options.testsuite) - else: - if parse_argv and not args: - if options.diff or any(os.path.exists(name) - for name in PROJECT_CONFIG): - args = ['.'] - else: - parser.error('input not specified') - options = read_config(options, args, arglist, parser) - options.reporter = parse_argv and options.quiet == 1 and FileReport + if parse_argv and not args: + if options.diff or any(os.path.exists(name) + for name in PROJECT_CONFIG): + args = ['.'] + else: + parser.error('input not specified') + options = read_config(options, args, arglist, parser) + options.reporter = parse_argv and options.quiet == 1 and FileReport options.filename = _parse_multi_options(options.filename) options.exclude = normalize_paths(options.exclude) @@ -2639,11 +2630,7 @@ def _main(): style_guide = StyleGuide(parse_argv=True) options = style_guide.options - if options.testsuite: - from testsuite.support import run_tests - report = run_tests(style_guide) - else: - report = style_guide.check_files() + report = style_guide.check_files() if options.statistics: report.print_statistics() @@ -2651,9 +2638,6 @@ def _main(): if options.benchmark: report.print_benchmark() - if options.testsuite and not options.quiet: - report.print_results() - if report.total_errors: if options.count: sys.stderr.write(str(report.total_errors) + '\n') diff --git a/setup.cfg b/setup.cfg index 9d735614..2871b652 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,7 +48,6 @@ max_doc_length = 72 [coverage:run] plugins = covdefaults omit = testsuite/data -parallel = true [coverage:report] fail_under = 93 diff --git a/testing/data/W29.py b/testing/data/W29.py index e9ad5800..f8471976 100644 --- a/testing/data/W29.py +++ b/testing/data/W29.py @@ -14,8 +14,6 @@ class Foo(object): #: W191 W292 noeol if False: pass # indented with tabs -#: W292:1:36 noeol -# This line doesn't have a linefeed #: W292:1:5 E225:1:2 noeol 1+ 1 #: W292:1:27 E261:1:12 noeol diff --git a/testing/data/python39.py b/testing/data/python39.py new file mode 100644 index 00000000..723f0e1c --- /dev/null +++ b/testing/data/python39.py @@ -0,0 +1,2 @@ +#: W292:1:70 noeol +# This line doesn't have a linefeed (in 3.8 this is reported thrice!) diff --git a/testing/support.py b/testing/support.py index ca4744be..20a0eab8 100644 --- a/testing/support.py +++ b/testing/support.py @@ -1,8 +1,12 @@ from __future__ import annotations +import os.path + from pycodestyle import BaseReport from pycodestyle import StyleGuide +ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + class InMemoryReport(BaseReport): """ @@ -23,7 +27,7 @@ def error(self, line_number, offset, text, check): def errors_from_src(src: str) -> list[str]: - guide = StyleGuide(select=('E', 'W')) + guide = StyleGuide(select=('E', 'W'), max_doc_length=72) reporter = guide.init_report(InMemoryReport) guide.input_file( filename='in-memory-test-file.py', diff --git a/testsuite/support.py b/testsuite/support.py deleted file mode 100644 index 8bea0920..00000000 --- a/testsuite/support.py +++ /dev/null @@ -1,151 +0,0 @@ -from __future__ import annotations - -import os.path -import re -import sys - -from pycodestyle import readlines -from pycodestyle import StandardReport - -SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') -ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) - - -class PseudoFile(list): - """Simplified file interface.""" - write = list.append - - def getvalue(self): - return ''.join(self) - - def flush(self): - pass - - -class TestReport(StandardReport): - """Collect the results for the tests.""" - - def __init__(self, options): - options.benchmark_keys += ['test cases', 'failed tests'] - super().__init__(options) - self._verbose = options.verbose - - def error(self, line_number, offset, text, check): - """Report an error, according to options.""" - code = text[:4] - if code in self.counters: - self.counters[code] += 1 - else: - self.counters[code] = 1 - detailed_code = f'{code}:{line_number}:{offset + 1}' - # Don't care about expected errors or warnings - if code not in self.expected and detailed_code not in self.expected: # pragma: no cover # noqa: E501 - err = (line_number, offset, detailed_code, text[5:], check.__doc__) - self._deferred_print.append(err) - self.file_errors += 1 - self.total_errors += 1 - return code - - def get_file_results(self): - # Check if the expected errors were found - label = f'{self.filename}:{self.line_offset}:1' - for extended_code in self.expected: - code = extended_code.split(':')[0] - if not self.counters.get(code): # pragma: no cover - self.file_errors += 1 - self.total_errors += 1 - print(f'{label}: error {extended_code} not found') - else: - self.counters[code] -= 1 - for code, extra in sorted(self.counters.items()): - if code not in self._benchmark_keys: - if extra and code in self.expected: # pragma: no cover - self.file_errors += 1 - self.total_errors += 1 - print('%s: error %s found too many times (+%d)' % - (label, code, extra)) - # Reset counters - del self.counters[code] - if self._verbose and not self.file_errors: # pragma: no cover - print('%s: passed (%s)' % - (label, ' '.join(self.expected) or 'Okay')) - self.counters['test cases'] += 1 - if self.file_errors: # pragma: no cover - self.counters['failed tests'] += 1 - return super().get_file_results() - - def print_results(self): - results = ("%(physical lines)d lines tested: %(files)d files, " - "%(test cases)d test cases%%s." % self.counters) - if self.total_errors: # pragma: no cover - print(results % ", %s failures" % self.total_errors) - else: - print(results % "") - print("Test failed." if self.total_errors else "Test passed.") - - -def init_tests(pep8style): - """ - Initialize testing framework. - - A test file can provide many tests. Each test starts with a - declaration. This declaration is a single line starting with '#:'. - It declares codes of expected failures, separated by spaces or - 'Okay' if no failure is expected. - If the file does not contain such declaration, it should pass all - tests. If the declaration is empty, following lines are not - checked, until next declaration. - - Examples: - - * Only E224 and W701 are expected: #: E224 W701 - * Following example is conform: #: Okay - * Don't check these lines: #: - """ - report = pep8style.init_report(TestReport) - runner = pep8style.input_file - - def run_tests(filename): - """Run all the tests from a file.""" - # Skip tests meant for higher versions of python - ver_match = re.search(r'python(\d)(\d+)?\.py$', filename) - if ver_match: - test_against_version = tuple(int(val or 0) - for val in ver_match.groups()) - if sys.version_info < test_against_version: # pragma: no cover - return - lines = readlines(filename) + ['#:\n'] - line_offset = 0 - codes = ['Okay'] - testcase = [] - count_files = report.counters['files'] - for index, line in enumerate(lines): - if not line.startswith('#:'): - if codes: - # Collect the lines of the test case - testcase.append(line) - continue - if codes and index: - if 'noeol' in codes: - testcase[-1] = testcase[-1].rstrip('\n') - codes = [c for c in codes - if c not in ('Okay', 'noeol')] - # Run the checker - runner(filename, testcase, expected=codes, - line_offset=line_offset) - # output the real line numbers - line_offset = index + 1 - # configure the expected errors - codes = line.split()[1:] - # empty the test case buffer - del testcase[:] - report.counters['files'] = count_files + 1 - return report.counters['failed tests'] - - pep8style.runner = run_tests - - -def run_tests(style): - if style.options.testsuite: - init_tests(style) - return style.check_files() diff --git a/testsuite/test_all.py b/testsuite/test_all.py index aa4a93b4..99a81183 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -1,29 +1,12 @@ import os.path -import unittest import pycodestyle -from testsuite.support import init_tests -from testsuite.support import ROOT_DIR +from testing.support import ROOT -class PycodestyleTestCase(unittest.TestCase): - """Test the standard errors and warnings (E and W).""" - - def setUp(self): - self._style = pycodestyle.StyleGuide( - paths=[os.path.join(ROOT_DIR, 'testsuite')], - select='E,W', quiet=True) - - def test_checkers_testsuite(self): - init_tests(self._style) - report = self._style.check_files() - self.assertFalse(report.total_errors, - msg='%s failure(s)' % report.total_errors) - - def test_own_dog_food(self): - files = [pycodestyle.__file__.rstrip('oc'), __file__.rstrip('oc'), - os.path.join(ROOT_DIR, 'setup.py')] - report = self._style.init_report(pycodestyle.StandardReport) - report = self._style.check_files(files) - self.assertEqual(list(report.messages.keys()), ['W504'], - msg='Failures: %s' % report.messages) +def test_own_dog_food(): + style = pycodestyle.StyleGuide(select='E,W', quiet=True) + files = [pycodestyle.__file__, __file__, os.path.join(ROOT, 'setup.py')] + report = style.init_report(pycodestyle.StandardReport) + report = style.check_files(files) + assert list(report.messages) == ['W504'], f'Failures: {report.messages}' diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 8f467b21..32f6ad2a 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -1,13 +1,13 @@ +import io import os.path import shlex import sys import unittest import pycodestyle -from testsuite.support import PseudoFile -from testsuite.support import ROOT_DIR +from testing.support import ROOT -E11 = os.path.join(ROOT_DIR, 'testing', 'data', 'E11.py') +E11 = os.path.join(ROOT, 'testing', 'data', 'E11.py') class DummyChecker: @@ -26,8 +26,8 @@ def setUp(self): self._saved_stdout = sys.stdout self._saved_stderr = sys.stderr self._saved_checks = pycodestyle._checks - sys.stdout = PseudoFile() - sys.stderr = PseudoFile() + sys.stdout = io.StringIO() + sys.stderr = io.StringIO() pycodestyle._checks = { k: {f: (vals[0][:], vals[1]) for (f, vals) in v.items()} for k, v in self._saved_checks.items() @@ -39,7 +39,10 @@ def tearDown(self): pycodestyle._checks = self._saved_checks def reset(self): - del sys.stdout[:], sys.stderr[:] + sys.stdout.seek(0) + sys.stdout.truncate() + sys.stderr.seek(0) + sys.stderr.truncate() def test_register_physical_check(self): def check_dummy(physical_line, line_number): @@ -107,8 +110,8 @@ def check_dummy(logical, tokens): def test_styleguide(self): report = pycodestyle.StyleGuide().check_files() self.assertEqual(report.total_errors, 0) - self.assertFalse(sys.stdout) - self.assertFalse(sys.stderr) + self.assertFalse(sys.stdout.getvalue()) + self.assertFalse(sys.stderr.getvalue()) self.reset() report = pycodestyle.StyleGuide().check_files(['missing-file']) @@ -116,15 +119,15 @@ def test_styleguide(self): self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 1) # < 3.3 returns IOError; >= 3.3 returns FileNotFoundError - self.assertTrue(stdout[0].startswith("missing-file:1:1: E902 ")) - self.assertFalse(sys.stderr) + assert stdout[0].startswith("missing-file:1:1: E902 ") + self.assertFalse(sys.stderr.getvalue()) self.reset() report = pycodestyle.StyleGuide().check_files([E11]) stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 24) - self.assertFalse(sys.stderr) + self.assertFalse(sys.stderr.getvalue()) self.reset() # Passing the paths in the constructor gives same result @@ -132,7 +135,7 @@ def test_styleguide(self): stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 24) - self.assertFalse(sys.stderr) + self.assertFalse(sys.stderr.getvalue()) self.reset() def test_styleguide_options(self): @@ -149,7 +152,7 @@ def test_styleguide_options(self): # Check unset options for o in ('benchmark', 'config', 'count', 'diff', 'quiet', 'show_pep8', 'show_source', - 'statistics', 'testsuite', 'verbose'): + 'statistics', 'verbose'): oval = getattr(pep8style.options, o) self.assertTrue(oval in (None, False), msg=f'{o} = {oval!r}') @@ -335,7 +338,7 @@ def test_check_nullbytes(self): "stdin:1:1: E901 TokenError: source code cannot contain null bytes", # noqa: E501 ] self.assertEqual(stdout.splitlines(), expected) - self.assertFalse(sys.stderr) + self.assertFalse(sys.stderr.getvalue()) self.assertEqual(count_errors, len(expected)) def test_styleguide_unmatched_triple_quotes(self): diff --git a/testsuite/test_data.py b/testsuite/test_data.py new file mode 100644 index 00000000..80a5529b --- /dev/null +++ b/testsuite/test_data.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +import collections +import os.path +import re +import sys + +import pytest + +import pycodestyle +from testing.support import errors_from_src +from testing.support import ROOT + +PY_RE = re.compile(r'^python(\d)(\d*)\.py$') +CASE_RE = re.compile('^(#:.*\n)', re.MULTILINE) + + +def _nsort(items: list[str]) -> list[str]: + return sorted( + items, + key=lambda s: [ + int(part) if part.isdigit() else part.lower() + for part in re.split(r'(\d+)', s) + ], + ) + + +def get_tests(): + ret = [] + for fname in _nsort(os.listdir(os.path.join(ROOT, 'testing', 'data'))): + match = PY_RE.match(fname) + if match is not None: + major, minor = int(match[1]), int(match[2] or '0') + mark = pytest.mark.skipif( + sys.version_info < (major, minor), + reason=f'requires Python {major}.{minor}', + ) + else: + mark = () + + fname = os.path.join('testing', 'data', fname) + fname_full = os.path.join(ROOT, fname) + src = ''.join(pycodestyle.readlines(fname_full)) + + line = 1 + parts_it = iter(CASE_RE.split(src)) + # the first case will not have a comment for it + s = next(parts_it) + if s.strip(): + id_s = f'{fname}:{line}' + ret.append(pytest.param('#: Okay', s, id=id_s, marks=mark)) + line += s.count('\n') + + for comment, s in zip(parts_it, parts_it): + if s.strip(): + id_s = f'{fname}:{line}' + ret.append(pytest.param(comment, s, id=id_s, marks=mark)) + line += s.count('\n') + 1 + + assert ret + return ret + + +@pytest.mark.parametrize(('case', 's'), get_tests()) +def test(case, s): + codes = collections.Counter() + exact = collections.Counter() + + assert case.startswith('#:') + for code in case[2:].strip().split(): + if code == 'Okay': + continue + elif code == 'noeol': + s = s.rstrip('\n') + elif ':' in code: + exact[code] += 1 + else: + codes[code] += 1 + + unexpected = collections.Counter() + for code in errors_from_src(s): + if exact[code]: + exact[code] -= 1 + elif codes[code[:4]]: + codes[code[:4]] -= 1 + else: # pragma: no cover + unexpected[code] += 1 + + messages = ( + *(f'-{k}\n' for k, v in codes.items() for _ in range(v)), + *(f'-{k}\n' for k, v in exact.items() for _ in range(v)), + *(f'+{k}\n' for k, v in unexpected.items() for _ in range(v)), + ) + if messages: # pragma: no cover + raise AssertionError(f'unexpected codes!\n{"".join(messages)}') diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index f25864e6..8de8886d 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -1,11 +1,11 @@ import configparser +import io import os.path import sys import unittest import pycodestyle -from testsuite.support import PseudoFile -from testsuite.support import ROOT_DIR +from testing.support import ROOT class ShellTestCase(unittest.TestCase): @@ -21,8 +21,8 @@ def setUp(self): self._config_filenames = [] self.stdin = '' sys.argv = ['pycodestyle'] - sys.stdout = PseudoFile() - sys.stderr = PseudoFile() + sys.stdout = io.StringIO() + sys.stderr = io.StringIO() def fake_config_parser_read(cp, fp, filename): self._config_filenames.append(filename) @@ -41,7 +41,10 @@ def stdin_get_value(self): return self.stdin def pycodestyle(self, *args): - del sys.stdout[:], sys.stderr[:] + sys.stdout.seek(0) + sys.stdout.truncate() + sys.stderr.seek(0) + sys.stderr.truncate() sys.argv[1:] = args try: pycodestyle._main() @@ -73,7 +76,7 @@ def test_print_usage(self): self.assertFalse(self._config_filenames) def test_check_simple(self): - E11 = os.path.join(ROOT_DIR, 'testing', 'data', 'E11.py') + E11 = os.path.join(ROOT, 'testing', 'data', 'E11.py') stdout, stderr, errcode = self.pycodestyle(E11) stdout = stdout.splitlines() self.assertEqual(errcode, 1) diff --git a/tox.ini b/tox.ini index 064a7f00..de802015 100644 --- a/tox.ini +++ b/tox.ini @@ -14,9 +14,7 @@ deps = pytest commands = python -m pycodestyle --statistics pycodestyle.py - coverage run -m pycodestyle --max-doc-length=72 --testsuite testing/data coverage run -m pytest testsuite - coverage combine coverage report [testenv:flake8] From c8c388e40ed6b79c931b8f6d10fe19510ee33ad5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 17 Jul 2023 20:16:29 -0400 Subject: [PATCH 357/412] move testsuite -> tests --- .gitattributes | 2 +- CONTRIBUTING.rst | 6 +++--- MANIFEST.in | 10 ---------- README.rst | 4 ++-- docs/advanced.rst | 2 +- docs/developer.rst | 2 +- docs/intro.rst | 18 +++++++++--------- setup.cfg | 2 +- {testsuite => tests}/__init__.py | 0 {testsuite => tests}/test_E101.py | 2 +- {testsuite => tests}/test_E901.py | 2 +- {testsuite => tests}/test_all.py | 0 {testsuite => tests}/test_api.py | 0 {testsuite => tests}/test_blank_lines.py | 0 {testsuite => tests}/test_data.py | 0 {testsuite => tests}/test_parser.py | 0 {testsuite => tests}/test_pycodestyle.py | 0 {testsuite => tests}/test_self_doctests.py | 0 {testsuite => tests}/test_shell.py | 2 +- {testsuite => tests}/test_util.py | 0 tox.ini | 2 +- 21 files changed, 22 insertions(+), 32 deletions(-) delete mode 100644 MANIFEST.in rename {testsuite => tests}/__init__.py (100%) rename {testsuite => tests}/test_E101.py (87%) rename {testsuite => tests}/test_E901.py (92%) rename {testsuite => tests}/test_all.py (100%) rename {testsuite => tests}/test_api.py (100%) rename {testsuite => tests}/test_blank_lines.py (100%) rename {testsuite => tests}/test_data.py (100%) rename {testsuite => tests}/test_parser.py (100%) rename {testsuite => tests}/test_pycodestyle.py (100%) rename {testsuite => tests}/test_self_doctests.py (100%) rename {testsuite => tests}/test_shell.py (99%) rename {testsuite => tests}/test_util.py (100%) diff --git a/.gitattributes b/.gitattributes index 0aadd30b..e1d5e1b9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -testsuite/E90.py -text +testing/data/E90.py -text diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 062b726a..e7a79ae3 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -82,16 +82,16 @@ include unit, integration and functional tests. To run the tests:: - $ pytest testsuite + $ pytest tests Running functional ~~~~~~~~~~~~~~~~~~ $ pip install -e . $ # Run all tests. - $ pytest testsuite + $ pytest tests/test_data.py $ # Run a subset of the tests. - $ pytest testsuite -k testing/data/E30.py + $ pytest tests/tests_data.py -k testing/data/E30.py .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index fb8bc97d..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,10 +0,0 @@ -include *.txt -include *.rst -include LICENSE -recursive-include docs * -recursive-include testsuite * -recursive-exclude docs *.pyc -recursive-exclude docs *.pyo -recursive-exclude testsuite *.pyc -recursive-exclude testsuite *.pyo -prune docs/_build diff --git a/README.rst b/README.rst index 2f6ddb87..bab8d71c 100644 --- a/README.rst +++ b/README.rst @@ -72,8 +72,8 @@ Example usage and output You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: - $ pycodestyle --show-source --show-pep8 testsuite/E40.py - testsuite/E40.py:2:10: E401 multiple imports on one line + $ pycodestyle --show-source --show-pep8 testing/data/E40.py + testing/data/E40.py:2:10: E401 multiple imports on one line import os, sys ^ Imports should usually be on separate lines. diff --git a/docs/advanced.rst b/docs/advanced.rst index 89700d56..9cdb5000 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -29,7 +29,7 @@ There's also a shortcut for checking a single file:: import pycodestyle - fchecker = pycodestyle.Checker('testsuite/E27.py', show_source=True) + fchecker = pycodestyle.Checker('tests/data/E27.py', show_source=True) file_errors = fchecker.check_all() print("Found %s errors (and warnings)" % file_errors) diff --git a/docs/developer.rst b/docs/developer.rst index 5a529e66..5aaee620 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -97,7 +97,7 @@ the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: - $ pytest testsuite + $ pytest tests $ python pycodestyle.py --verbose pycodestyle.py When contributing to pycodestyle, please observe our `Code of Conduct`_. diff --git a/docs/intro.rst b/docs/intro.rst index cdf5312b..8c74c782 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -78,8 +78,8 @@ Example usage and output You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: - $ pycodestyle --show-source --show-pep8 testsuite/E40.py - testsuite/E40.py:2:10: E401 multiple imports on one line + $ pycodestyle --show-source --show-pep8 testing/data/E40.py + testing/data/E40.py:2:10: E401 multiple imports on one line import os, sys ^ Imports should usually be on separate lines. @@ -105,14 +105,14 @@ Or you can display how often each error was found:: You can also make ``pycodestyle.py`` show the error text in different formats by using ``--format`` having options default/pylint/custom:: - $ pycodestyle testsuite/E40.py --format=default - testsuite/E40.py:2:10: E401 multiple imports on one line + $ pycodestyle testing/data/E40.py --format=default + testing/data/E40.py:2:10: E401 multiple imports on one line - $ pycodestyle testsuite/E40.py --format=pylint - testsuite/E40.py:2: [E401] multiple imports on one line + $ pycodestyle testing/data/E40.py --format=pylint + testing/data/E40.py:2: [E401] multiple imports on one line - $ pycodestyle testsuite/E40.py --format='%(path)s|%(row)d|%(col)d| %(code)s %(text)s' - testsuite/E40.py|2|10| E401 multiple imports on one line + $ pycodestyle testing/data/E40.py --format='%(path)s|%(row)d|%(col)d| %(code)s %(text)s' + testing/data/E40.py|2|10| E401 multiple imports on one line Variables in the ``custom`` format option @@ -433,7 +433,7 @@ special comment. This possibility should be reserved for special cases. Note: most errors can be listed with such one-liner:: - $ python pycodestyle.py --first --select E,W testsuite/ --format '%(code)s: %(text)s' + $ python pycodestyle.py --first --select E,W testing/data --format '%(code)s: %(text)s' .. _related-tools: diff --git a/setup.cfg b/setup.cfg index 2871b652..24c006f0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,7 +47,7 @@ max_doc_length = 72 [coverage:run] plugins = covdefaults -omit = testsuite/data +omit = testing/data [coverage:report] fail_under = 93 diff --git a/testsuite/__init__.py b/tests/__init__.py similarity index 100% rename from testsuite/__init__.py rename to tests/__init__.py diff --git a/testsuite/test_E101.py b/tests/test_E101.py similarity index 87% rename from testsuite/test_E101.py rename to tests/test_E101.py index c713370f..e3f2e5d0 100644 --- a/testsuite/test_E101.py +++ b/tests/test_E101.py @@ -1,4 +1,4 @@ -"""moved from testsuite files due to 3.12 making this a TokenError""" +"""moved from data files due to 3.12 making this a TokenError""" import sys import unittest diff --git a/testsuite/test_E901.py b/tests/test_E901.py similarity index 92% rename from testsuite/test_E901.py rename to tests/test_E901.py index 402b85a4..a1dbcfa9 100644 --- a/testsuite/test_E901.py +++ b/tests/test_E901.py @@ -1,4 +1,4 @@ -"""moved from testsuite files due to 3.12 changing syntax errors""" +"""moved from data files due to 3.12 changing syntax errors""" import sys import unittest diff --git a/testsuite/test_all.py b/tests/test_all.py similarity index 100% rename from testsuite/test_all.py rename to tests/test_all.py diff --git a/testsuite/test_api.py b/tests/test_api.py similarity index 100% rename from testsuite/test_api.py rename to tests/test_api.py diff --git a/testsuite/test_blank_lines.py b/tests/test_blank_lines.py similarity index 100% rename from testsuite/test_blank_lines.py rename to tests/test_blank_lines.py diff --git a/testsuite/test_data.py b/tests/test_data.py similarity index 100% rename from testsuite/test_data.py rename to tests/test_data.py diff --git a/testsuite/test_parser.py b/tests/test_parser.py similarity index 100% rename from testsuite/test_parser.py rename to tests/test_parser.py diff --git a/testsuite/test_pycodestyle.py b/tests/test_pycodestyle.py similarity index 100% rename from testsuite/test_pycodestyle.py rename to tests/test_pycodestyle.py diff --git a/testsuite/test_self_doctests.py b/tests/test_self_doctests.py similarity index 100% rename from testsuite/test_self_doctests.py rename to tests/test_self_doctests.py diff --git a/testsuite/test_shell.py b/tests/test_shell.py similarity index 99% rename from testsuite/test_shell.py rename to tests/test_shell.py index 8de8886d..e4a75e1c 100644 --- a/testsuite/test_shell.py +++ b/tests/test_shell.py @@ -194,7 +194,7 @@ def test_check_diff(self): )) # no matching file in the diff - diff_lines[3] = "+++ b/testsuite/lost/E11.py" + diff_lines[3] = "+++ b/testing/lost/E11.py" self.stdin = '\n'.join(diff_lines) stdout, stderr, errcode = self.pycodestyle('--diff') self.assertFalse(errcode) diff --git a/testsuite/test_util.py b/tests/test_util.py similarity index 100% rename from testsuite/test_util.py rename to tests/test_util.py diff --git a/tox.ini b/tox.ini index de802015..c9b9255f 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ deps = pytest commands = python -m pycodestyle --statistics pycodestyle.py - coverage run -m pytest testsuite + coverage run -m pytest tests coverage report [testenv:flake8] From 7701377c0bdb999c380d67f82c3d555fa4c5d69c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 17 Jul 2023 20:58:16 -0400 Subject: [PATCH 358/412] handle noqa for multiline fstrings in 3.12 --- pycodestyle.py | 27 +++++++++++++++++---------- testing/data/noqa.py | 6 ++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 9388ae52..c902ad72 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1842,6 +1842,7 @@ def __init__(self, filename=None, lines=None, self.max_line_length = options.max_line_length self.max_doc_length = options.max_doc_length self.indent_size = options.indent_size + self.fstring_start = 0 self.multiline = False # in a multiline string? self.hang_closing = options.hang_closing self.indent_size = options.indent_size @@ -2030,13 +2031,15 @@ def maybe_check_physical(self, token, prev_physical): # if the file does not end with a newline, the NEWLINE # token is inserted by the parser, but it does not contain # the previous physical line in `token[4]` - if token[4] == '': + if token.line == '': self.check_physical(prev_physical) else: - self.check_physical(token[4]) + self.check_physical(token.line) + elif token.type == FSTRING_START: # pragma: >=3.12 cover + self.fstring_start = token.start[0] elif ( - token[0] in {tokenize.STRING, FSTRING_MIDDLE} and - '\n' in token[1] + token.type == tokenize.STRING and '\n' in token.string or + token.type == FSTRING_END ): # Less obviously, a string that contains newlines is a # multiline string, either triple-quoted or with internal @@ -2053,14 +2056,18 @@ def maybe_check_physical(self, token, prev_physical): # - have to wind self.line_number back because initially it # points to the last line of the string, and we want # check_physical() to give accurate feedback - if noqa(token[4]): + if noqa(token.line): return + if token.type == FSTRING_END: # pragma: >=3.12 cover + start = self.fstring_start + else: + start = token.start[0] + end = token.end[0] + self.multiline = True - self.line_number = token[2][0] - _, src, (_, offset), _, _ = token - src = self.lines[self.line_number - 1][:offset] + src - for line in src.split('\n')[:-1]: - self.check_physical(line + '\n') + self.line_number = start + for line_number in range(start, end): + self.check_physical(self.lines[line_number - 1] + '\n') self.line_number += 1 self.multiline = False diff --git a/testing/data/noqa.py b/testing/data/noqa.py index 02fdd4f8..3d02492e 100644 --- a/testing/data/noqa.py +++ b/testing/data/noqa.py @@ -12,4 +12,10 @@ a = 1 if a == None: # noqa pass + +# should silence E501 +s = f''' +loong {y} looooooooooooooong loooooooooooooong looooooooong loooooooong looooooooong +{x} +''' # noqa #: From 146c422aba6346fbdd17c043b77faa411a7bed8e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 17 Jul 2023 21:24:51 -0400 Subject: [PATCH 359/412] 3.12: fix fstring starting with escape --- pycodestyle.py | 6 +++--- testing/data/E50.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index c902ad72..db0c78ab 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -2026,8 +2026,10 @@ def maybe_check_physical(self, token, prev_physical): """If appropriate for token, check current physical line(s).""" # Called after every token, but act only on end of line. + if token.type == FSTRING_START: # pragma: >=3.12 cover + self.fstring_start = token.start[0] # a newline token ends a single physical line. - if _is_eol_token(token): + elif _is_eol_token(token): # if the file does not end with a newline, the NEWLINE # token is inserted by the parser, but it does not contain # the previous physical line in `token[4]` @@ -2035,8 +2037,6 @@ def maybe_check_physical(self, token, prev_physical): self.check_physical(prev_physical) else: self.check_physical(token.line) - elif token.type == FSTRING_START: # pragma: >=3.12 cover - self.fstring_start = token.start[0] elif ( token.type == tokenize.STRING and '\n' in token.string or token.type == FSTRING_END diff --git a/testing/data/E50.py b/testing/data/E50.py index ab7ddd45..6ab50c88 100644 --- a/testing/data/E50.py +++ b/testing/data/E50.py @@ -92,6 +92,10 @@ def foo(): """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis pulvinar vitae """ +#: E501 +loooooong = 'looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong' +f"""\ +""" #: Okay """ This From a21f8e3d1ebb74070c82c313e02807536976c8bf Mon Sep 17 00:00:00 2001 From: Lev Blit Date: Fri, 21 Jul 2023 15:06:44 +0300 Subject: [PATCH 360/412] fix some wrong renames from testsuite to testing/data vs test --- docs/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 9cdb5000..4769e06f 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -29,7 +29,7 @@ There's also a shortcut for checking a single file:: import pycodestyle - fchecker = pycodestyle.Checker('tests/data/E27.py', show_source=True) + fchecker = pycodestyle.Checker('testing/data/E27.py', show_source=True) file_errors = fchecker.check_all() print("Found %s errors (and warnings)" % file_errors) From e2a366da80cd83af1f9fb95bb28ba0a332e23597 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Jul 2023 12:40:06 -0400 Subject: [PATCH 361/412] remove handling of python 2 ur'' strings --- pycodestyle.py | 3 +-- testing/data/E12not.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index db0c78ab..b10c498d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -686,8 +686,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if verbose >= 4: print(f"bracket depth {depth} indent to {start[1]}") # deal with implicit string concatenation - elif (token_type in (tokenize.STRING, tokenize.COMMENT) or - text in ('u', 'ur', 'b', 'br')): + elif token_type in (tokenize.STRING, tokenize.COMMENT): indent_chances[start[1]] = str # visual indent after assert/raise/with elif not row and not depth and text in ["assert", "raise", "with"]: diff --git a/testing/data/E12not.py b/testing/data/E12not.py index 76776591..86c0fdd7 100644 --- a/testing/data/E12not.py +++ b/testing/data/E12not.py @@ -429,9 +429,6 @@ def unicode2html(s): help = u"print total number of errors " \ u"to standard error" -help = ur"print total number of errors " \ - ur"to standard error" - help = b"print total number of errors " \ b"to standard error" From e0c1069ee97c4f041d69e29141f992416f4f100e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Jul 2023 12:44:30 -0400 Subject: [PATCH 362/412] handle fstring continuation in 3.12 --- pycodestyle.py | 2 +- testing/data/E12not.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index b10c498d..445281c1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -686,7 +686,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if verbose >= 4: print(f"bracket depth {depth} indent to {start[1]}") # deal with implicit string concatenation - elif token_type in (tokenize.STRING, tokenize.COMMENT): + elif token_type in (tokenize.STRING, tokenize.COMMENT, FSTRING_START): indent_chances[start[1]] = str # visual indent after assert/raise/with elif not row and not depth and text in ["assert", "raise", "with"]: diff --git a/testing/data/E12not.py b/testing/data/E12not.py index 86c0fdd7..a92739e5 100644 --- a/testing/data/E12not.py +++ b/testing/data/E12not.py @@ -435,6 +435,9 @@ def unicode2html(s): help = br"print total number of errors " \ br"to standard error" +help = f"print total number of errors " \ + f"to standard error" + d = dict('foo', help="exclude files or directories which match these " "comma separated patterns (default: %s)" % DEFAULT_EXCLUDE) From 21abd9b6dcbfa38635bc85a2c2327ec11ad91ffc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Jul 2023 13:18:24 -0400 Subject: [PATCH 363/412] Release 2.11.0 --- CHANGES.txt | 18 +++++++++++++++++- pycodestyle.py | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9570eecf..931a6ee3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,12 +1,28 @@ Changelog ========= +2.11.0 (2023-07-29) +------------------- + +Changes: + +* Drop EOL python 3.6 / 3.7. PR #1129, #1160. +* Add support for python 3.12. PR #1147, #1148, #1152, #1153, #1154, #1163, + #1164, #1165, #1166, #1176, #1177, #1182. +* E721: adjust handling of type comparison. Allowed forms are now + ``isinstance(x, t)`` or ``type(x) is t``. PR #1086, #1167. +* Remove handling of python 2 ``<>`` operator. PR #1161. +* W606: removed. ``async`` / ``await`` are always keywords. PR #1162. +* Internal: move tests to pytest. PR #1168, #1169, #1171, #1173, #1174, #1175. +* Remove handling of python 2 ``ur''`` strings. PR #1181. + + 2.10.0 (2022-11-23) ------------------- Changes: -* E231: allow trailing comma inside 1-tuples in `[]`. PR #1108. +* E231: allow trailing comma inside 1-tuples in ``[]``. PR #1108. * W601, W602, W603, W604: removed (no longer relevant in python 3). PR #1111. * E741: also apply to lambdas. PR #1106. * E741: fix false positive for comparison operators. PR #1118. diff --git a/pycodestyle.py b/pycodestyle.py index 445281c1..c492436b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Mon, 31 Jul 2023 19:20:51 -0400 Subject: [PATCH 364/412] add minimal rtd configuration --- .readthedocs.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..4b74802e --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,6 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" From dcf87677c9b6611ba31e0a3d9b009842071fdc56 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 12:28:38 +0000 Subject: [PATCH 365/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.9.0 → v3.10.1](https://github.com/asottile/pyupgrade/compare/v3.9.0...v3.10.1) - [github.com/pycqa/flake8: 6.0.0 → 6.1.0](https://github.com/pycqa/flake8/compare/6.0.0...6.1.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f57e1dd9..98a90b84 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.9.0 + rev: v3.10.1 hooks: - id: pyupgrade args: [--py38-plus] @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 From 6b6c75c073a5cff56d872252b8eb08512162ca57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:02:04 +0000 Subject: [PATCH 366/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.11.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.11.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.11.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.11.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 98a90b84..035978a3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.10.0 + rev: v3.11.0 hooks: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.11.0 hooks: - id: pyupgrade args: [--py38-plus] From 0fd02d7dcbb4485b5130c7119893eed94dd58353 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:18:57 +0000 Subject: [PATCH 367/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.11.0 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.11.0...v3.13.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 035978a3..a1798df8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.11.0 + rev: v3.13.0 hooks: - id: pyupgrade args: [--py38-plus] From f1b552e44f0e7055fdf34503580c60eebf3deb17 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:58:01 +0000 Subject: [PATCH 368/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.11.0 → v3.12.0](https://github.com/asottile/reorder-python-imports/compare/v3.11.0...v3.12.0) - [github.com/asottile/pyupgrade: v3.13.0 → v3.14.0](https://github.com/asottile/pyupgrade/compare/v3.13.0...v3.14.0) - [github.com/asottile/setup-cfg-fmt: v2.4.0 → v2.5.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.4.0...v2.5.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1798df8..07d60338 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,17 +8,17 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.11.0 + rev: v3.12.0 hooks: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 + rev: v3.14.0 hooks: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.4.0 + rev: v2.5.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 From 04d9c84b8804e32d50f637cf1f169e8235f1b562 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 21:55:50 +0000 Subject: [PATCH 369/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/asottile/pyupgrade: v3.14.0 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.14.0...v3.15.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07d60338..8b1ea6d2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml - id: debug-statements @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.14.0 + rev: v3.15.0 hooks: - id: pyupgrade args: [--py38-plus] From 0c5e01a41d86ca30385622948252f350878921c1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 12 Oct 2023 09:19:56 -0400 Subject: [PATCH 370/412] fix 3.12 false positive when fstring chunk is a keyword --- pycodestyle.py | 1 + testing/data/python36.py | 1 + 2 files changed, 2 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index c492436b..18514f06 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -490,6 +490,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): # appear e.g. as "if x is None:", and async/await, which were # valid identifier names in old Python versions. if (tok0.end == tok1.start and + tok0.type == tokenize.NAME and keyword.iskeyword(tok0.string) and tok0.string not in SINGLETONS and not (tok0.string == 'except' and tok1.string == '*') and diff --git a/testing/data/python36.py b/testing/data/python36.py index 94ec2dc5..aefd6540 100644 --- a/testing/data/python36.py +++ b/testing/data/python36.py @@ -1,2 +1,3 @@ #: Okay f'{hello}:{world}' +f'in{x}' From cefd59cf8e8a199a978da491e75a37fe4d37de82 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 12 Oct 2023 19:38:21 -0400 Subject: [PATCH 371/412] Release 2.11.1 --- CHANGES.txt | 7 +++++++ pycodestyle.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 931a6ee3..55c000aa 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Changelog ========= +2.11.1 (2023-10-12) +------------------- + +Changes: + +* E275: fix false positive with fstrings containing keyword parts in python 3.12 + 2.11.0 (2023-07-29) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 18514f06..3354346a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Tue, 21 Nov 2023 11:28:02 +0100 Subject: [PATCH 372/412] testing: add a pair of E303 testcases Add two test cases to verify that pycodestyle correctly flags 2 empty lines in non-toplevel contexts, even outside of functions: one for two empty lines between class methods and one for two empty lines in the indented body of an `if:` statement at toplevel. --- testing/data/E30.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/testing/data/E30.py b/testing/data/E30.py index ebe4e9d2..7ef46890 100644 --- a/testing/data/E30.py +++ b/testing/data/E30.py @@ -75,6 +75,20 @@ def a(): #: +#: E303:6:5 +class xyz: + def a(self): + pass + + + def b(self): + pass +#: E303:5:5 +if True: + a = 1 + + + a = 2 #: E304:3:1 @decorator From 9dc066bc792ef2b58e87ef2fd5b5e0203c520c30 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 27 Dec 2023 00:07:24 -0500 Subject: [PATCH 373/412] improve backtracking of COMPARE_TYPE_REGEX --- pycodestyle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3354346a..2f092c40 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -126,8 +126,8 @@ COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+|:=)(\s*)') From 2a9ee347e09ec4f00bc9e085cf4c7110a257595b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 21:35:32 +0000 Subject: [PATCH 374/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 6.1.0 → 7.0.0](https://github.com/pycqa/flake8/compare/6.1.0...7.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8b1ea6d2..abecae48 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 + rev: 7.0.0 hooks: - id: flake8 From 2a612c15c24afaf09fe585fcb5038a3cac54c3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20D=C3=BCmig?= Date: Tue, 6 Feb 2024 22:24:25 +0200 Subject: [PATCH 375/412] prevent false positive for E721 on member function E721 was reported on the result of member functions of name "type" with one parameter, if those occurred on the left side of the comparison. --- pycodestyle.py | 2 +- testing/data/E72.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 2f092c40..9adbb6b1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -127,7 +127,7 @@ r'(in|is)\s') COMPARE_TYPE_REGEX = re.compile( r'[=!]=\s+type(?:\s*\(\s*([^)]*[^\s)])\s*\))' - r'|\btype(?:\s*\(\s*([^)]*[^\s)])\s*\))\s+[=!]=' + r'|(?%&^]+|:=)(\s*)') diff --git a/testing/data/E72.py b/testing/data/E72.py index ac55a958..5d1046cb 100644 --- a/testing/data/E72.py +++ b/testing/data/E72.py @@ -5,6 +5,8 @@ if type(res) != type(""): pass #: Okay +res.type("") == "" +#: Okay import types if res == types.IntType: From 45e2d9f9726d9682f9baadc11c61cc6c7af56c72 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 01:17:01 +0000 Subject: [PATCH 376/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.0 → v3.15.1](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.15.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abecae48..5aecb4eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 + rev: v3.15.1 hooks: - id: pyupgrade args: [--py38-plus] From 72668f67e94d58e69914dd4b139907956ef79575 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 8 Mar 2024 15:42:38 -0500 Subject: [PATCH 377/412] fix E502 being disabled after a comment --- pycodestyle.py | 2 ++ testing/data/E50.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index 9adbb6b1..6443a42a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1269,6 +1269,8 @@ def explicit_line_join(logical_line, tokens): comment = True if start[0] != prev_start and parens and backslash and not comment: yield backslash, "E502 the backslash is redundant between brackets" + if start[0] != prev_start: + comment = False # Reset comment flag on newline if end[0] != prev_end: if line.rstrip('\r\n').endswith('\\'): backslash = (end[0], len(line.splitlines()[-1]) - 1) diff --git a/testing/data/E50.py b/testing/data/E50.py index 6ab50c88..4870fcc2 100644 --- a/testing/data/E50.py +++ b/testing/data/E50.py @@ -25,6 +25,13 @@ if (foo is None and bar is "e000" and \ blah == 'yeah'): blah = 'yeahnah' +#: E502 W503 W503 +y = ( + 2 + 2 # \ + + 3 # \ + + 4 \ + + 3 +) # #: Okay a = ('AAA' From 6febb0d05ff37a177ddf10190804b0e0076d84d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:47:40 +0000 Subject: [PATCH 378/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.1 → v3.15.2](https://github.com/asottile/pyupgrade/compare/v3.15.1...v3.15.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5aecb4eb..0b95e30d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.15.1 + rev: v3.15.2 hooks: - id: pyupgrade args: [--py38-plus] From 04f579dd76aa0294db65c3dbbbd110c80609137e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 23:37:50 +0000 Subject: [PATCH 379/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b95e30d..541017f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-yaml - id: debug-statements From 53591a50faf21c27d9ffaeda75dd869fbec19a20 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 23:13:12 +0000 Subject: [PATCH 380/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.12.0 → v3.13.0](https://github.com/asottile/reorder-python-imports/compare/v3.12.0...v3.13.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 541017f9..f1eec7fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 + rev: v3.13.0 hooks: - id: reorder-python-imports args: [--py38-plus] From 5be6ca17fcd04b5e8d7bf4d8c57daf9cd17b0bea Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 23:29:23 +0000 Subject: [PATCH 381/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.2 → v3.16.0](https://github.com/asottile/pyupgrade/compare/v3.15.2...v3.16.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f1eec7fd..9abea703 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.15.2 + rev: v3.16.0 hooks: - id: pyupgrade args: [--py38-plus] From 3cedd4c74b5f467fc4c1cde5a8ac2503a54b60db Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jun 2024 14:29:53 -0400 Subject: [PATCH 382/412] add new error E204 for whitespace after decorator @ --- docs/intro.rst | 2 ++ pycodestyle.py | 7 +++++++ testing/data/E20.py | 17 +++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/docs/intro.rst b/docs/intro.rst index 8c74c782..a7187a27 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -262,6 +262,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E203 | whitespace before ',', ';', or ':' | +------------+----------------------------------------------------------------------+ +| E204 | whitespace after decorator '@' | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | E211 | whitespace before '(' | +------------+----------------------------------------------------------------------+ diff --git a/pycodestyle.py b/pycodestyle.py index 6443a42a..139ae2ba 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -120,6 +120,7 @@ ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') DOCSTRING_REGEX = re.compile(r'u?r?["\']') EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[\[({][ \t]|[ \t][\]}),;:](?!=)') +WHITESPACE_AFTER_DECORATOR_REGEX = re.compile(r'@\s') 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') @@ -438,6 +439,9 @@ def extraneous_whitespace(logical_line): E203: if x == 4: print x, y; x, y = y , x E203: if x == 4: print x, y ; x, y = y, x E203: if x == 4 : print x, y; x, y = y, x + + Okay: @decorator + E204: @ decorator """ line = logical_line for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): @@ -451,6 +455,9 @@ def extraneous_whitespace(logical_line): code = ('E202' if char in '}])' else 'E203') # if char in ',;:' yield found, f"{code} whitespace before '{char}'" + if WHITESPACE_AFTER_DECORATOR_REGEX.match(logical_line): + yield 1, "E204 whitespace after decorator '@'" + @register_check def whitespace_around_keywords(logical_line): diff --git a/testing/data/E20.py b/testing/data/E20.py index 20c6dfd8..ed21b213 100644 --- a/testing/data/E20.py +++ b/testing/data/E20.py @@ -75,4 +75,21 @@ x, y = y, x a[b1, :] == a[b1, ...] b = a[:, b1] +#: E204:1:2 +@ decorator +def f(): + pass +#: E204:1:2 +@ decorator +def f(): + pass +#: E204:1:2 +@ decorator +def f(): + pass +#: E204:2:6 +if True: + @ decorator + def f(): + pass #: From 6f60985ab563c3657da8d44ba391128b40c9ea24 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jun 2024 17:28:03 -0400 Subject: [PATCH 383/412] Release 2.12.0 --- CHANGES.txt | 9 +++++++++ pycodestyle.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 55c000aa..6e8f9f67 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,15 @@ Changelog ========= +2.12.0 (2024-06-15) +------------------- + +Changes: + +* E721: Fix false positive of the form `x.type(...) ==`. PR #1228. +* E502: Fix false-negative with a backslash escape in a comment. PR #1234. +* E204: New lint forbidding whitespace after decorator `@`. PR #1247. + 2.11.1 (2023-10-12) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 139ae2ba..19f88c2a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Mon, 17 Jun 2024 23:33:17 +0000 Subject: [PATCH 384/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.0.0 → 7.1.0](https://github.com/pycqa/flake8/compare/7.0.0...7.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9abea703..0681be99 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.1.0 hooks: - id: flake8 From 28aeabb3cbd13154ce0171c2c2d2c02ff577493c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:25:52 +0000 Subject: [PATCH 385/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.16.0 → v3.17.0](https://github.com/asottile/pyupgrade/compare/v3.16.0...v3.17.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0681be99..37793c4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.16.0 + rev: v3.17.0 hooks: - id: pyupgrade args: [--py38-plus] From 37c9f605259c686daeb9c00f33df886cd83b3d1c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 4 Aug 2024 16:16:13 -0400 Subject: [PATCH 386/412] adjust logical line for FSTRING_MIDDLE brace escaping --- pycodestyle.py | 5 ++++- tests/test_pycodestyle.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 19f88c2a..4df30f62 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1949,7 +1949,10 @@ def build_tokens_line(self): if token_type == tokenize.STRING: text = mute_string(text) elif token_type == FSTRING_MIDDLE: # pragma: >=3.12 cover - text = 'x' * len(text) + # fstring tokens are "unescaped" braces -- re-escape! + brace_count = text.count('{') + text.count('}') + text = 'x' * (len(text) + brace_count) + end = (end[0], end[1] + brace_count) if prev_row: (start_row, start_col) = start if prev_row != start_row: # different row diff --git a/tests/test_pycodestyle.py b/tests/test_pycodestyle.py index 8885f0d8..444d59f0 100644 --- a/tests/test_pycodestyle.py +++ b/tests/test_pycodestyle.py @@ -1,5 +1,10 @@ +import io +import sys +import tokenize + import pytest +from pycodestyle import Checker from pycodestyle import expand_indent from pycodestyle import mute_string @@ -27,3 +32,17 @@ def test_expand_indent(s, expected): ) def test_mute_string(s, expected): assert mute_string(s) == expected + + +def test_fstring_logical_line(): + src = '''\ +f'hello {{ {thing} }} world' +''' + checker = Checker(lines=src.splitlines()) + checker.tokens = list(tokenize.generate_tokens(io.StringIO(src).readline)) + checker.build_tokens_line() + + if sys.version_info >= (3, 12): # pragma: >3.12 cover + assert checker.logical_line == "f'xxxxxxxxx{thing}xxxxxxxxx'" + else: + assert checker.logical_line == "f'xxxxxxxxxxxxxxxxxxxxxxxxx'" From 5cff01b3a8ea81961483b7ff885df66db6b44e4e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 4 Aug 2024 16:25:54 -0400 Subject: [PATCH 387/412] Release 2.12.1 --- CHANGES.txt | 8 ++++++++ pycodestyle.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 6e8f9f67..9834484f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,14 @@ Changelog ========= +2.12.1 (2024-08-04) +------------------- + +Changes: + +* Properly preserve escaped `{` and `}` in fstrings in logical lines in 3.12+. + PR #1252. + 2.12.0 (2024-06-15) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 4df30f62..c4c8dbaa 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Tue, 6 Aug 2024 00:22:09 +0000 Subject: [PATCH 388/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.1.0 → 7.1.1](https://github.com/pycqa/flake8/compare/7.1.0...7.1.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 37793c4c..2827172a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.1.0 + rev: 7.1.1 hooks: - id: flake8 From 035b52f15eb6ecca44460567d1961ab47a758c12 Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Fri, 9 Aug 2024 04:30:09 -0400 Subject: [PATCH 389/412] Combine rstrip calls in trailing_whitespace check This provides a small speed-up on large codebases. --- pycodestyle.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index c4c8dbaa..ec07aaac 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -233,9 +233,11 @@ def trailing_whitespace(physical_line): W291: spam(1) \n# W293: class Foo(object):\n \n bang = 12 """ - physical_line = physical_line.rstrip('\n') # chr(10), newline - physical_line = physical_line.rstrip('\r') # chr(13), carriage return - physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L + # Strip these trailing characters: + # - chr(10), newline + # - chr(13), carriage return + # - chr(12), form feed, ^L + physical_line = physical_line.rstrip('\n\r\x0c') stripped = physical_line.rstrip(' \t\v') if physical_line != stripped: if stripped: From bb413524046e46b65162c4248a3fe2979d3b1c06 Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Fri, 9 Aug 2024 04:41:43 -0400 Subject: [PATCH 390/412] Use list comprehensions to avoid append() calls This provides a small speed-up on large codebases. --- pycodestyle.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index c4c8dbaa..3d32b581 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1910,9 +1910,7 @@ def readline(self): def run_check(self, check, argument_names): """Run a check plugin.""" - arguments = [] - for name in argument_names: - arguments.append(getattr(self, name)) + arguments = [getattr(self, name) for name in argument_names] return check(*arguments) def init_checker_state(self, name, argument_names): From 6a19e1ff9357e8e02c07b22050dfb99dc048f770 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 01:47:21 +0000 Subject: [PATCH 391/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2827172a..6894e4c1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-yaml - id: debug-statements From 414f4700ee5dea6e41e9a0c36cb373b86e0031a7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Oct 2024 20:42:55 -0400 Subject: [PATCH 392/412] py39-plus --- .github/workflows/main.yml | 15 +++++++++------ .pre-commit-config.yaml | 4 ++-- pycodestyle.py | 7 ++----- setup.cfg | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 393067b1..c53a94f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,6 @@ jobs: - os: ubuntu-latest py: pypy3.10 toxenv: py - - os: ubuntu-latest - py: 3.8 - toxenv: py - os: ubuntu-latest py: 3.9 toxenv: py @@ -29,7 +26,13 @@ jobs: py: '3.11' toxenv: py - os: ubuntu-latest - py: '3.12-dev' + py: '3.12' + toxenv: py + - os: ubuntu-latest + py: '3.13' + toxenv: py + - os: ubuntu-latest + py: '3.14-dev' toxenv: py - os: ubuntu-latest py: 3.9 @@ -40,10 +43,10 @@ jobs: - uses: actions/setup-python@v4 with: python-version: ${{ matrix.py }} - if: matrix.py != '3.12-dev' + if: matrix.py != '3.14-dev' - uses: deadsnakes/action@v3.0.1 with: python-version: ${{ matrix.py }} - if: matrix.py == '3.12-dev' + if: matrix.py == '3.14-dev' - run: pip install tox - run: tox -e ${{ matrix.toxenv }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6894e4c1..ca1632dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,12 @@ repos: rev: v3.13.0 hooks: - id: reorder-python-imports - args: [--py38-plus] + args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade rev: v3.17.0 hooks: - id: pyupgrade - args: [--py38-plus] + args: [--py39-plus] - repo: https://github.com/asottile/setup-cfg-fmt rev: v2.5.0 hooks: diff --git a/pycodestyle.py b/pycodestyle.py index 6425596e..07eeb49e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -785,7 +785,6 @@ def whitespace_before_parameters(logical_line, tokens): # Allow "return (a.foo for a in range(5))" not keyword.iskeyword(prev_text) and ( - sys.version_info < (3, 9) or # 3.12+: type is a soft keyword but no braces after prev_text == 'type' or not keyword.issoftkeyword(prev_text) @@ -960,10 +959,8 @@ def missing_whitespace(logical_line, tokens): # Allow argument unpacking: foo(*args, **kwargs). if prev_type == tokenize.OP and prev_text in '}])' or ( prev_type != tokenize.OP and - prev_text not in KEYWORDS and ( - sys.version_info < (3, 9) or - not keyword.issoftkeyword(prev_text) - ) + prev_text not in KEYWORDS and + not keyword.issoftkeyword(prev_text) ): need_space = None elif text in WS_OPTIONAL_OPERATORS: diff --git a/setup.cfg b/setup.cfg index 24c006f0..09a89190 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,7 @@ project_urls = [options] py_modules = pycodestyle -python_requires = >=3.8 +python_requires = >=3.9 include_package_data = True zip_safe = False From 03e3a3aff319c688104aa117ab49eaef2b8284b3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:07:58 +0000 Subject: [PATCH 393/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.13.0 → v3.14.0](https://github.com/asottile/reorder-python-imports/compare/v3.13.0...v3.14.0) - [github.com/asottile/pyupgrade: v3.17.0 → v3.18.0](https://github.com/asottile/pyupgrade/compare/v3.17.0...v3.18.0) - [github.com/asottile/setup-cfg-fmt: v2.5.0 → v2.7.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.5.0...v2.7.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca1632dc..73815320 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,17 +8,17 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.13.0 + rev: v3.14.0 hooks: - id: reorder-python-imports args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.17.0 + rev: v3.18.0 hooks: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.5.0 + rev: v2.7.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 From c226236652c264c36c544c05345c24a5eff6f8a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:36:31 +0000 Subject: [PATCH 394/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.18.0 → v3.19.0](https://github.com/asottile/pyupgrade/compare/v3.18.0...v3.19.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 73815320..f3d0f804 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.18.0 + rev: v3.19.0 hooks: - id: pyupgrade args: [--py39-plus] From bf4c16d2891150d0d43df3a6e9f7f5b6361544ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:26:08 +0000 Subject: [PATCH 395/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.19.0 → v3.19.1](https://github.com/asottile/pyupgrade/compare/v3.19.0...v3.19.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f3d0f804..b353c3cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.19.0 + rev: v3.19.1 hooks: - id: pyupgrade args: [--py39-plus] From 5f7b5d261996c2c25530a82eae2cf5eeffc22c3c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:43:58 +0000 Subject: [PATCH 396/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.1.1 → 7.1.2](https://github.com/pycqa/flake8/compare/7.1.1...7.1.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b353c3cd..63996874 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.1.1 + rev: 7.1.2 hooks: - id: flake8 From 30627b318756c66ee0ff37a798f8b9339a7c0a83 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 18 Mar 2025 13:49:20 -0400 Subject: [PATCH 397/412] add support for PEP 696 generic defaults --- pycodestyle.py | 28 +++++++++++++++++++--------- testing/data/python313.py | 9 +++++++++ tests/test_E901.py | 4 ++-- 3 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 testing/data/python313.py diff --git a/pycodestyle.py b/pycodestyle.py index 07eeb49e..3324850a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -135,6 +135,7 @@ LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') +STARTSWITH_GENERIC_REGEX = re.compile(r'^(async\s+def|def|class|type)\s+\w+\[') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( r'^\s*({})\b'.format('|'.join(s.replace(' ', r'\s+') for s in ( @@ -1019,12 +1020,13 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): E251: return magic(r = real, i = imag) E252: def complex(real, image: float=0.0): """ - parens = 0 + paren_stack = [] no_space = False require_space = False prev_end = None annotated_func_arg = False in_def = bool(STARTSWITH_DEF_REGEX.match(logical_line)) + in_generic = bool(STARTSWITH_GENERIC_REGEX.match(logical_line)) message = "E251 unexpected spaces around keyword / parameter equals" missing_message = "E252 missing whitespace around parameter equals" @@ -1042,15 +1044,23 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): yield (prev_end, missing_message) if token_type == tokenize.OP: if text in '([': - parens += 1 - elif text in ')]': - parens -= 1 - elif in_def and text == ':' and parens == 1: + paren_stack.append(text) + elif text in ')]' and paren_stack: + paren_stack.pop() + elif ( + text == ':' and ( + # def f(arg: tp = default): ... + (in_def and paren_stack == ['(']) or + # def f[T: tp = default](): ... + # class C[T: tp = default](): ... + (in_generic and paren_stack == ['[']) + ) + ): annotated_func_arg = True - elif parens == 1 and text == ',': + elif len(paren_stack) == 1 and text == ',': annotated_func_arg = False - elif parens and text == '=': - if annotated_func_arg and parens == 1: + elif paren_stack and text == '=': + if annotated_func_arg and len(paren_stack) == 1: require_space = True if start == prev_end: yield (prev_end, missing_message) @@ -1058,7 +1068,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): no_space = True if start != prev_end: yield (prev_end, message) - if not parens: + if not paren_stack: annotated_func_arg = False prev_end = end diff --git a/testing/data/python313.py b/testing/data/python313.py new file mode 100644 index 00000000..5247ae9c --- /dev/null +++ b/testing/data/python313.py @@ -0,0 +1,9 @@ +type Alias[T: (int, str) = str] = list[T] + + +class C[T: (int, str) = str]: + pass + + +def f[T: (int, str) = str](t: T) -> T: + pass diff --git a/tests/test_E901.py b/tests/test_E901.py index a1dbcfa9..caf68d50 100644 --- a/tests/test_E901.py +++ b/tests/test_E901.py @@ -23,7 +23,7 @@ def lasting(self, duration=300): ''' errors = errors_from_src(src) if sys.version_info < (3, 12): # pragma: <3.12 cover - expected = ['E122:4:1', 'E251:5:13', 'E251:5:15'] + expected = ['E122:4:1'] else: # pragma: >=3.12 cover - expected = ['E122:4:1', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 + expected = ['E122:4:1', 'E901:5:1'] # noqa: E501 self.assertEqual(errors, expected) From 778d00d11e66f71af5bac0cc5888caa37f327fae Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 19 Mar 2025 18:27:10 -0400 Subject: [PATCH 398/412] handle PEP 696 generics without bounds --- pycodestyle.py | 17 ++++++++--------- testing/data/python313.py | 9 +++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3324850a..5f79a257 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1045,17 +1045,16 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): if token_type == tokenize.OP: if text in '([': paren_stack.append(text) + # PEP 696 defaults always use spaced-style `=` + # type A[T = default] = ... + # def f[T = default](): ... + # class C[T = default](): ... + if in_generic and paren_stack == ['[']: + annotated_func_arg = True elif text in ')]' and paren_stack: paren_stack.pop() - elif ( - text == ':' and ( - # def f(arg: tp = default): ... - (in_def and paren_stack == ['(']) or - # def f[T: tp = default](): ... - # class C[T: tp = default](): ... - (in_generic and paren_stack == ['[']) - ) - ): + # def f(arg: tp = default): ... + elif text == ':' and in_def and paren_stack == ['(']: annotated_func_arg = True elif len(paren_stack) == 1 and text == ',': annotated_func_arg = False diff --git a/testing/data/python313.py b/testing/data/python313.py index 5247ae9c..5540aced 100644 --- a/testing/data/python313.py +++ b/testing/data/python313.py @@ -1,9 +1,18 @@ type Alias[T: (int, str) = str] = list[T] +type Alias2[T = str] = list[T] class C[T: (int, str) = str]: pass +class C2[T = str]: + pass + + def f[T: (int, str) = str](t: T) -> T: pass + + +def f2[T = str](t: T) -> T: + pass From 3f52426121985a1717c214aeac47650d5c729621 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Mar 2025 13:34:59 -0400 Subject: [PATCH 399/412] Release 2.13.0 --- CHANGES.txt | 9 +++++++++ pycodestyle.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9834484f..aeaef654 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,15 @@ Changelog ========= +2.13.0 (2025-03-29) +------------------- + +Changes: + +* Improve performance. PR #1254. PR #1255. +* Drop EOL python 3.8. PR #1267. +* E251: fix false positive for PEP 696 defaults. PR #1278. PR #1279. + 2.12.1 (2024-08-04) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 5f79a257..c38afb39 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Mon, 31 Mar 2025 20:51:51 +0000 Subject: [PATCH 400/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v2.7.0 → v2.8.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.7.0...v2.8.0) - [github.com/pycqa/flake8: 7.1.2 → 7.2.0](https://github.com/pycqa/flake8/compare/7.1.2...7.2.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 63996874..f4218f9f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,10 +18,10 @@ repos: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.7.0 + rev: v2.8.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.1.2 + rev: 7.2.0 hooks: - id: flake8 From 7182ac8a56cdff4a05305b85e5e140922aaf56ba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 20:52:10 +0000 Subject: [PATCH 401/412] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 09a89190..7565ec63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,6 @@ classifiers = Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers - License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 From ae41b3420360a9f6732b316f5746c56146a37932 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Fri, 23 May 2025 16:07:37 -0400 Subject: [PATCH 402/412] updates for python 3.14 --- pycodestyle.py | 42 +++++++++++++++++++++++++++++++-------- testing/data/python314.py | 19 ++++++++++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 testing/data/python314.py diff --git a/pycodestyle.py b/pycodestyle.py index c38afb39..96291b0d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -158,6 +158,13 @@ else: # pragma: <3.12 cover FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1 +if sys.version_info >= (3, 14): # pragma: >=3.14 cover + TSTRING_START = tokenize.TSTRING_START + TSTRING_MIDDLE = tokenize.TSTRING_MIDDLE + TSTRING_END = tokenize.TSTRING_END +else: # pragma: <3.14 cover + TSTRING_START = TSTRING_MIDDLE = TSTRING_END = -1 + _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -697,7 +704,12 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if verbose >= 4: print(f"bracket depth {depth} indent to {start[1]}") # deal with implicit string concatenation - elif token_type in (tokenize.STRING, tokenize.COMMENT, FSTRING_START): + elif token_type in { + tokenize.STRING, + tokenize.COMMENT, + FSTRING_START, + TSTRING_START + }: indent_chances[start[1]] = str # visual indent after assert/raise/with elif not row and not depth and text in ["assert", "raise", "with"]: @@ -873,6 +885,8 @@ def missing_whitespace(logical_line, tokens): brace_stack.append(text) elif token_type == FSTRING_START: # pragma: >=3.12 cover brace_stack.append('f') + elif token_type == TSTRING_START: # pragma: >=3.14 cover + brace_stack.append('t') elif token_type == tokenize.NAME and text == 'lambda': brace_stack.append('l') elif brace_stack: @@ -880,6 +894,8 @@ def missing_whitespace(logical_line, tokens): brace_stack.pop() elif token_type == FSTRING_END: # pragma: >=3.12 cover brace_stack.pop() + elif token_type == TSTRING_END: # pragma: >=3.14 cover + brace_stack.pop() elif ( brace_stack[-1] == 'l' and token_type == tokenize.OP and @@ -899,6 +915,9 @@ def missing_whitespace(logical_line, tokens): # 3.12+ fstring format specifier elif text == ':' and brace_stack[-2:] == ['f', '{']: # pragma: >=3.12 cover # noqa: E501 pass + # 3.14+ tstring format specifier + elif text == ':' and brace_stack[-2:] == ['t', '{']: # pragma: >=3.14 cover # noqa: E501 + pass # tuple (and list for some reason?) elif text == ',' and next_char in ')]': pass @@ -948,7 +967,9 @@ def missing_whitespace(logical_line, tokens): # allow keyword args or defaults: foo(bar=None). brace_stack[-1:] == ['('] or # allow python 3.8 fstring repr specifier - brace_stack[-2:] == ['f', '{'] + brace_stack[-2:] == ['f', '{'] or + # allow python 3.8 fstring repr specifier + brace_stack[-2:] == ['t', '{'] ) ): pass @@ -1639,11 +1660,11 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): prefixes = [] for token_type, text, start, _, _ in tokens: - if token_type in {tokenize.STRING, FSTRING_START}: + if token_type in {tokenize.STRING, FSTRING_START, TSTRING_START}: # Extract string modifiers (e.g. u or r) prefixes.append(text[:text.index(text[-1])].lower()) - if token_type in {tokenize.STRING, FSTRING_MIDDLE}: + if token_type in {tokenize.STRING, FSTRING_MIDDLE, TSTRING_MIDDLE}: if 'r' not in prefixes[-1]: start_line, start_col = start pos = text.find('\\') @@ -1661,7 +1682,7 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): ) pos = text.find('\\', pos + 1) - if token_type in {tokenize.STRING, FSTRING_END}: + if token_type in {tokenize.STRING, FSTRING_END, TSTRING_END}: prefixes.pop() @@ -1859,7 +1880,7 @@ def __init__(self, filename=None, lines=None, self.max_line_length = options.max_line_length self.max_doc_length = options.max_doc_length self.indent_size = options.indent_size - self.fstring_start = 0 + self.fstring_start = self.tstring_start = 0 self.multiline = False # in a multiline string? self.hang_closing = options.hang_closing self.indent_size = options.indent_size @@ -1954,7 +1975,7 @@ def build_tokens_line(self): continue if token_type == tokenize.STRING: text = mute_string(text) - elif token_type == FSTRING_MIDDLE: # pragma: >=3.12 cover + elif token_type in {FSTRING_MIDDLE, TSTRING_MIDDLE}: # pragma: >=3.12 cover # noqa: E501 # fstring tokens are "unescaped" braces -- re-escape! brace_count = text.count('{') + text.count('}') text = 'x' * (len(text) + brace_count) @@ -2046,6 +2067,8 @@ def maybe_check_physical(self, token, prev_physical): if token.type == FSTRING_START: # pragma: >=3.12 cover self.fstring_start = token.start[0] + elif token.type == TSTRING_START: # pragma: >=3.14 cover + self.tstring_start = token.start[0] # a newline token ends a single physical line. elif _is_eol_token(token): # if the file does not end with a newline, the NEWLINE @@ -2057,7 +2080,8 @@ def maybe_check_physical(self, token, prev_physical): self.check_physical(token.line) elif ( token.type == tokenize.STRING and '\n' in token.string or - token.type == FSTRING_END + token.type == FSTRING_END or + token.type == TSTRING_END ): # Less obviously, a string that contains newlines is a # multiline string, either triple-quoted or with internal @@ -2078,6 +2102,8 @@ def maybe_check_physical(self, token, prev_physical): return if token.type == FSTRING_END: # pragma: >=3.12 cover start = self.fstring_start + elif token.type == TSTRING_END: # pragma: >=3.12 cover + start = self.tstring_start else: start = token.start[0] end = token.end[0] diff --git a/testing/data/python314.py b/testing/data/python314.py new file mode 100644 index 00000000..6f96df7f --- /dev/null +++ b/testing/data/python314.py @@ -0,0 +1,19 @@ +#: Okay +try: + raise AssertionError('hi') +except AssertionError, ValueError: + pass + +t'hello {world}' +t'{hello}:{world}' +t'in{x}' +t'hello{world=}' +#: Okay +# new nested f-strings +t'{ + thing +} {t'{other} {thing}'}' +#: E201:1:4 E202:1:17 +t'{ an_error_now }' +#: Okay +t'{x:02x}' From a98638490e3c799efeebf0af638940d5a581b3c9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 20:57:48 +0000 Subject: [PATCH 403/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.14.0 → v3.15.0](https://github.com/asottile/reorder-python-imports/compare/v3.14.0...v3.15.0) - [github.com/asottile/pyupgrade: v3.19.1 → v3.20.0](https://github.com/asottile/pyupgrade/compare/v3.19.1...v3.20.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4218f9f..a3cda000 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.14.0 + rev: v3.15.0 hooks: - id: reorder-python-imports args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.19.1 + rev: v3.20.0 hooks: - id: pyupgrade args: [--py39-plus] From 46bc333dc127b73f1af2127bf44dbd74e7bf34cb Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sun, 8 Jun 2025 13:44:28 -0400 Subject: [PATCH 404/412] add sphinx configuration for rtfd --- .readthedocs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 4b74802e..87bb5a6a 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,3 +4,5 @@ build: os: ubuntu-22.04 tools: python: "3.11" +sphinx: + configuration: docs/conf.py From 8621e318655267c2a6cfa15bfd3f7cc02a60881f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 11 Jun 2025 11:53:02 -0400 Subject: [PATCH 405/412] fix false positive with TypeVar defaults with more than one argument --- pycodestyle.py | 15 ++++++++------- testing/data/python313.py | 4 ++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 96291b0d..3439f35a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1066,12 +1066,6 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): if token_type == tokenize.OP: if text in '([': paren_stack.append(text) - # PEP 696 defaults always use spaced-style `=` - # type A[T = default] = ... - # def f[T = default](): ... - # class C[T = default](): ... - if in_generic and paren_stack == ['[']: - annotated_func_arg = True elif text in ')]' and paren_stack: paren_stack.pop() # def f(arg: tp = default): ... @@ -1080,7 +1074,14 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): elif len(paren_stack) == 1 and text == ',': annotated_func_arg = False elif paren_stack and text == '=': - if annotated_func_arg and len(paren_stack) == 1: + if ( + # PEP 696 defaults always use spaced-style `=` + # type A[T = default] = ... + # def f[T = default](): ... + # class C[T = default](): ... + (in_generic and paren_stack == ['[']) or + (annotated_func_arg and paren_stack == ['(']) + ): require_space = True if start == prev_end: yield (prev_end, missing_message) diff --git a/testing/data/python313.py b/testing/data/python313.py index 5540aced..ae70e427 100644 --- a/testing/data/python313.py +++ b/testing/data/python313.py @@ -10,6 +10,10 @@ class C2[T = str]: pass +class C3[T, U: str = str]: + pass + + def f[T: (int, str) = str](t: T) -> T: pass From 814a0d1259444a21ed318e64edaf6a530c2aeeb8 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Fri, 20 Jun 2025 14:48:57 -0400 Subject: [PATCH 406/412] Release 2.14.0 --- CHANGES.txt | 9 +++++++++ pycodestyle.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index aeaef654..aef35485 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,15 @@ Changelog ========= +2.14.0 (2025-06-20) +------------------- + +Changes: + +* Add support for python 3.14. PR #1283. +* Fix false positive for TypeVar defaults with more than one argument. + PR #1286. + 2.13.0 (2025-03-29) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 3439f35a..9df2ae4c 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Mon, 23 Jun 2025 21:13:40 +0000 Subject: [PATCH 407/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.2.0 → 7.3.0](https://github.com/pycqa/flake8/compare/7.2.0...7.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3cda000..112097f1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.2.0 + rev: 7.3.0 hooks: - id: flake8 From 24cec77f1a0f66983bcf43079a6f34b9438a0818 Mon Sep 17 00:00:00 2001 From: namaevae Date: Thu, 17 Jul 2025 09:33:35 +0300 Subject: [PATCH 408/412] this reduces execution time from ~3300 ns to ~2800 --- pycodestyle.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 9df2ae4c..b3b38377 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -493,6 +493,17 @@ def whitespace_around_keywords(logical_line): yield match.start(2), "E271 multiple spaces after keyword" +if sys.version_info < (3, 10): + from itertools import tee + + def pairwise(iterable): + a, b = tee(iterable) + next(b, None) + return zip(a, b) +else: + from itertools import pairwise + + @register_check def missing_whitespace_after_keyword(logical_line, tokens): r"""Keywords should be followed by whitespace. @@ -502,7 +513,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): E275: from importable.module import(bar, baz) E275: if(foo): bar """ - for tok0, tok1 in zip(tokens, tokens[1:]): + for tok0, tok1 in pairwise(tokens): # This must exclude the True/False/None singletons, which can # appear e.g. as "if x is None:", and async/await, which were # valid identifier names in old Python versions. @@ -512,7 +523,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): tok0.string not in SINGLETONS and not (tok0.string == 'except' and tok1.string == '*') and not (tok0.string == 'yield' and tok1.string == ')') and - tok1.string not in ':\n'): + (tok1.string and tok1.string != ':' and tok1.string != '\n')): yield tok0.end, "E275 missing whitespace after keyword" From 41e842c87673f4ceaf66958472715c5c5ef64edf Mon Sep 17 00:00:00 2001 From: namaevae Date: Thu, 17 Jul 2025 09:55:18 +0300 Subject: [PATCH 409/412] this reduces execution time from ~1500 ns to ~1250 ns --- pycodestyle.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 9df2ae4c..97714220 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1160,6 +1160,22 @@ def imports_on_separate_lines(logical_line): yield found, "E401 multiple imports on one line" +_STRING_PREFIXES = frozenset(('u', 'U', 'b', 'B', 'r', 'R')) + + +def _is_string_literal(line): + if line: + first_char = line[0] + if first_char in _STRING_PREFIXES: + first_char = line[1] + return first_char == '"' or first_char == "'" + return False + + +_ALLOWED_KEYWORDS_IN_IMPORTS = frozenset([ + 'try', 'except', 'else', 'finally', 'with', 'if', 'elif']) + + @register_check def module_imports_on_top_of_file( logical_line, indent_level, checker_state, noqa): @@ -1178,15 +1194,6 @@ def module_imports_on_top_of_file( Okay: if x:\n import os """ # noqa - def is_string_literal(line): - if line[0] in 'uUbB': - line = line[1:] - if line and line[0] in 'rR': - line = line[1:] - return line and (line[0] == '"' or line[0] == "'") - - allowed_keywords = ( - 'try', 'except', 'else', 'finally', 'with', 'if', 'elif') if indent_level: # Allow imports in conditional statement/function return @@ -1194,17 +1201,17 @@ def is_string_literal(line): return if noqa: return - line = logical_line - if line.startswith('import ') or line.startswith('from '): + if logical_line.startswith('import ') or logical_line.startswith('from '): if checker_state.get('seen_non_imports', False): yield 0, "E402 module level import not at top of file" - elif re.match(DUNDER_REGEX, line): + elif re.match(DUNDER_REGEX, logical_line): return - elif any(line.startswith(kw) for kw in allowed_keywords): + elif any(logical_line.startswith(kw) + for kw in _ALLOWED_KEYWORDS_IN_IMPORTS): # Allow certain keywords intermixed with imports in order to # support conditional or filtered importing return - elif is_string_literal(line): + elif _is_string_literal(logical_line): # The first literal is a docstring, allow it. Otherwise, report # error. if checker_state.get('seen_docstring', False): From edab88367756980f73ab9f40f9ba9c4764c8e9b4 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 2 Aug 2025 12:59:30 -0400 Subject: [PATCH 410/412] further improvements to module_imports_on_top_of_file --- pycodestyle.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 97714220..dbd2afa7 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1172,8 +1172,8 @@ def _is_string_literal(line): return False -_ALLOWED_KEYWORDS_IN_IMPORTS = frozenset([ - 'try', 'except', 'else', 'finally', 'with', 'if', 'elif']) +_ALLOWED_KEYWORDS_IN_IMPORTS = ( + 'try', 'except', 'else', 'finally', 'with', 'if', 'elif') @register_check @@ -1201,25 +1201,25 @@ def module_imports_on_top_of_file( return if noqa: return - if logical_line.startswith('import ') or logical_line.startswith('from '): + if logical_line.startswith(('import ', 'from ')): if checker_state.get('seen_non_imports', False): yield 0, "E402 module level import not at top of file" - elif re.match(DUNDER_REGEX, logical_line): - return - elif any(logical_line.startswith(kw) - for kw in _ALLOWED_KEYWORDS_IN_IMPORTS): - # Allow certain keywords intermixed with imports in order to - # support conditional or filtered importing - return - elif _is_string_literal(logical_line): - # The first literal is a docstring, allow it. Otherwise, report - # error. - if checker_state.get('seen_docstring', False): - checker_state['seen_non_imports'] = True + elif not checker_state.get('seen_non_imports', False): + if DUNDER_REGEX.match(logical_line): + return + elif logical_line.startswith(_ALLOWED_KEYWORDS_IN_IMPORTS): + # Allow certain keywords intermixed with imports in order to + # support conditional or filtered importing + return + elif _is_string_literal(logical_line): + # The first literal is a docstring, allow it. Otherwise, + # report error. + if checker_state.get('seen_docstring', False): + checker_state['seen_non_imports'] = True + else: + checker_state['seen_docstring'] = True else: - checker_state['seen_docstring'] = True - else: - checker_state['seen_non_imports'] = True + checker_state['seen_non_imports'] = True @register_check From f9301e4dfa829460d3ce1c066d763a48a4bdd372 Mon Sep 17 00:00:00 2001 From: namaevae Date: Thu, 17 Jul 2025 09:58:07 +0300 Subject: [PATCH 411/412] this reduces execution time from ~5700 ns to ~3400 ns --- pycodestyle.py | 65 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 9df2ae4c..b86f9875 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1627,6 +1627,29 @@ def ambiguous_identifier(logical_line, tokens): prev_start = start +# https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals +_PYTHON_3000_VALID_ESC = frozenset([ + '\n', + '\\', + '\'', + '"', + 'a', + 'b', + 'f', + 'n', + 'r', + 't', + 'v', + '0', '1', '2', '3', '4', '5', '6', '7', + 'x', + + # Escape sequences only recognized in string literals + 'N', + 'u', + 'U', +]) + + @register_check def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): r"""Invalid escape sequences are deprecated in Python 3.6. @@ -1637,41 +1660,27 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): if noqa: return - # https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals - valid = [ - '\n', - '\\', - '\'', - '"', - 'a', - 'b', - 'f', - 'n', - 'r', - 't', - 'v', - '0', '1', '2', '3', '4', '5', '6', '7', - 'x', - - # Escape sequences only recognized in string literals - 'N', - 'u', - 'U', - ] - prefixes = [] for token_type, text, start, _, _ in tokens: - if token_type in {tokenize.STRING, FSTRING_START, TSTRING_START}: + if ( + token_type == tokenize.STRING or + token_type == FSTRING_START or + token_type == TSTRING_START + ): # Extract string modifiers (e.g. u or r) prefixes.append(text[:text.index(text[-1])].lower()) - if token_type in {tokenize.STRING, FSTRING_MIDDLE, TSTRING_MIDDLE}: + if ( + token_type == tokenize.STRING or + token_type == FSTRING_MIDDLE or + token_type == TSTRING_MIDDLE + ): if 'r' not in prefixes[-1]: start_line, start_col = start pos = text.find('\\') while pos >= 0: pos += 1 - if text[pos] not in valid: + if text[pos] not in _PYTHON_3000_VALID_ESC: line = start_line + text.count('\n', 0, pos) if line == start_line: col = start_col + pos @@ -1683,7 +1692,11 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): ) pos = text.find('\\', pos + 1) - if token_type in {tokenize.STRING, FSTRING_END, TSTRING_END}: + if ( + token_type == tokenize.STRING or + token_type == FSTRING_END or + token_type == TSTRING_END + ): prefixes.pop() From 76bbcb23cbcd0bc2443ec959489e5164372de9ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 22:22:17 +0000 Subject: [PATCH 412/412] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 112097f1..66058378 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-yaml - id: debug-statements