src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py

branch
eric7
changeset 11150
73d80859079c
parent 11148
15e30f0c76a8
diff -r fc45672fae42 -r 73d80859079c src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py	Thu Feb 27 09:22:15 2025 +0100
+++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py	Thu Feb 27 14:42:39 2025 +0100
@@ -12,14 +12,10 @@
 import contextlib
 import copy
 import itertools
-import math
 import re
 import sys
 import tokenize
 
-from collections import Counter, defaultdict, namedtuple
-from dataclasses import dataclass
-from keyword import iskeyword
 from string import Formatter
 
 try:
@@ -37,58 +33,19 @@
 
 import AstUtilities
 
+from CodeStyleTopicChecker import CodeStyleTopicChecker
+
+from .BugBearVisitor import BugBearVisitor
+from .DateTimeVisitor import DateTimeVisitor
+from .DefaultMatchCaseVisitor import DefaultMatchCaseVisitor
 from .eradicate import Eradicator
 from .MiscellaneousDefaults import MiscellaneousCheckerDefaultArgs
-
-BugbearMutableLiterals = ("Dict", "List", "Set")
-BugbearMutableComprehensions = ("ListComp", "DictComp", "SetComp")
-BugbearMutableCalls = (
-    "Counter",
-    "OrderedDict",
-    "collections.Counter",
-    "collections.OrderedDict",
-    "collections.defaultdict",
-    "collections.deque",
-    "defaultdict",
-    "deque",
-    "dict",
-    "list",
-    "set",
-)
-BugbearImmutableCalls = (
-    "tuple",
-    "frozenset",
-    "types.MappingProxyType",
-    "MappingProxyType",
-    "re.compile",
-    "operator.attrgetter",
-    "operator.itemgetter",
-    "operator.methodcaller",
-    "attrgetter",
-    "itemgetter",
-    "methodcaller",
-)
+from .ReturnVisitor import ReturnVisitor
+from .SysVersionVisitor import SysVersionVisitor
+from .TextVisitor import TextVisitor
 
 
-def composeCallPath(node):
-    """
-    Generator function to assemble the call path of a given node.
-
-    @param node node to assemble call path for
-    @type ast.Node
-    @yield call path components
-    @ytype str
-    """
-    if isinstance(node, ast.Attribute):
-        yield from composeCallPath(node.value)
-        yield node.attr
-    elif isinstance(node, ast.Call):
-        yield from composeCallPath(node.func)
-    elif isinstance(node, ast.Name):
-        yield node.id
-
-
-class MiscellaneousChecker:
+class MiscellaneousChecker(CodeStyleTopicChecker):
     """
     Class implementing a checker for miscellaneous checks.
     """
@@ -234,7 +191,7 @@
         "M-801",
         ## one element tuple
         "M-811",
-        ## return statements
+        ## return statements  # noqa: M-891
         "M-831",
         "M-832",
         "M-833",
@@ -251,6 +208,7 @@
         "M-901",
         "M-902",
     ]
+    Category = "M"
 
     Formatter = Formatter()
     FormatFieldRegex = re.compile(r"^((?:\s|.)*?)(\..*|\[.*\])?$")
@@ -282,16 +240,19 @@
         @param args dictionary of arguments for the miscellaneous checks
         @type dict
         """
-        self.__select = tuple(select)
-        self.__ignore = tuple(ignore)
-        self.__expected = expected[:]
-        self.__repeat = repeat
-        self.__filename = filename
-        self.__source = source[:]
-        self.__tree = copy.deepcopy(tree)
-        self.__args = args
+        super().__init__(
+            MiscellaneousChecker.Category,
+            source,
+            filename,
+            tree,
+            select,
+            ignore,
+            expected,
+            repeat,
+            args,
+        )
 
-        linesIterator = iter(self.__source)
+        linesIterator = iter(self.source)
         self.__tokens = list(tokenize.generate_tokens(lambda: next(linesIterator)))
 
         self.__pep3101FormatRegex = re.compile(
@@ -302,12 +263,6 @@
 
         self.__eradicator = Eradicator()
 
-        # statistics counters
-        self.counters = {}
-
-        # collection of detected errors
-        self.errors = []
-
         checkersWithCodes = [
             (self.__checkCoding, ("M-101", "M-102")),
             (self.__checkCopyright, ("M-111", "M-112")),
@@ -466,9 +421,10 @@
             (self.__checkCommentedCode, ("M-891",)),
             (self.__checkDefaultMatchCase, ("M-901", "M-902")),
         ]
+        self._initializeCheckers(checkersWithCodes)
 
         # the eradicate whitelist
-        commentedCodeCheckerArgs = self.__args.get(
+        commentedCodeCheckerArgs = self.args.get(
             "CommentedCodeChecker",
             MiscellaneousCheckerDefaultArgs["CommentedCodeChecker"],
         )
@@ -480,77 +436,6 @@
             commentedCodeCheckerWhitelist, extend_default=False
         )
 
-        self.__checkers = []
-        for checker, codes in checkersWithCodes:
-            if any(not (code and self.__ignoreCode(code)) for code in codes):
-                self.__checkers.append(checker)
-
-    def __ignoreCode(self, code):
-        """
-        Private method to check if the message code should be ignored.
-
-        @param code message code to check for
-        @type str
-        @return flag indicating to ignore the given code
-        @rtype bool
-        """
-        return code in self.__ignore or (
-            code.startswith(self.__ignore) and not code.startswith(self.__select)
-        )
-
-    def __error(self, lineNumber, offset, code, *args):
-        """
-        Private method to record an issue.
-
-        @param lineNumber line number of the issue
-        @type int
-        @param offset position within line of the issue
-        @type int
-        @param code message code
-        @type str
-        @param args arguments for the message
-        @type list
-        """
-        if self.__ignoreCode(code):
-            return
-
-        if code in self.counters:
-            self.counters[code] += 1
-        else:
-            self.counters[code] = 1
-
-        # Don't care about expected codes
-        if code in self.__expected:
-            return
-
-        if code and (self.counters[code] == 1 or self.__repeat):
-            # record the issue with one based line number
-            self.errors.append(
-                {
-                    "file": self.__filename,
-                    "line": lineNumber + 1,
-                    "offset": offset,
-                    "code": code,
-                    "args": args,
-                }
-            )
-
-    def run(self):
-        """
-        Public method to check the given source against miscellaneous
-        conditions.
-        """
-        if not self.__filename:
-            # don't do anything, if essential data is missing
-            return
-
-        if not self.__checkers:
-            # don't do anything, if no codes were selected
-            return
-
-        for check in self.__checkers:
-            check()
-
     def __getCoding(self):
         """
         Private method to get the defined coding of the source.
@@ -558,7 +443,7 @@
         @return tuple containing the line number and the coding
         @rtype tuple of int and str
         """
-        for lineno, line in enumerate(self.__source[:5]):
+        for lineno, line in enumerate(self.source[:5], start=1):
             matched = re.search(r"coding[:=]\s*([-\w_.]+)", line, re.IGNORECASE)
             if matched:
                 return lineno, matched.group(1)
@@ -570,28 +455,28 @@
         Private method to check the presence of a coding line and valid
         encodings.
         """
