Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set up mypy #482

Merged
merged 1 commit into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 35 additions & 41 deletions bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from typing import Dict, Iterable, Iterator, List, Set, Union

import attr
import pycodestyle
import pycodestyle # type: ignore[import-untyped]

__version__ = "24.4.26"

Expand Down Expand Up @@ -259,7 +259,7 @@ def _check_redundant_excepthandlers(names, node):
# Remove redundant exceptions that the automatic system either handles
# poorly (usually aliases) or can't be checked (e.g. it's not an
# built-in exception).
for primary, equivalents in B014.redundant_exceptions.items():
for primary, equivalents in B014_REDUNDANT_EXCEPTIONS.items():
if primary in good:
good = [g for g in good if g not in equivalents]

Expand Down Expand Up @@ -366,17 +366,16 @@ class B040CaughtException:
class BugBearVisitor(ast.NodeVisitor):
filename = attr.ib()
lines = attr.ib()
b008_b039_extend_immutable_calls = attr.ib(factory=set)
b902_classmethod_decorators = attr.ib(factory=set)
node_window = attr.ib(factory=list)
errors = attr.ib(factory=list)
futures = attr.ib(factory=set)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was unused

contexts = attr.ib(factory=list)
b008_b039_extend_immutable_calls: set[str] = attr.ib(factory=set)
b902_classmethod_decorators: set[str] = attr.ib(factory=set)
node_window: list[ast.AST] = attr.ib(factory=list)
errors: list[error] = attr.ib(factory=list)
contexts: list[Context] = attr.ib(factory=list)
b040_caught_exception: B040CaughtException | None = attr.ib(default=None)

NODE_WINDOW_SIZE = 4
_b023_seen = attr.ib(factory=set, init=False)
_b005_imports = attr.ib(factory=set, init=False)
_b023_seen: set[error] = attr.ib(factory=set, init=False)
_b005_imports: set[str] = attr.ib(factory=set, init=False)

if False:
# Useful for tracing what the hell is going on.
Expand Down Expand Up @@ -641,7 +640,7 @@ def check_for_b005(self, node):
for name in node.names:
self._b005_imports.add(f"{node.module}.{name.name or name.asname}")
elif isinstance(node, ast.Call):
if node.func.attr not in B005.methods:
if node.func.attr not in B005_METHODS:
return # method name doesn't match

if (
Expand All @@ -657,10 +656,6 @@ def check_for_b005(self, node):
):
return # used arguments don't match the builtin strip

call_path = ".".join(compose_call_path(node.func.value))
if call_path in B005.valid_paths:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was empty and I'm not sure what it was meant for so I removed it.

return # path is exempt

value = node.args[0].value
if len(value) == 1:
return # stripping just one character
Expand Down Expand Up @@ -843,7 +838,7 @@ def check_for_b019(self, node):
if decorator in {"classmethod", "staticmethod"}:
return

