Skip to content

Commit 07b1c7d

Browse files
guillaume-dequennesonartech
authored andcommitted
SONARPY-4113 Improve issue reporting for S8518 (#1075)
GitOrigin-RevId: faaa6ec657ff23b9b2a50f4d4651444ad5d1bbf1
1 parent 1931ce3 commit 07b1c7d

2 files changed

Lines changed: 41 additions & 12 deletions

File tree

python-checks/src/main/java/org/sonar/python/checks/EnumerateUnpackingCheck.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package org.sonar.python.checks;
1818

19+
import java.util.ArrayList;
20+
import java.util.List;
1921
import org.sonar.check.Rule;
2022
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2123
import org.sonar.plugins.python.api.SubscriptionContext;
@@ -40,6 +42,7 @@
4042
public class EnumerateUnpackingCheck extends PythonSubscriptionCheck {
4143

4244
private static final String MESSAGE = "Unpack the value from 'enumerate()' directly instead of using an index lookup.";
45+
private static final String SECONDARY_MESSAGE = "Replace this index lookup with the unpacked value.";
4346
private static final TypeMatcher ENUMERATE_MATCHER = TypeMatchers.isType("enumerate");
4447

4548
@Override
@@ -84,26 +87,30 @@ private static void check(SubscriptionContext ctx) {
8487
return;
8588
}
8689

87-
boolean hasSubscriptWriteTarget = TreeUtils.hasDescendant(forStatement.body(),
88-
node -> isMatchingSubscript(node, indexSymbol, iterableSymbol)
89-
&& isSubscriptWriteTarget((SubscriptionExpression) node));
90+
List<SubscriptionExpression> matchingSubscripts = new ArrayList<>();
91+
collectMatchingSubscripts(forStatement.body(), indexSymbol, iterableSymbol, matchingSubscripts);
9092

91-
if (hasSubscriptWriteTarget) {
93+
if (matchingSubscripts.isEmpty()) {
94+
return;
95+
}
96+
if (matchingSubscripts.stream().anyMatch(EnumerateUnpackingCheck::isSubscriptWriteTarget)) {
9297
return;
9398
}
9499

95-
boolean hasRedundantSubscript = TreeUtils.hasDescendant(forStatement.body(),
96-
node -> isMatchingSubscript(node, indexSymbol, iterableSymbol));
100+
PreciseIssue issue = ctx.addIssue(call, MESSAGE);
101+
matchingSubscripts.forEach(subscript -> issue.secondary(subscript, SECONDARY_MESSAGE));
102+
}
97103

98-
if (hasRedundantSubscript) {
99-
ctx.addIssue(call, MESSAGE);
104+
private static void collectMatchingSubscripts(Tree tree, SymbolV2 indexSymbol, SymbolV2 iterableSymbol, List<SubscriptionExpression> result) {
105+
for (Tree child : tree.children()) {
106+
if (child instanceof SubscriptionExpression subscription && isMatchingSubscript(subscription, indexSymbol, iterableSymbol)) {
107+
result.add(subscription);
108+
}
109+
collectMatchingSubscripts(child, indexSymbol, iterableSymbol, result);
100110
}
101111
}
102112

103-
private static boolean isMatchingSubscript(Tree node, SymbolV2 indexSymbol, SymbolV2 iterableSymbol) {
104-
if (!(node instanceof SubscriptionExpression subscription)) {
105-
return false;
106-
}
113+
private static boolean isMatchingSubscript(SubscriptionExpression subscription, SymbolV2 indexSymbol, SymbolV2 iterableSymbol) {
107114
if (subscription.subscripts().expressions().size() != 1) {
108115
return false;
109116
}

python-checks/src/test/resources/checks/enumerateUnpacking.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,34 @@ def noncompliant_basic():
44
for i, _ in enumerate(lst): # Noncompliant {{Unpack the value from 'enumerate()' directly instead of using an index lookup.}}
55
# ^^^^^^^^^^^^^^
66
print(lst[i])
7+
# ^^^^^^< {{Replace this index lookup with the unpacked value.}}
78

89

910
def noncompliant_start_zero_keyword():
1011
colors = ['red', 'green', 'blue']
1112
for i, _ in enumerate(colors, start=0): # Noncompliant
13+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^
1214
print(colors[i])
15+
# ^^^^^^^^^< {{Replace this index lookup with the unpacked value.}}
1316

1417

1518
def noncompliant_rhs_of_augmented_assignment():
1619
items = "abcdef"
1720
result = ""
1821
for ind, _ in enumerate(items): # Noncompliant
22+
# ^^^^^^^^^^^^^^^^
1923
result += items[ind]
24+
# ^^^^^^^^^^< {{Replace this index lookup with the unpacked value.}}
25+
26+
27+
def noncompliant_multiple_usages():
28+
lst = ['a', 'b', 'c']
29+
for i, _ in enumerate(lst): # Noncompliant
30+
# ^^^^^^^^^^^^^^
31+
print(lst[i])
32+
# ^^^^^^< {{Replace this index lookup with the unpacked value.}}
33+
print(lst[i].upper())
34+
# ^^^^^^< {{Replace this index lookup with the unpacked value.}}
2035

2136

2237
def compliant_proper_unpacking():
@@ -90,6 +105,13 @@ def compliant_write_assignment():
90105
items[i] = item.upper()
91106

92107

108+
def compliant_mixed_read_and_write():
109+
items = ['a', 'b', 'c']
110+
for i, _ in enumerate(items):
111+
print(items[i])
112+
items[i] = items[i].upper()
113+
114+
93115
def compliant_write_augmented_assignment():
94116
counts = [0, 0, 0]
95117
for i, val in enumerate(counts):

0 commit comments

Comments
 (0)