Thu, 30 Nov 2023 16:39:46 +0100
Code Style Checker
- Updated the logging checker to support more cases (flake8_logging_format 0.9.0).
--- a/docs/ThirdParty.md Thu Nov 30 11:59:40 2023 +0100 +++ b/docs/ThirdParty.md Thu Nov 30 16:39:46 2023 +0100 @@ -28,6 +28,7 @@ | flake8-future-annotations | 1.1.0 | MIT License (MIT) | | flake8-implicit-str-concat | 0.4.0 | MIT License (MIT) | | flake8-local-import | 1.0.6 | MIT License (MIT) | +| flake8-logging-format | 0.9.0 | UNKNOWN | | flake8-pep585 | 0.1.7 | Mozilla Public License Version 2.0 | | flake8-pep604 | 1.1.0 | MIT License (MIT) | | flake8-simplify | 0.21.0 | MIT License (MIT) |
--- a/docs/changelog.md Thu Nov 30 11:59:40 2023 +0100 +++ b/docs/changelog.md Thu Nov 30 16:39:46 2023 +0100 @@ -4,6 +4,7 @@ - bug fixes - Code Style Checker - Updated these checkers to support more cases. + - Logging - Miscellaneous - Simplify - Virtual Environments
--- a/eric7.epj Thu Nov 30 11:59:40 2023 +0100 +++ b/eric7.epj Thu Nov 30 16:39:46 2023 +0100 @@ -66,9 +66,9 @@ "CopyrightAuthor": "", "CopyrightMinFileSize": 0, "DocstringType": "eric_black", - "EnabledCheckerCategories": "C, D, E, I, M, NO, N, Y, U, W", + "EnabledCheckerCategories": "C, D, E, I, L, M, NO, N, Y, U, W", "ExcludeFiles": "*/ThirdParty/*, */coverage/*, */Ui_*.py, */Examples/*, */pycodestyle.py,*/pyflakes/checker.py,*/mccabe.py,*/eradicate.py,*/ast_unparse.py,*/piplicenses.py,*/pipdeptree.py,*/MCUScripts/*,*/MicroPython/Tools/*", - "ExcludeMessages": "C101,E203,E265,E266,E305,E402,M201,M701,M702,M811,M834,M852,N802,N803,N807,N808,N821,W293,W503,Y119,Y401,Y402", + "ExcludeMessages": "C101,E203,E265,E266,E305,E402,M201,M701,M702,M811,M834,N802,N803,N807,N808,N821,W293,W503,Y119,Y401,Y402", "FixCodes": "", "FixIssues": false, "FutureChecker": "", @@ -1484,6 +1484,10 @@ "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/LocalImportVisitor.py", "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/__init__.py", "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/translations.py", + "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Logging/LoggingChecker.py", + "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Logging/LoggingFormatVisitor.py", + "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Logging/__init__.py", + "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Logging/translations.py", "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py", "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousDefaults.py", "src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/__init__.py",
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py Thu Nov 30 11:59:40 2023 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py Thu Nov 30 16:39:46 2023 +0100 @@ -21,6 +21,7 @@ from Complexity.ComplexityChecker import ComplexityChecker from DocStyle.DocStyleChecker import DocStyleChecker from Imports.ImportsChecker import ImportsChecker +from Logging.LoggingChecker import LoggingChecker from Miscellaneous.MiscellaneousChecker import MiscellaneousChecker from NameOrder.NameOrderChecker import NameOrderChecker from Naming.NamingStyleChecker import NamingStyleChecker @@ -582,6 +583,21 @@ stats.update(asyncChecker.counters) errors += asyncChecker.errors + # checking logging statements + loggingChecker = LoggingChecker( + source, + filename, + tree, + select, + ignore, + [], + repeatMessages, + {}, # no arguments yet + ) + loggingChecker.run() + stats.update(loggingChecker.counters) + errors += loggingChecker.errors + elif syntaxError: errors = [syntaxError] stats.update(syntaxStats)
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py Thu Nov 30 11:59:40 2023 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py Thu Nov 30 16:39:46 2023 +0100 @@ -87,6 +87,7 @@ "D": QCoreApplication.translate("CheckerCategories", "Documentation"), "E": QCoreApplication.translate("CheckerCategories", "Errors"), "I": QCoreApplication.translate("CheckerCategories", "Imports"), + "L": QCoreApplication.translate("CheckerCategories", "Logging"), "M": QCoreApplication.translate("CheckerCategories", "Miscellaneous"), "N": QCoreApplication.translate("CheckerCategories", "Naming"), "NO": QCoreApplication.translate("CheckerCategories", "Name Order"),
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerUtilities.py Thu Nov 30 11:59:40 2023 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerUtilities.py Thu Nov 30 16:39:46 2023 +0100 @@ -40,6 +40,8 @@ itm.setIcon(column, EricPixmapCache.getIcon("docstringError")) elif messageCategory == "I": itm.setIcon(column, EricPixmapCache.getIcon("imports")) + elif messageCategory == "L": + itm.setIcon(column, EricPixmapCache.getIcon("logViewer")) elif messageCategory == "NO": itm.setIcon(column, EricPixmapCache.getIcon("nameOrderError")) elif messageCategory == "P":
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Logging/LoggingChecker.py Thu Nov 30 16:39:46 2023 +0100 @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a checker for logging related issues. +""" + +import copy + + +class LoggingChecker: + """ + Class implementing a checker for logging related issues. + """ + + Codes = [ + ## Logging format + "L101", + "L102", + "L103", + "L104", + "L110", + ] + + def __init__(self, source, filename, tree, select, ignore, expected, repeat, args): + """ + Constructor + + @param source source code to be checked + @type list of str + @param filename name of the source file + @type str + @param tree AST tree of the source code + @type ast.Module + @param select list of selected codes + @type list of str + @param ignore list of codes to be ignored + @type list of str + @param expected list of expected codes + @type list of str + @param repeat flag indicating to report each occurrence of a code + @type bool + @param args dictionary of arguments for the various checks + @type dict + """ + self.__select = tuple(select) + self.__ignore = ("",) if select else tuple(ignore) + self.__expected = expected[:] + self.__repeat = repeat + self.__filename = filename + self.__source = source[:] + self.__tree = copy.deepcopy(tree) + self.__args = args + + # statistics counters + self.counters = {} + + # collection of detected errors + self.errors = [] + + checkersWithCodes = [ + (self.__checkLoggingFormat, ("L101", "L102", "L103", "L104", "L110")), + ] + + 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.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 __checkLoggingFormat(self): + """ + Private method to check logging statements. + """ + from .LoggingFormatVisitor import LoggingFormatVisitor + + visitor = LoggingFormatVisitor() + visitor.visit(self.__tree) + for node, reason in visitor.violations: + self.__error(node.lineno - 1, node.col_offset, reason)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Logging/LoggingFormatVisitor.py Thu Nov 30 16:39:46 2023 +0100 @@ -0,0 +1,373 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a node visitor to check logging formatting issues. +""" + +import ast +import contextlib + + +_LoggingLevels = { + "debug", + "critical", + "error", + "exception", + "info", + "warn", + "warning", +} + + +# default LogRecord attributes that shouldn't be overwritten by extra dict +_ReservedAttrs = { + "args", "asctime", "created", "exc_info", "exc_text", "filename", + "funcName", "levelname", "levelno", "lineno", "module", + "msecs", "message", "msg", "name", "pathname", "process", + "processName", "relativeCreated", "stack_info", "thread", "threadName"} + +####################################################################### +## LoggingFormatVisitor +## +## adapted from: flake8-logging-format v0.9.0 +## +## Original: Copyright (c) 2017 Globality Engineering +####################################################################### + +class LoggingFormatVisitor(ast.NodeVisitor): + """ + Class implementing a node visitor to check logging formatting issues. + """ + + def __init__(self): + """ + Constructor + """ + super().__init__() + + self.__currentLoggingCall = None + self.__currentLoggingArgument = None + self.__currentLoggingLevel = None + self.__currentExtraKeyword = None + self.__currentExceptNames = [] + 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 __getExceptHandlerName(self, node): + """ + Private method to get the exception name from an ExceptHandler node. + + @param node reference to the node to be checked + @type ast.ExceptHandler + @return exception name + @rtype str + """ + name = node.name + if not name: + return None + + return name + + def __getIdAttr(self, value): + """ + Private method to check if value has id attribute and return it. + + @param value value to get id from + @type ast.Name + @return ID of value + @rtype str + """ + """Check if value has id attribute and return it. + + :param value: The value to get id from. + :return: The value.id. + """ + if not hasattr(value, "id") and hasattr(value, "value"): + value = value.value + + return value.id + + 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 + """ + with contextlib.suppress(AttributeError): + if self.__getIdAttr(node.func.value) in ("parser", "warnings"): + return None + + if node.func.attr in _LoggingLevels: + return node.func.attr + + 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 __shouldCheckExtraFieldClash(self, node): + """ + Private method to check, if the extra field clash check should be done. + + @param node reference to the node to be processed + @type ast.Dict + @return flag indicating to perform the check + @rtype bool + """ + return all( + ( + self.__withinLoggingStatement(), + self.__withinExtraKeyword(node), + ) + ) + + def __shouldCheckExtraException(self, node): + """ + Private method to check, if the check for extra exceptions should be done. + +c @type ast.Dict + @return flag indicating to perform the check + @rtype bool + """ + return all( + ( + self.__withinLoggingStatement(), + self.__withinExtraKeyword(node), + len(self.__currentExceptNames) > 0, + ) + ) + + def __isBareException(self, node): + """ + Private method to check, if the node is a bare exception name from an except + block. + + @param node reference to the node to be processed + @type ast.AST + @return flag indicating a bare exception + @rtype TYPE + """ + return isinstance(node, ast.Name) and node.id in self.__currentExceptNames + + def __isStrException(self, node): + """ + Private method to check if the node is the expression str(e) or unicode(e), + where e is an exception name from an except block. + + @param node reference to the node to be processed + @type ast.AST + @return flag indicating a string exception + @rtype TYPE + """ + return ( + isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id in ('str', 'unicode') + and node.args + and self.__isBareException(node.args[0]) + ) + + def __checkExceptionArg(self, node): + """ + Private method to check an exception argument. + + @param node reference to the node to be processed + @type ast.AST + """ + if self.__isBareException(node) or self.__isStrException(node): + self.violations.append((node, "L130")) + + def __checkExcInfo(self, node): + """ + Private method to check, if the exc_info keyword is used with logging.error or + logging.exception. + + @param node reference to the node to be processed + @type ast.AST + """ + if self.__currentLoggingLevel not in ('error', 'exception'): + return + + for kw in node.keywords: + if kw.arg == 'exc_info': + if self.__currentLoggingLevel == 'error': + violation = "L131" + else: + violation = "L132" + self.violations.append((node, violation)) + + 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() + and self.__withinLoggingArgument() + and self.__isFormatCall(node) + ): + self.violations.append((node, "L101")) + super().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().generic_visit(node) + return + + # we are entering a new logging statement + self.__currentLoggingCall = node + + if loggingLevel == "warn": + self.violations.append((node, "L110")) + + self.__checkExcInfo(node) + + for index, child in enumerate(ast.iter_child_nodes(node)): + if index == 1: + self.__currentLoggingArgument = child + if index >= 1: + self.__checkExceptionArg(child) + if index > 1 and isinstance(child, ast.keyword) and child.arg == "extra": + self.__currentExtraKeyword = child + + super().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, "L102")) + + # handle string concat + if isinstance(node.op, ast.Add): + self.violations.append((node, "L103")) + + super().generic_visit(node) + + def visit_Dict(self, node): + """ + Public method to handle dict arguments. + + @param node reference to the node to be processed + @type ast.Dict + """ + if self.__shouldCheckExtraFieldClash(node): + for key in node.keys: + # key can be None if the dict uses double star syntax + if key is not None and key.s in _ReservedAttrs: + self.violations.append((node, "L121", key.s)) + + if self.__shouldCheckExtraException(node): + for value in node.values: + self.__checkExceptionArg(value) + + super().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 ( + self.__withinLoggingStatement() + and any(isinstance(i, ast.FormattedValue) for i in node.values) + and self.__withinLoggingArgument() + ): + self.violations.append((node, "L104")) + + super().generic_visit(node) + + + def visit_ExceptHandler(self, node): + """ + Public method to handle an exception handler. + + @param node reference to the node to be processed + @type ast.ExceptHandler + """ + name = self.__getExceptHandlerName(node) + if not name: + super().generic_visit(node) + return + + self.__currentExceptNames.append(name) + + super().generic_visit(node) + + self.__currentExceptNames.pop()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Logging/__init__.py Thu Nov 30 16:39:46 2023 +0100 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing the logging style checker. +"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Logging/translations.py Thu Nov 30 16:39:46 2023 +0100 @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> +# + + +""" +Module implementing message translations for the code style plugin messages +(logging part). +""" + +from PyQt6.QtCore import QCoreApplication + +_loggingMessages = { + ## Logging format + "L101": QCoreApplication.translate( + "LoggingChecker", + "logging statement uses string.format()", + ), + "L102": QCoreApplication.translate( + "LoggingChecker", + "logging statement uses '%'", # __IGNORE_WARNING_M601__ + ), + "L103": QCoreApplication.translate( + "LoggingChecker", + "logging statement uses '+'", + ), + "L104": QCoreApplication.translate( + "LoggingChecker", + "logging statement uses f-string", + ), + "L110": QCoreApplication.translate( + "LoggingChecker", + "logging statement uses 'warn' instead of 'warning'", + ), + "L121": QCoreApplication.translate( + "LoggingChecker", + "logging statement uses an extra field that clashes with a LogRecord field: {0}" + ), + "L130": QCoreApplication.translate( + "LoggingChecker", + "logging statement uses exception in arguments" + ), + "L131": QCoreApplication.translate( + "LoggingChecker", + "logging: .exception(...) should be used instead of .error(..., exc_info=True)" + ), + "L132": QCoreApplication.translate( + "LoggingChecker", + "logging statement has redundant exc_info" + ), +} + +_loggingMessagesSampleArgs = { + "L121": ["pathname"] +}
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py Thu Nov 30 11:59:40 2023 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py Thu Nov 30 16:39:46 2023 +0100 @@ -182,12 +182,6 @@ "M625", "M631", "M632", - ## Logging - "M651", - "M652", - "M653", - "M654", - "M655", ## Future statements "M701", "M702", @@ -400,7 +394,6 @@ "M632", ), ), - (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")), (self.__checkFuture, ("M701", "M702")), (self.__checkGettext, ("M711",)), (self.__checkPrintStatements, ("M801",)), @@ -1278,15 +1271,6 @@ key1.value, ) - 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) - def __checkGettext(self): """ Private method to check the 'gettext' import statement. @@ -1437,7 +1421,7 @@ self.__error(node.lineno - 1, node.col_offset, "M217", node.name) ####################################################################### - ## The following method check for implicitly concatenated strings + ## The following methods check for implicitly concatenated strings. ## ## These methods are adapted from: flake8-implicit-str-concat v0.4.0 ## Original: Copyright (c) 2023 Dylan Turner @@ -1660,182 +1644,6 @@ super().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().__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 - """ - with contextlib.suppress(AttributeError): - if node.func.value.id == "warnings": - return None - - if node.func.attr in LoggingVisitor.LoggingLevels: - return node.func.attr - - 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() - and self.__withinLoggingArgument() - and self.__isFormatCall(node) - ): - self.violations.append((node, "M651")) - super().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().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().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().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 ( - self.__withinLoggingStatement() - and any(isinstance(i, ast.FormattedValue) for i in node.values) - and self.__withinLoggingArgument() - ): - self.violations.append((node, "M654")) - - super().generic_visit(node) - - ####################################################################### ## BugBearVisitor ##
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/translations.py Thu Nov 30 11:59:40 2023 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/translations.py Thu Nov 30 16:39:46 2023 +0100 @@ -510,27 +510,6 @@ "MiscellaneousChecker", "format call provides unused keyword ({0})", ), - ## Logging - "M651": QCoreApplication.translate( - "MiscellaneousChecker", - "logging statement uses string.format()", - ), - "M652": QCoreApplication.translate( - "MiscellaneousChecker", - "logging statement uses '%'", # __IGNORE_WARNING_M601__ - ), - "M653": QCoreApplication.translate( - "MiscellaneousChecker", - "logging statement uses '+'", - ), - "M654": QCoreApplication.translate( - "MiscellaneousChecker", - "logging statement uses f-string", - ), - "M655": QCoreApplication.translate( - "MiscellaneousChecker", - "logging statement uses 'warn' instead of 'warning'", - ), ## Future statements "M701": QCoreApplication.translate( "MiscellaneousChecker",
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/translations.py Thu Nov 30 11:59:40 2023 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/translations.py Thu Nov 30 16:39:46 2023 +0100 @@ -20,6 +20,7 @@ from .Complexity.translations import _complexityMessages, _complexityMessagesSampleArgs from .DocStyle.translations import _docStyleMessages, _docStyleMessagesSampleArgs from .Imports.translations import _importsMessages, _importsMessagesSampleArgs +from .Logging.translations import _loggingMessages, _loggingMessagesSampleArgs from .Miscellaneous.translations import ( _miscellaneousMessages, _miscellaneousMessagesSampleArgs, @@ -430,6 +431,7 @@ "D": _docStyleMessages, "E": _pycodestyleErrorMessages, "I": _importsMessages, + "L": _loggingMessages, "M": _miscellaneousMessages, "N": _namingStyleMessages, "NO": _nameOrderMessages, @@ -448,6 +450,7 @@ "D": _docStyleMessagesSampleArgs, "E": _pycodestyleErrorMessagesSampleArgs, "I": _importsMessagesSampleArgs, + "L": _loggingMessagesSampleArgs, "M": _miscellaneousMessagesSampleArgs, "NO": _nameOrderMessagesSampleArgs, "S": _securityMessagesSampleArgs,