if decorator in B019.caches:
if decorator in B019_CACHES:
self.errors.append(
B019(
node.decorator_list[idx].lineno,
Expand Down Expand Up @@ -1229,7 +1224,7 @@ def check_for_b902( # noqa: C901 (too complex)
def is_classmethod(decorators: Set[str]) -> bool:
return (
any(name in decorators for name in self.b902_classmethod_decorators)
or node.name in B902.implicit_classmethods
or node.name in B902_IMPLICIT_CLASSMETHODS
)

if len(self.contexts) < 2 or not isinstance(
Expand All @@ -1251,17 +1246,17 @@ def is_classmethod(decorators: Set[str]) -> bool:
bases = {b.id for b in cls.bases if isinstance(b, ast.Name)}
if any(basetype in bases for basetype in ("type", "ABCMeta", "EnumMeta")):
if is_classmethod(decorators):
expected_first_args = B902.metacls
expected_first_args = B902_METACLS
kind = "metaclass class"
else:
expected_first_args = B902.cls
expected_first_args = B902_CLS
kind = "metaclass instance"
else:
if is_classmethod(decorators):
expected_first_args = B902.cls
expected_first_args = B902_CLS
kind = "class"
else:
expected_first_args = B902.self
expected_first_args = B902_SELF
kind = "instance"

args = getattr(node.args, "posonlyargs", []) + node.args.args
Expand Down Expand Up @@ -1708,7 +1703,7 @@ def compose_call_path(node):
yield node.id


def _tansform_slice_to_py39(slice: ast.Slice) -> ast.Slice | ast.Name:
def _transform_slice_to_py39(slice: ast.expr | ast.Slice) -> ast.Slice | ast.expr:
"""Transform a py38 style slice to a py39 style slice.

In py39 the slice was changed to have simple names directly assigned:
Expand Down Expand Up @@ -1769,18 +1764,18 @@ class B909Checker(ast.NodeVisitor):
"discard",
)

def __init__(self, name: str, key: str):
def __init__(self, name: str, key: str) -> None:
self.name = name
self.key = key
self.mutations = defaultdict(list)
self.mutations: dict[int, list[ast.AST]] = defaultdict(list)
self._conditional_block = 0

def visit_Assign(self, node: ast.Assign):
def visit_Assign(self, node: ast.Assign) -> None:
for target in node.targets:
if (
isinstance(target, ast.Subscript)
and _to_name_str(target.value) == self.name
and _to_name_str(_tansform_slice_to_py39(target.slice)) != self.key
and _to_name_str(_transform_slice_to_py39(target.slice)) != self.key
):
self.mutations[self._conditional_block].append(node)
self.generic_visit(node)
Expand Down Expand Up @@ -1897,7 +1892,7 @@ def __init__(
)
self.error_code_calls = error_code_calls
self.error_code_literals = error_code_literals
for node in B006.mutable_literals + B006.mutable_comprehensions:
for node in B006_MUTABLE_LITERALS + B006_MUTABLE_COMPREHENSIONS:
setattr(self, f"visit_{node}", self.visit_mutable_literal_or_comprehension)
self.errors = []
self.arg_depth = 0
Expand All @@ -1921,12 +1916,12 @@ def visit_mutable_literal_or_comprehension(self, node):

def visit_Call(self, node):
call_path = ".".join(compose_call_path(node.func))
if call_path in B006.mutable_calls:
if call_path in B006_MUTABLE_CALLS:
self.errors.append(self.error_code_calls(node.lineno, node.col_offset))
self.generic_visit(node)
return

if call_path in B008.immutable_calls | self.b008_b039_extend_immutable_calls:
if call_path in B008_IMMUTABLE_CALLS | self.b008_b039_extend_immutable_calls:
self.generic_visit(node)
return

Expand Down Expand Up @@ -2031,8 +2026,7 @@ def visit_Lambda(self, node):
"expressions to remove string fragments."
)
)
B005.methods = {"lstrip", "rstrip", "strip"}
B005.valid_paths = {}
B005_METHODS = {"lstrip", "rstrip", "strip"}

B006 = Error(
message=(
Expand All @@ -2044,9 +2038,9 @@ def visit_Lambda(self, node):
)

# Note: these are also used by B039
B006.mutable_literals = ("Dict", "List", "Set")
B006.mutable_comprehensions = ("ListComp", "DictComp", "SetComp")
B006.mutable_calls = {
B006_MUTABLE_LITERALS = ("Dict", "List", "Set")
B006_MUTABLE_COMPREHENSIONS = ("ListComp", "DictComp", "SetComp")
B006_MUTABLE_CALLS = {
"Counter",
"OrderedDict",
"collections.Counter",
Expand Down Expand Up @@ -2076,7 +2070,7 @@ def visit_Lambda(self, node):
)

# Note: these are also used by B039
B008.immutable_calls = {
B008_IMMUTABLE_CALLS = {
"tuple",
"frozenset",
"types.MappingProxyType",
Expand Down Expand Up @@ -2126,7 +2120,7 @@ def visit_Lambda(self, node):
"Write `except {2}{1}:`, which catches exactly the same exceptions."
)
)
B014.redundant_exceptions = {
B014_REDUNDANT_EXCEPTIONS = {
"OSError": {
# All of these are actually aliases of OSError since Python 3.3
"IOError",
Expand Down Expand Up @@ -2175,7 +2169,7 @@ def visit_Lambda(self, node):
"preventing garbage collection."
)
)
B019.caches = {
B019_CACHES = {
"functools.cache",
"functools.lru_cache",
"cache",
Expand Down Expand Up @@ -2312,10 +2306,10 @@ def visit_Lambda(self, node):
"canonical first argument name in methods, i.e. {}."
)
)
B902.implicit_classmethods = {"__new__", "__init_subclass__", "__class_getitem__"}
B902.self = ["self"] # it's a list because the first is preferred
B902.cls = ["cls", "klass"] # ditto.
B902.metacls = ["metacls", "metaclass", "typ", "mcs"] # ditto.
B902_IMPLICIT_CLASSMETHODS = {"__new__", "__init_subclass__", "__class_getitem__"}
B902_SELF = ["self"] # it's a list because the first is preferred
B902_CLS = ["cls", "klass"] # ditto.
B902_METACLS = ["metacls", "metaclass", "typ", "mcs"] # ditto.

B903 = Error(
message=(
Expand Down
10 changes: 8 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# The test environment and commands
[tox]
# default environments to run without `-e`
envlist = py38, py39, py310, py311, py312, py313, pep8_naming
envlist = py38, py39, py310, py311, py312, py313, pep8_naming, mypy

[gh-actions]
python =
3.8: py38
3.9: py39
3.10: py310
3.11: py311,pep8_naming
3.12: py312
3.12: py312,mypy
3.13-dev: py313

[testenv]
Expand All @@ -31,3 +31,9 @@ deps =
commands =
coverage run tests/test_bugbear.py -k b902 {posargs}
coverage report -m

[testenv:mypy]
deps =
mypy==1.10.1
commands =
mypy bugbear.py