Skip to content

Commit

Permalink
Set up mypy
Browse files Browse the repository at this point in the history
This is minimal for now (didn't add any annotations, didn't turn on strict mode),
just so we can get started. In later PRs I can add more strictness.
  • Loading branch information
JelleZijlstra committed Jul 2, 2024
1 parent 3157b89 commit fbfc0be
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 43 deletions.
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)
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:
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

0 comments on commit fbfc0be

Please sign in to comment.