Skip to content

Commit 49d363d

Browse files
SONARPY-1025 Add quick fix for S5719 (InstanceAndClassMethodsAtLeastOnePositionalCheck) (#1142)
1 parent 9a53632 commit 49d363d

File tree

2 files changed

+62
-9
lines changed

2 files changed

+62
-9
lines changed

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

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,32 @@
3131
import org.sonar.plugins.python.api.symbols.Usage;
3232
import org.sonar.plugins.python.api.tree.ClassDef;
3333
import org.sonar.plugins.python.api.tree.FunctionDef;
34-
import org.sonar.plugins.python.api.tree.Name;
3534
import org.sonar.plugins.python.api.tree.Parameter;
3635
import org.sonar.plugins.python.api.tree.Tree;
36+
import org.sonar.python.quickfix.IssueWithQuickFix;
37+
import org.sonar.python.quickfix.PythonQuickFix;
3738
import org.sonar.python.tree.TreeUtils;
3839

40+
import static org.sonar.python.quickfix.PythonTextEdit.insertBefore;
41+
3942
@Rule(key="S5719")
4043
public class InstanceAndClassMethodsAtLeastOnePositionalCheck extends PythonSubscriptionCheck {
4144

4245
private static final List<String> KNOWN_CLASS_METHODS = Arrays.asList("__new__", "__init_subclass__");
4346

47+
private enum MethodIssueType {
48+
CLASS_METHOD("Add a class parameter", "cls"),
49+
REGULAR_METHOD("Add a \"self\" or class parameter", "self", "cls");
50+
51+
private final String message;
52+
private final List<String> insertions;
53+
54+
MethodIssueType(String message, String... insertions) {
55+
this.message = message;
56+
this.insertions = Arrays.asList(insertions);
57+
}
58+
}
59+
4460
private static boolean isUsageInClassBody(Usage usage, ClassDef classDef) {
4561
// We want all usages that are not function declarations and their closes parent is the class definition
4662
return usage.kind() != Usage.Kind.FUNC_DECLARATION
@@ -65,19 +81,30 @@ private static void handleFunctionDef(SubscriptionContext ctx, ClassDef classDef
6581

6682
List<String> decoratorNames = functionDef.decorators()
6783
.stream()
68-
.map(decorator ->
69-
TreeUtils.decoratorNameFromExpression(decorator.expression())
70-
).filter(Objects::nonNull).collect(Collectors.toList());
84+
.map(decorator -> TreeUtils.decoratorNameFromExpression(decorator.expression()))
85+
.filter(Objects::nonNull).collect(Collectors.toList());
7186

7287
if (decoratorNames.contains("staticmethod")) {
7388
return;
7489
}
7590

7691
String name = functionSymbol.name();
7792
if (KNOWN_CLASS_METHODS.contains(name) || decoratorNames.contains("classmethod")) {
78-
ctx.addIssue(functionDef.defKeyword(), functionDef.rightPar(), "Add a class parameter");
93+
addIssue(ctx, functionDef, MethodIssueType.CLASS_METHOD);
7994
} else {
80-
ctx.addIssue(functionDef.defKeyword(), functionDef.rightPar(), "Add a \"self\" or class parameter");
95+
addIssue(ctx, functionDef, MethodIssueType.REGULAR_METHOD);
96+
}
97+
}
98+
99+
private static void addIssue(SubscriptionContext ctx, FunctionDef functionDef, MethodIssueType type) {
100+
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(functionDef.defKeyword(), functionDef.rightPar(),
101+
type.message);
102+
103+
for (String insertion : type.insertions) {
104+
PythonQuickFix quickFix = PythonQuickFix.newQuickFix(String.format("Add '%s' as the first argument.", insertion))
105+
.addTextEdit(insertBefore(functionDef.rightPar(), insertion))
106+
.build();
107+
issue.addQuickFix(quickFix);
81108
}
82109
}
83110

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,41 @@
2020
package org.sonar.python.checks;
2121

2222
import org.junit.Test;
23+
import org.sonar.plugins.python.api.PythonCheck;
24+
import org.sonar.python.checks.quickfix.PythonQuickFixVerifier;
2325
import org.sonar.python.checks.utils.PythonCheckVerifier;
2426

2527
public class InstanceAndClassMethodsAtLeastOnePositionalCheckTest {
2628

29+
private final PythonCheck check = new InstanceAndClassMethodsAtLeastOnePositionalCheck();
2730
@Test
2831
public void test() {
29-
PythonCheckVerifier.verify(
30-
"src/test/resources/checks/instanceAndClassMethodAtLeastOnePositional.py",
31-
new InstanceAndClassMethodsAtLeastOnePositionalCheck());
32+
PythonCheckVerifier.verify("src/test/resources/checks/instanceAndClassMethodAtLeastOnePositional.py", check);
3233
}
3334

35+
@Test
36+
public void class_method_quickfix() {
37+
String codeWithIssue = "class Foo():\n" +
38+
" @classmethod\n" +
39+
" def bar(): pass";
40+
String fixedCodeWithClassParameter = "class Foo():\n" +
41+
" @classmethod\n" +
42+
" def bar(cls): pass";
43+
44+
PythonQuickFixVerifier.verify(check, codeWithIssue, fixedCodeWithClassParameter);
45+
}
46+
47+
@Test
48+
public void regular_method_quickfix() {
49+
String codeWithIssue = "class Foo():\n" +
50+
" def bar(): pass";
51+
String fixedCodeWithSelfParameter = "class Foo():\n" +
52+
" def bar(self): pass";
53+
String fixedCodeWithClassParameter = "class Foo():\n" +
54+
" def bar(cls): pass";
55+
56+
PythonQuickFixVerifier.verify(check, codeWithIssue,
57+
fixedCodeWithSelfParameter,
58+
fixedCodeWithClassParameter);
59+
}
3460
}

0 commit comments

Comments
 (0)