--- a/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py Sat Mar 10 14:57:23 2018 +0100 +++ b/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py Sun Mar 11 15:27:23 2018 +0100 @@ -32,6 +32,7 @@ "M611", "M612", "M613", "M621", "M622", "M623", "M624", "M625", "M631", "M632", + "M651", "M652", "M653", "M654", "M655", "M701", "M702", @@ -108,6 +109,7 @@ (self.__checkFormatString, ("M611", "M612", "M613", "M621", "M622", "M623", "M624", "M625", "M631", "M632")), + (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")), (self.__checkFuture, ("M701", "M702")), (self.__checkPrintStatements, ("M801",)), (self.__checkTuple, ("M811", )), @@ -644,6 +646,15 @@ if key2.s < key1.s: self.__error(key2.lineno - 1, key2.col_offset, "M201", key2.s, key1.s) + + def __checkLogging(self): + """ + Private method to check logging statements. + """ + visitor = LoggingVisitor() + visitor.visit(self.__tree) + for node, reason in visitor.violations: + self.__error(node.lineno - 1, node.col_offset, reason) class TextVisitor(ast.NodeVisitor): @@ -784,5 +795,177 @@ self.calls[node.args[0]] = (node, True) super(TextVisitor, self).generic_visit(node) + +class LoggingVisitor(ast.NodeVisitor): + """ + Class implementing a node visitor to check logging statements. + """ + LoggingLevels = { + "debug", + "critical", + "error", + "info", + "warn", + "warning", + } + + def __init__(self): + """ + Constructor + """ + super(LoggingVisitor, self).__init__() + + self.__currentLoggingCall = None + self.__currentLoggingArgument = None + self.__currentLoggingLevel = None + self.__currentExtraKeyword = None + self.violations = [] + + def __withinLoggingStatement(self): + """ + Private method to check, if we are inside a logging statement. + + @return flag indicating we are inside a logging statement + @rtype bool + """ + return self.__currentLoggingCall is not None + + def __withinLoggingArgument(self): + """ + Private method to check, if we are inside a logging argument. + + @return flag indicating we are inside a logging argument + @rtype bool + """ + return self.__currentLoggingArgument is not None + + def __withinExtraKeyword(self, node): + """ + Private method to check, if we are inside the extra keyword. + + @param node reference to the node to be checked + @type ast.keyword + @return flag indicating we are inside the extra keyword + @rtype bool + """ + return self.__currentExtraKeyword is not None and \ + self.__currentExtraKeyword != node + + def __detectLoggingLevel(self, node): + """ + Private method to decide whether an AST Call is a logging call. + + @param node reference to the node to be processed + @type ast.Call + @return logging level + @rtype str or None + """ + try: + if node.func.value.id == "warnings": + return None + + if node.func.attr in LoggingVisitor.LoggingLevels: + return node.func.attr + except AttributeError: + pass + + return None + + def __isFormatCall(self, node): + """ + Private method to check if a function call uses format. + + @param node reference to the node to be processed + @type ast.Call + @return flag indicating the function call uses format + @rtype bool + """ + try: + return node.func.attr == "format" + except AttributeError: + return False + + def visit_Call(self, node): + """ + Public method to handle a function call. + + Every logging statement and string format is expected to be a function + call. + + @param node reference to the node to be processed + @type ast.Call + """ + # we are in a logging statement + if self.__withinLoggingStatement(): + if self.__withinLoggingArgument() and self.__isFormatCall(node): + self.violations.append((node, "M651")) + super(LoggingVisitor, self).generic_visit(node) + return + + loggingLevel = self.__detectLoggingLevel(node) + + if loggingLevel and self.__currentLoggingLevel is None: + self.__currentLoggingLevel = loggingLevel + + # we are in some other statement + if loggingLevel is None: + super(LoggingVisitor, self).generic_visit(node) + return + + # we are entering a new logging statement + self.__currentLoggingCall = node + + if loggingLevel == "warn": + self.violations.append((node, "M655")) + + for index, child in enumerate(ast.iter_child_nodes(node)): + if index == 1: + self.__currentLoggingArgument = child + if index > 1 and isinstance(child, ast.keyword) and \ + child.arg == "extra": + self.__currentExtraKeyword = child + + super(LoggingVisitor, self).visit(child) + + self.__currentLoggingArgument = None + self.__currentExtraKeyword = None + + self.__currentLoggingCall = None + self.__currentLoggingLevel = None + + def visit_BinOp(self, node): + """ + Public method to handle binary operations while processing the first + logging argument. + + @param node reference to the node to be processed + @type ast.BinOp + """ + if self.__withinLoggingStatement() and self.__withinLoggingArgument(): + # handle percent format + if isinstance(node.op, ast.Mod): + self.violations.append((node, "M652")) + + # handle string concat + if isinstance(node.op, ast.Add): + self.violations.append((node, "M653")) + + super(LoggingVisitor, 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 + """ + if sys.version_info >= (3, 6): + if self.__withinLoggingStatement(): + if any(isinstance(i, ast.FormattedValue) for i in node.values): + if self.__withinLoggingArgument(): + self.violations.append((node, "M654")) + + super(LoggingVisitor, self).generic_visit(node) + # # eflag: noqa = M702