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

branch
eric7
changeset 11140
b823386f7591
parent 11138
1f743bad6fd3
child 11142
2f0fb22c1d63
diff -r cd22e8e705f4 -r b823386f7591 src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py	Sun Feb 16 15:06:29 2025 +0100
+++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py	Mon Feb 17 16:28:25 2025 +0100
@@ -247,6 +247,9 @@
         "M853",
         ## commented code
         "M891",
+        ## structural pattern matching
+        "M901",
+        "M902",
     ]
 
     Formatter = Formatter()
@@ -452,6 +455,7 @@
             (self.__checkImplicitStringConcat, ("M851", "M852")),
             (self.__checkExplicitStringConcat, ("M853",)),
             (self.__checkCommentedCode, ("M891",)),
+            (self.__checkDefaultMatchCase, ("M901", "M902")),
         ]
 
         # the eradicate whitelist
@@ -1502,6 +1506,21 @@
             ):
                 self.__error(node.lineno - 1, node.col_offset, "M853")
 
+    #################################################################################
+    ## The following method checks default match cases.
+    #################################################################################
+
+    def __checkDefaultMatchCase(self):
+        """
+        Private method to check the default match case.
+        """
+        visitor = DefaultMatchCaseVisitor()
+        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):
     """
@@ -4237,7 +4256,7 @@
     Class implementing a node visitor to check the use of sys.version and
     sys.version_info.
 
-    Note: This class is modeled after flake8-2020 checker.
+    Note: This class is modeled after flake8-2020 v1.8.1.
     """
 
     def __init__(self):
@@ -4418,5 +4437,116 @@
         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), "M901"
+                elif self.__returnPrecedesExceptionRaising(case):
+                    yield self.__findBadNode(case), "M902"
+
+    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 = M891

eric ide

mercurial