1import ast
2import builtins
3import itertools
4import logging
5import re
6import sys
7from collections import namedtuple
8from contextlib import suppress
9from functools import lru_cache, partial
10from keyword import iskeyword
11
12import attr
13
14import pycodestyle
15
16__version__ = "21.9.2"
17
18LOG = logging.getLogger("flake8.bugbear")
19
20
21@attr.s(hash=False)
22class BugBearChecker:
23    name = "flake8-bugbear"
24    version = __version__
25
26    tree = attr.ib(default=None)
27    filename = attr.ib(default="(none)")
28    lines = attr.ib(default=None)
29    max_line_length = attr.ib(default=79)
30    visitor = attr.ib(init=False, default=attr.Factory(lambda: BugBearVisitor))
31    options = attr.ib(default=None)
32
33    def run(self):
34        if not self.tree or not self.lines:
35            self.load_file()
36        visitor = self.visitor(filename=self.filename, lines=self.lines)
37        visitor.visit(self.tree)
38        for e in itertools.chain(visitor.errors, self.gen_line_based_checks()):
39            if self.should_warn(e.message[:4]):
40                yield self.adapt_error(e)
41
42    def gen_line_based_checks(self):
43        """gen_line_based_checks() -> (error, error, error, ...)
44
45        The following simple checks are based on the raw lines, not the AST.
46        """
47        for lineno, line in enumerate(self.lines, start=1):
48            length = len(line) - 1
49            if length > 1.1 * self.max_line_length:
50                yield B950(lineno, length, vars=(length, self.max_line_length))
51
52    @classmethod
53    def adapt_error(cls, e):
54        """Adapts the extended error namedtuple to be compatible with Flake8."""
55        return e._replace(message=e.message.format(*e.vars))[:4]
56
57    def load_file(self):
58        """Loads the file in a way that auto-detects source encoding and deals
59        with broken terminal encodings for stdin.
60
61        Stolen from flake8_import_order because it's good.
62        """
63
64        if self.filename in ("stdin", "-", None):
65            self.filename = "stdin"
66            self.lines = pycodestyle.stdin_get_value().splitlines(True)
67        else:
68            self.lines = pycodestyle.readlines(self.filename)
69
70        if not self.tree:
71            self.tree = ast.parse("".join(self.lines))
72
73    @staticmethod
74    def add_options(optmanager):
75        """Informs flake8 to ignore B9xx by default."""
76        optmanager.extend_default_ignore(disabled_by_default)
77
78    @lru_cache()
79    def should_warn(self, code):
80        """Returns `True` if Bugbear should emit a particular warning.
81
82        flake8 overrides default ignores when the user specifies
83        `ignore = ` in configuration.  This is problematic because it means
84        specifying anything in `ignore = ` implicitly enables all optional
85        warnings.  This function is a workaround for this behavior.
86
87        As documented in the README, the user is expected to explicitly select
88        the warnings.
89        """
90        if code[:2] != "B9":
91            # Normal warnings are safe for emission.
92            return True
93
94        if self.options is None:
95            LOG.info(
96                "Options not provided to Bugbear, optional warning %s selected.", code
97            )
98            return True
99
100        for i in range(2, len(code) + 1):
101            if code[:i] in self.options.select:
102                return True
103
104        LOG.info(
105            "Optional warning %s not present in selected warnings: %r. Not "
106            "firing it at all.",
107            code,
108            self.options.select,
109        )
110        return False
111
112
113def _is_identifier(arg):
114    # Return True if arg is a valid identifier, per
115    # https://docs.python.org/2/reference/lexical_analysis.html#identifiers
116
117    if not isinstance(arg, ast.Str):
118        return False
119
120    return re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", arg.s) is not None
121
122
123def _to_name_str(node):
124    # Turn Name and Attribute nodes to strings, e.g "ValueError" or
125    # "pkg.mod.error", handling any depth of attribute accesses.
126    if isinstance(node, ast.Name):
127        return node.id
128    if isinstance(node, ast.Call):
129        return _to_name_str(node.func)
130    try:
131        return _to_name_str(node.value) + "." + node.attr
132    except AttributeError:
133        return _to_name_str(node.value)
134
135
136def _typesafe_issubclass(cls, class_or_tuple):
137    try:
138        return issubclass(cls, class_or_tuple)
139    except TypeError:
140        # User code specifies a type that is not a type in our current run. Might be
141        # their error, might be a difference in our environments. We don't know so we
142        # ignore this
143        return False
144
145
146@attr.s
147class BugBearVisitor(ast.NodeVisitor):
148    filename = attr.ib()
149    lines = attr.ib()
150    node_stack = attr.ib(default=attr.Factory(list))
151    node_window = attr.ib(default=attr.Factory(list))
152    errors = attr.ib(default=attr.Factory(list))
153    futures = attr.ib(default=attr.Factory(set))
154
155    NODE_WINDOW_SIZE = 4
156
157    if False:
158        # Useful for tracing what the hell is going on.
159
160        def __getattr__(self, name):
161            print(name)
162            return self.__getattribute__(name)
163
164    def visit(self, node):
165        self.node_stack.append(node)
166        self.node_window.append(node)
167        self.node_window = self.node_window[-self.NODE_WINDOW_SIZE :]
168        super().visit(node)
169        self.node_stack.pop()
170
171    def visit_ExceptHandler(self, node):
172        if node.type is None:
173            self.errors.append(
174                B001(node.lineno, node.col_offset, vars=("bare `except:`",))
175            )
176        elif isinstance(node.type, ast.Tuple):
177            names = [_to_name_str(e) for e in node.type.elts]
178            as_ = " as " + node.name if node.name is not None else ""
179            if len(names) == 0:
180                vs = ("`except (){}:`".format(as_),)
181                self.errors.append(B001(node.lineno, node.col_offset, vars=vs))
182            elif len(names) == 1:
183                self.errors.append(B013(node.lineno, node.col_offset, vars=names))
184            else:
185                # See if any of the given exception names could be removed, e.g. from:
186                #    (MyError, MyError)  # duplicate names
187                #    (MyError, BaseException)  # everything derives from the Base
188                #    (Exception, TypeError)  # builtins where one subclasses another
189                #    (IOError, OSError)  # IOError is an alias of OSError since Python3.3
190                # but note that other cases are impractical to hande from the AST.
191                # We expect this is mostly useful for users who do not have the
192                # builtin exception hierarchy memorised, and include a 'shadowed'
193                # subtype without realising that it's redundant.
194                good = sorted(set(names), key=names.index)
195                if "BaseException" in good:
196                    good = ["BaseException"]
197                # Find and remove aliases exceptions and only leave the primary alone
198                primaries = filter(
199                    lambda primary: primary in good, B014.exception_aliases.keys()
200                )
201                for primary in primaries:
202                    aliases = B014.exception_aliases[primary]
203                    good = list(filter(lambda e: e not in aliases, good))
204
205                for name, other in itertools.permutations(tuple(good), 2):
206                    if _typesafe_issubclass(
207                        getattr(builtins, name, type), getattr(builtins, other, ())
208                    ):
209                        if name in good:
210                            good.remove(name)
211                if good != names:
212                    desc = good[0] if len(good) == 1 else "({})".format(", ".join(good))
213                    self.errors.append(
214                        B014(
215                            node.lineno,
216                            node.col_offset,
217                            vars=(", ".join(names), as_, desc),
218                        )
219                    )
220        self.generic_visit(node)
221
222    def visit_UAdd(self, node):
223        trailing_nodes = list(map(type, self.node_window[-4:]))
224        if trailing_nodes == [ast.UnaryOp, ast.UAdd, ast.UnaryOp, ast.UAdd]:
225            originator = self.node_window[-4]
226            self.errors.append(B002(originator.lineno, originator.col_offset))
227        self.generic_visit(node)
228
229    def visit_Call(self, node):
230        if isinstance(node.func, ast.Attribute):
231            self.check_for_b005(node)
232        else:
233            with suppress(AttributeError, IndexError):
234                if (
235                    node.func.id in ("getattr", "hasattr")
236                    and node.args[1].s == "__call__"  # noqa: W503
237                ):
238                    self.errors.append(B004(node.lineno, node.col_offset))
239                if (
240                    node.func.id == "getattr"
241                    and len(node.args) == 2  # noqa: W503
242                    and _is_identifier(node.args[1])  # noqa: W503
243                    and not iskeyword(node.args[1].s)  # noqa: W503
244                ):
245                    self.errors.append(B009(node.lineno, node.col_offset))
246                elif (
247                    node.func.id == "setattr"
248                    and len(node.args) == 3  # noqa: W503
249                    and _is_identifier(node.args[1])  # noqa: W503
250                    and not iskeyword(node.args[1].s)  # noqa: W503
251                ):
252                    self.errors.append(B010(node.lineno, node.col_offset))
253
254        self.generic_visit(node)
255
256    def visit_Assign(self, node):
257        if len(node.targets) == 1:
258            t = node.targets[0]
259            if isinstance(t, ast.Attribute) and isinstance(t.value, ast.Name):
260                if (t.value.id, t.attr) == ("os", "environ"):
261                    self.errors.append(B003(node.lineno, node.col_offset))
262        self.generic_visit(node)
263
264    def visit_For(self, node):
265        self.check_for_b007(node)
266        self.generic_visit(node)
267
268    def visit_Assert(self, node):
269        self.check_for_b011(node)
270        self.generic_visit(node)
271
272    def visit_AsyncFunctionDef(self, node):
273        self.check_for_b902(node)
274        self.check_for_b006(node)
275        self.generic_visit(node)
276
277    def visit_FunctionDef(self, node):
278        self.check_for_b901(node)
279        self.check_for_b902(node)
280        self.check_for_b006(node)
281        self.generic_visit(node)
282
283    def visit_ClassDef(self, node):
284        self.check_for_b903(node)
285        self.generic_visit(node)
286
287    def visit_Try(self, node):
288        self.check_for_b012(node)
289        self.generic_visit(node)
290
291    def visit_Compare(self, node):
292        self.check_for_b015(node)
293        self.generic_visit(node)
294
295    def visit_Raise(self, node):
296        self.check_for_b016(node)
297        self.check_for_b904(node)
298        self.generic_visit(node)
299
300    def visit_With(self, node):
301        self.check_for_b017(node)
302        self.generic_visit(node)
303
304    def compose_call_path(self, node):
305        if isinstance(node, ast.Attribute):
306            yield from self.compose_call_path(node.value)
307            yield node.attr
308        elif isinstance(node, ast.Name):
309            yield node.id
310
311    def check_for_b005(self, node):
312        if node.func.attr not in B005.methods:
313            return  # method name doesn't match
314
315        if len(node.args) != 1 or not isinstance(node.args[0], ast.Str):
316            return  # used arguments don't match the builtin strip
317
318        call_path = ".".join(self.compose_call_path(node.func.value))
319        if call_path in B005.valid_paths:
320            return  # path is exempt
321
322        s = node.args[0].s
323        if len(s) == 1:
324            return  # stripping just one character
325
326        if len(s) == len(set(s)):
327            return  # no characters appear more than once
328
329        self.errors.append(B005(node.lineno, node.col_offset))
330
331    def check_for_b006(self, node):
332        for default in node.args.defaults + node.args.kw_defaults:
333            if isinstance(
334                default, (*B006.mutable_literals, *B006.mutable_comprehensions)
335            ):
336                self.errors.append(B006(default.lineno, default.col_offset))
337            elif isinstance(default, ast.Call):
338                call_path = ".".join(self.compose_call_path(default.func))
339                if call_path in B006.mutable_calls:
340                    self.errors.append(B006(default.lineno, default.col_offset))
341                elif call_path not in B008.immutable_calls:
342                    # Check if function call is actually a float infinity/NaN literal
343                    if call_path == "float" and len(default.args) == 1:
344                        float_arg = default.args[0]
345                        if sys.version_info < (3, 8, 0):
346                            # NOTE: pre-3.8, string literals are represented with ast.Str
347                            if isinstance(float_arg, ast.Str):
348                                str_val = float_arg.s
349                            else:
350                                str_val = ""
351                        else:
352                            # NOTE: post-3.8, string literals are represented with ast.Constant
353                            if isinstance(float_arg, ast.Constant):
354                                str_val = float_arg.value
355                                if not isinstance(str_val, str):
356                                    str_val = ""
357                            else:
358                                str_val = ""
359
360                        # NOTE: regex derived from documentation at:
361                        # https://docs.python.org/3/library/functions.html#float
362                        inf_nan_regex = r"^[+-]?(inf|infinity|nan)$"
363                        re_result = re.search(inf_nan_regex, str_val.lower())
364                        is_float_literal = re_result is not None
365                    else:
366                        is_float_literal = False
367
368                    if not is_float_literal:
369                        self.errors.append(B008(default.lineno, default.col_offset))
370
371    def check_for_b007(self, node):
372        targets = NameFinder()
373        targets.visit(node.target)
374        ctrl_names = set(filter(lambda s: not s.startswith("_"), targets.names))
375        body = NameFinder()
376        for expr in node.body:
377            body.visit(expr)
378        used_names = set(body.names)
379        for name in sorted(ctrl_names - used_names):
380            n = targets.names[name][0]
381            self.errors.append(B007(n.lineno, n.col_offset, vars=(name,)))
382
383    def check_for_b011(self, node):
384        if isinstance(node.test, ast.NameConstant) and node.test.value is False:
385            self.errors.append(B011(node.lineno, node.col_offset))
386
387    def check_for_b012(self, node):
388        def _loop(node, bad_node_types):
389            if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)):
390                return
391
392            if isinstance(node, (ast.While, ast.For)):
393                bad_node_types = (ast.Return,)
394
395            elif isinstance(node, bad_node_types):
396                self.errors.append(B012(node.lineno, node.col_offset))
397
398            for child in ast.iter_child_nodes(node):
399                _loop(child, bad_node_types)
400
401        for child in node.finalbody:
402            _loop(child, (ast.Return, ast.Continue, ast.Break))
403
404    def check_for_b015(self, node):
405        if isinstance(self.node_stack[-2], ast.Expr):
406            self.errors.append(B015(node.lineno, node.col_offset))
407
408    def check_for_b016(self, node):
409        if isinstance(node.exc, (ast.NameConstant, ast.Num, ast.Str)):
410            self.errors.append(B016(node.lineno, node.col_offset))
411
412    def check_for_b017(self, node):
413        """Checks for use of the evil syntax 'with assertRaises(Exception):'
414
415        This form of assertRaises will catch everything that subclasses
416        Exception, which happens to be the vast majority of Python internal
417        errors, including the ones raised when a non-existing method/function
418        is called, or a function is called with an invalid dictionary key
419        lookup.
420        """
421        item = node.items[0]
422        item_context = item.context_expr
423        if (
424            hasattr(item_context, "func")
425            and hasattr(item_context.func, "attr")  # noqa W503
426            and item_context.func.attr == "assertRaises"  # noqa W503
427            and len(item_context.args) == 1  # noqa W503
428            and isinstance(item_context.args[0], ast.Name)  # noqa W503
429            and item_context.args[0].id == "Exception"  # noqa W503
430            and not item.optional_vars  # noqa W503
431        ):
432            self.errors.append(B017(node.lineno, node.col_offset))
433
434    def check_for_b904(self, node):
435        """Checks `raise` without `from` inside an `except` clause.
436
437        In these cases, you should use explicit exception chaining from the
438        earlier error, or suppress it with `raise ... from None`.  See
439        https://docs.python.org/3/tutorial/errors.html#exception-chaining
440        """
441        if (
442            node.cause is None
443            and node.exc is not None
444            and not (isinstance(node.exc, ast.Name) and node.exc.id.islower())
445            and any(isinstance(n, ast.ExceptHandler) for n in self.node_stack)
446        ):
447            self.errors.append(B904(node.lineno, node.col_offset))
448
449    def walk_function_body(self, node):
450        def _loop(parent, node):
451            if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)):
452                return
453            yield parent, node
454            for child in ast.iter_child_nodes(node):
455                yield from _loop(node, child)
456
457        for child in node.body:
458            yield from _loop(node, child)
459
460    def check_for_b901(self, node):
461        if node.name == "__await__":
462            return
463
464        has_yield = False
465        return_node = None
466
467        for parent, x in self.walk_function_body(node):
468            # Only consider yield when it is part of an Expr statement.
469            if isinstance(parent, ast.Expr) and isinstance(
470                x, (ast.Yield, ast.YieldFrom)
471            ):
472                has_yield = True
473
474            if isinstance(x, ast.Return) and x.value is not None:
475                return_node = x
476
477            if has_yield and return_node is not None:
478                self.errors.append(B901(return_node.lineno, return_node.col_offset))
479                break
480
481    def check_for_b902(self, node):
482        if not isinstance(self.node_stack[-2], ast.ClassDef):
483            return
484
485        decorators = NameFinder()
486        decorators.visit(node.decorator_list)
487
488        if "staticmethod" in decorators.names:
489            # TODO: maybe warn if the first argument is surprisingly `self` or
490            # `cls`?
491            return
492
493        bases = {b.id for b in self.node_stack[-2].bases if isinstance(b, ast.Name)}
494        if "type" in bases:
495            if (
496                "classmethod" in decorators.names
497                or node.name in B902.implicit_classmethods  # noqa: W503
498            ):
499                expected_first_args = B902.metacls
500                kind = "metaclass class"
501            else:
502                expected_first_args = B902.cls
503                kind = "metaclass instance"
504        else:
505            if (
506                "classmethod" in decorators.names
507                or node.name in B902.implicit_classmethods  # noqa: W503
508            ):
509                expected_first_args = B902.cls
510                kind = "class"
511            else:
512                expected_first_args = B902.self
513                kind = "instance"
514
515        args = getattr(node.args, "posonlyargs", []) + node.args.args
516        vararg = node.args.vararg
517        kwarg = node.args.kwarg
518        kwonlyargs = node.args.kwonlyargs
519
520        if args:
521            actual_first_arg = args[0].arg
522            lineno = args[0].lineno
523            col = args[0].col_offset
524        elif vararg:
525            actual_first_arg = "*" + vararg.arg
526            lineno = vararg.lineno
527            col = vararg.col_offset
528        elif kwarg:
529            actual_first_arg = "**" + kwarg.arg
530            lineno = kwarg.lineno
531            col = kwarg.col_offset
532        elif kwonlyargs:
533            actual_first_arg = "*, " + kwonlyargs[0].arg
534            lineno = kwonlyargs[0].lineno
535            col = kwonlyargs[0].col_offset
536        else:
537            actual_first_arg = "(none)"
538            lineno = node.lineno
539            col = node.col_offset
540
541        if actual_first_arg not in expected_first_args:
542            if not actual_first_arg.startswith(("(", "*")):
543                actual_first_arg = repr(actual_first_arg)
544            self.errors.append(
545                B902(lineno, col, vars=(actual_first_arg, kind, expected_first_args[0]))
546            )
547
548    def check_for_b903(self, node):
549        body = node.body
550        if (
551            body
552            and isinstance(body[0], ast.Expr)  # noqa: W503
553            and isinstance(body[0].value, ast.Str)  # noqa: W503
554        ):
555            # Ignore the docstring
556            body = body[1:]
557
558        if (
559            len(body) != 1
560            or not isinstance(body[0], ast.FunctionDef)  # noqa: W503
561            or body[0].name != "__init__"  # noqa: W503
562        ):
563            # only classes with *just* an __init__ method are interesting
564            return
565
566        # all the __init__ function does is a series of assignments to attributes
567        for stmt in body[0].body:
568            if not isinstance(stmt, ast.Assign):
569                return
570            targets = stmt.targets
571            if len(targets) > 1 or not isinstance(targets[0], ast.Attribute):
572                return
573            if not isinstance(stmt.value, ast.Name):
574                return
575
576        self.errors.append(B903(node.lineno, node.col_offset))
577
578
579@attr.s
580class NameFinder(ast.NodeVisitor):
581    """Finds a name within a tree of nodes.
582
583    After `.visit(node)` is called, `found` is a dict with all name nodes inside,
584    key is name string, value is the node (useful for location purposes).
585    """
586
587    names = attr.ib(default=attr.Factory(dict))
588
589    def visit_Name(self, node):
590        self.names.setdefault(node.id, []).append(node)
591
592    def visit(self, node):
593        """Like super-visit but supports iteration over lists."""
594        if not isinstance(node, list):
595            return super().visit(node)
596
597        for elem in node:
598            super().visit(elem)
599        return node
600
601
602error = namedtuple("error", "lineno col message type vars")
603Error = partial(partial, error, type=BugBearChecker, vars=())
604
605
606B001 = Error(
607    message=(
608        "B001 Do not use {}, it also catches unexpected "
609        "events like memory errors, interrupts, system exit, and so on.  "
610        "Prefer `except Exception:`.  If you're sure what you're doing, "
611        "be explicit and write `except BaseException:`."
612    )
613)
614
615B002 = Error(
616    message=(
617        "B002 Python does not support the unary prefix increment. Writing "
618        "++n is equivalent to +(+(n)), which equals n. You meant n += 1."
619    )
620)
621
622B003 = Error(
623    message=(
624        "B003 Assigning to `os.environ` doesn't clear the environment. "
625        "Subprocesses are going to see outdated variables, in disagreement "
626        "with the current process. Use `os.environ.clear()` or the `env=` "
627        "argument to Popen."
628    )
629)
630
631B004 = Error(
632    message=(
633        "B004 Using `hasattr(x, '__call__')` to test if `x` is callable "
634        "is unreliable. If `x` implements custom `__getattr__` or its "
635        "`__call__` is itself not callable, you might get misleading "
636        "results. Use `callable(x)` for consistent results."
637    )
638)
639
640B005 = Error(
641    message=(
642        "B005 Using .strip() with multi-character strings is misleading "
643        "the reader. It looks like stripping a substring. Move your "
644        "character set to a constant if this is deliberate. Use "
645        ".replace() or regular expressions to remove string fragments."
646    )
647)
648B005.methods = {"lstrip", "rstrip", "strip"}
649B005.valid_paths = {}
650
651B006 = Error(
652    message=(
653        "B006 Do not use mutable data structures for argument defaults.  They "
654        "are created during function definition time. All calls to the function "
655        "reuse this one instance of that data structure, persisting changes "
656        "between them."
657    )
658)
659B006.mutable_literals = (ast.Dict, ast.List, ast.Set)
660B006.mutable_comprehensions = (ast.ListComp, ast.DictComp, ast.SetComp)
661B006.mutable_calls = {
662    "Counter",
663    "OrderedDict",
664    "collections.Counter",
665    "collections.OrderedDict",
666    "collections.defaultdict",
667    "collections.deque",
668    "defaultdict",
669    "deque",
670    "dict",
671    "list",
672    "set",
673}
674B007 = Error(
675    message=(
676        "B007 Loop control variable {!r} not used within the loop body. "
677        "If this is intended, start the name with an underscore."
678    )
679)
680B008 = Error(
681    message=(
682        "B008 Do not perform function calls in argument defaults.  The call is "
683        "performed only once at function definition time. All calls to your "
684        "function will reuse the result of that definition-time function call.  If "
685        "this is intended, assign the function call to a module-level variable and "
686        "use that variable as a default value."
687    )
688)
689B008.immutable_calls = {
690    "tuple",
691    "frozenset",
692    "types.MappingProxyType",
693    "MappingProxyType",
694    "re.compile",
695    "operator.attrgetter",
696    "operator.itemgetter",
697    "operator.methodcaller",
698    "attrgetter",
699    "itemgetter",
700    "methodcaller",
701}
702B009 = Error(
703    message=(
704        "B009 Do not call getattr with a constant attribute value, "
705        "it is not any safer than normal property access."
706    )
707)
708B010 = Error(
709    message=(
710        "B010 Do not call setattr with a constant attribute value, "
711        "it is not any safer than normal property access."
712    )
713)
714B011 = Error(
715    message=(
716        "B011 Do not call assert False since python -O removes these calls. "
717        "Instead callers should raise AssertionError()."
718    )
719)
720B012 = Error(
721    message=(
722        "B012 return/continue/break inside finally blocks cause exceptions "
723        "to be silenced. Exceptions should be silenced in except blocks. Control "
724        "statements can be moved outside the finally block."
725    )
726)
727B013 = Error(
728    message=(
729        "B013 A length-one tuple literal is redundant.  "
730        "Write `except {0}:` instead of `except ({0},):`."
731    )
732)
733B014 = Error(
734    message=(
735        "B014 Redundant exception types in `except ({0}){1}:`.  "
736        "Write `except {2}{1}:`, which catches exactly the same exceptions."
737    )
738)
739B014.exception_aliases = {
740    "OSError": {
741        "IOError",
742        "EnvironmentError",
743        "WindowsError",
744        "mmap.error",
745        "socket.error",
746        "select.error",
747    }
748}
749B015 = Error(
750    message=(
751        "B015 Pointless comparison. This comparison does nothing but waste "
752        "CPU instructions. Either prepend `assert` or remove it."
753    )
754)
755B016 = Error(
756    message=(
757        "B016 Cannot raise a literal. Did you intend to return it or raise "
758        "an Exception?"
759    )
760)
761B017 = Error(
762    message=(
763        "B017 assertRaises(Exception): should be considered evil. "
764        "It can lead to your test passing even if the code being tested is "
765        "never executed due to a typo. Either assert for a more specific "
766        "exception (builtin or custom), use assertRaisesRegex, or use the "
767        "context manager form of assertRaises."
768    )
769)
770
771# Warnings disabled by default.
772B901 = Error(
773    message=(
774        "B901 Using `yield` together with `return x`. Use native "
775        "`async def` coroutines or put a `# noqa` comment on this "
776        "line if this was intentional."
777    )
778)
779B902 = Error(
780    message=(
781        "B902 Invalid first argument {} used for {} method. Use the "
782        "canonical first argument name in methods, i.e. {}."
783    )
784)
785B902.implicit_classmethods = {"__new__", "__init_subclass__", "__class_getitem__"}
786B902.self = ["self"]  # it's a list because the first is preferred
787B902.cls = ["cls", "klass"]  # ditto.
788B902.metacls = ["metacls", "metaclass", "typ", "mcs"]  # ditto.
789
790B903 = Error(
791    message=(
792        "B903 Data class should either be immutable or use __slots__ to "
793        "save memory. Use collections.namedtuple to generate an immutable "
794        "class, or enumerate the attributes in a __slot__ declaration in "
795        "the class to leave attributes mutable."
796    )
797)
798
799B904 = Error(
800    message=(
801        "B904 Within an `except` clause, raise exceptions with `raise ... from err` or"
802        " `raise ... from None` to distinguish them from errors in exception handling. "
803        " See https://docs.python.org/3/tutorial/errors.html#exception-chaining for"
804        " details."
805    )
806)
807
808B950 = Error(message="B950 line too long ({} > {} characters)")
809
810disabled_by_default = ["B901", "B902", "B903", "B904", "B950"]
811