diff -Nru pycodestyle-2.3.1/CHANGES.txt pycodestyle-2.5.0/CHANGES.txt --- pycodestyle-2.3.1/CHANGES.txt 2017-01-30 23:58:13.000000000 +0000 +++ pycodestyle-2.5.0/CHANGES.txt 2019-01-29 13:56:44.000000000 +0000 @@ -1,6 +1,77 @@ Changelog ========= +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) +------------------ + +New checks: + +* Add W504 warning for checking that a break doesn't happen after a binary + 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. PR #684. +* Add E252 error for missing whitespace around equal sign in type annotated + function arguments with defaults values. PR #717. + +Changes: + +* An internal bisect search has replaced a linear search in order to improve + efficiency. PR #648. +* pycodestyle now uses PyPI trove classifiers in order to document supported + python versions on PyPI. PR #654. +* 'setup.cfg' '[wheel]' section has been renamed to '[bdist_wheel]', as + the former is legacy. PR #653. +* pycodestyle now handles very long lines much more efficiently for python + 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. PR #663. +* The distribution of pycodestyle now includes the license text in order to + 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. PR #733. + +Bugs: + +* Prevent a 'DeprecationWarning', and a 'SyntaxError' in future python, caused + by an invalid escape sequence. PR #625. +* Correctly report E501 when the first line of a docstring is too long. + Resolves #622. PR #630. +* Support variable annotation when variable start by a keyword, such as class + 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. PR #647. +* Fix a spelling mistake in the description of E722. PR #697. +* 'pycodestyle --diff' now does not break if your 'gitconfig' enables + 'mnemonicprefix'. PR #706. + 2.3.1 (2017-01-31) ------------------ @@ -20,7 +91,7 @@ * 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 -Nru pycodestyle-2.3.1/CONTRIBUTING.rst pycodestyle-2.5.0/CONTRIBUTING.rst --- pycodestyle-2.3.1/CONTRIBUTING.rst 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/CONTRIBUTING.rst 2019-01-29 13:46:14.000000000 +0000 @@ -19,7 +19,7 @@ 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 @@ -62,10 +62,40 @@ 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`_. +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. + + +Running unittest +~~~~~~~~~~~~~~~~ + +The tests are written using stdlib ``unittest`` module, the existing tests +include unit, integration and functional tests. + +To run the tests:: + + $ python setup.py test + +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 -Nru pycodestyle-2.3.1/debian/changelog pycodestyle-2.5.0/debian/changelog --- pycodestyle-2.3.1/debian/changelog 2017-06-20 08:18:17.000000000 +0000 +++ pycodestyle-2.5.0/debian/changelog 2019-10-02 09:46:08.000000000 +0000 @@ -1,3 +1,40 @@ +pycodestyle (2.5.0-1~cloud0) bionic-train; urgency=medium + + * New update for the Ubuntu Cloud Archive. + + -- Openstack Ubuntu Testing Bot Wed, 02 Oct 2019 09:46:08 +0000 + +pycodestyle (2.5.0-1) unstable; urgency=medium + + * New upstream release + * Bump debhelper compat level to 12 and use debhelper-compat + * Bump standards version to 4.4.0 (no changes) + * Mark pycodestyle binary package as Multi-Arch: foreign (Closes: #930602) + * debian/patches/Keep-compatibility-with-stdlib-tokenize-py-changes.patch: + Drop, applied upstream + + -- Ondřej Nový Tue, 16 Jul 2019 15:48:09 +0200 + +pycodestyle (2.4.0-2) unstable; urgency=medium + + * Breaks older python{3,}-flake8 + + -- Ondřej Nový Fri, 28 Sep 2018 11:22:22 +0200 + +pycodestyle (2.4.0-1) unstable; urgency=medium + + * New upstream release + * Standards version is 4.2.1 now (no changes needed) + * d/control: Set Vcs-* to salsa.debian.org + * d/tests: Use AUTOPKGTEST_TMP instead of ADTTMP + * Convert git repository from git-dpm to gbp layout + * d/p/Keep-compability-with-stdlib-tokenize-py-changes.patch: + Fix Python 3.7 (Closes: #904876) + * Bump debhelper compat level to 11 + * Add upstream metadata + + -- Ondřej Nový Thu, 27 Sep 2018 10:05:52 +0200 + pycodestyle (2.3.1-2) unstable; urgency=medium * Uploading to unstable diff -Nru pycodestyle-2.3.1/debian/compat pycodestyle-2.5.0/debian/compat --- pycodestyle-2.3.1/debian/compat 2017-06-20 08:18:17.000000000 +0000 +++ pycodestyle-2.5.0/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -10 diff -Nru pycodestyle-2.3.1/debian/control pycodestyle-2.5.0/debian/control --- pycodestyle-2.3.1/debian/control 2017-06-20 08:18:17.000000000 +0000 +++ pycodestyle-2.5.0/debian/control 2019-07-16 13:44:27.000000000 +0000 @@ -3,19 +3,20 @@ Priority: optional Maintainer: Debian Python Modules Team Uploaders: Ondřej Nový -Build-Depends: debhelper (>= 10), +Build-Depends: debhelper-compat (= 12), dh-python, python-all, python-setuptools, python3-all, python3-setuptools, -Standards-Version: 3.9.8 +Standards-Version: 4.4.0 Homepage: https://pypi.python.org/pypi/pycodestyle -Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/pycodestyle.git -Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/pycodestyle.git +Vcs-Git: https://salsa.debian.org/python-team/modules/pycodestyle.git +Vcs-Browser: https://salsa.debian.org/python-team/modules/pycodestyle Package: pycodestyle Architecture: all +Multi-Arch: foreign Depends: python3-pkg-resources, python3-pycodestyle (=${binary:Version}), ${misc:Depends}, @@ -29,6 +30,7 @@ Architecture: all Depends: ${misc:Depends}, ${python:Depends}, +Breaks: python-flake8 (<< 3.5.0-2~) Replaces: pycodestyle (<< 1.6.2-0.1), Description: Python style guide checker (formerly called pep8) - Python 2.x Features a plugin architecture allowing for adding new checks is easily. @@ -41,6 +43,7 @@ Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, +Breaks: python3-flake8 (<< 3.5.0-2~) Description: Python style guide checker (formerly called pep8) - Python 3.x Features a plugin architecture allowing for adding new checks is easily. Parseable output listing line numbers of the error location. Consists of diff -Nru pycodestyle-2.3.1/debian/.git-dpm pycodestyle-2.5.0/debian/.git-dpm --- pycodestyle-2.3.1/debian/.git-dpm 2017-06-20 08:18:17.000000000 +0000 +++ pycodestyle-2.5.0/debian/.git-dpm 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -# see git-dpm(1) from git-dpm package -9d249cc076c3180b65de9b90d173fcb1b15dc665 -9d249cc076c3180b65de9b90d173fcb1b15dc665 -9d249cc076c3180b65de9b90d173fcb1b15dc665 -9d249cc076c3180b65de9b90d173fcb1b15dc665 -pycodestyle_2.3.1.orig.tar.gz -bdbd7415485975649fbce9b461aae26157898c56 -89460 -debianTag="debian/%e%v" -patchedTag="patched/%e%v" -upstreamTag="upstream/%e%u" diff -Nru pycodestyle-2.3.1/debian/tests/cli pycodestyle-2.5.0/debian/tests/cli --- pycodestyle-2.3.1/debian/tests/cli 2017-06-20 08:18:17.000000000 +0000 +++ pycodestyle-2.5.0/debian/tests/cli 2019-07-16 13:44:27.000000000 +0000 @@ -2,7 +2,7 @@ set -e -cd "$ADTTMP" +cd "$AUTOPKGTEST_TMP" cat > E40.py < E40.py < E40.py <`_ 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.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 @@ * 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. @@ -104,7 +103,7 @@ 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 diff -Nru pycodestyle-2.3.1/docs/intro.rst pycodestyle-2.5.0/docs/intro.rst --- pycodestyle-2.3.1/docs/intro.rst 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/docs/intro.rst 2019-01-29 13:46:14.000000000 +0000 @@ -43,8 +43,8 @@ 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 `_. @@ -157,6 +157,8 @@ --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 @@ 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) @@ -196,8 +198,10 @@ Example:: [pycodestyle] + count = False ignore = E226,E302,E41 max-line-length = 160 + statistics = True 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 @@ -228,6 +232,7 @@ +------------+----------------------------------------------------------------------+ | E116 | unexpected indentation (comment) | +------------+----------------------------------------------------------------------+ +| E117 | over-indented | +------------+----------------------------------------------------------------------+ | E121 (\*^) | continuation line under-indented for hanging indent | +------------+----------------------------------------------------------------------+ @@ -400,7 +405,11 @@ +------------+----------------------------------------------------------------------+ | **W5** | *Line break warning* | +------------+----------------------------------------------------------------------+ -| W503 (*) | line break occurred before a binary operator | +| W503 (*)   | line break before binary operator                         | ++------------+----------------------------------------------------------------------+ +| W504 (*)   | line break after binary operator                         | ++------------+----------------------------------------------------------------------+ +| W505 (\*^) | doc line too long (82 > 79 characters) | +------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **W6** | *Deprecation warning* | @@ -413,13 +422,21 @@ +------------+----------------------------------------------------------------------+ | W604 | backticks are deprecated, use 'repr()' | +------------+----------------------------------------------------------------------+ +| 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**, **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**. +**(*)** 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**. 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 -Nru pycodestyle-2.3.1/LICENSE pycodestyle-2.5.0/LICENSE --- pycodestyle-2.3.1/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ pycodestyle-2.5.0/LICENSE 2018-04-10 11:24:38.000000000 +0000 @@ -0,0 +1,25 @@ +Copyright © 2006-2009 Johann C. Rocholl +Copyright © 2009-2014 Florent Xicluna +Copyright © 2014-2018 Ian Lee + +Licensed under the terms of the Expat License + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +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. diff -Nru pycodestyle-2.3.1/MANIFEST.in pycodestyle-2.5.0/MANIFEST.in --- pycodestyle-2.3.1/MANIFEST.in 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/MANIFEST.in 2018-01-18 04:48:46.000000000 +0000 @@ -1,5 +1,6 @@ include *.txt include *.rst +include LICENSE recursive-include docs * recursive-include testsuite * recursive-exclude docs *.pyc diff -Nru pycodestyle-2.3.1/PKG-INFO pycodestyle-2.5.0/PKG-INFO --- pycodestyle-2.3.1/PKG-INFO 2017-01-30 23:59:36.000000000 +0000 +++ pycodestyle-2.5.0/PKG-INFO 2019-01-29 14:00:35.000000000 +0000 @@ -1,11 +1,12 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: pycodestyle -Version: 2.3.1 +Version: 2.5.0 Summary: Python style guide checker Home-page: https://pycodestyle.readthedocs.io/ Author: Ian Lee Author-email: IanLee1521@gmail.com License: Expat license +Description-Content-Type: UNKNOWN Description: pycodestyle (formerly called pep8) - Python style guide checker =============================================================== @@ -18,7 +19,7 @@ :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 @@ -119,6 +120,77 @@ Changelog ========= + 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) + ------------------ + + New checks: + + * Add W504 warning for checking that a break doesn't happen after a binary + 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. PR #684. + * Add E252 error for missing whitespace around equal sign in type annotated + function arguments with defaults values. PR #717. + + Changes: + + * An internal bisect search has replaced a linear search in order to improve + efficiency. PR #648. + * pycodestyle now uses PyPI trove classifiers in order to document supported + python versions on PyPI. PR #654. + * 'setup.cfg' '[wheel]' section has been renamed to '[bdist_wheel]', as + the former is legacy. PR #653. + * pycodestyle now handles very long lines much more efficiently for python + 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. PR #663. + * The distribution of pycodestyle now includes the license text in order to + 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. PR #733. + + Bugs: + + * Prevent a 'DeprecationWarning', and a 'SyntaxError' in future python, caused + by an invalid escape sequence. PR #625. + * Correctly report E501 when the first line of a docstring is too long. + Resolves #622. PR #630. + * Support variable annotation when variable start by a keyword, such as class + 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. PR #647. + * Fix a spelling mistake in the description of E722. PR #697. + * 'pycodestyle --diff' now does not break if your 'gitconfig' enables + 'mnemonicprefix'. PR #706. + 2.3.1 (2017-01-31) ------------------ @@ -138,7 +210,7 @@ * 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. @@ -908,5 +980,13 @@ Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* diff -Nru pycodestyle-2.3.1/pycodestyle.egg-info/PKG-INFO pycodestyle-2.5.0/pycodestyle.egg-info/PKG-INFO --- pycodestyle-2.3.1/pycodestyle.egg-info/PKG-INFO 2017-01-30 23:59:35.000000000 +0000 +++ pycodestyle-2.5.0/pycodestyle.egg-info/PKG-INFO 2019-01-29 14:00:35.000000000 +0000 @@ -1,11 +1,12 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: pycodestyle -Version: 2.3.1 +Version: 2.5.0 Summary: Python style guide checker Home-page: https://pycodestyle.readthedocs.io/ Author: Ian Lee Author-email: IanLee1521@gmail.com License: Expat license +Description-Content-Type: UNKNOWN Description: pycodestyle (formerly called pep8) - Python style guide checker =============================================================== @@ -18,7 +19,7 @@ :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 @@ -119,6 +120,77 @@ Changelog ========= + 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) + ------------------ + + New checks: + + * Add W504 warning for checking that a break doesn't happen after a binary + 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. PR #684. + * Add E252 error for missing whitespace around equal sign in type annotated + function arguments with defaults values. PR #717. + + Changes: + + * An internal bisect search has replaced a linear search in order to improve + efficiency. PR #648. + * pycodestyle now uses PyPI trove classifiers in order to document supported + python versions on PyPI. PR #654. + * 'setup.cfg' '[wheel]' section has been renamed to '[bdist_wheel]', as + the former is legacy. PR #653. + * pycodestyle now handles very long lines much more efficiently for python + 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. PR #663. + * The distribution of pycodestyle now includes the license text in order to + 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. PR #733. + + Bugs: + + * Prevent a 'DeprecationWarning', and a 'SyntaxError' in future python, caused + by an invalid escape sequence. PR #625. + * Correctly report E501 when the first line of a docstring is too long. + Resolves #622. PR #630. + * Support variable annotation when variable start by a keyword, such as class + 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. PR #647. + * Fix a spelling mistake in the description of E722. PR #697. + * 'pycodestyle --diff' now does not break if your 'gitconfig' enables + 'mnemonicprefix'. PR #706. + 2.3.1 (2017-01-31) ------------------ @@ -138,7 +210,7 @@ * 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. @@ -908,5 +980,13 @@ Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* diff -Nru pycodestyle-2.3.1/pycodestyle.egg-info/SOURCES.txt pycodestyle-2.5.0/pycodestyle.egg-info/SOURCES.txt --- pycodestyle-2.3.1/pycodestyle.egg-info/SOURCES.txt 2017-01-30 23:59:36.000000000 +0000 +++ pycodestyle-2.5.0/pycodestyle.egg-info/SOURCES.txt 2019-01-29 14:00:35.000000000 +0000 @@ -1,5 +1,6 @@ CHANGES.txt CONTRIBUTING.rst +LICENSE MANIFEST.in README.rst dev-requirements.txt @@ -21,7 +22,6 @@ pycodestyle.egg-info/namespace_packages.txt pycodestyle.egg-info/not-zip-safe pycodestyle.egg-info/top_level.txt -testsuite/.E30not.py.swp testsuite/E10.py testsuite/E11.py testsuite/E12.py @@ -51,9 +51,11 @@ testsuite/latin-1.py testsuite/noqa.py testsuite/python3.py +testsuite/python35.py testsuite/support.py testsuite/test_all.py testsuite/test_api.py +testsuite/test_blank_lines.py testsuite/test_parser.py testsuite/test_shell.py testsuite/test_util.py diff -Nru pycodestyle-2.3.1/pycodestyle.py pycodestyle-2.5.0/pycodestyle.py --- pycodestyle-2.3.1/pycodestyle.py 2017-01-30 23:57:04.000000000 +0000 +++ pycodestyle-2.5.0/pycodestyle.py 2019-01-29 13:58:00.000000000 +0000 @@ -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 @@ -56,6 +57,17 @@ import time import tokenize 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 @@ -66,10 +78,10 @@ except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.3.1' +__version__ = '2.5.0' 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') @@ -84,6 +96,14 @@ 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, +} +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', @@ -91,13 +111,16 @@ 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(['^', '&', '|', '<<', '>>', '%']) +# 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]) @@ -110,7 +133,7 @@ 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') @@ -121,10 +144,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\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})'.format('|'.join(s.replace(' ', '\s+') for s in ( + r'^\s*({0})\b'.format('|'.join(s.replace(' ', r'\s+') for s in ( 'def', 'async def', 'for', 'async for', 'if', 'elif', 'else', @@ -136,27 +159,55 @@ ) 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': {}} -############################################################################## -# Plugins (check functions) for physical lines -############################################################################## +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. 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 + 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) @@ -165,8 +216,9 @@ 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. + r"""On new projects, spaces-only are strongly recommended over tabs. Okay: if True:\n return W191: if True:\n\treturn @@ -176,11 +228,12 @@ return indent.index('\t'), "W191 indentation contains tabs" +@register_check 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# @@ -197,6 +250,7 @@ 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. @@ -207,29 +261,35 @@ """ 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" -def maximum_line_length(physical_line, max_line_length, multiline, noqa): +@register_check +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 - 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. """ line = physical_line.rstrip() length = len(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. + # 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() if ((len(chunks) == 1 and multiline) or (len(chunks) == 2 and chunks[0] == '#')) and \ @@ -246,24 +306,28 @@ "(%d > %d characters)" % (length, max_line_length)) -############################################################################## +######################################################################## # Plugins (check functions) for logical lines -############################################################################## +######################################################################## +@register_check 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 @@ -279,40 +343,65 @@ 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: + """ # noqa + 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 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 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]: + # 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) 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 def extraneous_whitespace(logical_line): r"""Avoid extraneous whitespace. @@ -345,6 +434,7 @@ yield found, "%s whitespace before '%s'" % (code, char) +@register_check def whitespace_around_keywords(logical_line): r"""Avoid extraneous whitespace around keywords. @@ -368,9 +458,10 @@ 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. + 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) @@ -385,6 +476,7 @@ 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,12 +503,13 @@ 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. - 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 @@ -441,7 +534,12 @@ 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, indent_char, noqa, verbose): r"""Continuation lines indentation. @@ -452,8 +550,8 @@ 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 ) @@ -538,7 +636,8 @@ 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]: @@ -556,7 +655,8 @@ # 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 @@ -641,6 +741,7 @@ 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 +774,7 @@ prev_end = end +@register_check def whitespace_around_operator(logical_line): r"""Avoid extraneous whitespace around an operator. @@ -696,6 +798,7 @@ 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. @@ -766,7 +869,7 @@ 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 @@ -788,6 +891,7 @@ prev_end = end +@register_check def whitespace_around_comma(logical_line): r"""Avoid extraneous whitespace after a comma or a colon. @@ -806,11 +910,13 @@ 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. 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) @@ -823,13 +929,18 @@ 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 @@ -837,6 +948,10 @@ 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 @@ -844,24 +959,30 @@ 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 == '=' and not annotated_func_arg: - no_space = True - if start != prev_end: - yield (prev_end, message) + elif parens and text == '=': + 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 prev_end = end +@register_check 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). @@ -897,6 +1018,7 @@ prev_end = end +@register_check def imports_on_separate_lines(logical_line): r"""Place imports on separate lines. @@ -916,12 +1038,13 @@ 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. - 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 @@ -936,7 +1059,7 @@ 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:] @@ -946,7 +1069,7 @@ 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 @@ -959,11 +1082,12 @@ 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: @@ -972,8 +1096,10 @@ checker_state['seen_non_imports'] = True +@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. @@ -1004,7 +1130,7 @@ 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) @@ -1032,13 +1158,15 @@ found = line.find(';', found + 1) +@register_check 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") @@ -1071,7 +1199,47 @@ parens -= 1 -def break_around_binary_operator(logical_line, tokens): +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_before_binary_operator(logical_line, tokens): r""" Avoid breaks before binary operators. @@ -1080,46 +1248,62 @@ 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) + """ + 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" + + +@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 before the + operator, not after it. + + W504: (width == 0 +\n height == 0) + W504: (width == 0 and\n height == 0) + W504: var = (1 &\n ~2) + + 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) + + The following should be W504 but unary_context is tricky with these 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 "()[]{},:.;@=%~") - - 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 + 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 + if (_is_binary_operator(previous_token_type, previous_text) and + line_break and + not unary_context and + not _is_binary_operator(token_type, text)): + yield prev_start, "W504 line break after binary operator" + prev_start = start +@register_check def comparison_to_singleton(logical_line, noqa): r"""Comparison to singletons should use "is" or "is not". @@ -1132,10 +1316,10 @@ 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: @@ -1154,6 +1338,7 @@ (code, singleton, msg)) +@register_check def comparison_negative(logical_line): r"""Negative comparison should be done using "not in" and "is not". @@ -1175,6 +1360,7 @@ 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(). @@ -1183,9 +1369,9 @@ 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): @@ -1198,8 +1384,10 @@ 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 whenever possible. + r"""When catching exceptions, mention specific exceptions when + possible. Okay: except Exception: Okay: except BaseException: @@ -1211,14 +1399,15 @@ 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 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 @@ -1227,9 +1416,9 @@ 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: @@ -1249,7 +1438,7 @@ 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 @@ -1266,8 +1455,10 @@ 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. + 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') @@ -1277,6 +1468,7 @@ 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 +1482,7 @@ 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 +1496,7 @@ yield pos, "W603 '<>' is deprecated, use '!='" +@register_check def python_3000_backticks(logical_line): r"""Use repr() instead of backticks in Python 3. @@ -1314,9 +1508,194 @@ 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. + + 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', + '\\', + '\'', + '"', + '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: + 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) + 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: + line = start_line + string.count('\n', 0, pos) + if line == start_line: + col = start_col + len(prefix) + len(quote) + pos + else: + col = pos - string.rfind('\n', 0, pos) - 1 + yield ( + (line, col - 1), + "W605 invalid escape sequence '\\%s'" % + string[pos], + ) + 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): + 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,): @@ -1346,7 +1725,7 @@ """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): @@ -1413,12 +1792,16 @@ 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) - for (path, rows) in rv.items() - if rows and filename_match(path, 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): @@ -1461,58 +1844,9 @@ 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 -############################################################################## - - -_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): @@ -1529,6 +1863,7 @@ 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 @@ -1645,10 +1980,10 @@ """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]) @@ -1662,9 +1997,10 @@ 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: @@ -1688,7 +2024,7 @@ 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) @@ -1703,7 +2039,7 @@ 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. @@ -1711,15 +2047,16 @@ 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 @@ -1727,7 +2064,9 @@ 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 @@ -1772,14 +2111,6 @@ 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() @@ -1847,8 +2178,8 @@ 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. @@ -1877,7 +2208,7 @@ 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 @@ -1909,7 +2240,7 @@ 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 % { @@ -1928,8 +2259,8 @@ 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. @@ -1962,8 +2293,9 @@ # 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', None) 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: @@ -2046,7 +2378,7 @@ 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 @@ -2074,8 +2406,8 @@ 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(): @@ -2091,8 +2423,8 @@ 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', @@ -2128,6 +2460,10 @@ 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") @@ -2150,12 +2486,13 @@ 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() @@ -2190,8 +2527,7 @@ 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([]) @@ -2223,11 +2559,11 @@ def process_options(arglist=None, parse_argv=False, config_file=None, - parser=None): - """Process options passed either via arglist or via command line args. + parser=None, verbose=None): + """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() @@ -2247,6 +2583,10 @@ (options, args) = parser.parse_args(arglist) options.reporter = None + # If explicitly specified verbosity, override any `-v` CLI flag + if verbose is not None: + options.verbose = verbose + if options.ensure_value('testsuite', False): args.append(options.testsuite) elif not options.ensure_value('doctest', False): diff -Nru pycodestyle-2.3.1/README.rst pycodestyle-2.5.0/README.rst --- pycodestyle-2.3.1/README.rst 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/README.rst 2019-01-29 13:46:14.000000000 +0000 @@ -10,7 +10,7 @@ :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 -Nru pycodestyle-2.3.1/setup.cfg pycodestyle-2.5.0/setup.cfg --- pycodestyle-2.3.1/setup.cfg 2017-01-30 23:59:36.000000000 +0000 +++ pycodestyle-2.5.0/setup.cfg 2019-01-29 14:00:35.000000000 +0000 @@ -1,13 +1,16 @@ -[wheel] +[bdist_wheel] universal = 1 +[metadata] +license_file = LICENSE + [pycodestyle] select = -ignore = E226,E24 +ignore = E226,E24,W504 max_line_length = 79 +max_doc_length = 72 [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff -Nru pycodestyle-2.3.1/setup.py pycodestyle-2.5.0/setup.py --- pycodestyle-2.3.1/setup.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/setup.py 2019-01-29 13:46:14.000000000 +0000 @@ -34,6 +34,7 @@ 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,7 +52,14 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', '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', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', ], test_suite='testsuite.test_all.suite', diff -Nru pycodestyle-2.3.1/testsuite/E10.py pycodestyle-2.5.0/testsuite/E10.py --- pycodestyle-2.3.1/testsuite/E10.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E10.py 2019-01-29 13:46:14.000000000 +0000 @@ -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 @@ pass # -#: E101 W191 W191 +#: E101 E117 W191 W191 if True: foo(1, 2) diff -Nru pycodestyle-2.3.1/testsuite/E11.py pycodestyle-2.5.0/testsuite/E11.py --- pycodestyle-2.3.1/testsuite/E11.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E11.py 2019-01-29 13:46:14.000000000 +0000 @@ -1,7 +1,7 @@ #: E111 if x > 2: print x -#: E111 +#: E111 E117 if True: print #: E112 @@ -34,3 +34,6 @@ # finally: # sys.exit() self.master.start() +#: E117 +def start(): + print diff -Nru pycodestyle-2.3.1/testsuite/E12not.py pycodestyle-2.5.0/testsuite/E12not.py --- pycodestyle-2.3.1/testsuite/E12not.py 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E12not.py 2018-04-10 11:24:38.000000000 +0000 @@ -1,8 +1,7 @@ if ( x == ( 3 - ) or - y == 4): + ) or y == 4): pass y = x == 2 \ @@ -17,15 +16,14 @@ or y > 1 \ or x == 3: pass - - -if (foo == bar and - baz == frop): +#: W503 +if (foo == bar + and baz == frop): pass - +#: W503 if ( - foo == bar and - baz == frop + foo == bar + and baz == frop ): pass @@ -109,7 +107,7 @@ 'BBB' \ 'iii' \ 'CCC' - +#: W504 W504 abricot = (3 + 4 + 5 + 6) @@ -138,8 +136,7 @@ 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)) @@ -184,23 +181,23 @@ "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"""): @@ -224,22 +221,19 @@ 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' @@ -358,10 +352,10 @@ """ 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 -Nru pycodestyle-2.3.1/testsuite/E12.py pycodestyle-2.5.0/testsuite/E12.py --- pycodestyle-2.3.1/testsuite/E12.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E12.py 2018-04-10 11:24:38.000000000 +0000 @@ -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 @@ 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 @@ 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 -Nru pycodestyle-2.3.1/testsuite/E25.py pycodestyle-2.5.0/testsuite/E25.py --- pycodestyle-2.3.1/testsuite/E25.py 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E25.py 2018-04-19 03:33:10.000000000 +0000 @@ -39,6 +39,12 @@ 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 +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 diff -Nru pycodestyle-2.3.1/testsuite/E26.py pycodestyle-2.5.0/testsuite/E26.py --- pycodestyle-2.3.1/testsuite/E26.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E26.py 2018-05-19 12:13:18.000000000 +0000 @@ -50,10 +50,10 @@ #foo not parsed """ - ########################################################################### - # A SEPARATOR # - ########################################################################### + #################################################################### + # A SEPARATOR # + #################################################################### - # ####################################################################### # - # ########################## another separator ########################## # - # ####################################################################### # + # ################################################################ # + # ####################### another separator ###################### # + # ################################################################ # diff -Nru pycodestyle-2.3.1/testsuite/E30not.py pycodestyle-2.5.0/testsuite/E30not.py --- pycodestyle-2.3.1/testsuite/E30not.py 2017-01-30 23:56:48.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E30not.py 2019-01-29 13:46:14.000000000 +0000 @@ -162,3 +162,14 @@ 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 Binary files /tmp/tmp9nkKXZ/N0YJXkWpep/pycodestyle-2.3.1/testsuite/.E30not.py.swp and /tmp/tmp9nkKXZ/gVa9TWmOQ8/pycodestyle-2.5.0/testsuite/.E30not.py.swp differ diff -Nru pycodestyle-2.3.1/testsuite/E30.py pycodestyle-2.5.0/testsuite/E30.py --- pycodestyle-2.3.1/testsuite/E30.py 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E30.py 2019-01-29 13:46:14.000000000 +0000 @@ -157,9 +157,27 @@ 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 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 -Nru pycodestyle-2.3.1/testsuite/E50.py pycodestyle-2.5.0/testsuite/E50.py --- pycodestyle-2.3.1/testsuite/E50.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E50.py 2018-05-19 12:13:18.000000000 +0000 @@ -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,32 +74,38 @@ #: 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 W505 +# Regression test for #622 +def foo(): + """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis pulvinar vitae + """ #: Okay """ 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 @@ -113,6 +119,10 @@ # almost_empty_line # -#: E501 +#: E501 W505 # This # almost_empty_line + +# +#: Okay +#!/reallylongpath/toexecutable --maybe --with --some ARGUMENTS TO DO WITH WHAT EXECUTABLE TO RUN diff -Nru pycodestyle-2.3.1/testsuite/E70.py pycodestyle-2.5.0/testsuite/E70.py --- pycodestyle-2.3.1/testsuite/E70.py 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E70.py 2018-04-10 11:24:38.000000000 +0000 @@ -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 diff -Nru pycodestyle-2.3.1/testsuite/E90.py pycodestyle-2.5.0/testsuite/E90.py --- pycodestyle-2.3.1/testsuite/E90.py 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/E90.py 2019-01-29 13:46:14.000000000 +0000 @@ -2,7 +2,7 @@ } #: E901 = [x -#: E901 E101 W191 +#: E901 E101 E117 W191 while True: try: pass diff -Nru pycodestyle-2.3.1/testsuite/python35.py pycodestyle-2.5.0/testsuite/python35.py --- pycodestyle-2.3.1/testsuite/python35.py 1970-01-01 00:00:00.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/python35.py 2019-01-29 13:46:14.000000000 +0000 @@ -0,0 +1,6 @@ +#: E225 +def bar(a, b)->int: + pass +#: Okay +def baz(a, b) -> int: + pass diff -Nru pycodestyle-2.3.1/testsuite/python3.py pycodestyle-2.5.0/testsuite/python3.py --- pycodestyle-2.3.1/testsuite/python3.py 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/python3.py 2018-01-18 04:48:46.000000000 +0000 @@ -12,7 +12,28 @@ class Class: + # Camel-caes 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] + 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] = [] diff -Nru pycodestyle-2.3.1/testsuite/support.py pycodestyle-2.5.0/testsuite/support.py --- pycodestyle-2.3.1/testsuite/support.py 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/support.py 2019-01-29 13:46:14.000000000 +0000 @@ -83,6 +83,26 @@ 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. @@ -131,11 +151,11 @@ 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: @@ -148,6 +168,13 @@ 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'] @@ -195,7 +222,3 @@ if options.testsuite: init_tests(style) return style.check_files() - - -# nose should not collect these functions -init_tests.__test__ = run_tests.__test__ = False diff -Nru pycodestyle-2.3.1/testsuite/test_all.py pycodestyle-2.5.0/testsuite/test_all.py --- pycodestyle-2.3.1/testsuite/test_all.py 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/test_all.py 2018-04-10 11:24:38.000000000 +0000 @@ -43,16 +43,24 @@ 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) 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 -Nru pycodestyle-2.3.1/testsuite/test_api.py pycodestyle-2.5.0/testsuite/test_api.py --- pycodestyle-2.3.1/testsuite/test_api.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/test_api.py 2019-01-29 13:46:14.000000000 +0000 @@ -28,10 +28,10 @@ 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 @@ -125,7 +125,7 @@ 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 @@ 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() @@ -166,7 +166,7 @@ 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 @@ 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 @@ -209,6 +209,12 @@ 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 -Nru pycodestyle-2.3.1/testsuite/test_blank_lines.py pycodestyle-2.5.0/testsuite/test_blank_lines.py --- pycodestyle-2.3.1/testsuite/test_blank_lines.py 1970-01-01 00:00:00.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/test_blank_lines.py 2018-05-19 12:13:18.000000000 +0000 @@ -0,0 +1,554 @@ +""" +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) diff -Nru pycodestyle-2.3.1/testsuite/test_shell.py pycodestyle-2.5.0/testsuite/test_shell.py --- pycodestyle-2.3.1/testsuite/test_shell.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/test_shell.py 2019-01-29 13:46:14.000000000 +0000 @@ -77,8 +77,8 @@ 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 @@ "+ 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) @@ -189,5 +190,15 @@ self.stdin = '\n'.join(diff_lines) stdout, stderr, errcode = self.pycodestyle('--diff') 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) diff -Nru pycodestyle-2.3.1/testsuite/utf-8.py pycodestyle-2.5.0/testsuite/utf-8.py --- pycodestyle-2.3.1/testsuite/utf-8.py 2017-01-24 23:13:11.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/utf-8.py 2018-05-19 12:13:18.000000000 +0000 @@ -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) # 情 情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情情 # diff -Nru pycodestyle-2.3.1/testsuite/W19.py pycodestyle-2.5.0/testsuite/W19.py --- pycodestyle-2.3.1/testsuite/W19.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/W19.py 2019-01-29 13:46:14.000000000 +0000 @@ -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 +#: 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 +#: E101 E117 W191 W504 if (foo == bar and baz == frop): pass -#: E101 W191 +#: 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 +#: 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 @@ "bracket's line" ) # -#: E101 W191 +#: E101 E117 W191 W504 # you want vertical alignment, so use a parens if ((foo.bar("baz") and foo.bar("frop") )): print "yes" -#: E101 W191 +#: E101 E117 W191 W504 # also ok, but starting to look like LISP if ((foo.bar("baz") and foo.bar("frop"))): print "yes" -#: E101 W191 +#: E101 E117 W191 W504 if (a == 2 or b == "abc def ghi" "jkl mno"): return True -#: E101 W191 +#: 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 +#: 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 @@ 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 -Nru pycodestyle-2.3.1/testsuite/W60.py pycodestyle-2.5.0/testsuite/W60.py --- pycodestyle-2.3.1/testsuite/W60.py 2017-01-19 14:23:10.000000000 +0000 +++ pycodestyle-2.5.0/testsuite/W60.py 2019-01-29 13:46:14.000000000 +0000 @@ -13,3 +13,86 @@ x = 0 #: W604 val = `1 + 2` +#: W605:1:10 +regex = '\.png$' +#: W605:2:1 +regex = ''' +\.png$ +''' +#: W605:2:6 +f( + '\_' +) +#: W605:4:6 +""" +multi-line +literal +with \_ somewhere +in the middle +""" +#: Okay +regex = r'\.png$' +regex = '\\.png$' +regex = r''' +\.png$ +''' +regex = r''' +\\.png$ +''' +s = '\\' +regex = '\w' # noqa +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())