Skip to content

Commit 32c9133

Browse files
Seppli11thomas-serre-sonarsource
authored andcommitted
SONARPY-2973 add quickfixes to S7498 (#275)
Co-authored-by: Thomas Serre <118730793+thomas-serre-sonarsource@users.noreply.github.com> GitOrigin-RevId: 3f7a97bc58dd2d5d30df1e4bc82e3755b466c477
1 parent e695451 commit 32c9133

2 files changed

Lines changed: 55 additions & 10 deletions

File tree

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

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,44 +16,52 @@
1616
*/
1717
package org.sonar.python.checks;
1818

19-
import java.util.ArrayList;
20-
import java.util.Arrays;
21-
import java.util.List;
19+
import java.util.Map;
20+
import java.util.Optional;
2221
import org.sonar.check.Rule;
2322
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
23+
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
2424
import org.sonar.plugins.python.api.tree.Argument;
2525
import org.sonar.plugins.python.api.tree.CallExpression;
2626
import org.sonar.plugins.python.api.tree.Expression;
2727
import org.sonar.plugins.python.api.tree.RegularArgument;
2828
import org.sonar.plugins.python.api.tree.Tree;
2929
import org.sonar.plugins.python.api.types.v2.TriBool;
30+
import org.sonar.python.quickfix.TextEditUtils;
3031
import org.sonar.python.types.v2.TypeCheckBuilder;
32+
import org.sonar.python.types.v2.TypeCheckMap;
3133

3234
@Rule(key = "S7498")
3335
public class EmptyCollectionConstructorCheck extends PythonSubscriptionCheck {
3436

3537
private static final String MESSAGE = "Replace this constructor call with a literal.";
36-
private static final List<String> COLLECTION_CONSTRUCTORS = Arrays.asList("dict", "list", "tuple");
38+
private static final Map<String, String> COLLECTION_CONSTRUCTORS = Map.ofEntries(
39+
Map.entry("list", "[]"),
40+
Map.entry("tuple", "()"),
41+
Map.entry("dict", "{}")
42+
);
3743

38-
private List<TypeCheckBuilder> collectionConstructorTypeCheckers = null;
44+
private TypeCheckMap<String> collectionConstructorTypeCheckers = null;
3945
private TypeCheckBuilder dictChecker = null;
4046

4147
@Override
4248
public void initialize(Context context) {
4349
context.registerSyntaxNodeConsumer(Tree.Kind.FILE_INPUT, ctx -> {
4450
dictChecker = ctx.typeChecker().typeCheckBuilder().isTypeWithFqn("dict");
4551

46-
collectionConstructorTypeCheckers = new ArrayList<>();
47-
for (String constructor : COLLECTION_CONSTRUCTORS) {
48-
collectionConstructorTypeCheckers.add(ctx.typeChecker().typeCheckBuilder().isTypeWithFqn(constructor));
52+
collectionConstructorTypeCheckers = new TypeCheckMap<>();
53+
for (var constructorEntry : COLLECTION_CONSTRUCTORS.entrySet()) {
54+
TypeCheckBuilder constructorTypeChecker = ctx.typeChecker().typeCheckBuilder().isTypeWithFqn(constructorEntry.getKey());
55+
collectionConstructorTypeCheckers.put(constructorTypeChecker, constructorEntry.getValue());
4956
}
5057
});
5158

5259
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> {
5360
CallExpression callExpression = (CallExpression) ctx.syntaxNode();
5461

5562
if (isUnnecessaryCollectionConstructor(callExpression)) {
56-
ctx.addIssue(callExpression.callee(), MESSAGE);
63+
var issue = ctx.addIssue(callExpression.callee(), MESSAGE);
64+
createQuickFix(callExpression).ifPresent(issue::addQuickFix);
5765
}
5866
});
5967
}
@@ -65,7 +73,7 @@ private boolean isUnnecessaryCollectionConstructor(CallExpression callExpression
6573

6674
private boolean isCollectionConstructor(Expression calleeExpression) {
6775
var type = calleeExpression.typeV2();
68-
return collectionConstructorTypeCheckers.stream().map(checker -> checker.check(type)).anyMatch(TriBool.TRUE::equals);
76+
return collectionConstructorTypeCheckers.getOptionalForType(type).isPresent();
6977
}
7078

7179
private static boolean isEmptyCall(CallExpression callExpression) {
@@ -87,4 +95,11 @@ private static boolean hasOnlyKeywordArguments(CallExpression callExpression) {
8795
private static boolean isKeywordArg(Argument arg) {
8896
return arg instanceof RegularArgument regularArg && regularArg.keywordArgument() != null;
8997
}
98+
99+
private Optional<PythonQuickFix> createQuickFix(CallExpression callExpression) {
100+
return Optional.of(callExpression)
101+
.filter(EmptyCollectionConstructorCheck::isEmptyCall)
102+
.flatMap(callExpr -> collectionConstructorTypeCheckers.getOptionalForType(callExpression.callee().typeV2()))
103+
.map(replacementStr -> PythonQuickFix.newQuickFix("Replace with literal", TextEditUtils.replace(callExpression, replacementStr)));
104+
}
90105
}

python-checks/src/test/java/org/sonar/python/checks/EmptyCollectionConstructorCheckTest.java

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

19+
import java.util.stream.Stream;
1920
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.Arguments;
23+
import org.junit.jupiter.params.provider.MethodSource;
24+
import org.sonar.python.checks.quickfix.PythonQuickFixVerifier;
2025
import org.sonar.python.checks.utils.PythonCheckVerifier;
2126

2227
class EmptyCollectionConstructorCheckTest {
@@ -26,4 +31,29 @@ void test() {
2631
PythonCheckVerifier.verify("src/test/resources/checks/emptyCollectionConstructor.py", new EmptyCollectionConstructorCheck());
2732
}
2833

34+
private static Stream<Arguments> testQuickFixListConstructorSource() {
35+
return Stream.of(
36+
Arguments.of("dict()", "{}"),
37+
Arguments.of("tuple()", "()"),
38+
Arguments.of("list()", "[]")
39+
);
40+
}
41+
42+
@ParameterizedTest
43+
@MethodSource("testQuickFixListConstructorSource")
44+
void testQuickFixListConstructor(String before, String after) {
45+
PythonQuickFixVerifier.verify(
46+
new EmptyCollectionConstructorCheck(),
47+
before,
48+
after
49+
);
50+
51+
PythonQuickFixVerifier.verifyQuickFixMessages(new EmptyCollectionConstructorCheck(), before, "Replace with literal");
52+
}
53+
54+
@Test
55+
void testNoQuickFix() {
56+
PythonQuickFixVerifier.verifyNoQuickFixes(new EmptyCollectionConstructorCheck(), "dict(test=1)");
57+
}
58+
2959
}

0 commit comments

Comments
 (0)