diff --git a/CHANGELOG.md b/CHANGELOG.md index f97f08d..be6acee 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 GCI99 Optimize square computation (scalar vs vectorized method) + ### 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..60189c4 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, + OptimizeSquareComputation.class ); public static final String LANGUAGE = "py"; diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/checks/OptimizeSquareComputation.java b/src/main/java/org/greencodeinitiative/creedengo/python/checks/OptimizeSquareComputation.java new file mode 100644 index 0000000..a2dc239 --- /dev/null +++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/OptimizeSquareComputation.java @@ -0,0 +1,91 @@ +/* + * 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.BinaryExpression; +import org.sonar.plugins.python.api.tree.CallExpression; +import org.sonar.plugins.python.api.tree.Expression; +import org.sonar.plugins.python.api.tree.NumericLiteral; +import org.sonar.plugins.python.api.tree.QualifiedExpression; +import org.sonar.plugins.python.api.tree.RegularArgument; + +import static org.sonar.plugins.python.api.tree.Tree.Kind.*; + +@Rule(key = "GCI99") +public class OptimizeSquareComputation extends PythonSubscriptionCheck { + + public static final String DESCRIPTION = "Use x*x instead of x**2 or math.pow(x,2) to calculate the square of a value"; + + @Override + public void initialize(Context context) { + context.registerSyntaxNodeConsumer(CALL_EXPR, this::checkMathPowCall); + context.registerSyntaxNodeConsumer(POWER, this::checkPowerOf2); + } + + + private boolean isNumericLiteralWithValue(Expression expr, String value) { + if (expr.is(NUMERIC_LITERAL)) { + NumericLiteral numericLiteral = (NumericLiteral) expr; + return value.equals(numericLiteral.valueAsString()); + } + return false; + } + + private void checkMathPowCall(SubscriptionContext context) { + CallExpression callExpression = (CallExpression) context.syntaxNode(); + + + if (isMathPowCall(callExpression)) { + context.addIssue(callExpression, DESCRIPTION); + } + } + + private boolean isMathPowCall(CallExpression callExpression) { + Expression callee = callExpression.callee(); + + + if (callee.is(QUALIFIED_EXPR)) { + QualifiedExpression qualifiedExpr = (QualifiedExpression) callee; + String name = qualifiedExpr.name().name(); + + + if ("pow".equals(name)) { + + if (callExpression.arguments().size() >= 2) { + Expression secondArg = ((RegularArgument)callExpression.arguments().get(1)).expression(); + return isNumericLiteralWithValue(secondArg, "2"); + } + } + } + + return false; + } + + + private void checkPowerOf2(SubscriptionContext context) { + BinaryExpression power = (BinaryExpression) context.syntaxNode(); + + + if (isNumericLiteralWithValue(power.rightOperand(), "2")) { + context.addIssue(power, DESCRIPTION); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/greencodeinitiative/creedengo/python/checks/OptimizeSquareComputationTest.java b/src/test/java/org/greencodeinitiative/creedengo/python/checks/OptimizeSquareComputationTest.java new file mode 100644 index 0000000..8bcc6de --- /dev/null +++ b/src/test/java/org/greencodeinitiative/creedengo/python/checks/OptimizeSquareComputationTest.java @@ -0,0 +1,29 @@ +/* + * 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 OptimizeSquareComputationTest { + + @Test + public void test() { + PythonCheckVerifier.verify("src/test/resources/checks/optimizeSquareComputation.py", new OptimizeSquareComputation()); + } +} diff --git a/src/test/resources/checks/optimizeSquareComputation.py b/src/test/resources/checks/optimizeSquareComputation.py new file mode 100644 index 0000000..dd8fbea --- /dev/null +++ b/src/test/resources/checks/optimizeSquareComputation.py @@ -0,0 +1,42 @@ +import math + +x = 5 +result1 = x**2 # Noncompliant {{Use x*x instead of x**2 or math.pow(x,2) to calculate the square of a value}} + +z = 7 +result4 = math.pow(z, 2) # Noncompliant {{Use x*x instead of x**2 or math.pow(x,2) to calculate the square of a value}} + +a = 3 +result5 = a*a + +b = 4 +result6 = b*3 +result7 = 5*b +result8 = math.pow(b, 3) +result9 = b**3 + +c = 2.5 +result10 = c**2 # Noncompliant {{Use x*x instead of x**2 or math.pow(x,2) to calculate the square of a value}} +result11 = math.pow(c, 2) # Noncompliant {{Use x*x instead of x**2 or math.pow(x,2) to calculate the square of a value}} + + +d = 8 +e = 9 +result12 = math.pow(d+e, 2) # Noncompliant {{Use x*x instead of x**2 or math.pow(x,2) to calculate the square of a value}} +result13 = (d+e)**2 # Noncompliant {{Use x*x instead of x**2 or math.pow(x,2) to calculate the square of a value}} +result14 = (d+e)*(d+e) + + +def square(x): + return x**2 # Noncompliant {{Use x*x instead of x**2 or math.pow(x,2) to calculate the square of a value}} + +def better_square(x): + return x*x + + +import math as m +result15 = m.pow(d, 2) # Noncompliant {{Use x*x instead of x**2 or math.pow(x,2) to calculate the square of a value}} + +result16 = math.sqrt(d) +result17 = math.sin(d) +result18 = math.pow(d, 1.5) \ No newline at end of file