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