Skip to content

Commit 062eee2

Browse files
authored
Merge pull request #79 from cleophass/GCI106-python
GCI106 AvoidSqrtInLoop #Python #DLG #Build
2 parents d032dbe + 27a69f3 commit 062eee2

File tree

9 files changed

+274
-2
lines changed

9 files changed

+274
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- [#79](https://github.com/green-code-initiative/creedengo-python/pull/79) Add rule GCI106 Avoid SQRT in a loop
1213
- [#71](https://github.com/green-code-initiative/creedengo-python/pull/71) Add rule GCI96 Require Usecols Argument in Pandas Read Functions
1314
- [#72](https://github.com/green-code-initiative/creedengo-python/pull/72) Add rule GCI97 Optimize square computation (scalar vs vectorized method)
1415

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
<lombok.version>1.18.38</lombok.version>
7171

7272
<!-- temporary version waiting for a real automatic release in creedengo repository -->
73-
<creedengo-rules-specifications.version>2.3.0</creedengo-rules-specifications.version>
73+
<creedengo-rules-specifications.version>2.4.0</creedengo-rules-specifications.version>
7474

7575
<!-- URL of the Maven repository where sonarqube will be downloaded -->
7676
<test-it.orchestrator.artifactory.url>https://repo1.maven.org/maven2</test-it.orchestrator.artifactory.url>

src/it/java/org/greencodeinitiative/creedengo/python/integration/tests/GCIRulesIT.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ void testGCI96() {
287287

288288
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_10MIN);
289289
}
290+
291+
@Test
290292
void testGCI97(){
291293
String filePath = "src/optimizeSquareComputation.py";
292294
String ruleId = "creedengo-python:GCI97";
@@ -301,4 +303,18 @@ void testGCI97(){
301303
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_1MIN);
302304
}
303305

306+
@Test
307+
void testGCI106() {
308+
String filePath = "src/avoidSqrtInLoop.py";
309+
String ruleId = "creedengo-python:GCI106";
310+
String ruleMsg = "Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.";
311+
int[] startLines = new int[]{
312+
7, 11, 16, 21, 45, 52, 60
313+
};
314+
int[] endLines = new int[]{
315+
7, 11, 16, 21, 45, 52, 60
316+
};
317+
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_5MIN);
318+
}
319+
304320
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import math
2+
import numpy as np
3+
4+
results = []
5+
numbers = [1, 2, 3, 4, 5]
6+
for n in numbers:
7+
results.append(math.sqrt(n)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
8+
9+
results = []
10+
for x in [9, 16, 25]:
11+
results.append(np.sqrt(x)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
12+
13+
results = []
14+
i = 0
15+
while i < 3:
16+
results.append(np.sqrt(i)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
17+
i += 1
18+
19+
results = []
20+
for n in range(10):
21+
val = math.sqrt(n) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
22+
results.append(val)
23+
24+
class DataProcessor:
25+
numbers = np.array([1, 4, 9])
26+
sqrt_values = np.sqrt(numbers)
27+
28+
x = math.sqrt(25)
29+
30+
class Analyzer:
31+
def __init__(self, data):
32+
self.results = np.sqrt(np.array(data))
33+
34+
intermediate = []
35+
for n in range(3):
36+
intermediate.append(n)
37+
final = np.sqrt(np.array(intermediate))
38+
39+
results = np.sqrt([x for x in range(5)])
40+
41+
def non_compliant_math_sqrt_in_for_loop():
42+
numbers = [1, 2, 3, 4, 5]
43+
results = []
44+
for num in numbers:
45+
results.append(math.sqrt(num)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
46+
return results
47+
48+
def non_compliant_numpy_scalar_sqrt_in_for_loop():
49+
numbers = [1, 2, 3, 4, 5]
50+
results = []
51+
for num in numbers:
52+
results.append(np.sqrt(num)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
53+
return results
54+
55+
def non_compliant_math_sqrt_in_while_loop():
56+
numbers = [1, 2, 3, 4, 5]
57+
results = []
58+
i = 0
59+
while i < len(numbers):
60+
results.append(math.sqrt(numbers[i])) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
61+
i += 1
62+
return results
63+
64+
def compliant_numpy_vectorized_sqrt():
65+
numbers = np.array([1, 2, 3, 4, 5])
66+
results = np.sqrt(numbers)
67+
return results
68+
69+
def compliant_list_comprehension_with_vectorized_operation():
70+
numbers = [1, 2, 3, 4, 5]
71+
return np.sqrt(np.array(numbers))
72+
73+
def compliant_math_sqrt_outside_loop():
74+
value = 16
75+
return math.sqrt(value)

src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public class PythonRuleRepository implements RulesDefinition, PythonCustomRuleRe
4242
DetectUnoptimizedImageFormat.class,
4343
AvoidMultipleIfElseStatementCheck.class,
4444
PandasRequireUsecolsArgument.class,
45-
OptimizeSquareComputation.class
45+
OptimizeSquareComputation.class,
46+
AvoidSqrtInLoop.class
4647
);
4748

4849
public static final String LANGUAGE = "py";
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs
3+
* Copyright © 2024 Green Code Initiative (https://green-code-initiative.org)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package org.greencodeinitiative.creedengo.python.checks;
19+
20+
import org.sonar.check.Rule;
21+
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
22+
import org.sonar.plugins.python.api.SubscriptionContext;
23+
import org.sonar.plugins.python.api.tree.CallExpression;
24+
import org.sonar.plugins.python.api.tree.Expression;
25+
import org.sonar.plugins.python.api.tree.QualifiedExpression;
26+
import org.sonar.plugins.python.api.tree.Tree;
27+
28+
@Rule(key = "GCI106")
29+
public class AvoidSqrtInLoop extends PythonSubscriptionCheck {
30+
31+
public static final String DESCRIPTION = "Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.";
32+
33+
@Override
34+
public void initialize(Context context) {
35+
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, this::checkCallExpression);
36+
}
37+
38+
private void checkCallExpression(SubscriptionContext context) {
39+
CallExpression callExpression = (CallExpression) context.syntaxNode();
40+
41+
if (isSqrtCall(callExpression) && hasLoopParent(callExpression)) {
42+
context.addIssue(callExpression, DESCRIPTION);
43+
}
44+
}
45+
46+
private boolean isSqrtCall(CallExpression callExpression) {
47+
Expression callee = callExpression.callee();
48+
49+
// Check for direct calls to math.sqrt
50+
if (callee.is(Tree.Kind.QUALIFIED_EXPR)) {
51+
QualifiedExpression qualifiedExpression = (QualifiedExpression) callee;
52+
String methodName = qualifiedExpression.name().name();
53+
54+
if ("sqrt".equals(methodName)) {
55+
Expression qualifier = qualifiedExpression.qualifier();
56+
if (qualifier != null && qualifier.is(Tree.Kind.NAME)) {
57+
String qualifierName = qualifier.firstToken().value();
58+
return "math".equals(qualifierName) || "np".equals(qualifierName) || "numpy".equals(qualifierName);
59+
}
60+
}
61+
}
62+
63+
return false;
64+
}
65+
66+
private boolean hasLoopParent(Tree tree) {
67+
for (Tree parent = tree.parent(); parent != null; parent = parent.parent()) {
68+
Tree.Kind kind = parent.getKind();
69+
if (kind == Tree.Kind.FOR_STMT || kind == Tree.Kind.WHILE_STMT) {
70+
return true;
71+
}
72+
}
73+
return false;
74+
}
75+
}

src/main/resources/org/greencodeinitiative/creedengo/python/creedengo_way_profile.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"GCI89",
1313
"GCI96",
1414
"GCI97",
15+
"GCI106",
1516
"GCI203",
1617
"GCI404"
1718
]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs
3+
* Copyright © 2024 Green Code Initiative (https://green-code-initiative.org)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package org.greencodeinitiative.creedengo.python.checks;
19+
20+
import org.junit.Test;
21+
import org.sonar.python.checks.utils.PythonCheckVerifier;
22+
23+
public class AvoidSqrtInLoopTest {
24+
@Test
25+
public void test() {
26+
PythonCheckVerifier.verify("src/test/resources/checks/avoidSqrtInLoop.py", new AvoidSqrtInLoop());
27+
}
28+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import math
2+
import numpy as np
3+
4+
results = []
5+
numbers = [1, 2, 3, 4, 5]
6+
for n in numbers:
7+
results.append(math.sqrt(n)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
8+
9+
results = []
10+
for x in [9, 16, 25]:
11+
results.append(np.sqrt(x)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
12+
13+
results = []
14+
i = 0
15+
while i < 3:
16+
results.append(np.sqrt(i)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
17+
i += 1
18+
19+
results = []
20+
for n in range(10):
21+
val = math.sqrt(n) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
22+
results.append(val)
23+
24+
class DataProcessor:
25+
numbers = np.array([1, 4, 9])
26+
sqrt_values = np.sqrt(numbers)
27+
28+
x = math.sqrt(25)
29+
30+
class Analyzer:
31+
def __init__(self, data):
32+
self.results = np.sqrt(np.array(data))
33+
34+
intermediate = []
35+
for n in range(3):
36+
intermediate.append(n)
37+
final = np.sqrt(np.array(intermediate))
38+
39+
results = np.sqrt([x for x in range(5)])
40+
41+
def non_compliant_math_sqrt_in_for_loop():
42+
numbers = [1, 2, 3, 4, 5]
43+
results = []
44+
for num in numbers:
45+
results.append(math.sqrt(num)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
46+
return results
47+
48+
def non_compliant_numpy_scalar_sqrt_in_for_loop():
49+
numbers = [1, 2, 3, 4, 5]
50+
results = []
51+
for num in numbers:
52+
results.append(np.sqrt(num)) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
53+
return results
54+
55+
def non_compliant_math_sqrt_in_while_loop():
56+
numbers = [1, 2, 3, 4, 5]
57+
results = []
58+
i = 0
59+
while i < len(numbers):
60+
results.append(math.sqrt(numbers[i])) # Noncompliant {{Avoid using scalar sqrt functions in loops. Apply vectorized sqrt operations on arrays directly.}}
61+
i += 1
62+
return results
63+
64+
def compliant_numpy_vectorized_sqrt():
65+
numbers = np.array([1, 2, 3, 4, 5])
66+
results = np.sqrt(numbers)
67+
return results
68+
69+
def compliant_list_comprehension_with_vectorized_operation():
70+
numbers = [1, 2, 3, 4, 5]
71+
return np.sqrt(np.array(numbers))
72+
73+
def compliant_math_sqrt_outside_loop():
74+
value = 16
75+
return math.sqrt(value)

0 commit comments

Comments
 (0)