From 6e56d5a3e0bde14f0b89bcb603e461950d1ed590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ophas=20Fournier?= Date: Mon, 19 May 2025 16:37:45 +0200 Subject: [PATCH 1/2] GCI106 AvoidSqrtInLoop #Python #DLG #Build Co-authored-by: DataLabGroupe-CreditAgricole --- CHANGELOG.md | 2 + .../python/PythonRuleRepository.java | 3 +- .../python/checks/AvoidSqrtInLoop.java | 75 +++++++++++++++++++ .../python/checks/AvoidSqrtInLoopTest.java | 28 +++++++ src/test/resources/checks/avoidSqrtInLoop.py | 41 ++++++++++ 5 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/greencodeinitiative/creedengo/python/checks/AvoidSqrtInLoop.java create mode 100644 src/test/java/org/greencodeinitiative/creedengo/python/checks/AvoidSqrtInLoopTest.java create mode 100644 src/test/resources/checks/avoidSqrtInLoop.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bea9b72..f28b179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- add rule GCI106 Avoid SQRT in a loop + ### Changed - compatibility updates for SonarQube 25.5.0 diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java b/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java index c385979..671d665 100644 --- a/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java +++ b/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java @@ -40,7 +40,8 @@ public class PythonRuleRepository implements RulesDefinition, PythonCustomRuleRe AvoidFullSQLRequest.class, AvoidListComprehensionInIterations.class, DetectUnoptimizedImageFormat.class, - AvoidMultipleIfElseStatementCheck.class + AvoidMultipleIfElseStatementCheck.class, + AvoidSqrtInLoop.class ); public static final String LANGUAGE = "py"; diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/checks/AvoidSqrtInLoop.java b/src/main/java/org/greencodeinitiative/creedengo/python/checks/AvoidSqrtInLoop.java new file mode 100644 index 0000000..3ace5d2 --- /dev/null +++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/AvoidSqrtInLoop.java @@ -0,0 +1,75 @@ +/* + * creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs + * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +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.CallExpression; +import org.sonar.plugins.python.api.tree.Expression; +import org.sonar.plugins.python.api.tree.QualifiedExpression; +import org.sonar.plugins.python.api.tree.Tree; + +@Rule(key = "GCI106") +public class AvoidSqrtInLoop extends PythonSubscriptionCheck { + + public static final String DESCRIPTION = "Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly."; + + @Override + public void initialize(Context context) { + context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, this::checkCallExpression); + } + + private void checkCallExpression(SubscriptionContext context) { + CallExpression callExpression = (CallExpression) context.syntaxNode(); + + if (isSqrtCall(callExpression) && hasLoopParent(callExpression)) { + context.addIssue(callExpression, DESCRIPTION); + } + } + + private boolean isSqrtCall(CallExpression callExpression) { + Expression callee = callExpression.callee(); + + // Check for direct calls to math.sqrt + if (callee.is(Tree.Kind.QUALIFIED_EXPR)) { + QualifiedExpression qualifiedExpression = (QualifiedExpression) callee; + String methodName = qualifiedExpression.name().name(); + + if ("sqrt".equals(methodName)) { + Expression qualifier = qualifiedExpression.qualifier(); + if (qualifier != null && qualifier.is(Tree.Kind.NAME)) { + String qualifierName = qualifier.firstToken().value(); + return "math".equals(qualifierName) || "np".equals(qualifierName) || "numpy".equals(qualifierName); + } + } + } + + return false; + } + + private boolean hasLoopParent(Tree tree) { + for (Tree parent = tree.parent(); parent != null; parent = parent.parent()) { + Tree.Kind kind = parent.getKind(); + if (kind == Tree.Kind.FOR_STMT || kind == Tree.Kind.WHILE_STMT) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/test/java/org/greencodeinitiative/creedengo/python/checks/AvoidSqrtInLoopTest.java b/src/test/java/org/greencodeinitiative/creedengo/python/checks/AvoidSqrtInLoopTest.java new file mode 100644 index 0000000..859cdb2 --- /dev/null +++ b/src/test/java/org/greencodeinitiative/creedengo/python/checks/AvoidSqrtInLoopTest.java @@ -0,0 +1,28 @@ +/* + * creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs + * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.greencodeinitiative.creedengo.python.checks; + +import org.junit.Test; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class AvoidSqrtInLoopTest { + @Test + public void test() { + PythonCheckVerifier.verify("src/test/resources/checks/avoidSqrtInLoop.py", new AvoidSqrtInLoop()); + } +} \ No newline at end of file diff --git a/src/test/resources/checks/avoidSqrtInLoop.py b/src/test/resources/checks/avoidSqrtInLoop.py new file mode 100644 index 0000000..0dc5ca8 --- /dev/null +++ b/src/test/resources/checks/avoidSqrtInLoop.py @@ -0,0 +1,41 @@ +import math +import numpy as np + +def non_compliant_math_sqrt_in_for_loop(): + numbers = [1, 2, 3, 4, 5] + results = [] + for num in numbers: + results.append(math.sqrt(num)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}} + return results + +def non_compliant_numpy_scalar_sqrt_in_for_loop(): + numbers = [1, 2, 3, 4, 5] + results = [] + for num in numbers: + results.append(np.sqrt(num)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}} + return results + +def non_compliant_math_sqrt_in_while_loop(): + numbers = [1, 2, 3, 4, 5] + results = [] + i = 0 + while i < len(numbers): + results.append(math.sqrt(numbers[i])) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}} + i += 1 + return results + +def compliant_numpy_vectorized_sqrt(): + numbers = np.array([1, 2, 3, 4, 5]) + # Vectorized sqrt directly on the array - efficient + results = np.sqrt(numbers) + return results + +def compliant_list_comprehension_with_vectorized_operation(): + numbers = [1, 2, 3, 4, 5] + # Using numpy's vectorized sqrt on the entire array at once + return np.sqrt(np.array(numbers)) + +def compliant_math_sqrt_outside_loop(): + # Using math.sqrt outside of a loop is OK + value = 16 + return math.sqrt(value) \ No newline at end of file From 51304b5561d76a8adaf9ab61373b7562afde973e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ophas=20Fournier?= Date: Tue, 3 Jun 2025 09:52:37 +0200 Subject: [PATCH 2/2] GCI106 AvoidSqrtInLoop add Python tests Co-authored-by: DataLabGroupe-CreditAgricole --- src/test/resources/checks/avoidSqrtInLoop.py | 40 ++++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/test/resources/checks/avoidSqrtInLoop.py b/src/test/resources/checks/avoidSqrtInLoop.py index 0dc5ca8..c035f17 100644 --- a/src/test/resources/checks/avoidSqrtInLoop.py +++ b/src/test/resources/checks/avoidSqrtInLoop.py @@ -1,6 +1,43 @@ import math import numpy as np +results = [] +numbers = [1, 2, 3, 4, 5] +for n in numbers: + results.append(math.sqrt(n)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}} + +results = [] +for x in [9, 16, 25]: + results.append(np.sqrt(x)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}} + +results = [] +i = 0 +while i < 3: + results.append(np.sqrt(i)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}} + i += 1 + +results = [] +for n in range(10): + val = math.sqrt(n) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}} + results.append(val) + +class DataProcessor: + numbers = np.array([1, 4, 9]) + sqrt_values = np.sqrt(numbers) + +x = math.sqrt(25) + +class Analyzer: + def __init__(self, data): + self.results = np.sqrt(np.array(data)) + +intermediate = [] +for n in range(3): + intermediate.append(n) +final = np.sqrt(np.array(intermediate)) + +results = np.sqrt([x for x in range(5)]) + def non_compliant_math_sqrt_in_for_loop(): numbers = [1, 2, 3, 4, 5] results = [] @@ -26,16 +63,13 @@ def non_compliant_math_sqrt_in_while_loop(): def compliant_numpy_vectorized_sqrt(): numbers = np.array([1, 2, 3, 4, 5]) - # Vectorized sqrt directly on the array - efficient results = np.sqrt(numbers) return results def compliant_list_comprehension_with_vectorized_operation(): numbers = [1, 2, 3, 4, 5] - # Using numpy's vectorized sqrt on the entire array at once return np.sqrt(np.array(numbers)) def compliant_math_sqrt_outside_loop(): - # Using math.sqrt outside of a loop is OK value = 16 return math.sqrt(value) \ No newline at end of file