Skip to content

Commit

Permalink
SONARPY-1278 Make the quick fix creation part of the public API (#1377)
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaume-dequenne authored Feb 20, 2023
1 parent 0e1ee9b commit f0ac790
Show file tree
Hide file tree
Showing 43 changed files with 395 additions and 409 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@
import org.sonar.plugins.python.api.tree.StatementList;
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.quickfix.IssueWithQuickFix;
import org.sonar.python.quickfix.PythonQuickFix;
import org.sonar.python.quickfix.PythonTextEdit;
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
import org.sonar.python.quickfix.TextEditUtils;
import org.sonar.python.tree.TreeUtils;

import static org.sonar.python.quickfix.PythonTextEdit.removeUntil;
import static org.sonar.python.quickfix.TextEditUtils.removeUntil;

@Rule(key = "S3923")
public class AllBranchesAreIdenticalCheck extends PythonSubscriptionCheck {
Expand Down Expand Up @@ -72,7 +71,7 @@ private static void handleIfStatement(IfStatement ifStmt, SubscriptionContext ct
if (!CheckUtils.areEquivalent(body, elseBranch.body())) {
return;
}
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(ifStmt.keyword(), IF_STATEMENT_MESSAGE);
PreciseIssue issue = ctx.addIssue(ifStmt.keyword(), IF_STATEMENT_MESSAGE);
issue.secondary(secondaryIssueLocation(ifStmt.body()));
ifStmt.elifBranches().forEach(e -> issue.secondary(secondaryIssueLocation(e.body())));
issue.secondary(secondaryIssueLocation(elseBranch.body()));
Expand All @@ -91,7 +90,7 @@ private static void handleConditionalExpression(ConditionalExpression conditiona
return;
}
if (areIdentical(conditionalExpression.trueExpression(), conditionalExpression.falseExpression())) {
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(conditionalExpression.ifKeyword(), CONDITIONAL_MESSAGE);
PreciseIssue issue = ctx.addIssue(conditionalExpression.ifKeyword(), CONDITIONAL_MESSAGE);
addSecondaryLocations(issue, conditionalExpression.trueExpression());
addSecondaryLocations(issue, conditionalExpression.falseExpression());
issue.addQuickFix(computeQuickFixForConditional(conditionalExpression));
Expand Down Expand Up @@ -153,11 +152,11 @@ private static PythonQuickFix computeQuickFixForIfStatement(IfStatement ifStatem
PythonQuickFix.Builder builder = PythonQuickFix.newQuickFix("Remove the if statement");

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

// Shift all body statements to the left
// Skip first shift because already done by removeUntil of the if statement
PythonTextEdit.shiftLeft(elseClause.body()).stream()
TextEditUtils.shiftLeft(elseClause.body()).stream()
.skip(1)
.forEach(builder::addTextEdit);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.tree.ReprExpression;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.quickfix.IssueWithQuickFix;
import org.sonar.python.quickfix.PythonQuickFix;
import org.sonar.python.quickfix.PythonTextEdit;
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
import org.sonar.plugins.python.api.quickfix.PythonTextEdit;
import org.sonar.python.quickfix.TextEditUtils;

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

PythonTextEdit text1 = PythonTextEdit
PythonTextEdit text1 = TextEditUtils
.replace(node.openingBacktick(), "repr(");
PythonTextEdit text2 = PythonTextEdit
PythonTextEdit text2 = TextEditUtils
.replace(node.closingBacktick(), ")");
PythonQuickFix quickFix = PythonQuickFix.newQuickFix("Replace backtick with \"repr()\".")
.addTextEdit(text1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.Tree.Kind;
import org.sonar.plugins.python.api.tree.UnaryExpression;
import org.sonar.python.quickfix.IssueWithQuickFix;
import org.sonar.python.quickfix.PythonQuickFix;
import org.sonar.python.quickfix.PythonTextEdit;
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
import org.sonar.plugins.python.api.quickfix.PythonTextEdit;
import org.sonar.python.quickfix.TextEditUtils;
import org.sonar.python.tree.TreeUtils;

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

IssueWithQuickFix issue = ((IssueWithQuickFix) ctx.addIssue(original, String.format(MESSAGE, oppositeOperator)));
PreciseIssue issue = (ctx.addIssue(original, String.format(MESSAGE, oppositeOperator)));
createQuickFix(issue, oppositeOperator, binaryExp, original);
}
} else if (negatedExpr.is(Kind.IN, Kind.IS)) {
BinaryExpression isInExpr = (BinaryExpression) negatedExpr;
String oppositeOperator = oppositeOperator(isInExpr.operator(), isInExpr);

IssueWithQuickFix issue = ((IssueWithQuickFix) ctx.addIssue(original, String.format(MESSAGE, oppositeOperator)));
PreciseIssue issue = (ctx.addIssue(original, String.format(MESSAGE, oppositeOperator)));
createQuickFix(issue, oppositeOperator, isInExpr, original);
}
}
Expand Down Expand Up @@ -110,7 +110,7 @@ static String oppositeOperatorString(String stringOperator) {
}
}

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

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

private static PythonTextEdit getReplaceEdit(BinaryExpression toUse, String oppositeOperator, UnaryExpression notAncestor) {
return PythonTextEdit.replace(notAncestor, getNewExpression(toUse, oppositeOperator));
return TextEditUtils.replace(notAncestor, getNewExpression(toUse, oppositeOperator));
}

private static String getNewExpression(BinaryExpression toUse, String oppositeOperator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,14 @@
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.SubscriptionContext;
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
import org.sonar.plugins.python.api.tree.BinaryExpression;
import org.sonar.plugins.python.api.tree.ExceptClause;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.Tree.Kind;
import org.sonar.python.quickfix.IssueWithQuickFix;
import org.sonar.python.quickfix.PythonQuickFix;
import org.sonar.python.quickfix.PythonTextEdit;
import org.sonar.python.quickfix.TextEditUtils;
import org.sonar.python.tree.TreeUtils;

@Rule(key = "S5714")
Expand All @@ -57,7 +56,7 @@ private static void checkExceptClause(SubscriptionContext ctx) {
.map(Expressions::removeParentheses)
.filter(exception -> exception.is(Kind.OR, Kind.AND))
.ifPresent(exception -> {
var issue = (IssueWithQuickFix) ctx.addIssue(exception, MESSAGE);
var issue = ctx.addIssue(exception, MESSAGE);
addQuickFix(issue, exception);
});
}
Expand Down Expand Up @@ -85,7 +84,7 @@ private static List<String> collectNames(Expression expression) {
throw new IllegalArgumentException("Unsupported kind of tree element: " + expression.getKind().name());
}

private static void addQuickFix(IssueWithQuickFix issue, Expression expression) {
private static void addQuickFix(PreciseIssue issue, Expression expression) {
expression = Objects.requireNonNullElse((Expression) TreeUtils.firstAncestorOfKind(expression, Kind.PARENTHESIZED), expression);

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

var quickFix = PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE)
.addTextEdit(PythonTextEdit.replace(expression, text))
.addTextEdit(TextEditUtils.replace(expression, text))
.build();

issue.addQuickFix(quickFix);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.SubscriptionContext;
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.symbols.Usage;
import org.sonar.plugins.python.api.tree.AnnotatedAssignment;
Expand All @@ -45,9 +46,7 @@
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.TupleParameter;
import org.sonar.python.quickfix.IssueWithQuickFix;
import org.sonar.python.quickfix.PythonQuickFix;
import org.sonar.python.quickfix.PythonTextEdit;
import org.sonar.python.quickfix.TextEditUtils;
import org.sonar.python.tree.TreeUtils;
import org.sonar.python.types.TypeShed;

Expand Down Expand Up @@ -155,7 +154,7 @@ private void raiseIssueForNonGlobalVariable(SubscriptionContext ctx, Name variab
if (existingIssue != null) {
existingIssue.secondary(variable, REPEATED_VAR_MESSAGE);
} else {
var issue = (IssueWithQuickFix) ctx.addIssue(variable, MESSAGE);
var issue = ctx.addIssue(variable, MESSAGE);
variableIssuesRaised.put(symbol, issue);

var names = collectUsedNames(variable);
Expand All @@ -172,7 +171,7 @@ private static PythonQuickFix createQuickFix(Symbol symbol) {
.stream()
.map(Usage::tree)
.map(Tree::firstToken)
.map(token -> PythonTextEdit.insertBefore(token, RENAME_PREFIX))
.map(token -> TextEditUtils.insertBefore(token, RENAME_PREFIX))
.collect(Collectors.toList());

return PythonQuickFix.newQuickFix(String.format(QUICK_FIX_MESSAGE_FORMAT, symbol.name()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.SubscriptionContext;
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
import org.sonar.plugins.python.api.symbols.ClassSymbol;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.symbols.Usage;
import org.sonar.plugins.python.api.tree.ArgList;
import org.sonar.plugins.python.api.tree.ClassDef;
import org.sonar.plugins.python.api.tree.ExceptClause;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.types.InferredType;
import org.sonar.python.quickfix.IssueWithQuickFix;
import org.sonar.python.quickfix.PythonQuickFix;
import org.sonar.python.quickfix.PythonTextEdit;
import org.sonar.python.quickfix.TextEditUtils;
import org.sonar.python.tree.TreeUtils;

import static org.sonar.plugins.python.api.symbols.Symbol.Kind.CLASS;
Expand Down Expand Up @@ -74,13 +75,13 @@ private static void checkExceptClause(SubscriptionContext ctx) {
.filter(Predicate.not(CaughtExceptionsCheck::inheritsFromBaseException))
.isPresent();
if (!canBeOrExtendBaseException(expression.type()) || notInheritsFromBaseException) {
var issue = (IssueWithQuickFix) ctx.addIssue(expression, MESSAGE);
var issue = ctx.addIssue(expression, MESSAGE);
expressionSymbolOpt.ifPresent(symbol -> addQuickFix(issue, symbol));
}
});
}

private static void addQuickFix(IssueWithQuickFix issue, Symbol symbol) {
private static void addQuickFix(PreciseIssue issue, Symbol symbol) {
symbol.usages()
.stream()
.filter(Usage::isBindingUsage)
Expand All @@ -92,18 +93,20 @@ private static void addQuickFix(IssueWithQuickFix issue, Symbol symbol) {
Tree insertAfter = classDef.name();
String insertingText = "(Exception)";

if (classDef.leftPar() != null) {
if (classDef.args() == null) {
insertAfter = classDef.leftPar();
Token leftPar = classDef.leftPar();
if (leftPar != null) {
ArgList args = classDef.args();
if (args == null) {
insertAfter = leftPar;
insertingText = "Exception";
} else {
insertAfter = classDef.args();
insertAfter = args;
insertingText = ", Exception";
}
}

issue.addQuickFix(PythonQuickFix.newQuickFix(String.format(QUICK_FIX_MESSAGE_FORMAT, classDef.name().name()))
.addTextEdit(PythonTextEdit.insertAfter(insertAfter, insertingText))
.addTextEdit(TextEditUtils.insertAfter(insertAfter, insertingText))
.build());
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.SubscriptionContext;
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
import org.sonar.plugins.python.api.symbols.ClassSymbol;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.tree.ExceptClause;
Expand All @@ -38,9 +39,7 @@
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.Tuple;
import org.sonar.python.quickfix.IssueWithQuickFix;
import org.sonar.python.quickfix.PythonQuickFix;
import org.sonar.python.quickfix.PythonTextEdit;
import org.sonar.python.quickfix.TextEditUtils;
import org.sonar.python.tree.TreeUtils;

@Rule(key = "S5713")
Expand Down Expand Up @@ -68,7 +67,7 @@ private static void checkCaughtExceptions(SubscriptionContext ctx, Map<ClassSymb
caughtExceptionsBySymbol.forEach((currentSymbol, caughtExceptionsWithSameSymbol) -> {
Expression currentException = caughtExceptionsWithSameSymbol.get(0);
if (caughtExceptionsWithSameSymbol.size() > 1) {
IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(currentException, "Remove this duplicate Exception class.");
var issue = ctx.addIssue(currentException, "Remove this duplicate Exception class.");
addQuickFix(issue, currentException);
caughtExceptionsWithSameSymbol.stream().skip(1).forEach(e -> issue.secondary(e, "Duplicate."));
}
Expand All @@ -79,7 +78,7 @@ private static void checkCaughtExceptions(SubscriptionContext ctx, Map<ClassSymb
.collect(Collectors.toList());

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

caughtParentExceptions.stream()
Expand All @@ -89,7 +88,7 @@ private static void checkCaughtExceptions(SubscriptionContext ctx, Map<ClassSymb
});
}

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

return PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE)
.addTextEdit(PythonTextEdit.replace(exceptions, text))
.addTextEdit(TextEditUtils.replace(exceptions, text))
.build();
}).orElse(null);
} catch (IllegalArgumentException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
import org.sonar.plugins.python.api.tree.Parameter;
import org.sonar.plugins.python.api.tree.ParameterList;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.quickfix.IssueWithQuickFix;
import org.sonar.python.quickfix.PythonQuickFix;
import org.sonar.python.quickfix.PythonTextEdit;
import org.sonar.plugins.python.api.quickfix.PythonQuickFix;
import org.sonar.plugins.python.api.quickfix.PythonTextEdit;
import org.sonar.python.quickfix.TextEditUtils;
import org.sonar.python.tree.TreeUtils;

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

issue.addQuickFix(addClsAsTheFirstArgument(parameterName));
Expand All @@ -106,9 +106,9 @@ private PythonQuickFix addClsAsTheFirstArgument(Name parameterName) {
PythonTextEdit text;
if (isSecondArgInNewLine(parameterName)) {
int indent = secondArgIndent(parameterName);
text = PythonTextEdit.insertBefore(parameterName, newName + ",\n" + " ".repeat(indent));
text = TextEditUtils.insertBefore(parameterName, newName + ",\n" + " ".repeat(indent));
} else {
text = PythonTextEdit.insertBefore(parameterName, newName + ", ");
text = TextEditUtils.insertBefore(parameterName, newName + ", ");
}
return PythonQuickFix.newQuickFix(String.format("Add '%s' as the first argument.", newName))
.addTextEdit(text)
Expand All @@ -133,7 +133,7 @@ private PythonQuickFix renameTheFirstArgument(Name parameterName) {
String newName = newName();

return PythonQuickFix.newQuickFix(String.format("Rename '%s' to '%s'", parameterName.name(), newName))
.addTextEdit(PythonTextEdit.renameAllUsages(parameterName, newName))
.addTextEdit(TextEditUtils.renameAllUsages(parameterName, newName))
.build();
}

Expand Down
Loading

0 comments on commit f0ac790

Please sign in to comment.