From 83cecb0bf2b0985126c9b1f73cf1704419aa3a41 Mon Sep 17 00:00:00 2001 From: fabienhusse Date: Wed, 21 May 2025 15:32:56 +0200 Subject: [PATCH] :sparkles: feat: add new rule to check logging format interpolation --- .../DetectBadLoggingFormatInterpolation.java | 34 +++++++++++++++++++ ...tectBadLoggingFormatInterpolationTest.java | 14 ++++++++ ...tBadLoggingFormatInterpolationCompliant.py | 6 ++++ ...dLoggingFormatInterpolationNonCompliant.py | 7 ++++ 4 files changed, 61 insertions(+) create mode 100644 src/main/java/org/greencodeinitiative/creedengo/python/checks/DetectBadLoggingFormatInterpolation.java create mode 100644 src/test/java/org/greencodeinitiative/creedengo/python/checks/DetectBadLoggingFormatInterpolationTest.java create mode 100644 src/test/resources/checks/detectBadLoggingFormatInterpolationCompliant.py create mode 100644 src/test/resources/checks/detectBadLoggingFormatInterpolationNonCompliant.py diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/checks/DetectBadLoggingFormatInterpolation.java b/src/main/java/org/greencodeinitiative/creedengo/python/checks/DetectBadLoggingFormatInterpolation.java new file mode 100644 index 0000000..bc3274d --- /dev/null +++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/DetectBadLoggingFormatInterpolation.java @@ -0,0 +1,34 @@ +package org.greencodeinitiative.creedengo.python.checks; + +import org.sonar.check.Rule; +import org.sonar.plugins.python.api.PythonSubscriptionCheck; +import org.sonar.plugins.python.api.SubscriptionContext; +import org.sonar.plugins.python.api.tree.*; +import java.util.regex.Pattern; + +@Rule(key = "GCI1066") +public class DetectBadLoggingFormatInterpolation extends PythonSubscriptionCheck { + + protected static final String MESSAGE_RULE = "For logging format, prefer using %s with kwargs instead of builtin formatter \"\".format() or f\"\""; + private static final Pattern PATTERN_FORMAT = Pattern.compile("f['\"].*['\"]"); + private static final Pattern PATTERN_LOGGER = Pattern.compile("logging|logger"); + private static final Pattern PATTERN_LOGGING_METHOD = Pattern.compile("info|debug|error"); + + @Override + public void initialize(Context context) { + context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, this::visitNodeString); + } + + public void visitNodeString(SubscriptionContext ctx) { + CallExpression callExpression = (CallExpression) ctx.syntaxNode(); + + QualifiedExpression qualifiedExpression = (QualifiedExpression)((CallExpression)ctx.syntaxNode()).callee(); + + if(PATTERN_LOGGING_METHOD.matcher(qualifiedExpression.name().name()).matches() + && PATTERN_LOGGER.matcher(((Name)qualifiedExpression.qualifier()).name()).matches() + && PATTERN_FORMAT.matcher(((RegularArgument)callExpression.arguments().get(0)).expression().firstToken().value()).matches()) { + ctx.addIssue(callExpression, MESSAGE_RULE); + } + } + +} diff --git a/src/test/java/org/greencodeinitiative/creedengo/python/checks/DetectBadLoggingFormatInterpolationTest.java b/src/test/java/org/greencodeinitiative/creedengo/python/checks/DetectBadLoggingFormatInterpolationTest.java new file mode 100644 index 0000000..3e134fd --- /dev/null +++ b/src/test/java/org/greencodeinitiative/creedengo/python/checks/DetectBadLoggingFormatInterpolationTest.java @@ -0,0 +1,14 @@ +package org.greencodeinitiative.creedengo.python.checks; + +import org.junit.Test; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class DetectBadLoggingFormatInterpolationTest { + + @Test + public void test_bad_logging_format_interpolation() { + PythonCheckVerifier.verify("src/test/resources/checks/detectBadLoggingFormatInterpolationNonCompliant.py", new DetectBadLoggingFormatInterpolation()); + PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/detectBadLoggingFormatInterpolationCompliant.py", new DetectBadLoggingFormatInterpolation()); + } + +} diff --git a/src/test/resources/checks/detectBadLoggingFormatInterpolationCompliant.py b/src/test/resources/checks/detectBadLoggingFormatInterpolationCompliant.py new file mode 100644 index 0000000..f1f2303 --- /dev/null +++ b/src/test/resources/checks/detectBadLoggingFormatInterpolationCompliant.py @@ -0,0 +1,6 @@ +import logging +logger = logging.getLogger() +name = "world" + +logging.info("Hello %s", name) # Correct +logger.debug("Hello %s", name) # Correct \ No newline at end of file diff --git a/src/test/resources/checks/detectBadLoggingFormatInterpolationNonCompliant.py b/src/test/resources/checks/detectBadLoggingFormatInterpolationNonCompliant.py new file mode 100644 index 0000000..0b162ea --- /dev/null +++ b/src/test/resources/checks/detectBadLoggingFormatInterpolationNonCompliant.py @@ -0,0 +1,7 @@ +import logging +logger = logging.getLogger() +name = "world" + +#logging.info("Hello {}".format(name)) # Noncompliant {{For logging format, prefer using %s with kwargs instead of builtin formatter "".format() or f""}} +logger.debug(f'Hello {name}') # Noncompliant {{For logging format, prefer using %s with kwargs instead of builtin formatter "".format() or f""}} +logger.error(f"Hello {name}") # Noncompliant {{For logging format, prefer using %s with kwargs instead of builtin formatter "".format() or f""}} \ No newline at end of file