Skip to content

Commit f0ac790

Browse files
SONARPY-1278 Make the quick fix creation part of the public API (#1377)
1 parent 0e1ee9b commit f0ac790

File tree

43 files changed

+395
-409
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+395
-409
lines changed

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,11 @@
3434
import org.sonar.plugins.python.api.tree.StatementList;
3535
import org.sonar.plugins.python.api.tree.Token;
3636
import org.sonar.plugins.python.api.tree.Tree;
37-
import org.sonar.python.quickfix.IssueWithQuickFix;
38-
import org.sonar.python.quickfix.PythonQuickFix;
39-
import org.sonar.python.quickfix.PythonTextEdit;
37+
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
38+
import org.sonar.python.quickfix.TextEditUtils;
4039
import org.sonar.python.tree.TreeUtils;
4140

42-
import static org.sonar.python.quickfix.PythonTextEdit.removeUntil;
41+
import static org.sonar.python.quickfix.TextEditUtils.removeUntil;
4342

4443
@Rule(key = "S3923")
4544
public class AllBranchesAreIdenticalCheck extends PythonSubscriptionCheck {
@@ -72,7 +71,7 @@ private static void handleIfStatement(IfStatement ifStmt, SubscriptionContext ct
7271
if (!CheckUtils.areEquivalent(body, elseBranch.body())) {
7372
return;
7473
}
75-
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(ifStmt.keyword(), IF_STATEMENT_MESSAGE);
74+
PreciseIssue issue = ctx.addIssue(ifStmt.keyword(), IF_STATEMENT_MESSAGE);
7675
issue.secondary(secondaryIssueLocation(ifStmt.body()));
7776
ifStmt.elifBranches().forEach(e -> issue.secondary(secondaryIssueLocation(e.body())));
7877
issue.secondary(secondaryIssueLocation(elseBranch.body()));
@@ -91,7 +90,7 @@ private static void handleConditionalExpression(ConditionalExpression conditiona
9190
return;
9291
}
9392
if (areIdentical(conditionalExpression.trueExpression(), conditionalExpression.falseExpression())) {
94-
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(conditionalExpression.ifKeyword(), CONDITIONAL_MESSAGE);
93+
PreciseIssue issue = ctx.addIssue(conditionalExpression.ifKeyword(), CONDITIONAL_MESSAGE);
9594
addSecondaryLocations(issue, conditionalExpression.trueExpression());
9695
addSecondaryLocations(issue, conditionalExpression.falseExpression());
9796
issue.addQuickFix(computeQuickFixForConditional(conditionalExpression));
@@ -153,11 +152,11 @@ private static PythonQuickFix computeQuickFixForIfStatement(IfStatement ifStatem
153152
PythonQuickFix.Builder builder = PythonQuickFix.newQuickFix("Remove the if statement");
154153

155154
// Remove everything from if keyword to the last branch's body
156-
builder.addTextEdit(PythonTextEdit.removeUntil(ifStatement.keyword(), elseClause.body()));
155+
builder.addTextEdit(removeUntil(ifStatement.keyword(), elseClause.body()));
157156

158157
// Shift all body statements to the left
159158
// Skip first shift because already done by removeUntil of the if statement
160-
PythonTextEdit.shiftLeft(elseClause.body()).stream()
159+
TextEditUtils.shiftLeft(elseClause.body()).stream()
161160
.skip(1)
162161
.forEach(builder::addTextEdit);
163162

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2424
import org.sonar.plugins.python.api.tree.ReprExpression;
2525
import org.sonar.plugins.python.api.tree.Tree;
26-
import org.sonar.python.quickfix.IssueWithQuickFix;
27-
import org.sonar.python.quickfix.PythonQuickFix;
28-
import org.sonar.python.quickfix.PythonTextEdit;
26+
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
27+
import org.sonar.plugins.python.api.quickfix.PythonTextEdit;
28+
import org.sonar.python.quickfix.TextEditUtils;
2929

3030
@Rule(key = "BackticksUsage")
3131
public class BackticksUsageCheck extends PythonSubscriptionCheck {
@@ -34,11 +34,11 @@ public class BackticksUsageCheck extends PythonSubscriptionCheck {
3434
public void initialize(Context context) {
3535
context.registerSyntaxNodeConsumer(Tree.Kind.REPR, ctx -> {
3636
ReprExpression node = (ReprExpression) ctx.syntaxNode();
37-
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(node, "Use \"repr\" instead.");
37+
PreciseIssue issue = ctx.addIssue(node, "Use \"repr\" instead.");
3838

39-
PythonTextEdit text1 = PythonTextEdit
39+
PythonTextEdit text1 = TextEditUtils
4040
.replace(node.openingBacktick(), "repr(");
41-
PythonTextEdit text2 = PythonTextEdit
41+
PythonTextEdit text2 = TextEditUtils
4242
.replace(node.closingBacktick(), ")");
4343
PythonQuickFix quickFix = PythonQuickFix.newQuickFix("Replace backtick with \"repr()\".")
4444
.addTextEdit(text1)

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
import org.sonar.plugins.python.api.tree.Tree;
3232
import org.sonar.plugins.python.api.tree.Tree.Kind;
3333
import org.sonar.plugins.python.api.tree.UnaryExpression;
34-
import org.sonar.python.quickfix.IssueWithQuickFix;
35-
import org.sonar.python.quickfix.PythonQuickFix;
36-
import org.sonar.python.quickfix.PythonTextEdit;
34+
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
35+
import org.sonar.plugins.python.api.quickfix.PythonTextEdit;
36+
import org.sonar.python.quickfix.TextEditUtils;
3737
import org.sonar.python.tree.TreeUtils;
3838

3939
@Rule(key = "S1940")
@@ -57,14 +57,14 @@ private static void checkNotExpression(SubscriptionContext ctx, UnaryExpression
5757
if (!binaryExp.leftOperand().is(Kind.COMPARISON)) {
5858
String oppositeOperator = oppositeOperator(binaryExp.operator());
5959

60-
IssueWithQuickFix issue = ((IssueWithQuickFix) ctx.addIssue(original, String.format(MESSAGE, oppositeOperator)));
60+
PreciseIssue issue = (ctx.addIssue(original, String.format(MESSAGE, oppositeOperator)));
6161
createQuickFix(issue, oppositeOperator, binaryExp, original);
6262
}
6363
} else if (negatedExpr.is(Kind.IN, Kind.IS)) {
6464
BinaryExpression isInExpr = (BinaryExpression) negatedExpr;
6565
String oppositeOperator = oppositeOperator(isInExpr.operator(), isInExpr);
6666

67-
IssueWithQuickFix issue = ((IssueWithQuickFix) ctx.addIssue(original, String.format(MESSAGE, oppositeOperator)));
67+
PreciseIssue issue = (ctx.addIssue(original, String.format(MESSAGE, oppositeOperator)));
6868
createQuickFix(issue, oppositeOperator, isInExpr, original);
6969
}
7070
}
@@ -110,7 +110,7 @@ static String oppositeOperatorString(String stringOperator) {
110110
}
111111
}
112112

113-
private static void createQuickFix(IssueWithQuickFix issue, String oppositeOperator, BinaryExpression toUse, UnaryExpression notAncestor) {
113+
private static void createQuickFix(PreciseIssue issue, String oppositeOperator, BinaryExpression toUse, UnaryExpression notAncestor) {
114114
PythonTextEdit replaceEdit = getReplaceEdit(toUse, oppositeOperator, notAncestor);
115115

116116
PythonQuickFix quickFix = PythonQuickFix.newQuickFix(String.format("Use %s instead", oppositeOperator))
@@ -120,7 +120,7 @@ private static void createQuickFix(IssueWithQuickFix issue, String oppositeOpera
120120
}
121121

122122
private static PythonTextEdit getReplaceEdit(BinaryExpression toUse, String oppositeOperator, UnaryExpression notAncestor) {
123-
return PythonTextEdit.replace(notAncestor, getNewExpression(toUse, oppositeOperator));
123+
return TextEditUtils.replace(notAncestor, getNewExpression(toUse, oppositeOperator));
124124
}
125125

126126
private static String getNewExpression(BinaryExpression toUse, String oppositeOperator) {

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,14 @@
2727
import org.sonar.check.Rule;
2828
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2929
import org.sonar.plugins.python.api.SubscriptionContext;
30+
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
3031
import org.sonar.plugins.python.api.tree.BinaryExpression;
3132
import org.sonar.plugins.python.api.tree.ExceptClause;
3233
import org.sonar.plugins.python.api.tree.Expression;
3334
import org.sonar.plugins.python.api.tree.Name;
3435
import org.sonar.plugins.python.api.tree.Token;
3536
import org.sonar.plugins.python.api.tree.Tree.Kind;
36-
import org.sonar.python.quickfix.IssueWithQuickFix;
37-
import org.sonar.python.quickfix.PythonQuickFix;
38-
import org.sonar.python.quickfix.PythonTextEdit;
37+
import org.sonar.python.quickfix.TextEditUtils;
3938
import org.sonar.python.tree.TreeUtils;
4039

4140
@Rule(key = "S5714")
@@ -57,7 +56,7 @@ private static void checkExceptClause(SubscriptionContext ctx) {
5756
.map(Expressions::removeParentheses)
5857
.filter(exception -> exception.is(Kind.OR, Kind.AND))
5958
.ifPresent(exception -> {
60-
var issue = (IssueWithQuickFix) ctx.addIssue(exception, MESSAGE);
59+
var issue = ctx.addIssue(exception, MESSAGE);
6160
addQuickFix(issue, exception);
6261
});
6362
}
@@ -85,7 +84,7 @@ private static List<String> collectNames(Expression expression) {
8584
throw new IllegalArgumentException("Unsupported kind of tree element: " + expression.getKind().name());
8685
}
8786

88-
private static void addQuickFix(IssueWithQuickFix issue, Expression expression) {
87+
private static void addQuickFix(PreciseIssue issue, Expression expression) {
8988
expression = Objects.requireNonNullElse((Expression) TreeUtils.firstAncestorOfKind(expression, Kind.PARENTHESIZED), expression);
9089

9190
List<String> names;
@@ -100,7 +99,7 @@ private static void addQuickFix(IssueWithQuickFix issue, Expression expression)
10099
.collect(Collectors.joining(", ", "(", ")"));
101100

102101
var quickFix = PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE)
103-
.addTextEdit(PythonTextEdit.replace(expression, text))
102+
.addTextEdit(TextEditUtils.replace(expression, text))
104103
.build();
105104

106105
issue.addQuickFix(quickFix);

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.sonar.check.Rule;
3232
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
3333
import org.sonar.plugins.python.api.SubscriptionContext;
34+
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
3435
import org.sonar.plugins.python.api.symbols.Symbol;
3536
import org.sonar.plugins.python.api.symbols.Usage;
3637
import org.sonar.plugins.python.api.tree.AnnotatedAssignment;
@@ -45,9 +46,7 @@
4546
import org.sonar.plugins.python.api.tree.Token;
4647
import org.sonar.plugins.python.api.tree.Tree;
4748
import org.sonar.plugins.python.api.tree.TupleParameter;
48-
import org.sonar.python.quickfix.IssueWithQuickFix;
49-
import org.sonar.python.quickfix.PythonQuickFix;
50-
import org.sonar.python.quickfix.PythonTextEdit;
49+
import org.sonar.python.quickfix.TextEditUtils;
5150
import org.sonar.python.tree.TreeUtils;
5251
import org.sonar.python.types.TypeShed;
5352

@@ -155,7 +154,7 @@ private void raiseIssueForNonGlobalVariable(SubscriptionContext ctx, Name variab
155154
if (existingIssue != null) {
156155
existingIssue.secondary(variable, REPEATED_VAR_MESSAGE);
157156
} else {
158-
var issue = (IssueWithQuickFix) ctx.addIssue(variable, MESSAGE);
157+
var issue = ctx.addIssue(variable, MESSAGE);
159158
variableIssuesRaised.put(symbol, issue);
160159

161160
var names = collectUsedNames(variable);
@@ -172,7 +171,7 @@ private static PythonQuickFix createQuickFix(Symbol symbol) {
172171
.stream()
173172
.map(Usage::tree)
174173
.map(Tree::firstToken)
175-
.map(token -> PythonTextEdit.insertBefore(token, RENAME_PREFIX))
174+
.map(token -> TextEditUtils.insertBefore(token, RENAME_PREFIX))
176175
.collect(Collectors.toList());
177176

178177
return PythonQuickFix.newQuickFix(String.format(QUICK_FIX_MESSAGE_FORMAT, symbol.name()))

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,18 @@
2727
import org.sonar.check.Rule;
2828
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2929
import org.sonar.plugins.python.api.SubscriptionContext;
30+
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
3031
import org.sonar.plugins.python.api.symbols.ClassSymbol;
3132
import org.sonar.plugins.python.api.symbols.Symbol;
3233
import org.sonar.plugins.python.api.symbols.Usage;
34+
import org.sonar.plugins.python.api.tree.ArgList;
3335
import org.sonar.plugins.python.api.tree.ClassDef;
3436
import org.sonar.plugins.python.api.tree.ExceptClause;
3537
import org.sonar.plugins.python.api.tree.Expression;
38+
import org.sonar.plugins.python.api.tree.Token;
3639
import org.sonar.plugins.python.api.tree.Tree;
3740
import org.sonar.plugins.python.api.types.InferredType;
38-
import org.sonar.python.quickfix.IssueWithQuickFix;
39-
import org.sonar.python.quickfix.PythonQuickFix;
40-
import org.sonar.python.quickfix.PythonTextEdit;
41+
import org.sonar.python.quickfix.TextEditUtils;
4142
import org.sonar.python.tree.TreeUtils;
4243

4344
import static org.sonar.plugins.python.api.symbols.Symbol.Kind.CLASS;
@@ -74,13 +75,13 @@ private static void checkExceptClause(SubscriptionContext ctx) {
7475
.filter(Predicate.not(CaughtExceptionsCheck::inheritsFromBaseException))
7576
.isPresent();
7677
if (!canBeOrExtendBaseException(expression.type()) || notInheritsFromBaseException) {
77-
var issue = (IssueWithQuickFix) ctx.addIssue(expression, MESSAGE);
78+
var issue = ctx.addIssue(expression, MESSAGE);
7879
expressionSymbolOpt.ifPresent(symbol -> addQuickFix(issue, symbol));
7980
}
8081
});
8182
}
8283

83-
private static void addQuickFix(IssueWithQuickFix issue, Symbol symbol) {
84+
private static void addQuickFix(PreciseIssue issue, Symbol symbol) {
8485
symbol.usages()
8586
.stream()
8687
.filter(Usage::isBindingUsage)
@@ -92,18 +93,20 @@ private static void addQuickFix(IssueWithQuickFix issue, Symbol symbol) {
9293
Tree insertAfter = classDef.name();
9394
String insertingText = "(Exception)";
9495

95-
if (classDef.leftPar() != null) {
96-
if (classDef.args() == null) {
97-
insertAfter = classDef.leftPar();
96+
Token leftPar = classDef.leftPar();
97+
if (leftPar != null) {
98+
ArgList args = classDef.args();
99+
if (args == null) {
100+
insertAfter = leftPar;
98101
insertingText = "Exception";
99102
} else {
100-
insertAfter = classDef.args();
103+
insertAfter = args;
101104
insertingText = ", Exception";
102105
}
103106
}
104107

105108
issue.addQuickFix(PythonQuickFix.newQuickFix(String.format(QUICK_FIX_MESSAGE_FORMAT, classDef.name().name()))
106-
.addTextEdit(PythonTextEdit.insertAfter(insertAfter, insertingText))
109+
.addTextEdit(TextEditUtils.insertAfter(insertAfter, insertingText))
107110
.build());
108111
});
109112
}

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.sonar.check.Rule;
3030
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
3131
import org.sonar.plugins.python.api.SubscriptionContext;
32+
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
3233
import org.sonar.plugins.python.api.symbols.ClassSymbol;
3334
import org.sonar.plugins.python.api.symbols.Symbol;
3435
import org.sonar.plugins.python.api.tree.ExceptClause;
@@ -38,9 +39,7 @@
3839
import org.sonar.plugins.python.api.tree.Token;
3940
import org.sonar.plugins.python.api.tree.Tree;
4041
import org.sonar.plugins.python.api.tree.Tuple;
41-
import org.sonar.python.quickfix.IssueWithQuickFix;
42-
import org.sonar.python.quickfix.PythonQuickFix;
43-
import org.sonar.python.quickfix.PythonTextEdit;
42+
import org.sonar.python.quickfix.TextEditUtils;
4443
import org.sonar.python.tree.TreeUtils;
4544

4645
@Rule(key = "S5713")
@@ -68,7 +67,7 @@ private static void checkCaughtExceptions(SubscriptionContext ctx, Map<ClassSymb
6867
caughtExceptionsBySymbol.forEach((currentSymbol, caughtExceptionsWithSameSymbol) -> {
6968
Expression currentException = caughtExceptionsWithSameSymbol.get(0);
7069
if (caughtExceptionsWithSameSymbol.size() > 1) {
71-
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(currentException, "Remove this duplicate Exception class.");
70+
var issue = ctx.addIssue(currentException, "Remove this duplicate Exception class.");
7271
addQuickFix(issue, currentException);
7372
caughtExceptionsWithSameSymbol.stream().skip(1).forEach(e -> issue.secondary(e, "Duplicate."));
7473
}
@@ -79,7 +78,7 @@ private static void checkCaughtExceptions(SubscriptionContext ctx, Map<ClassSymb
7978
.collect(Collectors.toList());
8079

8180
if (!caughtParentExceptions.isEmpty()) {
82-
var issue = (IssueWithQuickFix) ctx.addIssue(currentException, "Remove this redundant Exception class; it derives from another which is already caught.");
81+
var issue = ctx.addIssue(currentException, "Remove this redundant Exception class; it derives from another which is already caught.");
8382
addQuickFix(issue, currentException);
8483

8584
caughtParentExceptions.stream()
@@ -89,7 +88,7 @@ private static void checkCaughtExceptions(SubscriptionContext ctx, Map<ClassSymb
8988
});
9089
}
9190

92-
private static void addQuickFix(IssueWithQuickFix issue, Expression currentException) {
91+
private static void addQuickFix(PreciseIssue issue, Expression currentException) {
9392
var quickFix = createQuickFix(currentException);
9493
if (quickFix != null) {
9594
issue.addQuickFix(quickFix);
@@ -138,7 +137,7 @@ private static PythonQuickFix createQuickFix(Expression currentException) {
138137
var text = names.size() == 1 ? names.get(0) : names.stream().collect(Collectors.joining(", ", "(", ")"));
139138

140139
return PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE)
141-
.addTextEdit(PythonTextEdit.replace(exceptions, text))
140+
.addTextEdit(TextEditUtils.replace(exceptions, text))
142141
.build();
143142
}).orElse(null);
144143
} catch (IllegalArgumentException e) {

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
import org.sonar.plugins.python.api.tree.Parameter;
3434
import org.sonar.plugins.python.api.tree.ParameterList;
3535
import org.sonar.plugins.python.api.tree.Tree;
36-
import org.sonar.python.quickfix.IssueWithQuickFix;
37-
import org.sonar.python.quickfix.PythonQuickFix;
38-
import org.sonar.python.quickfix.PythonTextEdit;
36+
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
37+
import org.sonar.plugins.python.api.quickfix.PythonTextEdit;
38+
import org.sonar.python.quickfix.TextEditUtils;
3939
import org.sonar.python.tree.TreeUtils;
4040

4141
@Rule(key = "S2710")
@@ -93,7 +93,7 @@ private void checkFirstParameterName(FunctionDef functionDef, SubscriptionContex
9393
return;
9494
}
9595
if (!classParameterNames().contains(parameterName.name())) {
96-
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(parameterName,
96+
PreciseIssue issue = ctx.addIssue(parameterName,
9797
String.format("Rename \"%s\" to a valid class parameter name or add the missing class parameter.", parameterName.name()));
9898

9999
issue.addQuickFix(addClsAsTheFirstArgument(parameterName));
@@ -106,9 +106,9 @@ private PythonQuickFix addClsAsTheFirstArgument(Name parameterName) {
106106
PythonTextEdit text;
107107
if (isSecondArgInNewLine(parameterName)) {
108108
int indent = secondArgIndent(parameterName);
109-
text = PythonTextEdit.insertBefore(parameterName, newName + ",\n" + " ".repeat(indent));
109+
text = TextEditUtils.insertBefore(parameterName, newName + ",\n" + " ".repeat(indent));
110110
} else {
111-
text = PythonTextEdit.insertBefore(parameterName, newName + ", ");
111+
text = TextEditUtils.insertBefore(parameterName, newName + ", ");
112112
}
113113
return PythonQuickFix.newQuickFix(String.format("Add '%s' as the first argument.", newName))
114114
.addTextEdit(text)
@@ -133,7 +133,7 @@ private PythonQuickFix renameTheFirstArgument(Name parameterName) {
133133
String newName = newName();
134134

135135
return PythonQuickFix.newQuickFix(String.format("Rename '%s' to '%s'", parameterName.name(), newName))
136-
.addTextEdit(PythonTextEdit.renameAllUsages(parameterName, newName))
136+
.addTextEdit(TextEditUtils.renameAllUsages(parameterName, newName))
137137
.build();
138138
}
139139

0 commit comments

Comments
 (0)