Hello community,
here is the log from the commit of package python-flake8-bugbear for
openSUSE:Factory checked in at 2020-04-22 20:41:12
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-flake8-bugbear (Old)
and /work/SRC/openSUSE:Factory/.python-flake8-bugbear.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-flake8-bugbear"
Wed Apr 22 20:41:12 2020 rev:6 rq:794563 version:20.1.4
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-flake8-bugbear/python-flake8-bugbear.changes
2019-09-17 13:36:07.109855206 +0200
+++
/work/SRC/openSUSE:Factory/.python-flake8-bugbear.new.2738/python-flake8-bugbear.changes
2020-04-22 20:41:14.398011745 +0200
@@ -1,0 +2,14 @@
+Thu Apr 16 11:09:56 UTC 2020 - Tomáš Chvátal <[email protected]>
+
+- Update to 20.1.4:
+ * Ignore keywords for B009/B010
+ * Silence B009/B010 for non-identifiers
+ * State an ignore might be needed for optional B9x checks
+ * Fix error on attributes-of-attributes in except (...): clauses
+ * Allow continue/break within loops in finally clauses for B012
+ * For B001, also check for except ():
+ * Introduce B013 and B014 to check tuples in except (..., ): statements
+ * Warn about continue/return/break in finally block (#100)
+ * Removed a colon from the descriptive message in B008. (#96)
+
+-------------------------------------------------------------------
Old:
----
flake8-bugbear-19.8.0.tar.gz
New:
----
flake8-bugbear-20.1.4.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-flake8-bugbear.spec ++++++
--- /var/tmp/diff_new_pack.TqdSQh/_old 2020-04-22 20:41:15.066013053 +0200
+++ /var/tmp/diff_new_pack.TqdSQh/_new 2020-04-22 20:41:15.066013053 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-flake8-bugbear
#
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -19,19 +19,24 @@
%define skip_python2 1
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-flake8-bugbear
-Version: 19.8.0
+Version: 20.1.4
Release: 0
Summary: A plugin for flake8 finding likely bugs and design problems in
your program
License: MIT
Group: Development/Languages/Python
URL: https://github.com/PyCQA/flake8-bugbear
Source:
https://files.pythonhosted.org/packages/source/f/flake8-bugbear/flake8-bugbear-%{version}.tar.gz
-BuildRequires: %{python_module attrs}
+BuildRequires: %{python_module attrs >= 19.2.0}
+BuildRequires: %{python_module base >= 3.6}
BuildRequires: %{python_module flake8 >= 3.0.0}
+BuildRequires: %{python_module hypothesis}
+BuildRequires: %{python_module hypothesmith}
BuildRequires: %{python_module pytest}
BuildRequires: %{python_module setuptools}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
+Requires: python-attrs >= 19.2.0
+Requires: python-flake8 >= 3.0.0
BuildArch: noarch
%python_subpackages
@@ -51,8 +56,8 @@
%python_expand %fdupes %{buildroot}%{$python_sitelib}
%check
-# https://github.com/PyCQA/flake8-bugbear/issues/89
-%pytest -k 'not test_selfclean_bugbear'
+# Fuzz testing needs fast machine for quick enough data responses
+%pytest -k 'not TestFuzz'
%files %{python_files}
%license LICENSE
++++++ flake8-bugbear-19.8.0.tar.gz -> flake8-bugbear-20.1.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/PKG-INFO
new/flake8-bugbear-20.1.4/PKG-INFO
--- old/flake8-bugbear-19.8.0/PKG-INFO 2019-08-12 06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/PKG-INFO 2020-02-01 05:10:59.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: flake8-bugbear
-Version: 19.8.0
+Version: 20.1.4
Summary: A plugin for flake8 finding likely bugs and design problems in your
program. Contains warnings that don't belong in pyflakes and pycodestyle.
Home-page: https://github.com/PyCQA/flake8-bugbear
Author: Łukasz Langa
@@ -31,6 +31,12 @@
3. A fearsome imaginary creature, especially one evoked to frighten
children.
+ It is felt that these lints don't belong in the main Python tools as
they
+ are very opinionated and do not have a PEP or standard behind them.
Due to
+ ``flake8`` being designed to be extensible, the original creators of
these lints
+ believed that a plugin was the best route. This has resulted in better
development
+ velocity for contributors and adaptive deployment for ``flake8`` users.
+
Installation
------------
@@ -102,6 +108,18 @@
**B011**: Do not call `assert False` since `python -O` removes these
calls.
Instead callers should `raise AssertionError()`.
+ **B012**: Use of `break`, `continue` or `return` inside `finally`
blocks will
+ silence exceptions or override return values from the `try` or
`except` blocks.
+ To silence an exception, do it explicitly in the `except` block. To
properly use
+ a `break`, `continue` or `return` refactor your code so these
statements are not
+ in the `finally` block.
+
+ **B013**: A length-one tuple literal is redundant. Write `except
SomeError:`
+ instead of `except (SomeError,):`.
+
+ **B014**: Redundant exception types in `except (Exception,
TypeError):`.
+ Write `except Exception:`, which catches exactly the same exceptions.
+
Python 3 compatibility warnings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -192,11 +210,14 @@
max-line-length = 80
max-complexity = 12
...
+ ignore = E501
select = C,E,F,W,B,B901
Note that we're enabling the complexity checks, the PEP8
``pycodestyle``
errors and warnings, the pyflakes fatals and all default Bugbear
checks.
Finally, we're also specifying B901 as a check that we want enabled.
+ Some checks might need other flake8 checks disabled - e.g. E501 must be
+ disabled for B950 to be hit.
If you'd like all optional warnings to be enabled for you (future proof
your config!), say ``B9`` instead of ``B901``. You will need Flake8
3.2+
@@ -213,27 +234,7 @@
Just run::
- python setup.py test
-
-
- OMG, this is Python 3 only!
- ---------------------------
-
- Relax, you can run ``flake8`` with all popular plugins as a *tool*
- perfectly fine under Python 3.5+ even if you want to analyze Python 2
- code. This way you'll be able to parse all of the new syntax supported
- on Python 3 but also *effectively all* the Python 2 syntax at the same
- time.
-
- If you're still invested in Python 2, there might be a small subset of
- deprecated syntax that you'd have to abandon... but you're already
doing
- that, right? `six <https://pypi.org/project/six/>`_ or
- `python-future <https://pypi.org/project/future/>`_ bridge the gaps.
-
- By making the code exclusively Python 3.5+, I'm able to focus on the
- quality of the checks and re-use all the nice features of the new
- releases (check out `pathlib
<https://docs.python.org/3/library/pathlib.html>`_)
- instead of wasting cycles on Unicode compatibility, etc.
+ python tests/test_bugbear.py
License
@@ -245,6 +246,35 @@
Change Log
----------
+ 20.1.4
+ ~~~~~~
+
+ * Ignore keywords for B009/B010
+
+ 20.1.3
+ ~~~~~~
+
+ * Silence B009/B010 for non-identifiers
+ * State an ignore might be needed for optional B9x checks
+
+ 20.1.2
+ ~~~~~~
+
+ * Fix error on attributes-of-attributes in `except (...):` clauses
+
+ 20.1.1
+ ~~~~~~
+
+ * Allow continue/break within loops in finally clauses for B012
+ * For B001, also check for ``except ():``
+ * Introduce B013 and B014 to check tuples in ``except (..., ):``
statements
+
+ 20.1.0
+ ~~~~~~
+
+ * Warn about continue/return/break in finally block (#100)
+ * Removed a colon from the descriptive message in B008. (#96)
+
19.8.0
~~~~~~
@@ -422,10 +452,10 @@
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Quality Assurance
-Requires-Python: >=3.5
+Requires-Python: >=3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/README.rst
new/flake8-bugbear-20.1.4/README.rst
--- old/flake8-bugbear-19.8.0/README.rst 2019-08-12 06:18:59.000000000
+0200
+++ new/flake8-bugbear-20.1.4/README.rst 2020-02-01 05:10:39.000000000
+0100
@@ -23,6 +23,12 @@
3. A fearsome imaginary creature, especially one evoked to frighten
children.
+It is felt that these lints don't belong in the main Python tools as they
+are very opinionated and do not have a PEP or standard behind them. Due to
+``flake8`` being designed to be extensible, the original creators of these
lints
+believed that a plugin was the best route. This has resulted in better
development
+velocity for contributors and adaptive deployment for ``flake8`` users.
+
Installation
------------
@@ -94,6 +100,18 @@
**B011**: Do not call `assert False` since `python -O` removes these calls.
Instead callers should `raise AssertionError()`.
+**B012**: Use of `break`, `continue` or `return` inside `finally` blocks will
+silence exceptions or override return values from the `try` or `except` blocks.
+To silence an exception, do it explicitly in the `except` block. To properly
use
+a `break`, `continue` or `return` refactor your code so these statements are
not
+in the `finally` block.
+
+**B013**: A length-one tuple literal is redundant. Write `except SomeError:`
+instead of `except (SomeError,):`.
+
+**B014**: Redundant exception types in `except (Exception, TypeError):`.
+Write `except Exception:`, which catches exactly the same exceptions.
+
Python 3 compatibility warnings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -184,11 +202,14 @@
max-line-length = 80
max-complexity = 12
...
+ ignore = E501
select = C,E,F,W,B,B901
Note that we're enabling the complexity checks, the PEP8 ``pycodestyle``
errors and warnings, the pyflakes fatals and all default Bugbear checks.
Finally, we're also specifying B901 as a check that we want enabled.
+Some checks might need other flake8 checks disabled - e.g. E501 must be
+disabled for B950 to be hit.
If you'd like all optional warnings to be enabled for you (future proof
your config!), say ``B9`` instead of ``B901``. You will need Flake8 3.2+
@@ -205,27 +226,7 @@
Just run::
- python setup.py test
-
-
-OMG, this is Python 3 only!
----------------------------
-
-Relax, you can run ``flake8`` with all popular plugins as a *tool*
-perfectly fine under Python 3.5+ even if you want to analyze Python 2
-code. This way you'll be able to parse all of the new syntax supported
-on Python 3 but also *effectively all* the Python 2 syntax at the same
-time.
-
-If you're still invested in Python 2, there might be a small subset of
-deprecated syntax that you'd have to abandon... but you're already doing
-that, right? `six <https://pypi.org/project/six/>`_ or
-`python-future <https://pypi.org/project/future/>`_ bridge the gaps.
-
-By making the code exclusively Python 3.5+, I'm able to focus on the
-quality of the checks and re-use all the nice features of the new
-releases (check out `pathlib
<https://docs.python.org/3/library/pathlib.html>`_)
-instead of wasting cycles on Unicode compatibility, etc.
+ python tests/test_bugbear.py
License
@@ -237,6 +238,35 @@
Change Log
----------
+20.1.4
+~~~~~~
+
+* Ignore keywords for B009/B010
+
+20.1.3
+~~~~~~
+
+* Silence B009/B010 for non-identifiers
+* State an ignore might be needed for optional B9x checks
+
+20.1.2
+~~~~~~
+
+* Fix error on attributes-of-attributes in `except (...):` clauses
+
+20.1.1
+~~~~~~
+
+* Allow continue/break within loops in finally clauses for B012
+* For B001, also check for ``except ():``
+* Introduce B013 and B014 to check tuples in ``except (..., ):`` statements
+
+20.1.0
+~~~~~~
+
+* Warn about continue/return/break in finally block (#100)
+* Removed a colon from the descriptive message in B008. (#96)
+
19.8.0
~~~~~~
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/bugbear.py
new/flake8-bugbear-20.1.4/bugbear.py
--- old/flake8-bugbear-19.8.0/bugbear.py 2019-08-12 06:18:59.000000000
+0200
+++ new/flake8-bugbear-20.1.4/bugbear.py 2020-02-01 05:10:39.000000000
+0100
@@ -1,15 +1,18 @@
import ast
+import builtins
from collections import namedtuple
from contextlib import suppress
from functools import lru_cache, partial
import itertools
+from keyword import iskeyword
import logging
+import re
import attr
import pycodestyle
-__version__ = "19.8.0"
+__version__ = "20.1.4"
LOG = logging.getLogger("flake8.bugbear")
@@ -109,6 +112,25 @@
return False
+def _is_identifier(arg):
+ # Return True if arg is a valid identifier, per
+ # https://docs.python.org/2/reference/lexical_analysis.html#identifiers
+
+ if not isinstance(arg, ast.Str):
+ return False
+
+ return re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", arg.s) is not None
+
+
+def _to_name_str(node):
+ # Turn Name and Attribute nodes to strings, e.g "ValueError" or
+ # "pkg.mod.error", handling any depth of attribute accesses.
+ if isinstance(node, ast.Name):
+ return node.id
+ assert isinstance(node, ast.Attribute)
+ return _to_name_str(node.value) + "." + node.attr
+
+
@attr.s
class BugBearVisitor(ast.NodeVisitor):
filename = attr.ib()
@@ -136,7 +158,44 @@
def visit_ExceptHandler(self, node):
if node.type is None:
- self.errors.append(B001(node.lineno, node.col_offset))
+ self.errors.append(
+ B001(node.lineno, node.col_offset, vars=("bare `except:`",))
+ )
+ elif isinstance(node.type, ast.Tuple):
+ names = [_to_name_str(e) for e in node.type.elts]
+ as_ = " as " + node.name if node.name is not None else ""
+ if len(names) == 0:
+ vs = ("`except (){}:`".format(as_),)
+ self.errors.append(B001(node.lineno, node.col_offset, vars=vs))
+ elif len(names) == 1:
+ self.errors.append(B013(node.lineno, node.col_offset,
vars=names))
+ else:
+ # See if any of the given exception names could be removed,
e.g. from:
+ # (MyError, MyError) # duplicate names
+ # (MyError, BaseException) # everything derives from the
Base
+ # (Exception, TypeError) # builtins where one subclasses
another
+ # but note that other cases are impractical to hande from the
AST.
+ # We expect this is mostly useful for users who do not have the
+ # builtin exception hierarchy memorised, and include a
'shadowed'
+ # subtype without realising that it's redundant.
+ good = sorted(set(names), key=names.index)
+ if "BaseException" in good:
+ good = ["BaseException"]
+ for name, other in itertools.permutations(tuple(good), 2):
+ if issubclass(
+ getattr(builtins, name, type), getattr(builtins,
other, ())
+ ):
+ if name in good:
+ good.remove(name)
+ if good != names:
+ desc = good[0] if len(good) == 1 else "({})".format(",
".join(good))
+ self.errors.append(
+ B014(
+ node.lineno,
+ node.col_offset,
+ vars=(", ".join(names), as_, desc),
+ )
+ )
self.generic_visit(node)
def visit_UAdd(self, node):
@@ -166,13 +225,15 @@
if (
node.func.id == "getattr"
and len(node.args) == 2 # noqa: W503
- and isinstance(node.args[1], ast.Str) # noqa: W503
+ and _is_identifier(node.args[1]) # noqa: W503
+ and not iskeyword(node.args[1].s) # noqa: W503
):
self.errors.append(B009(node.lineno, node.col_offset))
elif (
node.func.id == "setattr"
and len(node.args) == 3 # noqa: W503
- and isinstance(node.args[1], ast.Str) # noqa: W503
+ and _is_identifier(node.args[1]) # noqa: W503
+ and not iskeyword(node.args[1].s) # noqa: W503
):
self.errors.append(B010(node.lineno, node.col_offset))
@@ -226,6 +287,10 @@
self.check_for_b903(node)
self.generic_visit(node)
+ def visit_Try(self, node):
+ self.check_for_b012(node)
+ self.generic_visit(node)
+
def compose_call_path(self, node):
if isinstance(node, ast.Attribute):
yield from self.compose_call_path(node.value)
@@ -280,22 +345,42 @@
if isinstance(node.test, ast.NameConstant) and node.test.value is
False:
self.errors.append(B011(node.lineno, node.col_offset))
+ def check_for_b012(self, node):
+ def _loop(node, bad_node_types):
+ if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)):
+ return
+
+ if isinstance(node, (ast.While, ast.For)):
+ bad_node_types = (ast.Return,)
+
+ elif isinstance(node, bad_node_types):
+ self.errors.append(B012(node.lineno, node.col_offset))
+
+ for child in ast.iter_child_nodes(node):
+ _loop(child, bad_node_types)
+
+ for child in node.finalbody:
+ _loop(child, (ast.Return, ast.Continue, ast.Break))
+
+ def walk_function_body(self, node):
+ def _loop(parent, node):
+ if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)):
+ return
+ yield parent, node
+ for child in ast.iter_child_nodes(node):
+ yield from _loop(node, child)
+
+ for child in node.body:
+ yield from _loop(node, child)
+
def check_for_b901(self, node):
if node.name == "__await__":
return
- xs = [(None, x) for x in node.body]
-
has_yield = False
return_node = None
- while xs:
- parent, x = xs.pop()
- if isinstance(x, (ast.AsyncFunctionDef, ast.FunctionDef)):
- # Do not recurse into inner functions (`def` in
- # `def`).
- continue
-
+ for parent, x in self.walk_function_body(node):
# Only consider yield when it is part of an Expr statement.
if isinstance(parent, ast.Expr) and isinstance(
x, (ast.Yield, ast.YieldFrom)
@@ -309,9 +394,6 @@
self.errors.append(B901(return_node.lineno,
return_node.col_offset))
break
- for x2 in ast.iter_child_nodes(x):
- xs.append((x, x2))
-
def check_for_b902(self, node):
if not isinstance(self.node_stack[-2], ast.ClassDef):
return
@@ -438,7 +520,7 @@
B001 = Error(
- message="B001 Do not use bare `except:`, it also catches unexpected "
+ message="B001 Do not use {}, it also catches unexpected "
"events like memory errors, interrupts, system exit, and so on. "
"Prefer `except Exception:`. If you're sure what you're doing, "
"be explicit and write `except BaseException:`."
@@ -497,7 +579,7 @@
"If this is intended, start the name with an underscore."
)
B008 = Error(
- message="B008: Do not perform function calls in argument defaults. The
call is "
+ message="B008 Do not perform function calls in argument defaults. The
call is "
"performed only once at function definition time. All calls to your "
"function will reuse the result of that definition-time function call. If
"
"this is intended, assign the function call to a module-level variable and
"
@@ -516,7 +598,19 @@
message="B011 Do not call assert False since python -O removes these
calls. "
"Instead callers should raise AssertionError()."
)
-
+B012 = Error(
+ message="B012 return/continue/break inside finally blocks cause exceptions
"
+ "to be silenced. Exceptions should be silenced in except blocks. Control "
+ "statements can be moved outside the finally block."
+)
+B013 = Error(
+ message="B013 A length-one tuple literal is redundant. "
+ "Write `except {0}:` instead of `except ({0},):`."
+)
+B014 = Error(
+ message="B014 Redundant exception types in `except ({0}){1}:`. "
+ "Write `except {2}{1}:`, which catches exactly the same exceptions."
+)
# Those could be false positives but it's more dangerous to let them slip
# through if they're not.
@@ -562,6 +656,7 @@
"to the exception."
)
+# Warnings disabled by default.
B901 = Error(
message="B901 Using `yield` together with `return x`. Use native "
"`async def` coroutines or put a `# noqa` comment on this "
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/PKG-INFO
new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/PKG-INFO
--- old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/PKG-INFO 2019-08-12
06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/PKG-INFO 2020-02-01
05:10:59.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: flake8-bugbear
-Version: 19.8.0
+Version: 20.1.4
Summary: A plugin for flake8 finding likely bugs and design problems in your
program. Contains warnings that don't belong in pyflakes and pycodestyle.
Home-page: https://github.com/PyCQA/flake8-bugbear
Author: Łukasz Langa
@@ -31,6 +31,12 @@
3. A fearsome imaginary creature, especially one evoked to frighten
children.
+ It is felt that these lints don't belong in the main Python tools as
they
+ are very opinionated and do not have a PEP or standard behind them.
Due to
+ ``flake8`` being designed to be extensible, the original creators of
these lints
+ believed that a plugin was the best route. This has resulted in better
development
+ velocity for contributors and adaptive deployment for ``flake8`` users.
+
Installation
------------
@@ -102,6 +108,18 @@
**B011**: Do not call `assert False` since `python -O` removes these
calls.
Instead callers should `raise AssertionError()`.
+ **B012**: Use of `break`, `continue` or `return` inside `finally`
blocks will
+ silence exceptions or override return values from the `try` or
`except` blocks.
+ To silence an exception, do it explicitly in the `except` block. To
properly use
+ a `break`, `continue` or `return` refactor your code so these
statements are not
+ in the `finally` block.
+
+ **B013**: A length-one tuple literal is redundant. Write `except
SomeError:`
+ instead of `except (SomeError,):`.
+
+ **B014**: Redundant exception types in `except (Exception,
TypeError):`.
+ Write `except Exception:`, which catches exactly the same exceptions.
+
Python 3 compatibility warnings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -192,11 +210,14 @@
max-line-length = 80
max-complexity = 12
...
+ ignore = E501
select = C,E,F,W,B,B901
Note that we're enabling the complexity checks, the PEP8
``pycodestyle``
errors and warnings, the pyflakes fatals and all default Bugbear
checks.
Finally, we're also specifying B901 as a check that we want enabled.
+ Some checks might need other flake8 checks disabled - e.g. E501 must be
+ disabled for B950 to be hit.
If you'd like all optional warnings to be enabled for you (future proof
your config!), say ``B9`` instead of ``B901``. You will need Flake8
3.2+
@@ -213,27 +234,7 @@
Just run::
- python setup.py test
-
-
- OMG, this is Python 3 only!
- ---------------------------
-
- Relax, you can run ``flake8`` with all popular plugins as a *tool*
- perfectly fine under Python 3.5+ even if you want to analyze Python 2
- code. This way you'll be able to parse all of the new syntax supported
- on Python 3 but also *effectively all* the Python 2 syntax at the same
- time.
-
- If you're still invested in Python 2, there might be a small subset of
- deprecated syntax that you'd have to abandon... but you're already
doing
- that, right? `six <https://pypi.org/project/six/>`_ or
- `python-future <https://pypi.org/project/future/>`_ bridge the gaps.
-
- By making the code exclusively Python 3.5+, I'm able to focus on the
- quality of the checks and re-use all the nice features of the new
- releases (check out `pathlib
<https://docs.python.org/3/library/pathlib.html>`_)
- instead of wasting cycles on Unicode compatibility, etc.
+ python tests/test_bugbear.py
License
@@ -245,6 +246,35 @@
Change Log
----------
+ 20.1.4
+ ~~~~~~
+
+ * Ignore keywords for B009/B010
+
+ 20.1.3
+ ~~~~~~
+
+ * Silence B009/B010 for non-identifiers
+ * State an ignore might be needed for optional B9x checks
+
+ 20.1.2
+ ~~~~~~
+
+ * Fix error on attributes-of-attributes in `except (...):` clauses
+
+ 20.1.1
+ ~~~~~~
+
+ * Allow continue/break within loops in finally clauses for B012
+ * For B001, also check for ``except ():``
+ * Introduce B013 and B014 to check tuples in ``except (..., ):``
statements
+
+ 20.1.0
+ ~~~~~~
+
+ * Warn about continue/return/break in finally block (#100)
+ * Removed a colon from the descriptive message in B008. (#96)
+
19.8.0
~~~~~~
@@ -422,10 +452,10 @@
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Quality Assurance
-Requires-Python: >=3.5
+Requires-Python: >=3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/SOURCES.txt
new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/SOURCES.txt
--- old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/SOURCES.txt
2019-08-12 06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/SOURCES.txt
2020-02-01 05:10:59.000000000 +0100
@@ -21,6 +21,9 @@
tests/b007.py
tests/b009_b010.py
tests/b011.py
+tests/b012.py
+tests/b013.py
+tests/b014.py
tests/b301_b302_b305.py
tests/b303_b304.py
tests/b306.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/requires.txt
new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/requires.txt
--- old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/requires.txt
2019-08-12 06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/requires.txt
2020-02-01 05:10:59.000000000 +0100
@@ -1,2 +1,2 @@
flake8>=3.0.0
-attrs
+attrs>=19.2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/setup.cfg
new/flake8-bugbear-20.1.4/setup.cfg
--- old/flake8-bugbear-19.8.0/setup.cfg 2019-08-12 06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/setup.cfg 2020-02-01 05:10:59.000000000 +0100
@@ -1,9 +1,9 @@
[bdist_wheel]
-python-tag = py35.py36.py37
+python-tag = py36.py37.py38
[flake8]
-ignore = E302, E501
-max-line-length = 80
+ignore = E203, E302, E501, E999
+max-line-length = 88
max-complexity = 12
select = B,C,E,F,W,B9
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/setup.py
new/flake8-bugbear-20.1.4/setup.py
--- old/flake8-bugbear-19.8.0/setup.py 2019-08-05 17:36:44.000000000 +0200
+++ new/flake8-bugbear-20.1.4/setup.py 2020-02-01 05:10:39.000000000 +0100
@@ -7,7 +7,7 @@
import sys
-assert sys.version_info >= (3, 5, 0), "bugbear requires Python 3.5+"
+assert sys.version_info >= (3, 6, 0), "bugbear requires Python 3.6+"
current_dir = os.path.abspath(os.path.dirname(__file__))
@@ -37,8 +37,8 @@
license="MIT",
py_modules=["bugbear"],
zip_safe=False,
- python_requires=">=3.5",
- install_requires=["flake8 >= 3.0.0", "attrs"],
+ python_requires=">=3.6",
+ install_requires=["flake8 >= 3.0.0", "attrs>=19.2.0"],
test_suite="tests.test_bugbear",
classifiers=[
"Development Status :: 5 - Production/Stable",
@@ -49,9 +49,9 @@
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b001.py
new/flake8-bugbear-20.1.4/tests/b001.py
--- old/flake8-bugbear-19.8.0/tests/b001.py 2019-08-05 17:36:44.000000000
+0200
+++ new/flake8-bugbear-20.1.4/tests/b001.py 2020-02-01 05:10:39.000000000
+0100
@@ -1,6 +1,6 @@
"""
Should emit:
-B001 - on lines 8 and 40
+B001 - on lines 8, 40, and 54
"""
try:
@@ -47,3 +47,10 @@
except: # noqa
# warning silenced
return
+
+
+try:
+ pass
+except ():
+ # Literal empty tuple is just like bare except:
+ pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b009_b010.py
new/flake8-bugbear-20.1.4/tests/b009_b010.py
--- old/flake8-bugbear-19.8.0/tests/b009_b010.py 2019-08-05
17:36:44.000000000 +0200
+++ new/flake8-bugbear-20.1.4/tests/b009_b010.py 2020-02-01
05:10:39.000000000 +0100
@@ -1,7 +1,7 @@
"""
Should emit:
-B009 - Line 15
-B010 - Line 22
+B009 - Line 17, 18, 19
+B010 - Line 28, 29, 30
"""
# Valid getattr usage
@@ -10,13 +10,21 @@
getattr(foo, "bar{foo}".format(foo="a"), None)
getattr(foo, "bar{foo}".format(foo="a"))
getattr(foo, bar, None)
+getattr(foo, "123abc")
+getattr(foo, "except")
# Invalid usage
getattr(foo, "bar")
+getattr(foo, "_123abc")
+getattr(foo, "abc123")
# Valid setattr usage
setattr(foo, bar, None)
setattr(foo, "bar{foo}".format(foo="a"), None)
+setattr(foo, "123abc", None)
+getattr(foo, "except", None)
# Invalid usage
setattr(foo, "bar", None)
+setattr(foo, "_123abc", None)
+setattr(foo, "abc123", None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b012.py
new/flake8-bugbear-20.1.4/tests/b012.py
--- old/flake8-bugbear-19.8.0/tests/b012.py 1970-01-01 01:00:00.000000000
+0100
+++ new/flake8-bugbear-20.1.4/tests/b012.py 2020-02-01 05:10:39.000000000
+0100
@@ -0,0 +1,107 @@
+def a():
+ try:
+ pass
+ finally:
+ return # warning
+
+
+def b():
+ try:
+ pass
+ finally:
+ if 1 + 0 == 2 - 1:
+ return # warning
+
+
+def c():
+ try:
+ pass
+ finally:
+ try:
+ return # warning
+ except Exception:
+ pass
+
+
+def d():
+ try:
+ try:
+ pass
+ finally:
+ return # warning
+ finally:
+ pass
+
+
+def e():
+ if 1 == 2 - 1:
+ try:
+
+ def f():
+ try:
+ pass
+ finally:
+ return # warning
+
+ finally:
+ pass
+
+
+def g():
+ try:
+ pass
+ finally:
+
+ def h():
+ return # no warning
+
+ e()
+
+
+def i():
+ while True:
+ try:
+ pass
+ finally:
+ break # warning
+
+ def j():
+ while True:
+ break # no warning
+
+
+def h():
+ while True:
+ try:
+ pass
+ finally:
+ continue # warning
+
+ def j():
+ while True:
+ continue # no warning
+
+
+def k():
+ try:
+ pass
+ finally:
+ while True:
+ break # no warning
+ while True:
+ continue # no warning
+ while True:
+ return # warning
+
+
+while True:
+ try:
+ pass
+ finally:
+ continue # warning
+
+while True:
+ try:
+ pass
+ finally:
+ break # warning
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b013.py
new/flake8-bugbear-20.1.4/tests/b013.py
--- old/flake8-bugbear-19.8.0/tests/b013.py 1970-01-01 01:00:00.000000000
+0100
+++ new/flake8-bugbear-20.1.4/tests/b013.py 2020-02-01 05:10:39.000000000
+0100
@@ -0,0 +1,36 @@
+"""
+Should emit:
+B013 - on lines 10 and 28
+"""
+
+import re
+
+try:
+ pass
+except (ValueError,):
+ # pointless use of tuple
+ pass
+
+try:
+ pass
+except (ValueError):
+ # not using a tuple means it's OK (if odd)
+ pass
+
+try:
+ pass
+except ValueError:
+ # no warning here, all good
+ pass
+
+try:
+ pass
+except (re.error,):
+ # pointless use of tuple with dotted attribute
+ pass
+
+try:
+ pass
+except (a.b.c.d, b.c.d):
+ # attribute of attribute, etc.
+ pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b014.py
new/flake8-bugbear-20.1.4/tests/b014.py
--- old/flake8-bugbear-19.8.0/tests/b014.py 1970-01-01 01:00:00.000000000
+0100
+++ new/flake8-bugbear-20.1.4/tests/b014.py 2020-02-01 05:10:39.000000000
+0100
@@ -0,0 +1,50 @@
+"""
+Should emit:
+B014 - on lines 10, 16, 27, 41, and 48
+"""
+
+import re
+
+try:
+ pass
+except (Exception, TypeError):
+ # TypeError is a subclass of Exception, so it doesn't add anything
+ pass
+
+try:
+ pass
+except (OSError, OSError) as err:
+ # Duplicate exception types are useless
+ pass
+
+
+class MyError(Exception):
+ pass
+
+
+try:
+ pass
+except (MyError, MyError):
+ # Detect duplicate non-builtin errors
+ pass
+
+
+try:
+ pass
+except (MyError, Exception) as e:
+ # Don't assume that we're all subclasses of Exception
+ pass
+
+
+try:
+ pass
+except (MyError, BaseException) as e:
+ # But we *can* assume that everything is a subclass of BaseException
+ pass
+
+
+try:
+ pass
+except (re.error, re.error):
+ # Duplicate exception types as attributes
+ pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b901.py
new/flake8-bugbear-20.1.4/tests/b901.py
--- old/flake8-bugbear-19.8.0/tests/b901.py 2019-08-05 17:36:44.000000000
+0200
+++ new/flake8-bugbear-20.1.4/tests/b901.py 2020-02-01 05:10:39.000000000
+0100
@@ -62,7 +62,7 @@
def not_broken7():
- x = (yield from [])
+ x = yield from []
return x
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/test_bugbear.py
new/flake8-bugbear-20.1.4/tests/test_bugbear.py
--- old/flake8-bugbear-19.8.0/tests/test_bugbear.py 2019-08-12
06:18:59.000000000 +0200
+++ new/flake8-bugbear-20.1.4/tests/test_bugbear.py 2020-02-01
05:10:39.000000000 +0100
@@ -1,8 +1,14 @@
+import ast
+import os
from pathlib import Path
+import site
import subprocess
import unittest
-from bugbear import BugBearChecker
+from hypothesis import given
+from hypothesmith import from_grammar
+
+from bugbear import BugBearChecker, BugBearVisitor
from bugbear import (
B001,
B002,
@@ -15,6 +21,9 @@
B009,
B010,
B011,
+ B012,
+ B013,
+ B014,
B301,
B302,
B303,
@@ -38,7 +47,12 @@
filename = Path(__file__).absolute().parent / "b001.py"
bbc = BugBearChecker(filename=str(filename))
errors = list(bbc.run())
- self.assertEqual(errors, self.errors(B001(8, 0), B001(40, 4)))
+ expected = self.errors(
+ B001(8, 0, vars=("bare `except:`",)),
+ B001(40, 4, vars=("bare `except:`",)),
+ B001(54, 0, vars=("`except ():`",)),
+ )
+ self.assertEqual(errors, expected)
def test_b002(self):
filename = Path(__file__).absolute().parent / "b002.py"
@@ -109,7 +123,15 @@
filename = Path(__file__).absolute().parent / "b009_b010.py"
bbc = BugBearChecker(filename=str(filename))
errors = list(bbc.run())
- self.assertEqual(errors, self.errors(B009(15, 0), B010(22, 0)))
+ all_errors = [
+ B009(17, 0),
+ B009(18, 0),
+ B009(19, 0),
+ B010(28, 0),
+ B010(29, 0),
+ B010(30, 0),
+ ]
+ self.assertEqual(errors, self.errors(*all_errors))
def test_b011(self):
filename = Path(__file__).absolute().parent / "b011.py"
@@ -119,6 +141,46 @@
errors, self.errors(B011(8, 0, vars=("i",)), B011(10, 0,
vars=("k",)))
)
+ def test_b012(self):
+ filename = Path(__file__).absolute().parent / "b012.py"
+ bbc = BugBearChecker(filename=str(filename))
+ errors = list(bbc.run())
+ all_errors = [
+ B012(5, 8),
+ B012(13, 12),
+ B012(21, 12),
+ B012(31, 12),
+ B012(44, 20),
+ B012(66, 12),
+ B012(78, 12),
+ B012(94, 12),
+ B012(101, 8),
+ B012(107, 8),
+ ]
+ self.assertEqual(errors, self.errors(*all_errors))
+
+ def test_b013(self):
+ filename = Path(__file__).absolute().parent / "b013.py"
+ bbc = BugBearChecker(filename=str(filename))
+ errors = list(bbc.run())
+ expected = self.errors(
+ B013(10, 0, vars=("ValueError",)), B013(28, 0, vars=("re.error",))
+ )
+ self.assertEqual(errors, expected)
+
+ def test_b014(self):
+ filename = Path(__file__).absolute().parent / "b014.py"
+ bbc = BugBearChecker(filename=str(filename))
+ errors = list(bbc.run())
+ expected = self.errors(
+ B014(10, 0, vars=("Exception, TypeError", "", "Exception")),
+ B014(16, 0, vars=("OSError, OSError", " as err", "OSError")),
+ B014(27, 0, vars=("MyError, MyError", "", "MyError")),
+ B014(41, 0, vars=("MyError, BaseException", " as e",
"BaseException")),
+ B014(48, 0, vars=("re.error, re.error", "", "re.error")),
+ )
+ self.assertEqual(errors, expected)
+
def test_b301_b302_b305(self):
filename = Path(__file__).absolute().parent / "b301_b302_b305.py"
bbc = BugBearChecker(filename=str(filename))
@@ -199,7 +261,7 @@
)
self.assertEqual(proc.returncode, 0, proc.stdout.decode("utf8"))
self.assertEqual(proc.stdout, b"")
- # self.assertEqual(proc.stderr, b"")
+ self.assertEqual(proc.stderr, b"")
def test_selfclean_test_bugbear(self):
filename = Path(__file__).absolute()
@@ -211,7 +273,26 @@
)
self.assertEqual(proc.returncode, 0, proc.stdout.decode("utf8"))
self.assertEqual(proc.stdout, b"")
- # self.assertEqual(proc.stderr, b'')
+ self.assertEqual(proc.stderr, b"")
+
+
+class TestFuzz(unittest.TestCase):
+ @given(from_grammar().map(ast.parse))
+ def test_does_not_crash_on_any_valid_code(self, syntax_tree):
+ # Given any syntatically-valid source code, flake8-bugbear should
+ # not crash. This tests doesn't check that we do the *right* thing,
+ # just that we don't crash on valid-if-poorly-styled code!
+ BugBearVisitor(filename="<string>", lines=[]).visit(syntax_tree)
+
+ def test_does_not_crash_on_site_code(self):
+ # Because the generator isn't perfect, we'll also test on all the code
+ # we can easily find in our current Python environment - this includes
+ # the standard library, and all installed packages.
+ for base in sorted(set(site.PREFIXES)):
+ for dirname, _, files in os.walk(base):
+ for f in files:
+ if f.endswith(".py"):
+ BugBearChecker(filename=str(Path(dirname) / f))
if __name__ == "__main__":