-        if len(self.__source) == 0:
+        if len(self.source) == 0:
             return
 
         encodings = [
             e.lower().strip()
-            for e in self.__args.get(
+            for e in self.args.get(
                 "CodingChecker", MiscellaneousCheckerDefaultArgs["CodingChecker"]
             ).split(",")
         ]
         lineno, coding = self.__getCoding()
         if coding:
             if coding.lower() not in encodings:
-                self.__error(lineno, 0, "M-102", coding)
+                self.addError(lineno, 0, "M-102", coding)
         else:
-            self.__error(0, 0, "M-101")
+            self.addError(1, 0, "M-101")
 
     def __checkCopyright(self):
         """
         Private method to check the presence of a copyright statement.
         """
-        source = "".join(self.__source)
-        copyrightArgs = self.__args.get(
+        source = "".join(self.source)
+        copyrightArgs = self.args.get(
             "CopyrightChecker", MiscellaneousCheckerDefaultArgs["CopyrightChecker"]
         )
         copyrightMinFileSize = copyrightArgs.get(
@@ -612,7 +497,7 @@
 
         copyrightRe = re.compile(copyrightRegexStr.format(author=r".*"), re.IGNORECASE)
         if not copyrightRe.search(topOfSource):
-            self.__error(0, 0, "M-111")
+            self.addError(1, 0, "M-111")
             return
 
         if copyrightAuthor:
@@ -620,14 +505,14 @@
                 copyrightRegexStr.format(author=copyrightAuthor), re.IGNORECASE
             )
             if not copyrightAuthorRe.search(topOfSource):
-                self.__error(0, 0, "M-112")
+                self.addError(1, 0, "M-112")
 
     def __checkCommentedCode(self):
         """
         Private method to check for commented code.
         """
-        source = "".join(self.__source)
-        commentedCodeCheckerArgs = self.__args.get(
+        source = "".join(self.source)
+        commentedCodeCheckerArgs = self.args.get(
             "CommentedCodeChecker",
             MiscellaneousCheckerDefaultArgs["CommentedCodeChecker"],
         )
@@ -638,7 +523,7 @@
         for markedLine in self.__eradicator.commented_out_code_line_numbers(
             source, aggressive=aggressive
         ):
-            self.__error(markedLine - 1, 0, "M-891")
+            self.addError(markedLine, 0, "M-891")
 
     def __checkLineContinuation(self):
         """
@@ -646,7 +531,7 @@
         """
         # generate source lines without comments
         comments = [tok for tok in self.__tokens if tok[0] == tokenize.COMMENT]
-        stripped = self.__source[:]
+        stripped = self.source[:]
         for comment in comments:
             lineno = comment[3][0]
             start = comment[2][1]
@@ -661,25 +546,25 @@
             if strippedLine.endswith("\\") and not strippedLine.startswith(
                 ("assert", "with")
             ):
-                self.__error(lineIndex, len(line), "M-841")
+                self.addError(lineIndex + 1, len(line), "M-841")
 
     def __checkPrintStatements(self):
         """
         Private method to check for print statements.
         """
-        for node in ast.walk(self.__tree):
+        for node in ast.walk(self.tree):
             if (
                 isinstance(node, ast.Call) and getattr(node.func, "id", None) == "print"
             ) or (hasattr(ast, "Print") and isinstance(node, ast.Print)):
-                self.__error(node.lineno - 1, node.col_offset, "M-801")
+                self.addErrorFromNode(node, "M-801")
 
     def __checkTuple(self):
         """
         Private method to check for one element tuples.
         """
-        for node in ast.walk(self.__tree):
+        for node in ast.walk(self.tree):
             if isinstance(node, ast.Tuple) and len(node.elts) == 1:
-                self.__error(node.lineno - 1, node.col_offset, "M-811")
+                self.addErrorFromNode(node, "M-811")
 
     def __checkFuture(self):
         """
@@ -687,7 +572,7 @@
         """
         expectedImports = {
             i.strip()
-            for i in self.__args.get("FutureChecker", "").split(",")
+            for i in self.args.get("FutureChecker", "").split(",")
             if bool(i.strip())
         }
         if len(expectedImports) == 0:
@@ -698,7 +583,7 @@
         node = None
         hasCode = False
 
-        for node in ast.walk(self.__tree):
+        for node in ast.walk(self.tree):
             if isinstance(node, ast.ImportFrom) and node.module == "__future__":
                 imports |= {name.name for name in node.names}
             elif isinstance(node, ast.Expr):
@@ -714,26 +599,17 @@
 
         if imports < expectedImports:
             if imports:
-                self.__error(
-                    node.lineno - 1,
-                    node.col_offset,
-                    "M-701",
-                    ", ".join(expectedImports),
-                    ", ".join(imports),
+                self.addErrorFromNode(
+                    node, "M-701", ", ".join(expectedImports), ", ".join(imports)
                 )
             else:
-                self.__error(
-                    node.lineno - 1,
-                    node.col_offset,
-                    "M-702",
-                    ", ".join(expectedImports),
-                )
+                self.addErrorFromNode(node, "M-702", ", ".join(expectedImports))
 
     def __checkPep3101(self):
         """
         Private method to check for old style string formatting.
         """
-        for lineno, line in enumerate(self.__source):
+        for lineno, line in enumerate(self.source, start=1):
             match = self.__pep3101FormatRegex.search(line)
             if match:
                 lineLen = len(line)
@@ -750,7 +626,7 @@
                     c = line[pos]
                 if c in "diouxXeEfFgGcrs":
                     formatter += c
-                self.__error(lineno, formatPos, "M-601", formatter)
+                self.addError(lineno, formatPos, "M-601", formatter)
 
     def __checkFormatString(self):
         """
@@ -762,7 +638,7 @@
             coding = "utf-8"
 
         visitor = TextVisitor()
-        visitor.visit(self.__tree)
+        visitor.visit(self.tree)
         for node in visitor.nodes:
             text = node.value
             if isinstance(text, bytes):
@@ -773,12 +649,12 @@
             fields, implicit, explicit = self.__getFields(text)
             if implicit:
                 if node in visitor.calls:
-                    self.__error(node.lineno - 1, node.col_offset, "M-611")
+                    self.addErrorFromNode(node, "M-611")
                 else:
                     if node.is_docstring:
-                        self.__error(node.lineno - 1, node.col_offset, "M-612")
+                        self.addErrorFromNode(node, "M-612")
                     else:
-                        self.__error(node.lineno - 1, node.col_offset, "M-613")
+                        self.addErrorFromNode(node, "M-613")
 
             if node in visitor.calls:
                 call, strArgs = visitor.calls[node]
@@ -798,7 +674,7 @@
                     else:
                         names.add(fieldMatch.group(1))
 
-                keywords = {keyword.arg for keyword in call.keywords}
+                keywords = {kw.arg for kw in call.keywords}
                 numArgs = len(call.args)
                 if strArgs:
                     numArgs -= 1
@@ -816,35 +692,31 @@
                 # parameters but at least check if the args are used
                 if hasKwArgs and not names:
                     # No names but kwargs
-                    self.__error(call.lineno - 1, call.col_offset, "M-623")
+                    self.addErrorFromNode(call, "M-623")
                 if hasStarArgs and not numbers:
                     # No numbers but args
-                    self.__error(call.lineno - 1, call.col_offset, "M-624")
+                    self.addErrorFromNode(call, "M-624")
 
                 if not hasKwArgs and not hasStarArgs:
                     # can actually verify numbers and names
                     for number in sorted(numbers):
                         if number >= numArgs:
-                            self.__error(
-                                call.lineno - 1, call.col_offset, "M-621", number
-                            )
+                            self.addErrorFromNode(call, "M-621", number)
 
                     for name in sorted(names):
                         if name not in keywords:
-                            self.__error(
-                                call.lineno - 1, call.col_offset, "M-622", name
-                            )
+                            self.addErrorFromNode(call, "M-622", name)
 
                 for arg in range(numArgs):
                     if arg not in numbers:
-                        self.__error(call.lineno - 1, call.col_offset, "M-631", arg)
+                        self.addErrorFromNode(call, "M-631", arg)
 
-                for keyword in keywords:
-                    if keyword not in names:
-                        self.__error(call.lineno - 1, call.col_offset, "M-632", keyword)
+                for kw in keywords:
+                    if kw not in names:
+                        self.addErrorFromNode(call, "M-632", kw)
 
                 if implicit and explicit:
-                    self.__error(call.lineno - 1, call.col_offset, "M-625")
+                    self.addErrorFromNode(call, "M-625")
 
     def __getFields(self, string):
         """
@@ -887,11 +759,11 @@
         with contextlib.suppress(AttributeError):
             functionDefs.append(ast.AsyncFunctionDef)
 
-        ignoreBuiltinAssignments = self.__args.get(
+        ignoreBuiltinAssignments = self.args.get(
             "BuiltinsChecker", MiscellaneousCheckerDefaultArgs["BuiltinsChecker"]
         )
 
-        for node in ast.walk(self.__tree):
+        for node in ast.walk(self.tree):
             if isinstance(node, ast.Assign):
                 # assign statement
                 for element in node.targets:
@@ -904,45 +776,33 @@
                         ):
                             # ignore compatibility assignments
                             continue
-                        self.__error(
-                            element.lineno - 1, element.col_offset, "M-131", element.id
-                        )
+                        self.addErrorFromNode(element, "M-131", element.id)
                     elif isinstance(element, (ast.Tuple, ast.List)):
                         for tupleElement in element.elts:
                             if (
                                 isinstance(tupleElement, ast.Name)
                                 and tupleElement.id in self.__builtins
                             ):
-                                self.__error(
-                                    tupleElement.lineno - 1,
-                                    tupleElement.col_offset,
-                                    "M-131",
-                                    tupleElement.id,
+                                self.addErrorFromNode(
+                                    tupleElement, "M-131", tupleElement.id
                                 )
             elif isinstance(node, ast.For):
                 # for loop
                 target = node.target
                 if isinstance(target, ast.Name) and target.id in self.__builtins:
-                    self.__error(
-                        target.lineno - 1, target.col_offset, "M-131", target.id
-                    )
+                    self.addErrorFromNode(target, "M-131", target.id)
                 elif isinstance(target, (ast.Tuple, ast.List)):
                     for element in target.elts:
                         if (
                             isinstance(element, ast.Name)
                             and element.id in self.__builtins
                         ):
-                            self.__error(
-                                element.lineno - 1,
-                                element.col_offset,
-                                "M-131",
-                                element.id,
-                            )
+                            self.addErrorFromNode(element, "M-131", element.id)
             elif any(isinstance(node, functionDef) for functionDef in functionDefs):
                 # (asynchronous) function definition
                 for arg in node.args.args:
                     if isinstance(arg, ast.arg) and arg.arg in self.__builtins:
-                        self.__error(arg.lineno - 1, arg.col_offset, "M-132", arg.arg)
+                        self.addErrorFromNode(arg, "M-132", arg.arg)
 
     def __checkComprehensions(self):
         """
@@ -959,7 +819,7 @@
 
         visitedMapCalls = set()
 
-        for node in ast.walk(self.__tree):
+        for node in ast.walk(self.tree):
             if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
                 numPositionalArgs = len(node.args)
                 numKeywordArgs = len(node.keywords)
@@ -973,7 +833,7 @@
                         "list": "M-180",
                         "set": "M-181",
                     }[node.func.id]
-                    self.__error(node.lineno - 1, node.col_offset, errorCode)
+                    self.addErrorFromNode(node, errorCode)
 
                 elif (
                     numPositionalArgs == 1
@@ -987,7 +847,7 @@
                         errorCode = "M-182"
                     else:
                         errorCode = "M-184"
-                    self.__error(node.lineno - 1, node.col_offset, errorCode)
+                    self.addErrorFromNode(node, errorCode)
 
                 elif (
                     numPositionalArgs == 1
@@ -1000,9 +860,7 @@
                         "any": "M-199",
                         "all": "M-199",
                     }[node.func.id]
-                    self.__error(
-                        node.lineno - 1, node.col_offset, errorCode, node.func.id
-                    )
+                    self.addErrorFromNode(node, errorCode, node.func.id)
 
                 elif numPositionalArgs == 1 and (
                     isinstance(node.args[0], ast.Tuple)
@@ -1014,9 +872,8 @@
                         "tuple": "M-189a",
                         "list": "M-190a",
                     }[node.func.id]
-                    self.__error(
-                        node.lineno - 1,
-                        node.col_offset,
+                    self.addErrorFromNode(
+                        node,
                         errorCode,
                         type(node.args[0]).__name__.lower(),
                         node.func.id,
@@ -1032,12 +889,7 @@
                         type_ = "dict"
                     else:
                         type_ = "dict comprehension"
-                    self.__error(
-                        node.lineno - 1,
-                        node.col_offset,
-                        "M-198",
-                        type_,
-                    )
+                    self.addErrorFromNode(node, "M-198", type_)
 
                 elif (
                     numPositionalArgs == 1
@@ -1059,9 +911,8 @@
                         "set": "M-185",
                         "dict": "M-186",
                     }[node.func.id]
-                    self.__error(
-                        node.lineno - 1,
-                        node.col_offset,
+                    self.addErrorFromNode(
+                        node,
                         errorCode,
                         type(node.args[0]).__name__.lower(),
                         node.func.id,
@@ -1077,9 +928,7 @@
                     and numKeywordArgs == 0
                     and node.func.id in ("tuple", "list")
                 ):
-                    self.__error(
-                        node.lineno - 1, node.col_offset, "M-188", node.func.id
-                    )
+                    self.addErrorFromNode(node, "M-188", node.func.id)
 
                 elif (
                     node.func.id in {"list", "reversed"}
@@ -1100,17 +949,12 @@
                             )
 
                         if reverseFlagValue is None:
-                            self.__error(
-                                node.lineno - 1,
-                                node.col_offset,
-                                "M-193a",
-                                node.func.id,
-                                node.args[0].func.id,
+                            self.addErrorFromNode(
+                                node, "M-193a", node.func.id, node.args[0].func.id
                             )
                         else:
-                            self.__error(
-                                node.lineno - 1,
-                                node.col_offset,
+                            self.addErrorFromNode(
+                                node,
                                 "M-193b",
                                 node.func.id,
                                 node.args[0].func.id,
@@ -1118,12 +962,8 @@
                             )
 
                     else:
-                        self.__error(
-                            node.lineno - 1,
-                            node.col_offset,
-                            "M-193c",
-                            node.func.id,
-                            node.args[0].func.id,
+                        self.addErrorFromNode(
+                            node, "M-193c", node.func.id, node.args[0].func.id
                         )
 
                 elif (
@@ -1143,12 +983,8 @@
                         or (node.func.id == "set" and node.args[0].func.id == "set")
                     )
                 ):
-                    self.__error(
-                        node.lineno - 1,
-                        node.col_offset,
-                        "M-194",
-                        node.args[0].func.id,
-                        node.func.id,
+                    self.addErrorFromNode(
+                        node, "M-194", node.args[0].func.id, node.func.id
                     )
 
                 elif (
@@ -1163,9 +999,7 @@
                     and isinstance(node.args[0].slice.step.operand, ast.Constant)
                     and node.args[0].slice.step.operand.n == 1
                 ):
-                    self.__error(
-                        node.lineno - 1, node.col_offset, "M-195", node.func.id
-                    )
+                    self.addErrorFromNode(node, "M-195", node.func.id)
 
                 elif (
                     node.func.id == "map"
@@ -1173,12 +1007,7 @@
                     and len(node.args) == 2
                     and isinstance(node.args[0], ast.Lambda)
                 ):
-                    self.__error(
-                        node.lineno - 1,
-                        node.col_offset,
-                        "M-197",
-                        "generator expression",
-                    )
+                    self.addErrorFromNode(node, "M-197", "generator expression")
 
                 elif (
                     node.func.id in ("list", "set", "dict")
@@ -1206,9 +1035,7 @@
 
                     if rewriteable:
                         comprehensionType = f"{node.func.id} comprehension"
-                        self.__error(
-                            node.lineno - 1, node.col_offset, "M-197", comprehensionType
-                        )
+                        self.addErrorFromNode(node, "M-197", comprehensionType)
 
             elif isinstance(node, (ast.DictComp, ast.ListComp, ast.SetComp)) and (
                 len(node.generators) == 1
@@ -1231,12 +1058,7 @@
                     and isinstance(node.generators[0].target.elts[1], ast.Name)
                     and node.generators[0].target.elts[1].id == node.value.id
                 ):
-                    self.__error(
-                        node.lineno - 1,
-                        node.col_offset,
-                        "M-196",
-                        compType[node.__class__],
-                    )
+                    self.addErrorFromNode(node, "M-196", compType[node.__class__])
 
                 elif (
                     isinstance(node, ast.DictComp)
@@ -1245,12 +1067,7 @@
                     and isinstance(node.generators[0].target, ast.Name)
                     and node.key.id == node.generators[0].target.id
                 ):
-                    self.__error(
-                        node.lineno - 1,
-                        node.col_offset,
-                        "M-200",
-                        compType[node.__class__],
-                    )
+                    self.addErrorFromNode(node, "M-200", compType[node.__class__])
 
     def __dictShouldBeChecked(self, node):
         """
@@ -1265,8 +1082,9 @@
             return False
 
         if (
-            "__IGNORE_WARNING__" in self.__source[node.lineno - 1]
-            or "__IGNORE_WARNING_M251__" in self.__source[node.lineno - 1]
+            "__IGNORE_WARNING__" in self.source[node.lineno - 1]
+            or "__IGNORE_WARNING_M-251__" in self.source[node.lineno - 1]
+            or "noqa: M-251" in self.source[node.lineno - 1]
         ):
             return False
 
@@ -1277,52 +1095,39 @@
         """
         Private method to check, if dictionary keys appear in sorted order.
         """
-        for node in ast.walk(self.__tree):
+        for node in ast.walk(self.tree):
             if isinstance(node, ast.Dict) and self.__dictShouldBeChecked(node):
                 for key1, key2 in zip(node.keys, node.keys[1:]):
                     if key2.value < key1.value:
-                        self.__error(
-                            key2.lineno - 1,
-                            key2.col_offset,
-                            "M-251",
-                            key2.value,
-                            key1.value,
-                        )
+                        self.addErrorFromNode(key2, "M-251", key2.value, key1.value)
 
     def __checkGettext(self):
         """
         Private method to check the 'gettext' import statement.
         """
-        for node in ast.walk(self.__tree):
+        for node in ast.walk(self.tree):
             if isinstance(node, ast.ImportFrom) and any(
                 name.asname == "_" for name in node.names
             ):
-                self.__error(
-                    node.lineno - 1, node.col_offset, "M-711", node.names[0].name
-                )
+                self.addErrorFromNode(node, "M-711", node.names[0].name)
 
     def __checkBugBear(self):
         """
         Private method for bugbear checks.
         """
         visitor = BugBearVisitor()
-        visitor.visit(self.__tree)
+        visitor.visit(self.tree)
         for violation in visitor.violations:
-            node = violation[0]
-            reason = violation[1]
-            params = violation[2:]
-            self.__error(node.lineno - 1, node.col_offset, reason, *params)
+            self.addErrorFromNode(*violation)
 
     def __checkReturn(self):
         """
         Private method to check return statements.
         """
         visitor = ReturnVisitor()
-        visitor.visit(self.__tree)
+        visitor.visit(self.tree)
         for violation in visitor.violations:
-            node = violation[0]
-            reason = violation[1]
-            self.__error(node.lineno - 1, node.col_offset, reason)
+            self.addErrorFromNode(*violation)
 
     def __checkDateTime(self):
         """
@@ -1330,7 +1135,7 @@
         """
         # step 1: generate an augmented node tree containing parent info
         #         for each child node
-        tree = copy.deepcopy(self.__tree)
+        tree = copy.deepcopy(self.tree)
         for node in ast.walk(tree):
             for childNode in ast.iter_child_nodes(node):
                 childNode._dtCheckerParent = node
@@ -1339,27 +1144,23 @@
         visitor = DateTimeVisitor()
         visitor.visit(tree)
         for violation in visitor.violations:
-            node = violation[0]
-            reason = violation[1]
-            self.__error(node.lineno - 1, node.col_offset, reason)
+            self.addErrorFromNode(*violation)
 
     def __checkSysVersion(self):
         """
         Private method to check the use of sys.version and sys.version_info.
         """
         visitor = SysVersionVisitor()
-        visitor.visit(self.__tree)
+        visitor.visit(self.tree)
         for violation in visitor.violations:
-            node = violation[0]
-            reason = violation[1]
-            self.__error(node.lineno - 1, node.col_offset, reason)
+            self.addErrorFromNode(*violation)
 
     def __checkProperties(self):
         """
         Private method to check for issue with property related methods.
         """
         properties = []
-        for node in ast.walk(self.__tree):
+        for node in ast.walk(self.tree):
             if isinstance(node, ast.ClassDef):
                 properties.clear()
 
@@ -1371,12 +1172,7 @@
                         propertyCount += 1
                         properties.append(node.name)
                         if len(node.args.args) != 1:
-                            self.__error(
-                                node.lineno - 1,
-                                node.col_offset,
-                                "M-260",
-                                len(node.args.args),
-                            )
+                            self.addErrorFromNode(node, "M-260", len(node.args.args))
 
                     if isinstance(decorator, ast.Attribute):
                         # property setter method
@@ -1384,27 +1180,16 @@
                             propertyCount += 1
                             if node.name != decorator.value.id:
                                 if node.name in properties:
-                                    self.__error(
-                                        node.lineno - 1,
-                                        node.col_offset,
-                                        "M-265",
-                                        node.name,
-                                        decorator.value.id,
+                                    self.addErrorFromNode(
+                                        node, "M-265", node.name, decorator.value.id
                                     )
                                 else:
-                                    self.__error(
-                                        node.lineno - 1,
-                                        node.col_offset,
-                                        "M-263",
-                                        decorator.value.id,
-                                        node.name,
+                                    self.addErrorFromNode(
+                                        node, "M-263", decorator.value.id, node.name
                                     )
                             if len(node.args.args) != 2:
-                                self.__error(
-                                    node.lineno - 1,
-                                    node.col_offset,
-                                    "M-261",
-                                    len(node.args.args),
+                                self.addErrorFromNode(
+                                    node, "M-261", len(node.args.args)
                                 )
 
                         # property deleter method
@@ -1412,31 +1197,20 @@
                             propertyCount += 1
                             if node.name != decorator.value.id:
                                 if node.name in properties:
-                                    self.__error(
-                                        node.lineno - 1,
-                                        node.col_offset,
-                                        "M-266",
-                                        node.name,
-                                        decorator.value.id,
+                                    self.addErrorFromNode(
+                                        node, "M-266", node.name, decorator.value.id
                                     )
                                 else:
-                                    self.__error(
-                                        node.lineno - 1,
-                                        node.col_offset,
-                                        "M-264",
-                                        decorator.value.id,
-                                        node.name,
+                                    self.addErrorFromNode(
+                                        node, "M-264", decorator.value.id, node.name
                                     )
                             if len(node.args.args) != 1:
-                                self.__error(
-                                    node.lineno - 1,
-                                    node.col_offset,
-                                    "M-262",
-                                    len(node.args.args),
+                                self.addErrorFromNode(
+                                    node, "M-262", len(node.args.args)
                                 )
 
                 if propertyCount > 1:
-                    self.__error(node.lineno - 1, node.col_offset, "M-267", node.name)
+                    self.addErrorFromNode(node, "M-267", node.name)
 
     #######################################################################
     ## The following methods check for implicitly concatenated strings.
@@ -1509,8 +1283,8 @@
         )
         for a, b in pairwise(tokensWithoutWhitespace):
             if self.__isImplicitStringConcat(a, b):
-                self.__error(
-                    a.end[0] - 1,
+                self.addError(
+                    a.end[0],
                     a.end[1],
                     "M-851" if a.end[0] == b.start[0] else "M-852",
                 )
@@ -1519,7 +1293,7 @@
         """
         Private method to check for explicitly concatenated strings.
         """
-        for node in ast.walk(self.__tree):
+        for node in ast.walk(self.tree):
             if (
                 isinstance(node, ast.BinOp)
                 and isinstance(node.op, ast.Add)
@@ -1529,7 +1303,7 @@
                     for operand in (node.left, node.right)
                 )
             ):
-                self.__error(node.lineno - 1, node.col_offset, "M-853")
+                self.addErrorFromNode(node, "M-853")
 
     #################################################################################
     ## The following method checks default match cases.
@@ -1540,3038 +1314,6 @@
         Private method to check the default match case.
         """
         visitor = DefaultMatchCaseVisitor()
-        visitor.visit(self.__tree)
+        visitor.visit(self.tree)
         for violation in visitor.violations:
-            node = violation[0]
-            reason = violation[1]
-            self.__error(node.lineno - 1, node.col_offset, reason)
-
-
-class TextVisitor(ast.NodeVisitor):
-    """
-    Class implementing a node visitor for bytes and str instances.
-
-    It tries to detect docstrings as string of the first expression of each
-    module, class or function.
-    """
-
-    # modeled after the string format flake8 extension
-
-    def __init__(self):
-        """
-        Constructor
-        """
-        super().__init__()
-        self.nodes = []
-        self.calls = {}
-
-    def __addNode(self, node):
-        """
-        Private method to add a node to our list of nodes.
-
-        @param node reference to the node to add
-        @type ast.AST
-        """
-        if not hasattr(node, "is_docstring"):
-            node.is_docstring = False
-        self.nodes.append(node)
-
-    def visit_Constant(self, node):
-        """
-        Public method to handle constant nodes.
-
-        @param node reference to the bytes node
-        @type ast.Constant
-        """
-        if AstUtilities.isBaseString(node):
-            self.__addNode(node)
-        else:
-            super().generic_visit(node)
-
-    def __visitDefinition(self, node):
-        """
-        Private method handling class and function definitions.
-
-        @param node reference to the node to handle
-        @type ast.FunctionDef, ast.AsyncFunctionDef or ast.ClassDef
-        """
-        # Manually traverse class or function definition
-        # * Handle decorators normally
-        # * Use special check for body content
-        # * Don't handle the rest (e.g. bases)
-        for decorator in node.decorator_list:
-            self.visit(decorator)
-        self.__visitBody(node)
-
-    def __visitBody(self, node):
-        """
-        Private method to traverse the body of the node manually.
-
-        If the first node is an expression which contains a string or bytes it
-        marks that as a docstring.
-
-        @param node reference to the node to traverse
-        @type ast.AST
-        """
-        if (
-            node.body
-            and isinstance(node.body[0], ast.Expr)
-            and AstUtilities.isBaseString(node.body[0].value)
-        ):
-            node.body[0].value.is_docstring = True
-
-        for subnode in node.body:
-            self.visit(subnode)
-
-    def visit_Module(self, node):
-        """
-        Public method to handle a module.
-
-        @param node reference to the node to handle
-        @type ast.Module
-        """
-        self.__visitBody(node)
-
-    def visit_ClassDef(self, node):
-        """
-        Public method to handle a class definition.
-
-        @param node reference to the node to handle
-        @type ast.ClassDef
-        """
-        # Skipped nodes: ('name', 'bases', 'keywords', 'starargs', 'kwargs')
-        self.__visitDefinition(node)
-
-    def visit_FunctionDef(self, node):
-        """
-        Public method to handle a function definition.
-
-        @param node reference to the node to handle
-        @type ast.FunctionDef
-        """
-        # Skipped nodes: ('name', 'args', 'returns')
-        self.__visitDefinition(node)
-
-    def visit_AsyncFunctionDef(self, node):
-        """
-        Public method to handle an asynchronous function definition.
-
-        @param node reference to the node to handle
-        @type ast.AsyncFunctionDef
-        """
-        # Skipped nodes: ('name', 'args', 'returns')
-        self.__visitDefinition(node)
-
-    def visit_Call(self, node):
-        """
-        Public method to handle a function call.
-
-        @param node reference to the node to handle
-        @type ast.Call
-        """
-        if isinstance(node.func, ast.Attribute) and node.func.attr == "format":
-            if AstUtilities.isBaseString(node.func.value):
-                self.calls[node.func.value] = (node, False)
-            elif (
-                isinstance(node.func.value, ast.Name)
-                and node.func.value.id == "str"
-                and node.args
-                and AstUtilities.isBaseString(node.args[0])
-            ):
-                self.calls[node.args[0]] = (node, True)
-        super().generic_visit(node)
-
-
-#######################################################################
-## BugBearVisitor
-##
-## adapted from: flake8-bugbear v24.12.12
-##
-## Original: Copyright (c) 2016 Łukasz Langa
-#######################################################################
-
-BugBearContext = namedtuple("BugBearContext", ["node", "stack"])
-
-
-@dataclass
-class M540CaughtException:
-    """
-    Class to hold the data for a caught exception.
-    """
-
-    name: str
-    hasNote: bool
-
-
-class M541UnhandledKeyType:
-    """
-    Class to hold a dictionary key of a type that we do not check for duplicates.
-    """
-
-
-class M541VariableKeyType:
-    """
-    Class to hold the name of a variable key type.
-    """
-
-    def __init__(self, name):
-        """
-        Constructor
-
-        @param name name of the variable key type
-        @type str
-        """
-        self.name = name
-
-
-class BugBearVisitor(ast.NodeVisitor):
-    """
-    Class implementing a node visitor to check for various topics.
-    """
-
-    CONTEXTFUL_NODES = (
-        ast.Module,
-        ast.ClassDef,
-        ast.AsyncFunctionDef,
-        ast.FunctionDef,
-        ast.Lambda,
-        ast.ListComp,
-        ast.SetComp,
-        ast.DictComp,
-        ast.GeneratorExp,
-    )
-
-    FUNCTION_NODES = (
-        ast.AsyncFunctionDef,
-        ast.FunctionDef,
-        ast.Lambda,
-    )
-
-    NodeWindowSize = 4
-
-    def __init__(self):
-        """
-        Constructor
-        """
-        super().__init__()
-
-        self.nodeWindow = []
-        self.violations = []
-        self.contexts = []
-
-        self.__M523Seen = set()
-        self.__M505Imports = set()
-        self.__M540CaughtException = None
-
-        self.__inTryStar = ""
-
-    @property
-    def nodeStack(self):
-        """
-        Public method to get a reference to the most recent node stack.
-
-        @return reference to the most recent node stack
-        @rtype list
-        """
-        if len(self.contexts) == 0:
-            return []
-
-        context, stack = self.contexts[-1]
-        return stack
-
-    def __isIdentifier(self, arg):
-        """
-        Private method to check if arg is a valid identifier.
-
-        See https://docs.python.org/2/reference/lexical_analysis.html#identifiers
-
-        @param arg reference to an argument node
-        @type ast.Node
-        @return flag indicating a valid identifier
-        @rtype TYPE
-        """
-        if not AstUtilities.isString(arg):
-            return False
-
-        return (
-            re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", AstUtilities.getValue(arg))
-            is not None
-        )
-
-    def toNameStr(self, node):
-        """
-        Public method to turn Name and Attribute nodes to strings, handling any
-        depth of attribute accesses.
-
-
-        @param node reference to the node
-        @type ast.Name or ast.Attribute
-        @return string representation
-        @rtype str
-        """
-        if isinstance(node, ast.Name):
-            return node.id
-        elif isinstance(node, ast.Call):
-            return self.toNameStr(node.func)
-        elif isinstance(node, ast.Attribute):
-            inner = self.toNameStr(node.value)
-            if inner is None:
-                return None
-            return f"{inner}.{node.attr}"
-        else:
-            return None
-
-    def __typesafeIssubclass(self, obj, classOrTuple):
-        """
-        Private method implementing a type safe issubclass() function.
-
-        @param obj reference to the object to be tested
-        @type Any
-        @param classOrTuple type to check against
-        @type type
-        @return flag indicating a subclass
-        @rtype bool
-        """
-        try:
-            return issubclass(obj, classOrTuple)
-        except TypeError:
-            # User code specifies a type that is not a type in our current run.
-            # Might be their error, might be a difference in our environments.
-            # We don't know so we ignore this.
-            return False
-
-    def __getAssignedNames(self, loopNode):
-        """
-        Private method to get the names of a for loop.
-
-        @param loopNode reference to the node to be processed
-        @type ast.For
-        @yield DESCRIPTION
-        @ytype TYPE
-        """
-        loopTargets = (ast.For, ast.AsyncFor, ast.comprehension)
-        for node in self.__childrenInScope(loopNode):
-            if isinstance(node, (ast.Assign)):
-                for child in node.targets:
-                    yield from self.__namesFromAssignments(child)
-            if isinstance(node, loopTargets + (ast.AnnAssign, ast.AugAssign)):
-                yield from self.__namesFromAssignments(node.target)
-
-    def __namesFromAssignments(self, assignTarget):
-        """
-        Private method to get names of an assignment.
-
-        @param assignTarget reference to the node to be processed
-        @type ast.Node
-        @yield name of the assignment
-        @ytype str
-        """
-        if isinstance(assignTarget, ast.Name):
-            yield assignTarget.id
-        elif isinstance(assignTarget, ast.Starred):
-            yield from self.__namesFromAssignments(assignTarget.value)
-        elif isinstance(assignTarget, (ast.List, ast.Tuple)):
-            for child in assignTarget.elts:
-                yield from self.__namesFromAssignments(child)
-
-    def __childrenInScope(self, node):
-        """
-        Private method to get all child nodes in the given scope.
-
-        @param node reference to the node to be processed
-        @type ast.Node
-        @yield reference to a child node
-        @ytype ast.Node
-        """
-        yield node
-        if not isinstance(node, BugBearVisitor.FUNCTION_NODES):
-            for child in ast.iter_child_nodes(node):
-                yield from self.__childrenInScope(child)
-
-    def __flattenExcepthandler(self, node):
-        """
-        Private method to flatten the list of exceptions handled by an except handler.
-
-        @param node reference to the node to be processed
-        @type ast.Node
-        @yield reference to the exception type node
-        @ytype ast.Node
-        """
-        if not isinstance(node, ast.Tuple):
-            yield node
-            return
-
-        exprList = node.elts.copy()
-        while len(exprList):
-            expr = exprList.pop(0)
-            if isinstance(expr, ast.Starred) and isinstance(
-                expr.value, (ast.List, ast.Tuple)
-            ):
-                exprList.extend(expr.value.elts)
-                continue
-            yield expr
-
-    def __checkRedundantExcepthandlers(self, names, node, inTryStar):
-        """
-        Private method to check for redundant exception types in an exception handler.
-
-        @param names list of exception types to be checked
-        @type list of ast.Name
-        @param node reference to the exception handler node
-        @type ast.ExceptionHandler
-        @param inTryStar character indicating an 'except*' handler
-        @type str
-        @return tuple containing the error data
-        @rtype tuple of (ast.Node, str, str, str, str)
-        """
-        redundantExceptions = {
-            "OSError": {
-                # All of these are actually aliases of OSError since Python 3.3
-                "IOError",
-                "EnvironmentError",
-                "WindowsError",
-                "mmap.error",
-                "socket.error",
-                "select.error",
-            },
-            "ValueError": {
-                "binascii.Error",
-            },
-        }
-
-        # 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
-        #    (IOError, OSError)  # IOError is an alias of OSError since Python3.3
-        # but note that other cases are impractical to handle 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"]
-        # 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 redundantExceptions.items():
-            if primary in good:
-                good = [g for g in good if g not in equivalents]
-
-        for name, other in itertools.permutations(tuple(good), 2):
-            if (
-                self.__typesafeIssubclass(
-                    getattr(builtins, name, type), getattr(builtins, other, ())
-                )
-                and name in good
-            ):
-                good.remove(name)
-        if good != names:
-            desc = good[0] if len(good) == 1 else "({0})".format(", ".join(good))
-            as_ = " as " + node.name if node.name is not None else ""
-            return (node, "M-514", ", ".join(names), as_, desc, inTryStar)
-
-        return None
-
-    def __walkList(self, nodes):
-        """
-        Private method to walk a given list of nodes.
-
-        @param nodes list of nodes to walk
-        @type list of ast.Node
-        @yield node references as determined by the ast.walk() function
-        @ytype ast.Node
-        """
-        for node in nodes:
-            yield from ast.walk(node)
-
-    def __getNamesFromTuple(self, node):
-        """
-        Private method to get the names from an ast.Tuple node.
-
-        @param node ast node to be processed
-        @type ast.Tuple
-        @yield names
-        @ytype str
-        """
-        for dim in node.elts:
-            if isinstance(dim, ast.Name):
-                yield dim.id
-            elif isinstance(dim, ast.Tuple):
-                yield from self.__getNamesFromTuple(dim)
-
-    def __getDictCompLoopAndNamedExprVarNames(self, node):
-        """
-        Private method to get the names of comprehension loop variables.
-
-        @param node ast node to be processed
-        @type ast.DictComp
-        @yield loop variable names
-        @ytype str
-        """
-        finder = NamedExprFinder()
-        for gen in node.generators:
-            if isinstance(gen.target, ast.Name):
-                yield gen.target.id
-            elif isinstance(gen.target, ast.Tuple):
-                yield from self.__getNamesFromTuple(gen.target)
-
-            finder.visit(gen.ifs)
-
-        yield from finder.getNames().keys()
-
-    def __inClassInit(self):
-        """
-        Private method to check, if we are inside an '__init__' method.
-
-        @return flag indicating being within the '__init__' method
-        @rtype bool
-        """
-        return (
-            len(self.contexts) >= 2
-            and isinstance(self.contexts[-2].node, ast.ClassDef)
-            and isinstance(self.contexts[-1].node, ast.FunctionDef)
-            and self.contexts[-1].node.name == "__init__"
-        )
-
-    def visit_Return(self, node):
-        """
-        Public method to handle 'Return' nodes.
-
-        @param node reference to the node to be processed
-        @type ast.Return
-        """
-        if self.__inClassInit() and node.value is not None:
-            self.violations.append((node, "M-537"))
-
-        self.generic_visit(node)
-
-    def visit_Yield(self, node):
-        """
-        Public method to handle 'Yield' nodes.
-
-        @param node reference to the node to be processed
-        @type ast.Yield
-        """
-        if self.__inClassInit():
-            self.violations.append((node, "M-537"))
-
-        self.generic_visit(node)
-
-    def visit_YieldFrom(self, node) -> None:
-        """
-        Public method to handle 'YieldFrom' nodes.
-
-        @param node reference to the node to be processed
-        @type ast.YieldFrom
-        """
-        if self.__inClassInit():
-            self.violations.append((node, "M-537"))
-
-        self.generic_visit(node)
-
-    def visit(self, node):
-        """
-        Public method to traverse a given AST node.
-
-        @param node AST node to be traversed
-        @type ast.Node
-        """
-        isContextful = isinstance(node, BugBearVisitor.CONTEXTFUL_NODES)
-
-        if isContextful:
-            context = BugBearContext(node, [])
-            self.contexts.append(context)
-
-        self.nodeStack.append(node)
-        self.nodeWindow.append(node)
-        self.nodeWindow = self.nodeWindow[-BugBearVisitor.NodeWindowSize :]
-
-        super().visit(node)
-
-        self.nodeStack.pop()
-
-        if isContextful:
-            self.contexts.pop()
-
-        self.__checkForM518(node)
-
-    def visit_ExceptHandler(self, node):
-        """
-        Public method to handle exception handlers.
-
-        @param node reference to the node to be processed
-        @type ast.ExceptHandler
-        """
-        if node.type is None:
-            # bare except is handled by pycodestyle already
-            self.generic_visit(node)
-            return
-
-        oldM540CaughtException = self.__M540CaughtException
-        if node.name is None:
-            self.__M540CaughtException = None
-        else:
-            self.__M540CaughtException = M540CaughtException(node.name, False)
-
-        names = self.__checkForM513_M514_M529_M530(node)
-
-        if "BaseException" in names and not ExceptBaseExceptionVisitor(node).reRaised():
-            self.violations.append((node, "M-536"))
-
-        self.generic_visit(node)
-
-        if (
-            self.__M540CaughtException is not None
-            and self.__M540CaughtException.hasNote
-        ):
-            self.violations.append((node, "M-540"))
-        self.__M540CaughtException = oldM540CaughtException
-
-    def visit_UAdd(self, node):
-        """
-        Public method to handle unary additions.
-
-        @param node reference to the node to be processed
-        @type ast.UAdd
-        """
-        trailingNodes = list(map(type, self.nodeWindow[-4:]))
-        if trailingNodes == [ast.UnaryOp, ast.UAdd, ast.UnaryOp, ast.UAdd]:
-            originator = self.nodeWindow[-4]
-            self.violations.append((originator, "M-502"))
-
-        self.generic_visit(node)
-
-    def visit_Call(self, node):
-        """
-        Public method to handle a function call.
-
-        @param node reference to the node to be processed
-        @type ast.Call
-        """
-        isM540AddNote = False
-
-        if isinstance(node.func, ast.Attribute):
-            self.__checkForM505(node)
-            isM540AddNote = self.__checkForM540AddNote(node.func)
-        else:
-            with contextlib.suppress(AttributeError, IndexError):
-                # bad super() call
-                if isinstance(node.func, ast.Name) and node.func.id == "super":
-                    args = node.args
-                    if (
-                        len(args) == 2
-                        and isinstance(args[0], ast.Attribute)
-                        and isinstance(args[0].value, ast.Name)
-                        and args[0].value.id == "self"
-                        and args[0].attr == "__class__"
-                    ):
-                        self.violations.append((node, "M-582"))
-
-                # bad getattr and setattr
-                if (
-                    node.func.id in ("getattr", "hasattr")
-                    and node.args[1].value == "__call__"
-                ):
-                    self.violations.append((node, "M-504"))
-                if (
-                    node.func.id == "getattr"
-                    and len(node.args) == 2
-                    and self.__isIdentifier(node.args[1])
-                    and iskeyword(AstUtilities.getValue(node.args[1]))
-                ):
-                    self.violations.append((node, "M-509"))
-                elif (
-                    node.func.id == "setattr"
-                    and len(node.args) == 3
-                    and self.__isIdentifier(node.args[1])
-                    and iskeyword(AstUtilities.getValue(node.args[1]))
-                ):
-                    self.violations.append((node, "M-510"))
-
-        self.__checkForM526(node)
-
-        self.__checkForM528(node)
-        self.__checkForM534(node)
-        self.__checkForM539(node)
-
-        # no need for copying, if used in nested calls it will be set to None
-        currentM540CaughtException = self.__M540CaughtException
-        if not isM540AddNote:
-            self.__checkForM540Usage(node.args)
-            self.__checkForM540Usage(node.keywords)
-
-        self.generic_visit(node)
-
-        if isM540AddNote:
-            # Avoid nested calls within the parameter list using the variable itself.
-            # e.g. `e.add_note(str(e))`
-            self.__M540CaughtException = currentM540CaughtException
-
-    def visit_Module(self, node):
-        """
-        Public method to handle a module node.
-
-        @param node reference to the node to be processed
-        @type ast.Module
-        """
-        self.generic_visit(node)
-
-    def visit_Assign(self, node):
-        """
-        Public method to handle assignments.
-
-        @param node reference to the node to be processed
-        @type ast.Assign
-        """
-        self.__checkForM540Usage(node.value)
-        if len(node.targets) == 1:
-            target = node.targets[0]
-            if (
-                isinstance(target, ast.Attribute)
-                and isinstance(target.value, ast.Name)
-                and (target.value.id, target.attr) == ("os", "environ")
-            ):
-                self.violations.append((node, "M-503"))
-
-        self.generic_visit(node)
-
-    def visit_For(self, node):
-        """
-        Public method to handle 'for' statements.
-
-        @param node reference to the node to be processed
-        @type ast.For
-        """
-        self.__checkForM507(node)
-        self.__checkForM520(node)
-        self.__checkForM523(node)
-        self.__checkForM531(node)
-        self.__checkForM569(node)
-
-        self.generic_visit(node)
-
-    def visit_AsyncFor(self, node):
-        """
-        Public method to handle 'for' statements.
-
-        @param node reference to the node to be processed
-        @type ast.AsyncFor
-        """
-        self.__checkForM507(node)
-        self.__checkForM520(node)
-        self.__checkForM523(node)
-        self.__checkForM531(node)
-
-        self.generic_visit(node)
-
-    def visit_While(self, node):
-        """
-        Public method to handle 'while' statements.
-
-        @param node reference to the node to be processed
-        @type ast.While
-        """
-        self.__checkForM523(node)
-
-        self.generic_visit(node)
-
-    def visit_ListComp(self, node):
-        """
-        Public method to handle list comprehensions.
-
-        @param node reference to the node to be processed
-        @type ast.ListComp
-        """
-        self.__checkForM523(node)
-
-        self.generic_visit(node)
-
-    def visit_SetComp(self, node):
-        """
-        Public method to handle set comprehensions.
-
-        @param node reference to the node to be processed
-        @type ast.SetComp
-        """
-        self.__checkForM523(node)
-
-        self.generic_visit(node)
-
-    def visit_DictComp(self, node):
-        """
-        Public method to handle dictionary comprehensions.
-
-        @param node reference to the node to be processed
-        @type ast.DictComp
-        """
-        self.__checkForM523(node)
-        self.__checkForM535(node)
-
-        self.generic_visit(node)
-
-    def visit_GeneratorExp(self, node):
-        """
-        Public method to handle generator expressions.
-
-        @param node reference to the node to be processed
-        @type ast.GeneratorExp
-        """
-        self.__checkForM523(node)
-
-        self.generic_visit(node)
-
-    def visit_Assert(self, node):
-        """
-        Public method to handle 'assert' statements.
-
-        @param node reference to the node to be processed
-        @type ast.Assert
-        """
-        if (
-            AstUtilities.isNameConstant(node.test)
-            and AstUtilities.getValue(node.test) is False
-        ):
-            self.violations.append((node, "M-511"))
-
-        self.generic_visit(node)
-
-    def visit_AsyncFunctionDef(self, node):
-        """
-        Public method to handle async function definitions.
-
-        @param node reference to the node to be processed
-        @type ast.AsyncFunctionDef
-        """
-        self.__checkForM506_M508(node)
-
-        self.generic_visit(node)
-
-    def visit_FunctionDef(self, node):
-        """
-        Public method to handle function definitions.
-
-        @param node reference to the node to be processed
-        @type ast.FunctionDef
-        """
-        self.__checkForM506_M508(node)
-        self.__checkForM519(node)
-        self.__checkForM521(node)
-
-        self.generic_visit(node)
-
-    def visit_ClassDef(self, node):
-        """
-        Public method to handle class definitions.
-
-        @param node reference to the node to be processed
-        @type ast.ClassDef
-        """
-        self.__checkForM521(node)
-        self.__checkForM524_M527(node)
-
-        self.generic_visit(node)
-
-    def visit_Try(self, node):
-        """
-        Public method to handle 'try' statements.
-
-        @param node reference to the node to be processed
-        @type ast.Try
-        """
-        self.__checkForM512(node)
-        self.__checkForM525(node)
-
-        self.generic_visit(node)
-
-    def visit_TryStar(self, node):
-        """
-        Public method to handle 'except*' statements.
-
-        @param node reference to the node to be processed
-        @type ast.TryStar
-        """
-        outerTryStar = self.__inTryStar
-        self.__inTryStar = "*"
-        self.visit_Try(node)
-        self.__inTryStar = outerTryStar
-
-    def visit_Compare(self, node):
-        """
-        Public method to handle comparison statements.
-
-        @param node reference to the node to be processed
-        @type ast.Compare
-        """
-        self.__checkForM515(node)
-
-        self.generic_visit(node)
-
-    def visit_Raise(self, node):
-        """
-        Public method to handle 'raise' statements.
-
-        @param node reference to the node to be processed
-        @type ast.Raise
-        """
-        if node.exc is None:
-            self.__M540CaughtException = None
-        else:
-            self.__checkForM540Usage(node.exc)
-            self.__checkForM540Usage(node.cause)
-        self.__checkForM516(node)
-
-        self.generic_visit(node)
-
-    def visit_With(self, node):
-        """
-        Public method to handle 'with' statements.
-
-        @param node reference to the node to be processed
-        @type ast.With
-        """
-        self.__checkForM517(node)
-        self.__checkForM522(node)
-
-        self.generic_visit(node)
-
-    def visit_JoinedStr(self, node):
-        """
-        Public method to handle f-string arguments.
-
-        @param node reference to the node to be processed
-        @type ast.JoinedStr
-        """
-        for value in node.values:
-            if isinstance(value, ast.FormattedValue):
-                return
-
-        self.violations.append((node, "M-581"))
-
-    def visit_AnnAssign(self, node):
-        """
-        Public method to check annotated assign statements.
-
-        @param node reference to the node to be processed
-        @type ast.AnnAssign
-        """
-        self.__checkForM532(node)
-        self.__checkForM540Usage(node.value)
-
-        self.generic_visit(node)
-
-    def visit_Import(self, node):
-        """
-        Public method to check imports.
-
-        @param node reference to the node to be processed
-        @type ast.Import
-        """
-        self.__checkForM505(node)
-
-        self.generic_visit(node)
-
-    def visit_ImportFrom(self, node):
-        """
-        Public method to check from imports.
-
-        @param node reference to the node to be processed
-        @type ast.Import
-        """
-        self.visit_Import(node)
-
-    def visit_Set(self, node):
-        """
-        Public method to check a set.
-
-        @param node reference to the node to be processed
-        @type ast.Set
-        """
-        self.__checkForM533(node)
-
-        self.generic_visit(node)
-
-    def visit_Dict(self, node):
-        """
-        Public method to check a dictionary.
-
-        @param node reference to the node to be processed
-        @type ast.Dict
-        """
-        self.__checkForM541(node)
-
-        self.generic_visit(node)
-
-    def __checkForM505(self, node):
-        """
-        Private method to check the use of *strip().
-
-        @param node reference to the node to be processed
-        @type ast.Call
-        """
-        if isinstance(node, ast.Import):
-            for name in node.names:
-                self.__M505Imports.add(name.asname or name.name)
-        elif isinstance(node, ast.ImportFrom):
-            for name in node.names:
-                self.__M505Imports.add(f"{node.module}.{name.name or name.asname}")
-        elif isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
-            if node.func.attr not in ("lstrip", "rstrip", "strip"):
-                return  # method name doesn't match
-
-            if (
-                isinstance(node.func.value, ast.Name)
-                and node.func.value.id in self.__M505Imports
-            ):
-                return  # method is being run on an imported module
-
-            if len(node.args) != 1 or not AstUtilities.isString(node.args[0]):
-                return  # used arguments don't match the builtin strip
-
-            value = AstUtilities.getValue(node.args[0])
-            if len(value) == 1:
-                return  # stripping just one character
-
-            if len(value) == len(set(value)):
-                return  # no characters appear more than once
-
-            self.violations.append((node, "M-505"))
-
-    def __checkForM506_M508(self, node):
-        """
-        Private method to check the use of mutable literals, comprehensions and calls.
-
-        @param node reference to the node to be processed
-        @type ast.AsyncFunctionDef or ast.FunctionDef
-        """
-        visitor = FunctionDefDefaultsVisitor("M-506", "M-508")
-        visitor.visit(node.args.defaults + node.args.kw_defaults)
-        self.violations.extend(visitor.errors)
-
-    def __checkForM507(self, node):
-        """
-        Private method to check for unused loop variables.
-
-        @param node reference to the node to be processed
-        @type ast.For or ast.AsyncFor
-        """
-        targets = NameFinder()
-        targets.visit(node.target)
-        ctrlNames = set(filter(lambda s: not s.startswith("_"), targets.getNames()))
-        body = NameFinder()
-        for expr in node.body:
-            body.visit(expr)
-        usedNames = set(body.getNames())
-        for name in sorted(ctrlNames - usedNames):
-            n = targets.getNames()[name][0]
-            self.violations.append((n, "M-507", name))
-
-    def __checkForM512(self, node):
-        """
-        Private method to check for return/continue/break inside finally blocks.
-
-        @param node reference to the node to be processed
-        @type ast.Try
-        """
-
-        def _loop(node, badNodeTypes):
-            if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)):
-                return
-
-            if isinstance(node, (ast.While, ast.For)):
-                badNodeTypes = (ast.Return,)
-
-            elif isinstance(node, badNodeTypes):
-                self.violations.append((node, "M-512", self.__inTryStar))
-
-            for child in ast.iter_child_nodes(node):
-                _loop(child, badNodeTypes)
-
-        for child in node.finalbody:
-            _loop(child, (ast.Return, ast.Continue, ast.Break))
-
-    def __checkForM513_M514_M529_M530(self, node):
-        """
-        Private method to check various exception handler situations.
-
-        @param node reference to the node to be processed
-        @type ast.ExceptHandler
-        @return list of exception handler names
-        @rtype list of str
-        """
-        handlers = self.__flattenExcepthandler(node.type)
-        names = []
-        badHandlers = []
-        ignoredHandlers = []
-
-        for handler in handlers:
-            if isinstance(handler, (ast.Name, ast.Attribute)):
-                name = self.toNameStr(handler)
-                if name is None:
-                    ignoredHandlers.append(handler)
-                else:
-                    names.append(name)
-            elif isinstance(handler, (ast.Call, ast.Starred)):
-                ignoredHandlers.append(handler)
-            else:
-                badHandlers.append(handler)
-        if badHandlers:
-            self.violations.append((node, "M-530"))
-        if len(names) == 0 and not badHandlers and not ignoredHandlers:
-            self.violations.append((node, "M-529", self.__inTryStar))
-        elif (
-            len(names) == 1
-            and not badHandlers
-            and not ignoredHandlers
-            and isinstance(node.type, ast.Tuple)
-        ):
-            self.violations.append((node, "M-513", *names, self.__inTryStar))
-        else:
-            maybeError = self.__checkRedundantExcepthandlers(
-                names, node, self.__inTryStar
-            )
-            if maybeError is not None:
-                self.violations.append(maybeError)
-        return names
-
-    def __checkForM515(self, node):
-        """
-        Private method to check for pointless comparisons.
-
-        @param node reference to the node to be processed
-        @type ast.Compare
-        """
-        if isinstance(self.nodeStack[-2], ast.Expr):
-            self.violations.append((node, "M-515"))
-
-    def __checkForM516(self, node):
-        """
-        Private method to check for raising a literal instead of an exception.
-
-        @param node reference to the node to be processed
-        @type ast.Raise
-        """
-        if (
-            AstUtilities.isNameConstant(node.exc)
-            or AstUtilities.isNumber(node.exc)
-            or AstUtilities.isString(node.exc)
-        ):
-            self.violations.append((node, "M-516"))
-
-    def __checkForM517(self, node):
-        """
-        Private method to check for use of the evil syntax
-        'with assertRaises(Exception): or 'with pytest.raises(Exception):'.
-
-        @param node reference to the node to be processed
-        @type ast.With
-        """
-        item = node.items[0]
-        itemContext = item.context_expr
-        if (
-            hasattr(itemContext, "func")
-            and (
-                (
-                    isinstance(itemContext.func, ast.Attribute)
-                    and (
-                        itemContext.func.attr == "assertRaises"
-                        or (
-                            itemContext.func.attr == "raises"
-                            and isinstance(itemContext.func.value, ast.Name)
-                            and itemContext.func.value.id == "pytest"
-                            and "match" not in (kwd.arg for kwd in itemContext.keywords)
-                        )
-                    )
-                )
-                or (
-                    isinstance(itemContext.func, ast.Name)
-                    and itemContext.func.id == "raises"
-                    and isinstance(itemContext.func.ctx, ast.Load)
-                    and "pytest.raises" in self.__M505Imports
-                    and "match" not in (kwd.arg for kwd in itemContext.keywords)
-                )
-            )
-            and len(itemContext.args) == 1
-            and isinstance(itemContext.args[0], ast.Name)
-            and itemContext.args[0].id in ("Exception", "BaseException")
-            and not item.optional_vars
-        ):
-            self.violations.append((node, "M-517"))
-
-    def __checkForM518(self, node):
-        """
-        Private method to check for useless expressions.
-
-        @param node reference to the node to be processed
-        @type ast.FunctionDef
-        """
-        if not isinstance(node, ast.Expr):
-            return
-
-        if isinstance(
-            node.value,
-            (ast.List, ast.Set, ast.Dict, ast.Tuple),
-        ) or (
-            isinstance(node.value, ast.Constant)
-            and (
-                isinstance(
-                    node.value.value,
-                    (int, float, complex, bytes, bool),
-                )
-                or node.value.value is None
-            )
-        ):
-            self.violations.append((node, "M-518", node.value.__class__.__name__))
-
-    def __checkForM519(self, node):
-        """
-        Private method to check for use of 'functools.lru_cache' or 'functools.cache'.
-
-        @param node reference to the node to be processed
-        @type ast.FunctionDef
-        """
-        caches = {
-            "functools.cache",
-            "functools.lru_cache",
-            "cache",
-            "lru_cache",
-        }
-
-        if (
-            len(node.decorator_list) == 0
-            or len(self.contexts) < 2
-            or not isinstance(self.contexts[-2].node, ast.ClassDef)
-        ):
-            return
-
-        # Preserve decorator order so we can get the lineno from the decorator node
-        # rather than the function node (this location definition changes in Python 3.8)
-        resolvedDecorators = (
-            ".".join(composeCallPath(decorator)) for decorator in node.decorator_list
-        )
-        for idx, decorator in enumerate(resolvedDecorators):
-            if decorator in {"classmethod", "staticmethod"}:
-                return
-
-            if decorator in caches:
-                self.violations.append((node.decorator_list[idx], "M-519"))
-                return
-
-    def __checkForM520(self, node):
-        """
-        Private method to check for a loop that modifies its iterable.
-
-        @param node reference to the node to be processed
-        @type ast.For or ast.AsyncFor
-        """
-        targets = NameFinder()
-        targets.visit(node.target)
-        ctrlNames = set(targets.getNames())
-
-        iterset = M520NameFinder()
-        iterset.visit(node.iter)
-        itersetNames = set(iterset.getNames())
-
-        for name in sorted(ctrlNames):
-            if name in itersetNames:
-                n = targets.getNames()[name][0]
-                self.violations.append((n, "M-520"))
-
-    def __checkForM521(self, node):
-        """
-        Private method to check for use of an f-string as docstring.
-
-        @param node reference to the node to be processed
-        @type ast.FunctionDef or ast.ClassDef
-        """
-        if (
-            node.body
-            and isinstance(node.body[0], ast.Expr)
-            and isinstance(node.body[0].value, ast.JoinedStr)
-        ):
-            self.violations.append((node.body[0].value, "M-521"))
-
-    def __checkForM522(self, node):
-        """
-        Private method to check for use of an f-string as docstring.
-
-        @param node reference to the node to be processed
-        @type ast.With
-        """
-        item = node.items[0]
-        itemContext = item.context_expr
-        if (
-            hasattr(itemContext, "func")
-            and hasattr(itemContext.func, "value")
-            and hasattr(itemContext.func.value, "id")
-            and itemContext.func.value.id == "contextlib"
-            and hasattr(itemContext.func, "attr")
-            and itemContext.func.attr == "suppress"
-            and len(itemContext.args) == 0
-        ):
-            self.violations.append((node, "M-522"))
-
-    def __checkForM523(self, loopNode):
-        """
-        Private method to check that functions (including lambdas) do not use loop
-        variables.
-
-        @param loopNode reference to the node to be processed
-        @type ast.For, ast.AsyncFor, ast.While, ast.ListComp, ast.SetComp,ast.DictComp,
-            or ast.GeneratorExp
-        """
-        safe_functions = []
-        suspiciousVariables = []
-        for node in ast.walk(loopNode):
-            # check if function is immediately consumed to avoid false alarm
-            if isinstance(node, ast.Call):
-                # check for filter&reduce
-                if (
-                    isinstance(node.func, ast.Name)
-                    and node.func.id in ("filter", "reduce", "map")
-                ) or (
-                    isinstance(node.func, ast.Attribute)
-                    and node.func.attr == "reduce"
-                    and isinstance(node.func.value, ast.Name)
-                    and node.func.value.id == "functools"
-                ):
-                    for arg in node.args:
-                        if isinstance(arg, BugBearVisitor.FUNCTION_NODES):
-                            safe_functions.append(arg)
-
-                # check for key=
-                for keyword in node.keywords:
-                    if keyword.arg == "key" and isinstance(
-                        keyword.value, BugBearVisitor.FUNCTION_NODES
-                    ):
-                        safe_functions.append(keyword.value)
-
-            # mark `return lambda: x` as safe
-            # does not (currently) check inner lambdas in a returned expression
-            # e.g. `return (lambda: x, )
-            if isinstance(node, ast.Return) and isinstance(
-                node.value, BugBearVisitor.FUNCTION_NODES
-            ):
-                safe_functions.append(node.value)
-
-            # find unsafe functions
-            if (
-                isinstance(node, BugBearVisitor.FUNCTION_NODES)
-                and node not in safe_functions
-            ):
-                argnames = {
-                    arg.arg for arg in ast.walk(node.args) if isinstance(arg, ast.arg)
-                }
-                if isinstance(node, ast.Lambda):
-                    bodyNodes = ast.walk(node.body)
-                else:
-                    bodyNodes = itertools.chain.from_iterable(map(ast.walk, node.body))
-                errors = []
-                for name in bodyNodes:
-                    if isinstance(name, ast.Name) and name.id not in argnames:
-                        if isinstance(name.ctx, ast.Load):
-                            errors.append((name.lineno, name.col_offset, name.id, name))
-                        elif isinstance(name.ctx, ast.Store):
-                            argnames.add(name.id)
-                for err in errors:
-                    if err[2] not in argnames and err not in self.__M523Seen:
-                        self.__M523Seen.add(err)  # dedupe across nested loops
-                        suspiciousVariables.append(err)
-
-        if suspiciousVariables:
-            reassignedInLoop = set(self.__getAssignedNames(loopNode))
-
-        for err in sorted(suspiciousVariables):
-            if reassignedInLoop.issuperset(err[2]):
-                self.violations.append((err[3], "M-523", err[2]))
-
-    def __checkForM524_M527(self, node):
-        """
-        Private method to check for inheritance from abstract classes in abc and lack of
-        any methods decorated with abstract*.
-
-        @param node reference to the node to be processed
-        @type ast.ClassDef
-        """  # __IGNORE_WARNING_D-234r__
-
-        def isAbcClass(value, name="ABC"):
-            if isinstance(value, ast.keyword):
-                return value.arg == "metaclass" and isAbcClass(value.value, "ABCMeta")
-
-            # class foo(ABC)
-            # class foo(abc.ABC)
-            return (isinstance(value, ast.Name) and value.id == name) or (
-                isinstance(value, ast.Attribute)
-                and value.attr == name
-                and isinstance(value.value, ast.Name)
-                and value.value.id == "abc"
-            )
-
-        def isAbstractDecorator(expr):
-            return (isinstance(expr, ast.Name) and expr.id[:8] == "abstract") or (
-                isinstance(expr, ast.Attribute) and expr.attr[:8] == "abstract"
-            )
-
-        def isOverload(expr):
-            return (isinstance(expr, ast.Name) and expr.id == "overload") or (
-                isinstance(expr, ast.Attribute) and expr.attr == "overload"
-            )
-
-        def emptyBody(body):
-            def isStrOrEllipsis(node):
-                return isinstance(node, ast.Constant) and (
-                    node.value is Ellipsis or isinstance(node.value, str)
-                )
-
-            # Function body consist solely of `pass`, `...`, and/or (doc)string literals
-            return all(
-                isinstance(stmt, ast.Pass)
-                or (isinstance(stmt, ast.Expr) and isStrOrEllipsis(stmt.value))
-                for stmt in body
-            )
-
-        # don't check multiple inheritance
-        if len(node.bases) + len(node.keywords) > 1:
-            return
-
-        # only check abstract classes
-        if not any(map(isAbcClass, (*node.bases, *node.keywords))):
-            return
-
-        hasMethod = False
-        hasAbstractMethod = False
-
-        if not any(map(isAbcClass, (*node.bases, *node.keywords))):
-            return
-
-        for stmt in node.body:
-            # Ignore abc's that declares a class attribute that must be set
-            if isinstance(stmt, ast.AnnAssign) and stmt.value is None:
-                hasAbstractMethod = True
-                continue
-
-            # only check function defs
-            if not isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
-                continue
-            hasMethod = True
-
-            hasAbstractDecorator = any(map(isAbstractDecorator, stmt.decorator_list))
-
-            hasAbstractMethod |= hasAbstractDecorator
-
-            if (
-                not hasAbstractDecorator
-                and emptyBody(stmt.body)
-                and not any(map(isOverload, stmt.decorator_list))
-            ):
-                self.violations.append((stmt, "M-527", stmt.name))
-
-        if hasMethod and not hasAbstractMethod:
-            self.violations.append((node, "M-524", node.name))
-
-    def __checkForM525(self, node):
-        """
-        Private method to check for exceptions being handled multiple times.
-
-        @param node reference to the node to be processed
-        @type ast.Try
-        """
-        seen = []
-
-        for handler in node.handlers:
-            if isinstance(handler.type, (ast.Name, ast.Attribute)):
-                name = ".".join(composeCallPath(handler.type))
-                seen.append(name)
-            elif isinstance(handler.type, ast.Tuple):
-                # to avoid checking the same as M514, remove duplicates per except
-                uniques = set()
-                for entry in handler.type.elts:
-                    name = ".".join(composeCallPath(entry))
-                    uniques.add(name)
-                seen.extend(uniques)
-
-        # sort to have a deterministic output
-        duplicates = sorted({x for x in seen if seen.count(x) > 1})
-        for duplicate in duplicates:
-            self.violations.append((node, "M-525", duplicate, self.__inTryStar))
-
-    def __checkForM526(self, node):
-        """
-        Private method to check for Star-arg unpacking after keyword argument.
-
-        @param node reference to the node to be processed
-        @type ast.Call
-        """
-        if not node.keywords:
-            return
-
-        starreds = [arg for arg in node.args if isinstance(arg, ast.Starred)]
-        if not starreds:
-            return
-
-        firstKeyword = node.keywords[0].value
-        for starred in starreds:
-            if (starred.lineno, starred.col_offset) > (
-                firstKeyword.lineno,
-                firstKeyword.col_offset,
-            ):
-                self.violations.append((node, "M-526"))
-
-    def __checkForM528(self, node):
-        """
-        Private method to check for warn without stacklevel.
-
-        @param node reference to the node to be processed
-        @type ast.Call
-        """
-        if (
-            isinstance(node.func, ast.Attribute)
-            and node.func.attr == "warn"
-            and isinstance(node.func.value, ast.Name)
-            and node.func.value.id == "warnings"
-            and not any(kw.arg == "stacklevel" for kw in node.keywords)
-            and len(node.args) < 3
-            and not any(isinstance(a, ast.Starred) for a in node.args)
-            and not any(kw.arg is None for kw in node.keywords)
-        ):
-            self.violations.append((node, "M-528"))
-
-    def __checkForM531(self, loopNode):
-        """
-        Private method to check that 'itertools.groupby' isn't iterated over more than
-        once.
-
-        A warning is emitted when the generator returned by 'groupby()' is used
-        more than once inside a loop body or when it's used in a nested loop.
-
-        @param loopNode reference to the node to be processed
-        @type ast.For or ast.AsyncFor
-        """
-        # for <loop_node.target> in <loop_node.iter>: ...
-        if isinstance(loopNode.iter, ast.Call):
-            node = loopNode.iter
-            if (isinstance(node.func, ast.Name) and node.func.id in ("groupby",)) or (
-                isinstance(node.func, ast.Attribute)
-                and node.func.attr == "groupby"
-                and isinstance(node.func.value, ast.Name)
-                and node.func.value.id == "itertools"
-            ):
-                # We have an invocation of groupby which is a simple unpacking
-                if isinstance(loopNode.target, ast.Tuple) and isinstance(
-                    loopNode.target.elts[1], ast.Name
-                ):
-                    groupName = loopNode.target.elts[1].id
-                else:
-                    # Ignore any 'groupby()' invocation that isn't unpacked
-                    return
-
-                numUsages = 0
-                for node in self.__walkList(loopNode.body):
-                    # Handled nested loops
-                    if isinstance(node, ast.For):
-                        for nestedNode in self.__walkList(node.body):
-                            if (
-                                isinstance(nestedNode, ast.Name)
-                                and nestedNode.id == groupName
-                            ):
-                                self.violations.append((nestedNode, "M-531"))
-
-                    # Handle multiple uses
-                    if isinstance(node, ast.Name) and node.id == groupName:
-                        numUsages += 1
-                        if numUsages > 1:
-                            self.violations.append((nestedNode, "M-531"))
-
-    def __checkForM532(self, node):
-        """
-        Private method to check for possible unintentional typing annotation.
-
-        @param node reference to the node to be processed
-        @type ast.AnnAssign
-        """
-        if (
-            node.value is None
-            and hasattr(node.target, "value")
-            and isinstance(node.target.value, ast.Name)
-            and (
-                isinstance(node.target, ast.Subscript)
-                or (
-                    isinstance(node.target, ast.Attribute)
-                    and node.target.value.id != "self"
-                )
-            )
-        ):
-            self.violations.append((node, "M-532"))
-
-    def __checkForM533(self, node):
-        """
-        Private method to check a set for duplicate items.
-
-        @param node reference to the node to be processed
-        @type ast.Set
-        """
-        seen = set()
-        for elt in node.elts:
-            if not isinstance(elt, ast.Constant):
-                continue
-            if elt.value in seen:
-                self.violations.append((node, "M-533", repr(elt.value)))
-            else:
-                seen.add(elt.value)
-
-    def __checkForM534(self, node):
-        """
-        Private method to check that re.sub/subn/split arguments flags/count/maxsplit
-        are passed as keyword arguments.
-
-        @param node reference to the node to be processed
-        @type ast.Call
-        """
-        if not isinstance(node.func, ast.Attribute):
-            return
-        func = node.func
-        if not isinstance(func.value, ast.Name) or func.value.id != "re":
-            return
-
-        def check(numArgs, paramName):
-            if len(node.args) > numArgs:
-                arg = node.args[numArgs]
-                self.violations.append((arg, "M-534", func.attr, paramName))
-
-        if func.attr in ("sub", "subn"):
-            check(3, "count")
-        elif func.attr == "split":
-            check(2, "maxsplit")
-
-    def __checkForM535(self, node):
-        """
-        Private method to check that a static key isn't used in a dict comprehension.
-
-        Record a warning if a likely unchanging key is used - either a constant,
-        or a variable that isn't coming from the generator expression.
-
-        @param node reference to the node to be processed
-        @type ast.DictComp
-        """
-        if isinstance(node.key, ast.Constant):
-            self.violations.append((node, "M-535", node.key.value))
-        elif isinstance(
-            node.key, ast.Name
-        ) and node.key.id not in self.__getDictCompLoopAndNamedExprVarNames(node):
-            self.violations.append((node, "M-535", node.key.id))
-
-    def __checkForM539(self, node):
-        """
-        Private method to check for correct ContextVar usage.
-
-        @param node reference to the node to be processed
-        @type ast.Call
-        """
-        if not (
-            (isinstance(node.func, ast.Name) and node.func.id == "ContextVar")
-            or (
-                isinstance(node.func, ast.Attribute)
-                and node.func.attr == "ContextVar"
-                and isinstance(node.func.value, ast.Name)
-                and node.func.value.id == "contextvars"
-            )
-        ):
-            return
-
-        # ContextVar only takes one kw currently, but better safe than sorry
-        for kw in node.keywords:
-            if kw.arg == "default":
-                break
-        else:
-            return
-
-        visitor = FunctionDefDefaultsVisitor("M-539", "M-539")
-        visitor.visit(kw.value)
-        self.violations.extend(visitor.errors)
-
-    def __checkForM540AddNote(self, node):
-        """
-        Private method to check add_note usage.
-
-        @param node reference to the node to be processed
-        @type ast.Attribute
-        @return flag
-        @rtype bool
-        """
-        if (
-            node.attr == "add_note"
-            and isinstance(node.value, ast.Name)
-            and self.__M540CaughtException
-            and node.value.id == self.__M540CaughtException.name
-        ):
-            self.__M540CaughtException.hasNote = True
-            return True
-
-        return False
-
-    def __checkForM540Usage(self, node):
-        """
-        Private method to check the usage of exceptions with added note.
-
-        @param node reference to the node to be processed
-        @type ast.expr or None
-        """  # noqa: D-234y
-
-        def superwalk(node: ast.AST | list[ast.AST]):
-            """
-            Function to walk an AST node or a list of AST nodes.
-
-            @param node reference to the node or a list of nodes to be processed
-            @type ast.AST or list[ast.AST]
-            @yield next node to be processed
-            @ytype ast.AST
-            """
-            if isinstance(node, list):
-                for n in node:
-                    yield from ast.walk(n)
-            else:
-                yield from ast.walk(node)
-
-        if not self.__M540CaughtException or node is None:
-            return
-
-        for n in superwalk(node):
-            if isinstance(n, ast.Name) and n.id == self.__M540CaughtException.name:
-                self.__M540CaughtException = None
-                break
-
-    def __checkForM541(self, node):
-        """
-        Private method to check for duplicate key value pairs in a dictionary literal.
-
-        @param node reference to the node to be processed
-        @type ast.Dict
-        """  # noqa: D-234r
-
-        def convertToValue(item):
-            """
-            Function to extract the value of a given item.
-
-            @param item node to extract value from
-            @type ast.Ast
-            @return value of the node
-            @rtype Any
-            """
-            if isinstance(item, ast.Constant):
-                return item.value
-            elif isinstance(item, ast.Tuple):
-                return tuple(convertToValue(i) for i in item.elts)
-            elif isinstance(item, ast.Name):
-                return M541VariableKeyType(item.id)
-            else:
-                return M541UnhandledKeyType()
-
-        keys = [convertToValue(key) for key in node.keys]
-        keyCounts = Counter(keys)
-        duplicateKeys = [key for key, count in keyCounts.items() if count > 1]
-        for key in duplicateKeys:
-            keyIndices = [i for i, iKey in enumerate(keys) if iKey == key]
-            seen = set()
-            for index in keyIndices:
-                value = convertToValue(node.values[index])
-                if value in seen:
-                    keyNode = node.keys[index]
-                    self.violations.append((keyNode, "M-541"))
-                seen.add(value)
-
-    def __checkForM569(self, node):
-        """
-        Private method to check for changes to a loop's mutable iterable.
-
-        @param node loop node to be checked
-        @type ast.For
-        """
-        if isinstance(node.iter, ast.Name):
-            name = self.toNameStr(node.iter)
-        elif isinstance(node.iter, ast.Attribute):
-            name = self.toNameStr(node.iter)
-        else:
-            return
-        checker = M569Checker(name, self)
-        checker.visit(node.body)
-        for mutation in checker.mutations:
-            self.violations.append((mutation, "M-569"))
-
-
-class M569Checker(ast.NodeVisitor):
-    """
-    Class traversing a 'for' loop body to check for modifications to a loop's
-    mutable iterable.
-    """
-
-    # https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types
-    MUTATING_FUNCTIONS = (
-        "append",
-        "sort",
-        "reverse",
-        "remove",
-        "clear",
-        "extend",
-        "insert",
-        "pop",
-        "popitem",
-    )
-
-    def __init__(self, name, bugbear):
-        """
-        Constructor
-
-        @param name name of the iterator
-        @type str
-        @param bugbear reference to the bugbear visitor
-        @type BugBearVisitor
-        """
-        self.__name = name
-        self.__bb = bugbear
-        self.mutations = []
-
-    def visit_Delete(self, node):
-        """
-        Public method handling 'Delete' nodes.
-
-        @param node reference to the node to be processed
-        @type ast.Delete
-        """
-        for target in node.targets:
-            if isinstance(target, ast.Subscript):
-                name = self.__bb.toNameStr(target.value)
-            elif isinstance(target, (ast.Attribute, ast.Name)):
-                name = self.__bb.toNameStr(target)
-            else:
-                name = ""  # fallback
-                self.generic_visit(target)
-
-            if name == self.__name:
-                self.mutations.append(node)
-
-    def visit_Call(self, node):
-        """
-        Public method handling 'Call' nodes.
-
-        @param node reference to the node to be processed
-        @type ast.Call
-        """
-        if isinstance(node.func, ast.Attribute):
-            name = self.__bb.toNameStr(node.func.value)
-            functionObject = name
-            functionName = node.func.attr
-
-            if (
-                functionObject == self.__name
-                and functionName in self.MUTATING_FUNCTIONS
-            ):
-                self.mutations.append(node)
-
-        self.generic_visit(node)
-
-    def visit(self, node):
-        """
-        Public method to inspect an ast node.
-
-        Like super-visit but supports iteration over lists.
-
-        @param node AST node to be traversed
-        @type TYPE
-        @return reference to the last processed node
-        @rtype ast.Node
-        """
-        if not isinstance(node, list):
-            return super().visit(node)
-
-        for elem in node:
-            super().visit(elem)
-        return node
-
-
-class ExceptBaseExceptionVisitor(ast.NodeVisitor):
-    """
-    Class to determine, if a 'BaseException' is re-raised.
-    """
-
-    def __init__(self, exceptNode):
-        """
-        Constructor
-
-        @param exceptNode exception node to be inspected
-        @type ast.ExceptHandler
-        """
-        super().__init__()
-        self.__root = exceptNode
-        self.__reRaised = False
-
-    def reRaised(self) -> bool:
-        """
-        Public method to check, if the exception is re-raised.
-
-        @return flag indicating a re-raised exception
-        @rtype bool
-        """
-        self.visit(self.__root)
-        return self.__reRaised
-
-    def visit_Raise(self, node):
-        """
-        Public method to handle 'Raise' nodes.
-
-        If we find a corresponding `raise` or `raise e` where e was from
-        `except BaseException as e:` then we mark re_raised as True and can
-        stop scanning.
-
-        @param node reference to the node to be processed
-        @type ast.Raise
-        """
-        if node.exc is None or (
-            isinstance(node.exc, ast.Name) and node.exc.id == self.__root.name
-        ):
-            self.__reRaised = True
-            return
-
-        super().generic_visit(node)
-
-    def visit_ExceptHandler(self, node: ast.ExceptHandler):
-        """
-        Public method to handle 'ExceptHandler' nodes.
-
-        @param node reference to the node to be processed
-        @type ast.ExceptHandler
-        """
-        if node is not self.__root:
-            return  # entered a nested except - stop searching
-
-        super().generic_visit(node)
-
-
-class NameFinder(ast.NodeVisitor):
-    """
-    Class to extract a name out of a tree of nodes.
-    """
-
-    def __init__(self):
-        """
-        Constructor
-        """
-        super().__init__()
-
-        self.__names = {}
-
-    def visit_Name(self, node):
-        """
-        Public method to handle 'Name' nodes.
-
-        @param node reference to the node to be processed
-        @type ast.Name
-        """
-        self.__names.setdefault(node.id, []).append(node)
-
-    def visit(self, node):
-        """
-        Public method to traverse a given AST node.
-
-        @param node AST node to be traversed
-        @type ast.Node
-        @return reference to the last processed node
-        @rtype ast.Node
-        """
-        if isinstance(node, list):
-            for elem in node:
-                super().visit(elem)
-            return node
-        else:
-            return super().visit(node)
-
-    def getNames(self):
-        """
-        Public method to return the extracted names and Name nodes.
-
-        @return dictionary containing the names as keys and the list of nodes
-        @rtype dict
-        """
-        return self.__names
-
-
-class NamedExprFinder(ast.NodeVisitor):
-    """
-    Class to extract names defined through an ast.NamedExpr.
-    """
-
-    def __init__(self):
-        """
-        Constructor
-        """
-        super().__init__()
-
-        self.__names = {}
-
-    def visit_NamedExpr(self, node: ast.NamedExpr):
-        """
-        Public method handling 'NamedExpr' nodes.
-
-        @param node reference to the node to be processed
-        @type ast.NamedExpr
-        """
-        self.__names.setdefault(node.target.id, []).append(node.target)
-
-        self.generic_visit(node)
-
-    def visit(self, node):
-        """
-        Public method to traverse a given AST node.
-
-        Like super-visit but supports iteration over lists.
-
-        @param node AST node to be traversed
-        @type TYPE
-        @return reference to the last processed node
-        @rtype ast.Node
-        """
-        if not isinstance(node, list):
-            super().visit(node)
-
-        for elem in node:
-            super().visit(elem)
-
-        return node
-
-    def getNames(self):
-        """
-        Public method to return the extracted names and Name nodes.
-
-        @return dictionary containing the names as keys and the list of nodes
-        @rtype dict
-        """
-        return self.__names
-
-
-class FunctionDefDefaultsVisitor(ast.NodeVisitor):
-    """
-    Class used by M506, M508 and M539.
-    """
-
-    def __init__(
-        self,
-        errorCodeCalls,  # M506 or M539
-        errorCodeLiterals,  # M508 or M539
-    ):
-        """
-        Constructor
-
-        @param errorCodeCalls error code for ast.Call nodes
-        @type str
-        @param errorCodeLiterals error code for literal nodes
-        @type str
-        """
-        self.__errorCodeCalls = errorCodeCalls
-        self.__errorCodeLiterals = errorCodeLiterals
-        for nodeType in BugbearMutableLiterals + BugbearMutableComprehensions:
-            setattr(
-                self, f"visit_{nodeType}", self.__visitMutableLiteralOrComprehension
-            )
-        self.errors = []
-        self.__argDepth = 0
-
-        super().__init__()
-
-    def __visitMutableLiteralOrComprehension(self, node):
-        """
-        Private method to flag mutable literals and comprehensions.
-
-        @param node AST node to be processed
-        @type ast.Dict, ast.List, ast.Set, ast.ListComp, ast.DictComp or ast.SetComp
-        """
-        # Flag M506 if mutable literal/comprehension is not nested.
-        # We only flag these at the top level of the expression as we
-        # cannot easily guarantee that nested mutable structures are not
-        # made immutable by outer operations, so we prefer no false positives.
-        # e.g.
-        # >>> def this_is_fine(a=frozenset({"a", "b", "c"})): ...
-        #
-        # >>> def this_is_not_fine_but_hard_to_detect(a=(lambda x: x)([1, 2, 3]))
-        #
-        # We do still search for cases of B008 within mutable structures though.
-        if self.__argDepth == 1:
-            self.errors.append((node, self.__errorCodeCalls))
-
-        # Check for nested functions.
-        self.generic_visit(node)
-
-    def visit_Call(self, node):
-        """
-        Public method to process Call nodes.
-
-        @param node AST node to be processed
-        @type ast.Call
-        """
-        callPath = ".".join(composeCallPath(node.func))
-        if callPath in BugbearMutableCalls:
-            self.errors.append((node, self.__errorCodeCalls))
-            self.generic_visit(node)
-            return
-
-        if callPath in BugbearImmutableCalls:
-            self.generic_visit(node)
-            return
-
-        # Check if function call is actually a float infinity/NaN literal
-        if callPath == "float" and len(node.args) == 1:
-            try:
-                value = float(ast.literal_eval(node.args[0]))
-            except Exception:  # secok
-                pass
-            else:
-                if math.isfinite(value):
-                    self.errors.append((node, self.__errorCodeLiterals))
-        else:
-            self.errors.append((node, self.__errorCodeLiterals))
-
-        # Check for nested functions.
-        self.generic_visit(node)
-
-    def visit_Lambda(self, node):
-        """
-        Public method to process Lambda nodes.
-
-        @param node AST node to be processed
-        @type ast.Lambda
-        """
-        # Don't recurse into lambda expressions
-        # as they are evaluated at call time.
-        pass
-
-    def visit(self, node):
-        """
-        Public method to traverse an AST node or a list of AST nodes.
-
-        This is an extended method that can also handle a list of AST nodes.
-
-        @param node AST node or list of AST nodes to be processed
-        @type ast.AST or list of ast.AST
-        """
-        self.__argDepth += 1
-        if isinstance(node, list):
-            for elem in node:
-                if elem is not None:
-                    super().visit(elem)
-        else:
-            super().visit(node)
-        self.__argDepth -= 1
-
-
-class M520NameFinder(NameFinder):
-    """
-    Class to extract a name out of a tree of nodes ignoring names defined within the
-    local scope of a comprehension.
-    """
-
-    def visit_GeneratorExp(self, node):
-        """
-        Public method to handle a generator expressions.
-
-        @param node reference to the node to be processed
-        @type ast.GeneratorExp
-        """
-        self.visit(node.generators)
-
-    def visit_ListComp(self, node):
-        """
-        Public method  to handle a list comprehension.
-
-        @param node reference to the node to be processed
-        @type TYPE
-        """
-        self.visit(node.generators)
-
-    def visit_DictComp(self, node):
-        """
-        Public method  to handle a dictionary comprehension.
-
-        @param node reference to the node to be processed
-        @type TYPE
-        """
-        self.visit(node.generators)
-
-    def visit_comprehension(self, node):
-        """
-        Public method  to handle the 'for' of a comprehension.
-
-        @param node reference to the node to be processed
-        @type ast.comprehension
-        """
-        self.visit(node.iter)
-
-    def visit_Lambda(self, node):
-        """
-        Public method  to handle a Lambda function.
-
-        @param node reference to the node to be processed
-        @type ast.Lambda
-        """
-        self.visit(node.body)
-        for lambdaArg in node.args.args:
-            self.getNames().pop(lambdaArg.arg, None)
-
-
-class ReturnVisitor(ast.NodeVisitor):
-    """
-    Class implementing a node visitor to check return statements.
-    """
-
-    Assigns = "assigns"
-    Refs = "refs"
-    Returns = "returns"
-
-    def __init__(self):
-        """
-        Constructor
-        """
-        super().__init__()
-
-        self.__stack = []
-        self.violations = []
-        self.__loopCount = 0
-
-    @property
-    def assigns(self):
-        """
-        Public method to get the Assign nodes.
-
-        @return dictionary containing the node name as key and line number
-            as value
-        @rtype dict
-        """
-        return self.__stack[-1][ReturnVisitor.Assigns]
-
-    @property
-    def refs(self):
-        """
-        Public method to get the References nodes.
-
-        @return dictionary containing the node name as key and line number
-            as value
-        @rtype dict
-        """
-        return self.__stack[-1][ReturnVisitor.Refs]
-
-    @property
-    def returns(self):
-        """
-        Public method to get the Return nodes.
-
-        @return dictionary containing the node name as key and line number
-            as value
-        @rtype dict
-        """
-        return self.__stack[-1][ReturnVisitor.Returns]
-
-    def visit_For(self, node):
-        """
-        Public method to handle a for loop.
-
-        @param node reference to the for node to handle
-        @type ast.For
-        """
-        self.__visitLoop(node)
-
-    def visit_AsyncFor(self, node):
-        """
-        Public method to handle an async for loop.
-
-        @param node reference to the async for node to handle
-        @type ast.AsyncFor
-        """
-        self.__visitLoop(node)
-
-    def visit_While(self, node):
-        """
-        Public method to handle a while loop.
-
-        @param node reference to the while node to handle
-        @type ast.While
-        """
-        self.__visitLoop(node)
-
-    def __visitLoop(self, node):
-        """
-        Private method to handle loop nodes.
-
-        @param node reference to the loop node to handle
-        @type ast.For, ast.AsyncFor or ast.While
-        """
-        self.__loopCount += 1
-        self.generic_visit(node)
-        self.__loopCount -= 1
-
-    def __visitWithStack(self, node):
-        """
-        Private method to traverse a given function node using a stack.
-
-        @param node AST node to be traversed
-        @type ast.FunctionDef or ast.AsyncFunctionDef
-        """
-        self.__stack.append(
-            {
-                ReturnVisitor.Assigns: defaultdict(list),
-                ReturnVisitor.Refs: defaultdict(list),
-                ReturnVisitor.Returns: [],
-            }
-        )
-
-        self.generic_visit(node)
-        self.__checkFunction(node)
-        self.__stack.pop()
-
-    def visit_FunctionDef(self, node):
-        """
-        Public method to handle a function definition.
-
-        @param node reference to the node to handle
-        @type ast.FunctionDef
-        """
-        self.__visitWithStack(node)
-
-    def visit_AsyncFunctionDef(self, node):
-        """
-        Public method to handle a function definition.
-
-        @param node reference to the node to handle
-        @type ast.AsyncFunctionDef
-        """
-        self.__visitWithStack(node)
-
-    def visit_Return(self, node):
-        """
-        Public method to handle a return node.
-
-        @param node reference to the node to handle
-        @type ast.Return
-        """
-        self.returns.append(node)
-        self.generic_visit(node)
-
-    def visit_Assign(self, node):
-        """
-        Public method to handle an assign node.
-
-        @param node reference to the node to handle
-        @type ast.Assign
-        """
-        if not self.__stack:
-            return
-
-        self.generic_visit(node.value)
-
-        target = node.targets[0]
-        if isinstance(target, ast.Tuple) and not isinstance(node.value, ast.Tuple):
-            # skip unpacking assign
-            return
-
-        self.__visitAssignTarget(target)
-
-    def visit_Name(self, node):
-        """
-        Public method to handle a name node.
-
-        @param node reference to the node to handle
-        @type ast.Name
-        """
-        if self.__stack:
-            self.refs[node.id].append(node.lineno)
-
-    def __visitAssignTarget(self, node):
-        """
-        Private method to handle an assign target node.
-
-        @param node reference to the node to handle
-        @type ast.AST
-        """
-        if isinstance(node, ast.Tuple):
-            for elt in node.elts:
-                self.__visitAssignTarget(elt)
-            return
-
-        if not self.__loopCount and isinstance(node, ast.Name):
-            self.assigns[node.id].append(node.lineno)
-            return
-
-        self.generic_visit(node)
-
-    def __checkFunction(self, node):
-        """
-        Private method to check a function definition node.
-
-        @param node reference to the node to check
-        @type ast.AsyncFunctionDef or ast.FunctionDef
-        """
-        if not self.returns or not node.body:
-            return
-
-        if len(node.body) == 1 and isinstance(node.body[-1], ast.Return):
-            # skip functions that consist of `return None` only
-            return
-
-        if not self.__resultExists():
-            self.__checkUnnecessaryReturnNone()
-            return
-
-        self.__checkImplicitReturnValue()
-        self.__checkImplicitReturn(node.body[-1])
-
-        for n in self.returns:
-            if n.value:
-                self.__checkUnnecessaryAssign(n.value)
-
-    def __isNone(self, node):
-        """
-        Private method to check, if a node value is None.
-
-        @param node reference to the node to check
-        @type ast.AST
-        @return flag indicating the node contains a None value
-        @rtype bool
-        """
-        return AstUtilities.isNameConstant(node) and AstUtilities.getValue(node) is None
-
-    def __isFalse(self, node):
-        """
-        Private method to check, if a node value is False.
-
-        @param node reference to the node to check
-        @type ast.AST
-        @return flag indicating the node contains a False value
-        @rtype bool
-        """
-        return (
-            AstUtilities.isNameConstant(node) and AstUtilities.getValue(node) is False
-        )
-
-    def __resultExists(self):
-        """
-        Private method to check the existance of a return result.
-
-        @return flag indicating the existence of a return result
-        @rtype bool
-        """
-        for node in self.returns:
-            value = node.value
-            if value and not self.__isNone(value):
-                return True
-
-        return False
-
-    def __checkImplicitReturnValue(self):
-        """
-        Private method to check for implicit return values.
-        """
-        for node in self.returns:
-            if not node.value:
-                self.violations.append((node, "M-832"))
-
-    def __checkUnnecessaryReturnNone(self):
-        """
-        Private method to check for an unnecessary 'return None' statement.
-        """
-        for node in self.returns:
-            if self.__isNone(node.value):
-                self.violations.append((node, "M-831"))
-
-    def __checkImplicitReturn(self, node):
-        """
-        Private method to check for an implicit return statement.
-
-        @param node reference to the node to check
-        @type ast.AST
-        """
-        if isinstance(node, ast.If):
-            if not node.body or not node.orelse:
-                self.violations.append((node, "M-833"))
-                return
-
-            self.__checkImplicitReturn(node.body[-1])
-            self.__checkImplicitReturn(node.orelse[-1])
-            return
-
-        if isinstance(node, (ast.For, ast.AsyncFor)) and node.orelse:
-            self.__checkImplicitReturn(node.orelse[-1])
-            return
-
-        if isinstance(node, (ast.With, ast.AsyncWith)):
-            self.__checkImplicitReturn(node.body[-1])
-            return
-
-        if isinstance(node, ast.Assert) and self.__isFalse(node.test):
-            return
-
-        try:
-            okNodes = (ast.Return, ast.Raise, ast.While, ast.Try)
-        except AttributeError:
-            okNodes = (ast.Return, ast.Raise, ast.While)
-        if not isinstance(node, okNodes):
-            self.violations.append((node, "M-833"))
-
-    def __checkUnnecessaryAssign(self, node):
-        """
-        Private method to check for an unnecessary assign statement.
-
-        @param node reference to the node to check
-        @type ast.AST
-        """
-        if not isinstance(node, ast.Name):
-            return
-
-        varname = node.id
-        returnLineno = node.lineno
-
-        if varname not in self.assigns:
-            return
-
-        if varname not in self.refs:
-            self.violations.append((node, "M-834"))
-            return
-
-        if self.__hasRefsBeforeNextAssign(varname, returnLineno):
-            return
-
-        self.violations.append((node, "M-834"))
-
-    def __hasRefsBeforeNextAssign(self, varname, returnLineno):
-        """
-        Private method to check for references before a following assign
-        statement.
-
-        @param varname variable name to check for
-        @type str
-        @param returnLineno line number of the return statement
-        @type int
-        @return flag indicating the existence of references
-        @rtype bool
-        """
-        beforeAssign = 0
-        afterAssign = None
-
-        for lineno in sorted(self.assigns[varname]):
-            if lineno > returnLineno:
-                afterAssign = lineno
-                break
-
-            if lineno <= returnLineno:
-                beforeAssign = lineno
-
-        for lineno in self.refs[varname]:
-            if lineno == returnLineno:
-                continue
-
-            if afterAssign:
-                if beforeAssign < lineno <= afterAssign:
-                    return True
-
-            elif beforeAssign < lineno:
-                return True
-
-        return False
-
-
-class DateTimeVisitor(ast.NodeVisitor):
-    """
-    Class implementing a node visitor to check datetime function calls.
-
-    Note: This class is modeled after flake8_datetimez checker.
-    """
-
-    def __init__(self):
-        """
-        Constructor
-        """
-        super().__init__()
-
-        self.violations = []
-
-    def __getFromKeywords(self, keywords, name):
-        """
-        Private method to get a keyword node given its name.
-
-        @param keywords list of keyword argument nodes
-        @type list of ast.AST
-        @param name name of the keyword node
-        @type str
-        @return keyword node
-        @rtype ast.AST
-        """
-        for keyword in keywords:
-            if keyword.arg == name:
-                return keyword
-
-        return None
-
-    def visit_Call(self, node):
-        """
-        Public method to handle a function call.
-
-        Every datetime related function call is check for use of the naive
-        variant (i.e. use without TZ info).
-
-        @param node reference to the node to be processed
-        @type ast.Call
-        """
-        # datetime.something()
-        isDateTimeClass = (
-            isinstance(node.func, ast.Attribute)
-            and isinstance(node.func.value, ast.Name)
-            and node.func.value.id == "datetime"
-        )
-
-        # datetime.datetime.something()
-        isDateTimeModuleAndClass = (
-            isinstance(node.func, ast.Attribute)
-            and isinstance(node.func.value, ast.Attribute)
-            and node.func.value.attr == "datetime"
-            and isinstance(node.func.value.value, ast.Name)
-            and node.func.value.value.id == "datetime"
-        )
-
-        if isDateTimeClass:
-            if node.func.attr == "datetime":
-                # datetime.datetime(2000, 1, 1, 0, 0, 0, 0,
-                #                   datetime.timezone.utc)
-                isCase1 = len(node.args) >= 8 and not (
-                    AstUtilities.isNameConstant(node.args[7])
-                    and AstUtilities.getValue(node.args[7]) is None
-                )
-
-                # datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
-                tzinfoKeyword = self.__getFromKeywords(node.keywords, "tzinfo")
-                isCase2 = tzinfoKeyword is not None and not (
-                    AstUtilities.isNameConstant(tzinfoKeyword.value)
-                    and AstUtilities.getValue(tzinfoKeyword.value) is None
-                )
-
-                if not (isCase1 or isCase2):
-                    self.violations.append((node, "M-301"))
-
-            elif node.func.attr == "time":
-                # time(12, 10, 45, 0, datetime.timezone.utc)
-                isCase1 = len(node.args) >= 5 and not (
-                    AstUtilities.isNameConstant(node.args[4])
-                    and AstUtilities.getValue(node.args[4]) is None
-                )
-
-                # datetime.time(12, 10, 45, tzinfo=datetime.timezone.utc)
-                tzinfoKeyword = self.__getFromKeywords(node.keywords, "tzinfo")
-                isCase2 = tzinfoKeyword is not None and not (
-                    AstUtilities.isNameConstant(tzinfoKeyword.value)
-                    and AstUtilities.getValue(tzinfoKeyword.value) is None
-                )
-
-                if not (isCase1 or isCase2):
-                    self.violations.append((node, "M-321"))
-
-            elif node.func.attr == "date":
-                self.violations.append((node, "M-311"))
-
-        if isDateTimeClass or isDateTimeModuleAndClass:
-            if node.func.attr == "today":
-                self.violations.append((node, "M-302"))
-
-            elif node.func.attr == "utcnow":
-                self.violations.append((node, "M-303"))
-
-            elif node.func.attr == "utcfromtimestamp":
-                self.violations.append((node, "M-304"))
-
-            elif node.func.attr in "now":
-                # datetime.now(UTC)
-                isCase1 = (
-                    len(node.args) == 1
-                    and len(node.keywords) == 0
-                    and not (
-                        AstUtilities.isNameConstant(node.args[0])
-                        and AstUtilities.getValue(node.args[0]) is None
-                    )
-                )
-
-                # datetime.now(tz=UTC)
-                tzKeyword = self.__getFromKeywords(node.keywords, "tz")
-                isCase2 = tzKeyword is not None and not (
-                    AstUtilities.isNameConstant(tzKeyword.value)
-                    and AstUtilities.getValue(tzKeyword.value) is None
-                )
-
-                if not (isCase1 or isCase2):
-                    self.violations.append((node, "M-305"))
-
-            elif node.func.attr == "fromtimestamp":
-                # datetime.fromtimestamp(1234, UTC)
-                isCase1 = (
-                    len(node.args) == 2
-                    and len(node.keywords) == 0
-                    and not (
-                        AstUtilities.isNameConstant(node.args[1])
-                        and AstUtilities.getValue(node.args[1]) is None
-                    )
-                )
-
-                # datetime.fromtimestamp(1234, tz=UTC)
-                tzKeyword = self.__getFromKeywords(node.keywords, "tz")
-                isCase2 = tzKeyword is not None and not (
-                    AstUtilities.isNameConstant(tzKeyword.value)
-                    and AstUtilities.getValue(tzKeyword.value) is None
-                )
-
-                if not (isCase1 or isCase2):
-                    self.violations.append((node, "M-306"))
-
-            elif node.func.attr == "strptime":
-                # datetime.strptime(...).replace(tzinfo=UTC)
-                parent = getattr(node, "_dtCheckerParent", None)
-                pparent = getattr(parent, "_dtCheckerParent", None)
-                if not (
-                    isinstance(parent, ast.Attribute) and parent.attr == "replace"
-                ) or not isinstance(pparent, ast.Call):
-                    isCase1 = False
-                else:
-                    tzinfoKeyword = self.__getFromKeywords(pparent.keywords, "tzinfo")
-                    isCase1 = tzinfoKeyword is not None and not (
-                        AstUtilities.isNameConstant(tzinfoKeyword.value)
-                        and AstUtilities.getValue(tzinfoKeyword.value) is None
-                    )
-
-                if not isCase1:
-                    self.violations.append((node, "M-307"))
-
-            elif node.func.attr == "fromordinal":
-                self.violations.append((node, "M-308"))
-
-        # date.something()
-        isDateClass = (
-            isinstance(node.func, ast.Attribute)
-            and isinstance(node.func.value, ast.Name)
-            and node.func.value.id == "date"
-        )
-
-        # datetime.date.something()
-        isDateModuleAndClass = (
-            isinstance(node.func, ast.Attribute)
-            and isinstance(node.func.value, ast.Attribute)
-            and node.func.value.attr == "date"
-            and isinstance(node.func.value.value, ast.Name)
-            and node.func.value.value.id == "datetime"
-        )
-
-        if isDateClass or isDateModuleAndClass:
-            if node.func.attr == "today":
-                self.violations.append((node, "M-312"))
-
-            elif node.func.attr == "fromtimestamp":
-                self.violations.append((node, "M-313"))
-
-            elif node.func.attr == "fromordinal":
-                self.violations.append((node, "M-314"))
-
-            elif node.func.attr == "fromisoformat":
-                self.violations.append((node, "M-315"))
-
-        self.generic_visit(node)
-
-
-class SysVersionVisitor(ast.NodeVisitor):
-    """
-    Class implementing a node visitor to check the use of sys.version and
-    sys.version_info.
-
-    Note: This class is modeled after flake8-2020 v1.8.1.
-    """
-
-    def __init__(self):
-        """
-        Constructor
-        """
-        super().__init__()
-
-        self.violations = []
-        self.__fromImports = {}
-
-    def visit_ImportFrom(self, node):
-        """
-        Public method to handle a from ... import ... statement.
-
-        @param node reference to the node to be processed
-        @type ast.ImportFrom
-        """
-        for alias in node.names:
-            if node.module is not None and not alias.asname:
-                self.__fromImports[alias.name] = node.module
-
-        self.generic_visit(node)
-
-    def __isSys(self, attr, node):
-        """
-        Private method to check for a reference to sys attribute.
-
-        @param attr attribute name
-        @type str
-        @param node reference to the node to be checked
-        @type ast.Node
-        @return flag indicating a match
-        @rtype bool
-        """
-        match = False
-        if (
-            isinstance(node, ast.Attribute)
-            and isinstance(node.value, ast.Name)
-            and node.value.id == "sys"
-            and node.attr == attr
-        ) or (
-            isinstance(node, ast.Name)
-            and node.id == attr
-            and self.__fromImports.get(node.id) == "sys"
-        ):
-            match = True
-
-        return match
-
-    def __isSysVersionUpperSlice(self, node, n):
-        """
-        Private method to check the upper slice of sys.version.
-
-        @param node reference to the node to be checked
-        @type ast.Node
-        @param n slice value to check against
-        @type int
-        @return flag indicating a match
-        @rtype bool
-        """
-        return (
-            self.__isSys("version", node.value)
-            and isinstance(node.slice, ast.Slice)
-            and node.slice.lower is None
-            and AstUtilities.isNumber(node.slice.upper)
-            and AstUtilities.getValue(node.slice.upper) == n
-            and node.slice.step is None
-        )
-
-    def visit_Subscript(self, node):
-        """
-        Public method to handle a subscript.
-
-        @param node reference to the node to be processed
-        @type ast.Subscript
-        """
-        if self.__isSysVersionUpperSlice(node, 1):
-            self.violations.append((node.value, "M-423"))
-        elif self.__isSysVersionUpperSlice(node, 3):
-            self.violations.append((node.value, "M-401"))
-        elif (
-            self.__isSys("version", node.value)
-            and isinstance(node.slice, ast.Index)
-            and AstUtilities.isNumber(node.slice.value)
-            and AstUtilities.getValue(node.slice.value) == 2
-        ):
-            self.violations.append((node.value, "M-402"))
-        elif (
-            self.__isSys("version", node.value)
-            and isinstance(node.slice, ast.Index)
-            and AstUtilities.isNumber(node.slice.value)
-            and AstUtilities.getValue(node.slice.value) == 0
-        ):
-            self.violations.append((node.value, "M-421"))
-
-        self.generic_visit(node)
-
-    def visit_Compare(self, node):
-        """
-        Public method to handle a comparison.
-
-        @param node reference to the node to be processed
-        @type ast.Compare
-        """
-        if (
-            isinstance(node.left, ast.Subscript)
-            and self.__isSys("version_info", node.left.value)
-            and isinstance(node.left.slice, ast.Index)
-            and AstUtilities.isNumber(node.left.slice.value)
-            and AstUtilities.getValue(node.left.slice.value) == 0
-            and len(node.ops) == 1
-            and isinstance(node.ops[0], ast.Eq)
-            and AstUtilities.isNumber(node.comparators[0])
-            and AstUtilities.getValue(node.comparators[0]) == 3
-        ):
-            self.violations.append((node.left, "M-411"))
-        elif (
-            self.__isSys("version", node.left)
-            and len(node.ops) == 1
-            and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
-            and AstUtilities.isString(node.comparators[0])
-        ):
-            if len(AstUtilities.getValue(node.comparators[0])) == 1:
-                errorCode = "M-422"
-            else:
-                errorCode = "M-403"
-            self.violations.append((node.left, errorCode))
-        elif (
-            isinstance(node.left, ast.Subscript)
-            and self.__isSys("version_info", node.left.value)
-            and isinstance(node.left.slice, ast.Index)
-            and AstUtilities.isNumber(node.left.slice.value)
-            and AstUtilities.getValue(node.left.slice.value) == 1
-            and len(node.ops) == 1
-            and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
-            and AstUtilities.isNumber(node.comparators[0])
-        ):
-            self.violations.append((node, "M-413"))
-        elif (
-            isinstance(node.left, ast.Attribute)
-            and self.__isSys("version_info", node.left.value)
-            and node.left.attr == "minor"
-            and len(node.ops) == 1
-            and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
-            and AstUtilities.isNumber(node.comparators[0])
-        ):
-            self.violations.append((node, "M-414"))
-
-        self.generic_visit(node)
-
-    def visit_Attribute(self, node):
-        """
-        Public method to handle an attribute.
-
-        @param node reference to the node to be processed
-        @type ast.Attribute
-        """
-        if (
-            isinstance(node.value, ast.Name)
-            and node.value.id == "six"
-            and node.attr == "PY3"
-        ):
-            self.violations.append((node, "M-412"))
-
-        self.generic_visit(node)
-
-    def visit_Name(self, node):
-        """
-        Public method to handle an name.
-
-        @param node reference to the node to be processed
-        @type ast.Name
-        """
-        if node.id == "PY3" and self.__fromImports.get(node.id) == "six":
-            self.violations.append((node, "M-412"))
-
-        self.generic_visit(node)
-
-
-class DefaultMatchCaseVisitor(ast.NodeVisitor):
-    """
-    Class implementing a node visitor to check default match cases.
-
-    Note: This class is modeled after flake8-spm v0.0.1.
-    """
-
-    def __init__(self):
-        """
-        Constructor
-        """
-        super().__init__()
-
-        self.violations = []
-
-    def visit_Match(self, node):
-        """
-        Public method to handle Match nodes.
-
-        @param node reference to the node to be processed
-        @type ast.Match
-        """
-        for badNode, issueCode in self.__badNodes(node):
-            self.violations.append((badNode, issueCode))
-
-        self.generic_visit(node)
-
-    def __badNodes(self, node):
-        """
-        Private method to yield bad match nodes.
-
-        @param node reference to the node to be processed
-        @type ast.Match
-        @yield tuple containing a reference to bad match case node and the corresponding
-            issue code
-        @ytype tyuple of (ast.AST, str)
-        """
-        for case in node.cases:
-            if self.__emptyMatchDefault(case):
-                if self.__lastStatementDoesNotRaise(case):
-                    yield self.__findBadNode(case), "M-901"
-                elif self.__returnPrecedesExceptionRaising(case):
-                    yield self.__findBadNode(case), "M-902"
-
-    def __emptyMatchDefault(self, case):
-        """
-        Private method to check for an empty default match case.
-
-        @param case reference to the node to be processed
-        @type ast.match_case
-        @return flag indicating an empty default match case
-        @rtype bool
-        """
-        pattern = case.pattern
-        return isinstance(pattern, ast.MatchAs) and (
-            pattern.pattern is None
-            or (
-                isinstance(pattern.pattern, ast.MatchAs)
-                and pattern.pattern.pattern is None
-            )
-        )
-
-    def __lastStatementDoesNotRaise(self, case):
-        """
-        Private method to check that the last case statement does not raise an
-        exception.
-
-        @param case reference to the node to be processed
-        @type ast.match_case
-        @return flag indicating that the last case statement does not raise an
-            exception
-        @rtype bool
-        """
-        return not isinstance(case.body[-1], ast.Raise)
-
-    def __returnPrecedesExceptionRaising(self, case):
-        """
-        Private method to check that no return precedes an exception raising.
-
-        @param case reference to the node to be processed
-        @type ast.match_case
-        @return flag indicating that a return precedes an exception raising
-        @rtype bool
-        """
-        returnIndex = -1
-        raiseIndex = -1
-        for index, body in enumerate(case.body):
-            if isinstance(body, ast.Return):
-                returnIndex = index
-            elif isinstance(body, ast.Raise):
-                raiseIndex = index
-        return returnIndex >= 0 and returnIndex < raiseIndex
-
-    def __findBadNode(self, case) -> ast.AST:
-        """
-        Private method returning a reference to the bad node of a case node.
-
-        @param case reference to the node to be processed
-        @type ast.match_case
-        @return reference to the bad node
-        @rtype ast.AST
-        """
-        for body in case.body:
-            # Handle special case when return precedes exception raising.
-            # In this case the bad node is that with the return statement.
-            if isinstance(body, ast.Return):
-                return body
-
-        return case.body[-1]
-
-
-#
-# eflag: noqa = M-891
+            self.addErrorFromNode(*violation)

eric ide

mercurial