Skip to content

Commit e43d6e7

Browse files
authored
Merge pull request #76 from cleophass/GCI103-python
GCI103 DictionaryItemsUnused #Python #DLG #Build
2 parents 062eee2 + b585092 commit e43d6e7

File tree

8 files changed

+299
-1
lines changed

8 files changed

+299
-1
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+
- [#76](https://github.com/green-code-initiative/creedengo-python/pull/76) Add rule GCI 103 Dictionary Items Unused. A rule specifying that dictionary iteration should consider the pertinence of the element used.
1213
- [#79](https://github.com/green-code-initiative/creedengo-python/pull/79) Add rule GCI106 Avoid SQRT in a loop
1314
- [#71](https://github.com/green-code-initiative/creedengo-python/pull/71) Add rule GCI96 Require Usecols Argument in Pandas Read Functions
1415
- [#72](https://github.com/green-code-initiative/creedengo-python/pull/72) Add rule GCI97 Optimize square computation (scalar vs vectorized method)

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,23 @@ void testGCI97(){
302302

303303
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_1MIN);
304304
}
305+
306+
@Test
307+
void testGCI103(){
308+
309+
String filePath = "src/dictionaryItemsUnused.py";
310+
String ruleId = "creedengo-python:GCI103";
311+
String ruleMsg = "Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used";
312+
int[] startLines = new int[]{
313+
5, 8, 12, 32, 35, 44
314+
};
315+
int[] endLines = new int[]{
316+
5, 8, 12, 32, 35, 44
317+
};
318+
319+
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_1MIN);
320+
321+
}
305322

306323
@Test
307324
void testGCI106() {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
2+
for a, b in my_dict.items():
3+
print(a, b)
4+
5+
for key, value in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
6+
result.append(key)
7+
8+
for key, value in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
9+
result.append(key)
10+
11+
12+
for key, value in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
13+
result.append(value)
14+
15+
16+
for key in my_dict.keys():
17+
result.append(key)
18+
19+
20+
for value in my_dict.values():
21+
result.append(value)
22+
23+
24+
for item in my_dict.items():
25+
result.append(item)
26+
27+
28+
entries = []
29+
for k, v in my_dict.items():
30+
entries.append((k, v))
31+
32+
for key, value in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
33+
do_something_with(key)
34+
35+
for k, v in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
36+
do_something_with(v)
37+
38+
for key, value in my_dict.items():
39+
print(f"{key}: {value}")
40+
41+
for k, v in my_dict.items():
42+
some_list.append((k, v))
43+
44+
for k, v in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
45+
used_keys.append(k)
46+
47+
if True:
48+
for k, v in my_dict.items():
49+
print(k)
50+
print(v)
51+
52+
copied_dict = dict(my_dict.items())
53+
54+
55+
for i, (k, v) in enumerate(my_dict.items()):
56+
print(i, k, v)
57+
58+
59+
{(k, v) for k, v in my_dict.items()}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ public class PythonRuleRepository implements RulesDefinition, PythonCustomRuleRe
4343
AvoidMultipleIfElseStatementCheck.class,
4444
PandasRequireUsecolsArgument.class,
4545
OptimizeSquareComputation.class,
46-
AvoidSqrtInLoop.class
46+
AvoidSqrtInLoop.class,
47+
DictionaryItemsUnused.class
4748
);
4849

4950
public static final String LANGUAGE = "py";
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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 java.util.HashMap;
21+
import java.util.Map;
22+
23+
import org.sonar.check.Rule;
24+
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
25+
import org.sonar.plugins.python.api.SubscriptionContext;
26+
import org.sonar.plugins.python.api.tree.CallExpression;
27+
import org.sonar.plugins.python.api.tree.Expression;
28+
import org.sonar.plugins.python.api.tree.ForStatement;
29+
import org.sonar.plugins.python.api.tree.Name;
30+
import org.sonar.plugins.python.api.tree.QualifiedExpression;
31+
import org.sonar.plugins.python.api.tree.Tree;
32+
33+
34+
@Rule(key ="GCI103")
35+
public class DictionaryItemsUnused extends PythonSubscriptionCheck {
36+
37+
public static final String DESCRIPTION = "Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used";
38+
39+
private final Map<ForStatement, ItemsLoopInfo> itemsLoops = new HashMap<>();
40+
41+
@Override
42+
public void initialize(Context context) {
43+
context.registerSyntaxNodeConsumer(Tree.Kind.FOR_STMT, this::processForLoop);
44+
}
45+
46+
private void processForLoop(SubscriptionContext context) {
47+
ForStatement forStmt = (ForStatement) context.syntaxNode();
48+
49+
if (forStmt.expressions().size() == 2) {
50+
Expression keyExpr = forStmt.expressions().get(0);
51+
Expression valueExpr = forStmt.expressions().get(1);
52+
Expression iterable = forStmt.testExpressions().get(0);
53+
54+
if (isItemsCall(iterable)) {
55+
String key = keyExpr.is(Tree.Kind.NAME) ? ((Name) keyExpr).name() : null;
56+
String value = valueExpr.is(Tree.Kind.NAME) ? ((Name) valueExpr).name() : null;
57+
58+
if (key != null && value != null) {
59+
ItemsLoopInfo info = new ItemsLoopInfo(key, value);
60+
itemsLoops.put(forStmt, info);
61+
62+
trackNameUsages(forStmt.body(), info);
63+
}
64+
}
65+
}
66+
67+
finalizeCheck(context);
68+
}
69+
70+
private boolean isItemsCall(Expression expr) {
71+
if (expr.is(Tree.Kind.CALL_EXPR)) {
72+
CallExpression callExpr = (CallExpression) expr;
73+
if (callExpr.callee().is(Tree.Kind.QUALIFIED_EXPR)) {
74+
QualifiedExpression qualExpr = (QualifiedExpression) callExpr.callee();
75+
boolean isItems = "items".equals(qualExpr.name().name());
76+
return isItems;
77+
}
78+
}
79+
return false;
80+
}
81+
82+
private void trackNameUsages(Tree node, ItemsLoopInfo info) {
83+
if (node instanceof Name) {
84+
String name = ((Name) node).name();
85+
info.markUsage(name);
86+
}
87+
88+
for (Tree child : node.children()) {
89+
trackNameUsages(child, info);
90+
}
91+
}
92+
93+
private void finalizeCheck(SubscriptionContext context) {
94+
ForStatement forStmt = (ForStatement) context.syntaxNode();
95+
ItemsLoopInfo info = itemsLoops.get(forStmt);
96+
97+
if (info != null) {
98+
99+
if (info.isOnlyOneUsed()) {
100+
context.addIssue(forStmt.firstToken(), DESCRIPTION);
101+
}
102+
103+
itemsLoops.remove(forStmt);
104+
}
105+
}
106+
107+
private static class ItemsLoopInfo {
108+
final String keyVar;
109+
final String valueVar;
110+
boolean keyUsed = false;
111+
boolean valueUsed = false;
112+
113+
ItemsLoopInfo(String keyVar, String valueVar) {
114+
this.keyVar = keyVar;
115+
this.valueVar = valueVar;
116+
}
117+
118+
void markUsage(String var) {
119+
if (var.equals(keyVar)) {
120+
keyUsed = true;
121+
}
122+
if (var.equals(valueVar)) {
123+
valueUsed = true;
124+
}
125+
}
126+
127+
boolean isOnlyOneUsed() {
128+
return (keyUsed && !valueUsed) || (!keyUsed && valueUsed);
129+
}
130+
}
131+
}

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+
"GCI103",
1516
"GCI106",
1617
"GCI203",
1718
"GCI404"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 DictionaryItemsUnusedTest {
24+
25+
@Test
26+
public void test() {
27+
PythonCheckVerifier.verify("src/test/resources/checks/dictionaryItemsUnused.py", new DictionaryItemsUnused());
28+
}
29+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
2+
for a, b in my_dict.items():
3+
print(a, b)
4+
5+
for key, value in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
6+
result.append(key)
7+
8+
for key, value in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
9+
result.append(key)
10+
11+
12+
for key, value in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
13+
result.append(value)
14+
15+
16+
for key in my_dict.keys():
17+
result.append(key)
18+
19+
20+
for value in my_dict.values():
21+
result.append(value)
22+
23+
24+
for item in my_dict.items():
25+
result.append(item)
26+
27+
28+
entries = []
29+
for k, v in my_dict.items():
30+
entries.append((k, v))
31+
32+
for key, value in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
33+
do_something_with(key)
34+
35+
for k, v in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
36+
do_something_with(v)
37+
38+
for key, value in my_dict.items():
39+
print(f"{key}: {value}")
40+
41+
for k, v in my_dict.items():
42+
some_list.append((k, v))
43+
44+
for k, v in my_dict.items(): # Noncompliant {{Use dict.keys() or dict.values() instead of dict.items() when only one part of the key-value pair is used}}
45+
used_keys.append(k)
46+
47+
if True:
48+
for k, v in my_dict.items():
49+
print(k)
50+
print(v)
51+
52+
copied_dict = dict(my_dict.items())
53+
54+
55+
for i, (k, v) in enumerate(my_dict.items()):
56+
print(i, k, v)
57+
58+
59+
{(k, v) for k, v in my_dict.items()}

0 commit comments

Comments
 (0)