From f0ac790946498323d8f0d746b2d2318e6d51a12e Mon Sep 17 00:00:00 2001 From: Guillaume Dequenne Date: Mon, 20 Feb 2023 15:01:05 +0100 Subject: [PATCH] SONARPY-1278 Make the quick fix creation part of the public API (#1377) --- .../checks/AllBranchesAreIdenticalCheck.java | 15 ++-- .../python/checks/BackticksUsageCheck.java | 12 +-- .../checks/BooleanCheckNotInvertedCheck.java | 14 +-- .../BooleanExpressionInExceptCheck.java | 11 ++- .../BuiltinShadowingAssignmentCheck.java | 9 +- .../python/checks/CaughtExceptionsCheck.java | 23 ++--- .../ChildAndParentExceptionCaughtCheck.java | 13 ++- .../ClassMethodFirstArgumentNameCheck.java | 14 +-- .../sonar/python/checks/DeadStoreCheck.java | 17 ++-- .../DuplicatedMethodImplementationCheck.java | 12 +-- .../python/checks/EmptyFunctionCheck.java | 9 +- .../python/checks/EmptyNestedBlockCheck.java | 11 ++- .../checks/ExceptionNotThrownCheck.java | 9 +- ...dentityComparisonWithCachedTypesCheck.java | 15 ++-- .../IdentityComparisonWithNewObjectCheck.java | 17 ++-- .../python/checks/IgnoredSystemExitCheck.java | 19 ++-- .../ImplicitStringConcatenationCheck.java | 10 +-- ...ClassMethodsAtLeastOnePositionalCheck.java | 7 +- .../python/checks/MissingDocstringCheck.java | 15 ++-- .../checks/ModifiedParameterValueCheck.java | 9 +- .../python/checks/NeedlessPassCheck.java | 9 +- ...mplementedErrorInOperatorMethodsCheck.java | 9 +- .../python/checks/RedundantJumpCheck.java | 11 ++- .../python/checks/TrailingCommentCheck.java | 14 +-- .../checks/TrailingWhitespaceCheck.java | 9 +- .../checks/UselessParenthesisCheck.java | 9 +- .../checks/regex/VerboseRegexCheck.java | 6 +- .../checks/tests/AssertAfterRaiseCheck.java | 12 +-- .../tests/AssertOnTupleLiteralCheck.java | 11 ++- .../quickfix/PythonQuickFixVerifier.java | 23 +++-- .../quickfix/PythonQuickFixVerifierTest.java | 10 +-- .../sonar/plugins/python/api/PythonCheck.java | 14 +++ .../python/api}/quickfix/PythonQuickFix.java | 7 +- .../python/api/quickfix/PythonTextEdit.java | 86 +++++++++++++++++++ .../python/api/quickfix/package-info.java} | 26 +----- .../org/sonar/python/SubscriptionVisitor.java | 3 +- ...PythonTextEdit.java => TextEditUtils.java} | 76 ++-------------- .../api}/quickfix/PythonQuickFixTest.java | 4 +- .../api/quickfix/PythonTextEditTest.java | 58 +++++++++++++ .../org/sonar/python/PythonCheckTest.java | 15 +++- .../quickfix/IssueWithQuickFixTest.java | 53 ------------ ...xtEditTest.java => TextEditUtilsTest.java} | 69 +++++---------- .../sonar/plugins/python/PythonScanner.java | 9 +- 43 files changed, 395 insertions(+), 409 deletions(-) rename python-frontend/src/main/java/org/sonar/{python => plugins/python/api}/quickfix/PythonQuickFix.java (95%) create mode 100644 python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/PythonTextEdit.java rename python-frontend/src/main/java/org/sonar/{python/quickfix/IssueWithQuickFix.java => plugins/python/api/quickfix/package-info.java} (58%) rename python-frontend/src/main/java/org/sonar/python/quickfix/{PythonTextEdit.java => TextEditUtils.java} (77%) rename python-frontend/src/test/java/org/sonar/{python => plugins/python/api}/quickfix/PythonQuickFixTest.java (90%) create mode 100644 python-frontend/src/test/java/org/sonar/plugins/python/api/quickfix/PythonTextEditTest.java delete mode 100644 python-frontend/src/test/java/org/sonar/python/quickfix/IssueWithQuickFixTest.java rename python-frontend/src/test/java/org/sonar/python/quickfix/{PythonTextEditTest.java => TextEditUtilsTest.java} (71%) diff --git a/python-checks/src/main/java/org/sonar/python/checks/AllBranchesAreIdenticalCheck.java b/python-checks/src/main/java/org/sonar/python/checks/AllBranchesAreIdenticalCheck.java index de588c43bd..d5864a3b44 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/AllBranchesAreIdenticalCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/AllBranchesAreIdenticalCheck.java @@ -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 { @@ -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())); @@ -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)); @@ -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); diff --git a/python-checks/src/main/java/org/sonar/python/checks/BackticksUsageCheck.java b/python-checks/src/main/java/org/sonar/python/checks/BackticksUsageCheck.java index 5d514a98a8..1c8ee9ed79 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/BackticksUsageCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/BackticksUsageCheck.java @@ -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 { @@ -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) diff --git a/python-checks/src/main/java/org/sonar/python/checks/BooleanCheckNotInvertedCheck.java b/python-checks/src/main/java/org/sonar/python/checks/BooleanCheckNotInvertedCheck.java index 598dc8ab06..51e458f3a4 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/BooleanCheckNotInvertedCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/BooleanCheckNotInvertedCheck.java @@ -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") @@ -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); } } @@ -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)) @@ -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) { diff --git a/python-checks/src/main/java/org/sonar/python/checks/BooleanExpressionInExceptCheck.java b/python-checks/src/main/java/org/sonar/python/checks/BooleanExpressionInExceptCheck.java index 50f85e1340..3550339306 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/BooleanExpressionInExceptCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/BooleanExpressionInExceptCheck.java @@ -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") @@ -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); }); } @@ -85,7 +84,7 @@ private static List 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 names; @@ -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); diff --git a/python-checks/src/main/java/org/sonar/python/checks/BuiltinShadowingAssignmentCheck.java b/python-checks/src/main/java/org/sonar/python/checks/BuiltinShadowingAssignmentCheck.java index 1c82a932af..41f269aae3 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/BuiltinShadowingAssignmentCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/BuiltinShadowingAssignmentCheck.java @@ -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; @@ -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; @@ -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); @@ -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())) diff --git a/python-checks/src/main/java/org/sonar/python/checks/CaughtExceptionsCheck.java b/python-checks/src/main/java/org/sonar/python/checks/CaughtExceptionsCheck.java index 06e0e837fe..c1e5dc625d 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/CaughtExceptionsCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/CaughtExceptionsCheck.java @@ -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; @@ -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) @@ -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()); }); } diff --git a/python-checks/src/main/java/org/sonar/python/checks/ChildAndParentExceptionCaughtCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ChildAndParentExceptionCaughtCheck.java index 74a2fca516..7295056301 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ChildAndParentExceptionCaughtCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ChildAndParentExceptionCaughtCheck.java @@ -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; @@ -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") @@ -68,7 +67,7 @@ private static void checkCaughtExceptions(SubscriptionContext ctx, Map { 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.")); } @@ -79,7 +78,7 @@ private static void checkCaughtExceptions(SubscriptionContext ctx, Map int line = originalMethod.name().firstToken().line(); String message = String.format(MESSAGE, originalMethod.name().name(), line); PreciseIssue issue = ctx.addIssue(suspiciousMethod.name(), message).secondary(originalMethod.name(), "Original"); - addQuickFix((IssueWithQuickFix) issue, originalMethod, suspiciousMethod); + addQuickFix(issue, originalMethod, suspiciousMethod); break; } } @@ -96,7 +96,7 @@ private static boolean isOnASingleLine(StatementList statementList, boolean hasD return statementList.statements().get(first).firstToken().line() == statementList.statements().get(statementList.statements().size() - 1).lastToken().line(); } - private static void addQuickFix(IssueWithQuickFix issue, FunctionDef originalMethod, FunctionDef suspiciousMethod) { + private static void addQuickFix(PreciseIssue issue, FunctionDef originalMethod, FunctionDef suspiciousMethod) { ParameterList parameters = originalMethod.parameters(); if (parameters != null) { List all = parameters.all(); @@ -122,7 +122,7 @@ private static void addQuickFix(IssueWithQuickFix issue, FunctionDef originalMet replacementText = replacementText + "self."; } replacementText = replacementText + originalMethod.name().name() + "()"; - PythonTextEdit edit = PythonTextEdit.replace(suspiciousMethod.body(), replacementText); + PythonTextEdit edit = TextEditUtils.replace(suspiciousMethod.body(), replacementText); PythonQuickFix fix = PythonQuickFix .newQuickFix(String.format(QUICK_FIX_MESSAGE, originalMethod.name().name())) diff --git a/python-checks/src/main/java/org/sonar/python/checks/EmptyFunctionCheck.java b/python-checks/src/main/java/org/sonar/python/checks/EmptyFunctionCheck.java index 3784c1c0d2..968de667c9 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/EmptyFunctionCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/EmptyFunctionCheck.java @@ -28,11 +28,10 @@ import org.sonar.plugins.python.api.tree.Statement; 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.plugins.python.api.quickfix.PythonQuickFix; import org.sonar.python.tree.TreeUtils; -import static org.sonar.python.quickfix.PythonTextEdit.insertLineBefore; +import static org.sonar.python.quickfix.TextEditUtils.insertLineBefore; @Rule(key = "S1186") public class EmptyFunctionCheck extends PythonSubscriptionCheck { @@ -67,13 +66,13 @@ public void initialize(Context context) { return; } String type = functionDef.isMethodDefinition() ? "method" : "function"; - IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(functionDef.name(), String.format(MESSAGE, type)); + PreciseIssue issue = ctx.addIssue(functionDef.name(), String.format(MESSAGE, type)); addQuickFixes(issue, functionDef, type); } }); } - private static void addQuickFixes(IssueWithQuickFix issue, FunctionDef functionDef, String functionType) { + private static void addQuickFixes(PreciseIssue issue, FunctionDef functionDef, String functionType) { Statement passStatement = functionDef.body().statements().get(0); issue.addQuickFix(PythonQuickFix.newQuickFix("Insert placeholder comment", insertLineBefore(passStatement, "# TODO document why this method is empty"))); diff --git a/python-checks/src/main/java/org/sonar/python/checks/EmptyNestedBlockCheck.java b/python-checks/src/main/java/org/sonar/python/checks/EmptyNestedBlockCheck.java index fd490e3453..5975faf5e2 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/EmptyNestedBlockCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/EmptyNestedBlockCheck.java @@ -30,9 +30,8 @@ import org.sonar.plugins.python.api.tree.Tree; import org.sonar.plugins.python.api.tree.Tree.Kind; import org.sonar.python.api.PythonTokenType; -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; @Rule(key = EmptyNestedBlockCheck.CHECK_KEY) @@ -67,11 +66,11 @@ public void initialize(Context context) { .map(Tree.class::cast) .orElseGet(statementListTree::firstToken); - var issue = (IssueWithQuickFix) ctx.addIssue(passTreeElement, MESSAGE); + var issue = ctx.addIssue(passTreeElement, MESSAGE); if (passTreeElement.firstToken().line() != parent.firstToken().line()) { var quickFix = PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE, - PythonTextEdit.insertLineBefore(passTreeElement, TODO_COMMENT_TEXT)); + TextEditUtils.insertLineBefore(passTreeElement, TODO_COMMENT_TEXT)); issue.addQuickFix(quickFix); } else { var indent = TreeUtils.findIndentationSize(passTreeElement); @@ -79,7 +78,7 @@ public void initialize(Context context) { var offset = parent.firstToken().column() + indent; var textToInsert = "\n" + " ".repeat(offset) + TODO_COMMENT_TEXT + "\n" + " ".repeat(offset); var quickFix = PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE, - PythonTextEdit.insertBefore(passTreeElement, textToInsert)); + TextEditUtils.insertBefore(passTreeElement, textToInsert)); issue.addQuickFix(quickFix); } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/ExceptionNotThrownCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ExceptionNotThrownCheck.java index 2a39688ac2..2cf9f2d8fb 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ExceptionNotThrownCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ExceptionNotThrownCheck.java @@ -33,9 +33,8 @@ import org.sonar.plugins.python.api.tree.CallExpression; import org.sonar.plugins.python.api.tree.Name; 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.semantic.BuiltinSymbols; // https://jira.sonarsource.com/browse/RSPEC-3984 @@ -58,10 +57,10 @@ private static Consumer check(Function extrac if (symb != null && symb.is(Symbol.Kind.CLASS) && isThrowable((ClassSymbol) symb)) { Tree parent = t.parent(); if (parent.is(Tree.Kind.EXPRESSION_STMT)) { - var issue = (IssueWithQuickFix) subscriptionContext.addIssue(t, MESSAGE); + var issue = subscriptionContext.addIssue(t, MESSAGE); var quickFix = PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE) - .addTextEdit(PythonTextEdit.insertBefore(t, "raise ")) + .addTextEdit(TextEditUtils.insertBefore(t, "raise ")) .build(); issue.addQuickFix(quickFix); diff --git a/python-checks/src/main/java/org/sonar/python/checks/IdentityComparisonWithCachedTypesCheck.java b/python-checks/src/main/java/org/sonar/python/checks/IdentityComparisonWithCachedTypesCheck.java index eeb3e57efc..42e606c4ff 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/IdentityComparisonWithCachedTypesCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/IdentityComparisonWithCachedTypesCheck.java @@ -33,9 +33,8 @@ import org.sonar.plugins.python.api.tree.Name; 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.plugins.python.api.quickfix.PythonQuickFix; +import org.sonar.python.quickfix.TextEditUtils; import org.sonar.python.tree.TreeUtils; import static java.util.Arrays.asList; @@ -66,18 +65,18 @@ private static void checkIsComparison(SubscriptionContext ctx) { var notToken = isExpr.notToken(); if (notToken == null) { var quickFix = PythonQuickFix.newQuickFix(IS_QUICK_FIX_MESSAGE) - .addTextEdit(PythonTextEdit.replace(isExpr.operator(), "==")) + .addTextEdit(TextEditUtils.replace(isExpr.operator(), "==")) .build(); - var issue = (IssueWithQuickFix) ctx.addIssue(isExpr.operator(), MESSAGE_IS); + var issue = ctx.addIssue(isExpr.operator(), MESSAGE_IS); issue.addQuickFix(quickFix); } else { var quickFix = PythonQuickFix.newQuickFix(IS_NOT_QUICK_FIX_MESSAGE) - .addTextEdit(PythonTextEdit.replace(isExpr.operator(), "!=")) - .addTextEdit(PythonTextEdit.removeUntil(notToken, isExpr.rightOperand())) + .addTextEdit(TextEditUtils.replace(isExpr.operator(), "!=")) + .addTextEdit(TextEditUtils.removeUntil(notToken, isExpr.rightOperand())) .build(); - var issue = (IssueWithQuickFix) ctx.addIssue(isExpr.operator(), notToken, MESSAGE_IS_NOT); + var issue = ctx.addIssue(isExpr.operator(), notToken, MESSAGE_IS_NOT); issue.addQuickFix(quickFix); } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/IdentityComparisonWithNewObjectCheck.java b/python-checks/src/main/java/org/sonar/python/checks/IdentityComparisonWithNewObjectCheck.java index 803459488c..c909f3244e 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/IdentityComparisonWithNewObjectCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/IdentityComparisonWithNewObjectCheck.java @@ -35,9 +35,8 @@ import org.sonar.plugins.python.api.tree.Name; 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.plugins.python.api.quickfix.PythonQuickFix; +import org.sonar.python.quickfix.TextEditUtils; import org.sonar.python.tree.TreeUtils; import static java.util.Arrays.asList; @@ -81,21 +80,21 @@ private static void checkIsComparison(SubscriptionContext subscriptionContext) { private static boolean checkOperand(Expression operand, IsExpression isExpr, SubscriptionContext ctx) { Optional> secondariesOpt = findIssueForOperand(operand); secondariesOpt.ifPresent(secondaryLocations -> { - IssueWithQuickFix issue; + PreciseIssue issue; var notToken = isExpr.notToken(); if (notToken != null) { var quickFix = PythonQuickFix.newQuickFix(IS_NOT_QUICK_FIX_MESSAGE) - .addTextEdit(PythonTextEdit.replace(isExpr.operator(), "!=")) - .addTextEdit(PythonTextEdit.removeUntil(notToken, isExpr.rightOperand())) + .addTextEdit(TextEditUtils.replace(isExpr.operator(), "!=")) + .addTextEdit(TextEditUtils.removeUntil(notToken, isExpr.rightOperand())) .build(); - issue = (IssueWithQuickFix) ctx.addIssue(isExpr.operator(), notToken, MESSAGE_IS_NOT); + issue = ctx.addIssue(isExpr.operator(), notToken, MESSAGE_IS_NOT); issue.addQuickFix(quickFix); } else { var quickFix = PythonQuickFix.newQuickFix(IS_QUICK_FIX_MESSAGE) - .addTextEdit(PythonTextEdit.replace(isExpr.operator(), "==")) + .addTextEdit(TextEditUtils.replace(isExpr.operator(), "==")) .build(); - issue = (IssueWithQuickFix) ctx.addIssue(isExpr.operator(), MESSAGE_IS); + issue = ctx.addIssue(isExpr.operator(), MESSAGE_IS); issue.addQuickFix(quickFix); } diff --git a/python-checks/src/main/java/org/sonar/python/checks/IgnoredSystemExitCheck.java b/python-checks/src/main/java/org/sonar/python/checks/IgnoredSystemExitCheck.java index f9f4b4b6ad..504a2c7202 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/IgnoredSystemExitCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/IgnoredSystemExitCheck.java @@ -37,9 +37,8 @@ import org.sonar.plugins.python.api.tree.Token; import org.sonar.plugins.python.api.tree.Tree; import org.sonar.plugins.python.api.tree.TryStatement; -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; @Rule(key = "S5754") @@ -135,7 +134,7 @@ private static void handlePossibleBareException(SubscriptionContext ctx, ExceptC ExceptionReRaiseCheckVisitor visitor = new ExceptionReRaiseCheckVisitor(null); exceptClause.accept(visitor); if (!visitor.isReRaised && !isSystemExitHandled) { - var issue = (IssueWithQuickFix) ctx.addIssue(exceptClause.exceptKeyword(), MESSAGE_BARE_EXCEPT); + var issue = ctx.addIssue(exceptClause.exceptKeyword(), MESSAGE_BARE_EXCEPT); addQuickFix(exceptClause, issue); } } @@ -161,37 +160,37 @@ private static boolean handleCaughtException(SubscriptionContext ctx, Expression } if (SYSTEM_EXIT_EXCEPTION_NAME.equals(caughtExceptionName)) { - var issue = (IssueWithQuickFix) ctx.addIssue(caughtException, MESSAGE_NOT_RERAISED_CAUGHT_EXCEPTION); + var issue = ctx.addIssue(caughtException, MESSAGE_NOT_RERAISED_CAUGHT_EXCEPTION); addQuickFix(caughtException, issue); return true; } if (BASE_EXCEPTION_NAME.equals(caughtExceptionName) && !handledSystemExit) { - var issue = (IssueWithQuickFix) ctx.addIssue(caughtException, MESSAGE_NOT_RERAISED_BASE_EXCEPTION); + var issue = ctx.addIssue(caughtException, MESSAGE_NOT_RERAISED_BASE_EXCEPTION); addQuickFix(caughtException, issue); } return false; } - private static void addQuickFix(Expression caughtException, IssueWithQuickFix issue) { + private static void addQuickFix(Expression caughtException, PreciseIssue issue) { Optional.of(caughtException) .map(e -> TreeUtils.firstAncestor(caughtException, p -> p.is(Tree.Kind.EXCEPT_CLAUSE))) .map(ExceptClause.class::cast) .ifPresent(exceptClause -> addQuickFix(exceptClause, issue)); } - private static void addQuickFix(ExceptClause exceptClause, IssueWithQuickFix issue) { + private static void addQuickFix(ExceptClause exceptClause, PreciseIssue issue) { var bodyStatements = exceptClause.body().statements(); var lastStatement = bodyStatements.get(bodyStatements.size() - 1); var quickFixBuilder = PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE); if (lastStatement.is(Tree.Kind.PASS_STMT) || TreeUtils.hasDescendant(lastStatement, c -> c.is(Tree.Kind.ELLIPSIS))) { - quickFixBuilder.addTextEdit(PythonTextEdit.replace(lastStatement, "raise")); + quickFixBuilder.addTextEdit(TextEditUtils.replace(lastStatement, "raise")); } else { Token lastToken = lastStatement.lastToken(); - quickFixBuilder.addTextEdit(PythonTextEdit.insertLineAfter(lastToken, lastStatement, "raise")); + quickFixBuilder.addTextEdit(TextEditUtils.insertLineAfter(lastToken, lastStatement, "raise")); } issue.addQuickFix(quickFixBuilder.build()); } diff --git a/python-checks/src/main/java/org/sonar/python/checks/ImplicitStringConcatenationCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ImplicitStringConcatenationCheck.java index 074e2d65fe..f283f704a6 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ImplicitStringConcatenationCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ImplicitStringConcatenationCheck.java @@ -27,11 +27,10 @@ import org.sonar.plugins.python.api.tree.StringElement; import org.sonar.plugins.python.api.tree.StringLiteral; import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.quickfix.IssueWithQuickFix; -import org.sonar.python.quickfix.PythonQuickFix; +import org.sonar.plugins.python.api.quickfix.PythonQuickFix; -import static org.sonar.python.quickfix.PythonTextEdit.insertAfter; -import static org.sonar.python.quickfix.PythonTextEdit.replaceRange; +import static org.sonar.python.quickfix.TextEditUtils.insertAfter; +import static org.sonar.python.quickfix.TextEditUtils.replaceRange; @Rule(key = "S5799") public class ImplicitStringConcatenationCheck extends PythonSubscriptionCheck { @@ -106,8 +105,7 @@ private static boolean isInFunctionOrArrayOrTupleOrExpressionOrSet(StringElement Tree.Kind.SET_LITERAL, Tree.Kind.TUPLE); } - private static void createQuickFix(PreciseIssue issueRaised, StringElement start, StringElement end) { - IssueWithQuickFix issue = (IssueWithQuickFix) issueRaised; + private static void createQuickFix(PreciseIssue issue, StringElement start, StringElement end) { String textStart = start.value(); String textEnd = end.value(); diff --git a/python-checks/src/main/java/org/sonar/python/checks/InstanceAndClassMethodsAtLeastOnePositionalCheck.java b/python-checks/src/main/java/org/sonar/python/checks/InstanceAndClassMethodsAtLeastOnePositionalCheck.java index 6a7a342fe9..5d4663a766 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/InstanceAndClassMethodsAtLeastOnePositionalCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/InstanceAndClassMethodsAtLeastOnePositionalCheck.java @@ -33,11 +33,10 @@ import org.sonar.plugins.python.api.tree.FunctionDef; import org.sonar.plugins.python.api.tree.Parameter; import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.quickfix.IssueWithQuickFix; -import org.sonar.python.quickfix.PythonQuickFix; +import org.sonar.plugins.python.api.quickfix.PythonQuickFix; import org.sonar.python.tree.TreeUtils; -import static org.sonar.python.quickfix.PythonTextEdit.insertAfter; +import static org.sonar.python.quickfix.TextEditUtils.insertAfter; @Rule(key="S5719") public class InstanceAndClassMethodsAtLeastOnePositionalCheck extends PythonSubscriptionCheck { @@ -97,7 +96,7 @@ private static void handleFunctionDef(SubscriptionContext ctx, ClassDef classDef } private static void addIssue(SubscriptionContext ctx, FunctionDef functionDef, MethodIssueType type) { - IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(functionDef.defKeyword(), functionDef.rightPar(), + PreciseIssue issue = ctx.addIssue(functionDef.defKeyword(), functionDef.rightPar(), type.message); String separator = functionDef.parameters() == null ? "" : ", "; for (String insertion : type.insertions) { diff --git a/python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java b/python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java index fa581f9c5e..426dcb5217 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java @@ -30,9 +30,8 @@ import org.sonar.plugins.python.api.tree.StringLiteral; import org.sonar.plugins.python.api.tree.Tree; 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.plugins.python.api.quickfix.PythonQuickFix; +import org.sonar.python.quickfix.TextEditUtils; @Rule(key = "S1720") public class MissingDocstringCheck extends PythonSubscriptionCheck { @@ -108,7 +107,7 @@ private static void raiseIssue(Tree tree, String message, DeclarationType type, } else { issue = ctx.addFileIssue(finalMessage); } - addQuickFix((IssueWithQuickFix) issue, tree, type); + addQuickFix(issue, tree, type); } private static Name getNameNode(Tree tree) { @@ -118,17 +117,17 @@ private static Name getNameNode(Tree tree) { return ((ClassDef) tree).name(); } - private static void addQuickFix(IssueWithQuickFix issue, Tree tree, DeclarationType type) { + private static void addQuickFix(PreciseIssue issue, Tree tree, DeclarationType type) { PythonQuickFix.Builder quickFix = PythonQuickFix.newQuickFix("Add docstring"); if (type == DeclarationType.MODULE) { - quickFix.addTextEdit(PythonTextEdit.insertAtPosition(1, 0, EMPTY_DOCSTRING)); + quickFix.addTextEdit(TextEditUtils.insertAtPosition(1, 0, EMPTY_DOCSTRING)); } else if (type == DeclarationType.CLASS) { ClassDef classDef = (ClassDef) tree; - quickFix.addTextEdit(PythonTextEdit.insertLineAfter(classDef.colon(), classDef.body(), EMPTY_DOCSTRING)); + quickFix.addTextEdit(TextEditUtils.insertLineAfter(classDef.colon(), classDef.body(), EMPTY_DOCSTRING)); } else { FunctionDef functionDef = (FunctionDef) tree; - quickFix.addTextEdit(PythonTextEdit.insertLineAfter(functionDef.colon(), functionDef.body(), EMPTY_DOCSTRING)); + quickFix.addTextEdit(TextEditUtils.insertLineAfter(functionDef.colon(), functionDef.body(), EMPTY_DOCSTRING)); } issue.addQuickFix(quickFix.build()); diff --git a/python-checks/src/main/java/org/sonar/python/checks/ModifiedParameterValueCheck.java b/python-checks/src/main/java/org/sonar/python/checks/ModifiedParameterValueCheck.java index e8f2a2cadc..0dcf07a54c 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/ModifiedParameterValueCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/ModifiedParameterValueCheck.java @@ -45,8 +45,7 @@ import org.sonar.plugins.python.api.tree.SubscriptionExpression; import org.sonar.plugins.python.api.tree.Tree; import org.sonar.plugins.python.api.types.BuiltinTypes; -import org.sonar.python.quickfix.IssueWithQuickFix; -import org.sonar.python.quickfix.PythonQuickFix; +import org.sonar.plugins.python.api.quickfix.PythonQuickFix; import org.sonar.python.tree.TreeUtils; import static org.sonar.plugins.python.api.tree.Tree.Kind.ASSIGNMENT_STMT; @@ -60,8 +59,8 @@ import static org.sonar.plugins.python.api.tree.Tree.Kind.QUALIFIED_EXPR; import static org.sonar.plugins.python.api.tree.Tree.Kind.SET_LITERAL; import static org.sonar.plugins.python.api.tree.Tree.Kind.SUBSCRIPTION; -import static org.sonar.python.quickfix.PythonTextEdit.insertLineBefore; -import static org.sonar.python.quickfix.PythonTextEdit.replace; +import static org.sonar.python.quickfix.TextEditUtils.insertLineBefore; +import static org.sonar.python.quickfix.TextEditUtils.replace; import static org.sonar.python.tree.TreeUtils.getSymbolFromTree; import static org.sonar.python.tree.TreeUtils.nonTupleParameters; @@ -134,7 +133,7 @@ public void initialize(Context context) { .ifPresent(paramSymbol -> { Map mutations = getMutations(defaultValue, paramSymbol); if (!mutations.isEmpty()) { - IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(parameter, MESSAGE); + PreciseIssue issue = ctx.addIssue(parameter, MESSAGE); mutations.keySet().forEach(t -> issue.secondary(t, mutations.get(t))); getQuickFix(functionDef, defaultValue, paramSymbol) diff --git a/python-checks/src/main/java/org/sonar/python/checks/NeedlessPassCheck.java b/python-checks/src/main/java/org/sonar/python/checks/NeedlessPassCheck.java index 9a791e040c..2e9eb42e6d 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/NeedlessPassCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/NeedlessPassCheck.java @@ -26,9 +26,8 @@ import org.sonar.plugins.python.api.tree.ExpressionStatement; import org.sonar.plugins.python.api.tree.Statement; import org.sonar.plugins.python.api.tree.StatementList; -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 static org.sonar.plugins.python.api.tree.Tree.Kind.EXPRESSION_STMT; import static org.sonar.plugins.python.api.tree.Tree.Kind.PASS_STMT; @@ -55,10 +54,10 @@ public void initialize(Context context) { .filter(st -> st.is(PASS_STMT)) .findFirst() .ifPresent(st -> { - var issue = (IssueWithQuickFix) ctx.addIssue(st, MESSAGE); + var issue = ctx.addIssue(st, MESSAGE); var quickFix = PythonQuickFix .newQuickFix(QUICK_FIX_MESSAGE) - .addTextEdit(PythonTextEdit.removeStatement(st)) + .addTextEdit(TextEditUtils.removeStatement(st)) .build(); issue.addQuickFix(quickFix); }); diff --git a/python-checks/src/main/java/org/sonar/python/checks/NotImplementedErrorInOperatorMethodsCheck.java b/python-checks/src/main/java/org/sonar/python/checks/NotImplementedErrorInOperatorMethodsCheck.java index df4422a57f..072e8b8246 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/NotImplementedErrorInOperatorMethodsCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/NotImplementedErrorInOperatorMethodsCheck.java @@ -31,9 +31,8 @@ import org.sonar.plugins.python.api.tree.HasSymbol; import org.sonar.plugins.python.api.tree.RaiseStatement; 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; @Rule(key="S5712") public class NotImplementedErrorInOperatorMethodsCheck extends PythonSubscriptionCheck { @@ -128,9 +127,9 @@ public void initialize(Context context) { for (RaiseStatement notImplementedErrorRaise : visitor.nonCompliantRaises) { var quickFix = PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE) - .addTextEdit(PythonTextEdit.replace(notImplementedErrorRaise, "return NotImplemented")) + .addTextEdit(TextEditUtils.replace(notImplementedErrorRaise, "return NotImplemented")) .build(); - var issue = (IssueWithQuickFix) ctx.addIssue(notImplementedErrorRaise, MESSAGE); + var issue = ctx.addIssue(notImplementedErrorRaise, MESSAGE); issue.addQuickFix(quickFix); } }); diff --git a/python-checks/src/main/java/org/sonar/python/checks/RedundantJumpCheck.java b/python-checks/src/main/java/org/sonar/python/checks/RedundantJumpCheck.java index 3b8cdef1e3..45724cba45 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/RedundantJumpCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/RedundantJumpCheck.java @@ -35,9 +35,8 @@ import org.sonar.plugins.python.api.tree.Tree; import org.sonar.plugins.python.api.tree.Tree.Kind; import org.sonar.python.cfg.PythonCfgBranchingBlock; -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; @Rule(key = "S3626") @@ -73,14 +72,14 @@ private static void checkCfg(@Nullable ControlFlowGraph cfg, SubscriptionContext } private static void addQuickFix(Tree lastElement, PreciseIssue issue) { - if (!(lastElement instanceof Statement) || !(issue instanceof IssueWithQuickFix)) { + if (!(lastElement instanceof Statement)) { return; } var quickFix = PythonQuickFix .newQuickFix(QUICK_FIX_DESCRIPTION) - .addTextEdit(PythonTextEdit.removeStatement((Statement) lastElement)) + .addTextEdit(TextEditUtils.removeStatement((Statement) lastElement)) .build(); - ((IssueWithQuickFix) issue).addQuickFix(quickFix); + issue.addQuickFix(quickFix); } private static String message(Tree jumpStatement) { diff --git a/python-checks/src/main/java/org/sonar/python/checks/TrailingCommentCheck.java b/python-checks/src/main/java/org/sonar/python/checks/TrailingCommentCheck.java index 0f95644b6e..a748bf624e 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/TrailingCommentCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/TrailingCommentCheck.java @@ -29,9 +29,9 @@ import org.sonar.plugins.python.api.tree.Token; import org.sonar.plugins.python.api.tree.Tree; import org.sonar.plugins.python.api.tree.Trivia; -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 = "S139") public class TrailingCommentCheck extends PythonSubscriptionCheck { @@ -64,7 +64,7 @@ public void initialize(Context context) { if (previousTokenLine == commentToken.line()) { String comment = commentToken.value(); if (!pattern.matcher(comment).matches()) { - IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(commentToken, MESSAGE); + var issue = ctx.addIssue(commentToken, MESSAGE); String line = getLines(ctx).get(commentToken.line() - 1); addQuickFix(issue, commentToken, line); } @@ -74,12 +74,12 @@ public void initialize(Context context) { }); } - private static void addQuickFix(IssueWithQuickFix issue, Token commentToken, String line) { + private static void addQuickFix(PreciseIssue issue, Token commentToken, String line) { String indent = calculateIndent(line); - PythonTextEdit insertComment = PythonTextEdit.insertAtPosition(commentToken.line(), 0, indent + commentToken.value() + "\n"); + PythonTextEdit insertComment = TextEditUtils.insertAtPosition(commentToken.line(), 0, indent + commentToken.value() + "\n"); int startColumnRemove = calculateStartColumnToRemove(commentToken, line); - PythonTextEdit removeTrailingComment = PythonTextEdit.removeRange(commentToken.line(), startColumnRemove, commentToken.line(), line.length()); + PythonTextEdit removeTrailingComment = TextEditUtils.removeRange(commentToken.line(), startColumnRemove, commentToken.line(), line.length()); PythonQuickFix fix = PythonQuickFix.newQuickFix(MESSAGE, removeTrailingComment, insertComment); issue.addQuickFix(fix); diff --git a/python-checks/src/main/java/org/sonar/python/checks/TrailingWhitespaceCheck.java b/python-checks/src/main/java/org/sonar/python/checks/TrailingWhitespaceCheck.java index 54a4434e47..4dea4e2300 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/TrailingWhitespaceCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/TrailingWhitespaceCheck.java @@ -25,9 +25,8 @@ import org.sonar.plugins.python.api.IssueLocation; import org.sonar.plugins.python.api.PythonCheck; import org.sonar.plugins.python.api.PythonVisitorContext; -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; @Rule(key = "S1131") public class TrailingWhitespaceCheck implements PythonCheck { @@ -42,10 +41,10 @@ public void scanFile(PythonVisitorContext ctx) { Matcher matcher = TRAILING_WS.matcher(lines[i]); if (matcher.find()) { int lineNumber = i + 1; - IssueWithQuickFix issue = new IssueWithQuickFix(this, IssueLocation.atLineLevel(MESSAGE, lineNumber)); + PreciseIssue issue = new PreciseIssue(this, IssueLocation.atLineLevel(MESSAGE, lineNumber)); issue.addQuickFix(PythonQuickFix.newQuickFix("Remove trailing whitespaces") - .addTextEdit(PythonTextEdit.removeRange(lineNumber, matcher.start(), lineNumber, matcher.end())) + .addTextEdit(TextEditUtils.removeRange(lineNumber, matcher.start(), lineNumber, matcher.end())) .build()); ctx.addIssue(issue); diff --git a/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisCheck.java b/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisCheck.java index c20f9a3e89..a60f106b24 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/UselessParenthesisCheck.java @@ -24,9 +24,8 @@ import org.sonar.plugins.python.api.tree.Expression; import org.sonar.plugins.python.api.tree.ParenthesizedExpression; 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; @Rule(key = UselessParenthesisCheck.CHECK_KEY) public class UselessParenthesisCheck extends PythonSubscriptionCheck { @@ -42,9 +41,9 @@ public void initialize(Context context) { ParenthesizedExpression parenthesized = (ParenthesizedExpression) ctx.syntaxNode(); Expression expression = parenthesized.expression(); if (expression.is(Tree.Kind.PARENTHESIZED, Tree.Kind.TUPLE, Tree.Kind.GENERATOR_EXPR)) { - var issue = (IssueWithQuickFix) ctx.addIssue(parenthesized.leftParenthesis(), MESSAGE).secondary(parenthesized.rightParenthesis(), null); + var issue = ctx.addIssue(parenthesized.leftParenthesis(), MESSAGE).secondary(parenthesized.rightParenthesis(), null); var quickFix = PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE) - .addTextEdit(PythonTextEdit.remove(parenthesized.leftParenthesis()), PythonTextEdit.remove(parenthesized.rightParenthesis())) + .addTextEdit(TextEditUtils.remove(parenthesized.leftParenthesis()), TextEditUtils.remove(parenthesized.rightParenthesis())) .build(); issue.addQuickFix(quickFix); } diff --git a/python-checks/src/main/java/org/sonar/python/checks/regex/VerboseRegexCheck.java b/python-checks/src/main/java/org/sonar/python/checks/regex/VerboseRegexCheck.java index 625afb845a..532be66d6b 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/regex/VerboseRegexCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/regex/VerboseRegexCheck.java @@ -26,10 +26,9 @@ import javax.annotation.Nullable; import org.sonar.check.Rule; import org.sonar.plugins.python.api.IssueLocation; +import org.sonar.plugins.python.api.quickfix.PythonQuickFix; +import org.sonar.plugins.python.api.quickfix.PythonTextEdit; import org.sonar.plugins.python.api.tree.CallExpression; -import org.sonar.python.quickfix.IssueWithQuickFix; -import org.sonar.python.quickfix.PythonQuickFix; -import org.sonar.python.quickfix.PythonTextEdit; import org.sonar.python.regex.PythonRegexIssueLocation; import org.sonarsource.analyzer.commons.regex.RegexIssueLocation; import org.sonarsource.analyzer.commons.regex.RegexParseResult; @@ -51,7 +50,6 @@ public void checkRegex(RegexParseResult regexParseResult, CallExpression regexFu @Override public PreciseIssue addIssue(RegexSyntaxElement regexTree, String message, @Nullable Integer cost, List secondaries) { return Optional.ofNullable(super.addIssue(regexTree, message, cost, secondaries)) - .map(IssueWithQuickFix.class::cast) .map(issue -> { Matcher matcher = issueMessagePattern.matcher(message); String quickFixReplacement = matcher.replaceFirst("$1"); diff --git a/python-checks/src/main/java/org/sonar/python/checks/tests/AssertAfterRaiseCheck.java b/python-checks/src/main/java/org/sonar/python/checks/tests/AssertAfterRaiseCheck.java index 91856bc9e3..1a10f331cd 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/tests/AssertAfterRaiseCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/tests/AssertAfterRaiseCheck.java @@ -34,9 +34,9 @@ import org.sonar.plugins.python.api.tree.Statement; import org.sonar.plugins.python.api.tree.Tree; import org.sonar.plugins.python.api.tree.WithStatement; -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.tests.UnittestUtils; import org.sonar.python.tree.TreeUtils; @@ -64,7 +64,7 @@ public void initialize(Context context) { Statement statement = statements.get(statements.size()-1); if (isAnAssert(statement)) { var message = statements.size() > 1 ? MESSAGE_MULTIPLE_STATEMENT : MESSAGE_SINGLE_STATEMENT; - var issue = (IssueWithQuickFix) ctx.addIssue(statement, message) + var issue = ctx.addIssue(statement, message) .secondary(IssueLocation.preciseLocation(withStatement.firstToken(), withStatement.colon(), MESSAGE_SECONDARY)); if (statements.size() > 1) { @@ -80,10 +80,10 @@ public void initialize(Context context) { private static List createTextEdits(WithStatement withStatement, Statement statement) { if (statement.firstToken().line() == withStatement.firstToken().line()) { var textToInsert = "\n" + " ".repeat(withStatement.firstToken().column()); - return List.of(PythonTextEdit.insertBefore(statement, textToInsert)); + return List.of(TextEditUtils.insertBefore(statement, textToInsert)); } else { int offset = statement.firstToken().column() - withStatement.firstToken().column(); - return PythonTextEdit.shiftLeft(statement, offset); + return TextEditUtils.shiftLeft(statement, offset); } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/tests/AssertOnTupleLiteralCheck.java b/python-checks/src/main/java/org/sonar/python/checks/tests/AssertOnTupleLiteralCheck.java index a5697fb6e8..70321918b4 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/tests/AssertOnTupleLiteralCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/tests/AssertOnTupleLiteralCheck.java @@ -24,9 +24,8 @@ import org.sonar.plugins.python.api.tree.AssertStatement; 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.plugins.python.api.quickfix.PythonQuickFix; +import org.sonar.python.quickfix.TextEditUtils; @Rule(key = "S5905") public class AssertOnTupleLiteralCheck extends PythonSubscriptionCheck { @@ -42,13 +41,13 @@ public void initialize(Context context) { if (assertStatement.condition().is(Tree.Kind.TUPLE) && assertStatement.message() == null) { var tuple = (Tuple) assertStatement.condition(); - var issue = (IssueWithQuickFix) ctx.addIssue(tuple, MESSAGE); + var issue = ctx.addIssue(tuple, MESSAGE); if (tuple.leftParenthesis() != null && tuple.rightParenthesis() != null) { // defensive condition issue.addQuickFix(PythonQuickFix.newQuickFix(QUICK_FIX_MESSAGE) - .addTextEdit(PythonTextEdit.remove(tuple.leftParenthesis())) - .addTextEdit(PythonTextEdit.remove(tuple.rightParenthesis())) + .addTextEdit(TextEditUtils.remove(tuple.leftParenthesis())) + .addTextEdit(TextEditUtils.remove(tuple.rightParenthesis())) .build()); } } diff --git a/python-checks/src/test/java/org/sonar/python/checks/quickfix/PythonQuickFixVerifier.java b/python-checks/src/test/java/org/sonar/python/checks/quickfix/PythonQuickFixVerifier.java index a86613c3ca..70d8dd1258 100644 --- a/python-checks/src/test/java/org/sonar/python/checks/quickfix/PythonQuickFixVerifier.java +++ b/python-checks/src/test/java/org/sonar/python/checks/quickfix/PythonQuickFixVerifier.java @@ -37,9 +37,8 @@ import org.sonar.python.SubscriptionVisitor; import org.sonar.python.caching.CacheContextImpl; import org.sonar.python.parser.PythonParser; -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.semantic.ProjectLevelSymbolTable; import org.sonar.python.tree.PythonTreeMaker; @@ -57,14 +56,14 @@ public static void verify(PythonCheck check, String codeWithIssue, String... cod .as("Number of issues") .overridingErrorMessage("Expected 1 issue but found %d", issues.size()) .hasSize(1); - IssueWithQuickFix issue = (IssueWithQuickFix) issues.get(0); + PreciseIssue issue = issues.get(0); - assertThat(issue.getQuickFixes()) + assertThat(issue.quickFixes()) .as("Number of quickfixes") - .overridingErrorMessage("Expected %d quickfix but found %d", codesFixed.length, issue.getQuickFixes().size()) + .overridingErrorMessage("Expected %d quickfix but found %d", codesFixed.length, issue.quickFixes().size()) .hasSize(codesFixed.length); - List appliedQuickFix = issue.getQuickFixes().stream() + List appliedQuickFix = issue.quickFixes().stream() .map(quickFix -> applyQuickFix(codeWithIssue, quickFix)) .collect(Collectors.toList()); @@ -82,19 +81,19 @@ public static void verifyNoQuickFixes(PythonCheck check, String codeWithIssue) { .as("Number of issues") .overridingErrorMessage("Expected 1 issue but found %d", issues.size()) .hasSize(1); - IssueWithQuickFix issue = (IssueWithQuickFix) issues.get(0); + PreciseIssue issue = issues.get(0); - assertThat(issue.getQuickFixes()) + assertThat(issue.quickFixes()) .as("Number of quick fixes") - .overridingErrorMessage("Expected no quick fixes for the issue but found %d", issue.getQuickFixes().size()) + .overridingErrorMessage("Expected no quick fixes for the issue but found %d", issue.quickFixes().size()) .isEmpty(); } public static void verifyQuickFixMessages(PythonCheck check, String codeWithIssue, String... expectedMessages) { Stream descriptions = PythonQuickFixVerifier .getIssuesWithQuickFix(check, codeWithIssue) - .stream().map(IssueWithQuickFix.class::cast) - .flatMap(issue -> issue.getQuickFixes().stream()) + .stream() + .flatMap(issue -> issue.quickFixes().stream()) .map(PythonQuickFix::getDescription); assertThat(descriptions).containsExactly(expectedMessages); diff --git a/python-checks/src/test/java/org/sonar/python/checks/quickfix/PythonQuickFixVerifierTest.java b/python-checks/src/test/java/org/sonar/python/checks/quickfix/PythonQuickFixVerifierTest.java index ab6ffb9da8..3fb6834db2 100644 --- a/python-checks/src/test/java/org/sonar/python/checks/quickfix/PythonQuickFixVerifierTest.java +++ b/python-checks/src/test/java/org/sonar/python/checks/quickfix/PythonQuickFixVerifierTest.java @@ -24,9 +24,9 @@ import org.sonar.plugins.python.api.PythonSubscriptionCheck; import org.sonar.plugins.python.api.tree.AssignmentStatement; 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 static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -85,9 +85,9 @@ private class SimpleCheck extends PythonSubscriptionCheck { public void initialize(Context context) { context.registerSyntaxNodeConsumer(Tree.Kind.ASSIGNMENT_STMT, ctx -> { AssignmentStatement assignment = ((AssignmentStatement) ctx.syntaxNode()); - IssueWithQuickFix issue = (IssueWithQuickFix) ctx.addIssue(assignment.equalTokens().get(0), ""); + PreciseIssue issue = ctx.addIssue(assignment.equalTokens().get(0), ""); - PythonTextEdit text = PythonTextEdit + PythonTextEdit text = TextEditUtils .insertBefore(assignment.equalTokens().get(0), "!"); PythonQuickFix quickFix = PythonQuickFix.newQuickFix("Add '!' here.") .addTextEdit(text) diff --git a/python-frontend/src/main/java/org/sonar/plugins/python/api/PythonCheck.java b/python-frontend/src/main/java/org/sonar/plugins/python/api/PythonCheck.java index eb682731c0..f64a4b960b 100644 --- a/python-frontend/src/main/java/org/sonar/plugins/python/api/PythonCheck.java +++ b/python-frontend/src/main/java/org/sonar/plugins/python/api/PythonCheck.java @@ -25,6 +25,7 @@ import org.sonar.api.Beta; import org.sonar.plugins.python.api.tree.Token; import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.plugins.python.api.quickfix.PythonQuickFix; public interface PythonCheck { @@ -42,6 +43,7 @@ class PreciseIssue { private final IssueLocation primaryLocation; private Integer cost; private final List secondaryLocations; + private final List quickFixes = new ArrayList<>(); public PreciseIssue(PythonCheck check, IssueLocation primaryLocation) { this.check = check; @@ -87,6 +89,18 @@ public List secondaryLocations() { return secondaryLocations; } + /** + * This only makes sense in SonarLint context. Should not be used in custom rules. + */ + @Beta + public void addQuickFix(PythonQuickFix quickFix){ + this.quickFixes.add(quickFix); + } + + public List quickFixes() { + return quickFixes; + } + public PythonCheck check() { return check; } diff --git a/python-frontend/src/main/java/org/sonar/python/quickfix/PythonQuickFix.java b/python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/PythonQuickFix.java similarity index 95% rename from python-frontend/src/main/java/org/sonar/python/quickfix/PythonQuickFix.java rename to python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/PythonQuickFix.java index 1dab6f9d03..4285bec596 100644 --- a/python-frontend/src/main/java/org/sonar/python/quickfix/PythonQuickFix.java +++ b/python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/PythonQuickFix.java @@ -17,15 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.python.quickfix; +package org.sonar.plugins.python.api.quickfix; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.sonar.api.Beta; -/** - * For internal use only. Can not be used outside SonarPython analyzer. - */ +@Beta public class PythonQuickFix { private final String description; private final List textEdits; diff --git a/python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/PythonTextEdit.java b/python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/PythonTextEdit.java new file mode 100644 index 0000000000..33e2d75309 --- /dev/null +++ b/python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/PythonTextEdit.java @@ -0,0 +1,86 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.python.api.quickfix; + +import java.util.Objects; +import org.sonar.api.Beta; + +@Beta +public class PythonTextEdit { + + private final String message; + private final int startLine; + private final int startLineOffset; + private final int endLine; + private final int endLineOffset; + + public PythonTextEdit(String message, int startLine, int startLineOffset, int endLine, int endLineOffset) { + this.message = message; + this.startLine = startLine; + this.startLineOffset = startLineOffset; + this.endLine = endLine; + this.endLineOffset = endLineOffset; + } + + public String replacementText() { + return message; + } + + public int startLine() { + return startLine; + } + + public int startLineOffset() { + return startLineOffset; + } + + public int endLine() { + return endLine; + } + + public int endLineOffset() { + return endLineOffset; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PythonTextEdit that = (PythonTextEdit) o; + return startLine == that.startLine && startLineOffset == that.startLineOffset && endLine == that.endLine + && endLineOffset == that.endLineOffset && Objects.equals(message, that.message); + } + + @Override + public int hashCode() { + return Objects.hash(message, startLine, startLineOffset, endLine, endLineOffset); + } + + @Override + public String toString() { + return "PythonTextEdit{" + + "message='" + message + '\'' + + ", startLine=" + startLine + + ", startLineOffset=" + startLineOffset + + ", endLine=" + endLine + + ", endLineOffset=" + endLineOffset + + '}'; + } +} diff --git a/python-frontend/src/main/java/org/sonar/python/quickfix/IssueWithQuickFix.java b/python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/package-info.java similarity index 58% rename from python-frontend/src/main/java/org/sonar/python/quickfix/IssueWithQuickFix.java rename to python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/package-info.java index 584112af6a..4c3bc105e9 100644 --- a/python-frontend/src/main/java/org/sonar/python/quickfix/IssueWithQuickFix.java +++ b/python-frontend/src/main/java/org/sonar/plugins/python/api/quickfix/package-info.java @@ -17,27 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.python.quickfix; +@ParametersAreNonnullByDefault +package org.sonar.plugins.python.api.quickfix; -import java.util.ArrayList; -import java.util.List; -import org.sonar.plugins.python.api.IssueLocation; -import org.sonar.plugins.python.api.PythonCheck; - -public class IssueWithQuickFix extends PythonCheck.PreciseIssue { - - private final List quickFixes = new ArrayList<>(); - - public IssueWithQuickFix(PythonCheck check, IssueLocation primaryLocation) { - super(check, primaryLocation); - } - - public void addQuickFix(PythonQuickFix quickFix){ - this.quickFixes.add(quickFix); - } - - public List getQuickFixes() { - return quickFixes; - } - -} +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/python-frontend/src/main/java/org/sonar/python/SubscriptionVisitor.java b/python-frontend/src/main/java/org/sonar/python/SubscriptionVisitor.java index 03467c0789..820047f4d9 100644 --- a/python-frontend/src/main/java/org/sonar/python/SubscriptionVisitor.java +++ b/python-frontend/src/main/java/org/sonar/python/SubscriptionVisitor.java @@ -45,7 +45,6 @@ import org.sonar.plugins.python.api.tree.Token; import org.sonar.plugins.python.api.tree.Tree; import org.sonar.plugins.python.api.tree.Tree.Kind; -import org.sonar.python.quickfix.IssueWithQuickFix; import org.sonar.python.regex.PythonAnalyzerRegexSource; import org.sonar.python.regex.PythonRegexIssueLocation; import org.sonar.python.regex.RegexContext; @@ -149,7 +148,7 @@ public PythonCheck.PreciseIssue addLineIssue(String message, int lineNumber) { } private PythonCheck.PreciseIssue addIssue(IssueLocation issueLocation) { - PythonCheck.PreciseIssue newIssue = new IssueWithQuickFix(check, issueLocation); + PythonCheck.PreciseIssue newIssue = new PythonCheck.PreciseIssue(check, issueLocation); pythonVisitorContext.addIssue(newIssue); return newIssue; } diff --git a/python-frontend/src/main/java/org/sonar/python/quickfix/PythonTextEdit.java b/python-frontend/src/main/java/org/sonar/python/quickfix/TextEditUtils.java similarity index 77% rename from python-frontend/src/main/java/org/sonar/python/quickfix/PythonTextEdit.java rename to python-frontend/src/main/java/org/sonar/python/quickfix/TextEditUtils.java index 128c221fbc..be09f7a338 100644 --- a/python-frontend/src/main/java/org/sonar/python/quickfix/PythonTextEdit.java +++ b/python-frontend/src/main/java/org/sonar/python/quickfix/TextEditUtils.java @@ -22,8 +22,8 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; +import org.sonar.plugins.python.api.quickfix.PythonTextEdit; import org.sonar.plugins.python.api.symbols.Symbol; import org.sonar.plugins.python.api.symbols.Usage; import org.sonar.plugins.python.api.tree.HasSymbol; @@ -33,24 +33,9 @@ import org.sonar.plugins.python.api.tree.Tree; import org.sonar.python.tree.TreeUtils; -/** - * For internal use only. Can not be used outside SonarPython analyzer. - */ -public class PythonTextEdit { - - private final String message; - private final int startLine; - private final int startLineOffset; - private final int endLine; - private final int endLineOffset; +public class TextEditUtils { - public PythonTextEdit(String message, int startLine, int startLineOffset, int endLine, int endLineOffset) { - this.message = message; - this.startLine = startLine; - this.startLineOffset = startLineOffset; - this.endLine = endLine; - this.endLineOffset = endLineOffset; - } + private TextEditUtils() {} /** * Insert a line with the same offset as the given tree, before the given tree. @@ -152,7 +137,7 @@ public static PythonTextEdit removeStatement(Statement statement) { // Statement is the single element in the block // Replace by `pass` keyword if (previous == null && next == null) { - return PythonTextEdit.replace(statement, "pass"); + return replace(statement, "pass"); } boolean hasPreviousSiblingOnLine = previous != null && firstTokenOfStmt.line() == TreeUtils.getTreeSeparatorOrLastToken(previous.lastToken()).line(); @@ -162,17 +147,17 @@ public static PythonTextEdit removeStatement(Statement statement) { // Statement is first on the line or between at least two statements // Remove from first token to last toke of statement Token firstNextToken = next.firstToken(); - return PythonTextEdit.removeRange(firstTokenOfStmt.line(), firstTokenOfStmt.column(), firstNextToken.line(), firstNextToken.column()); + return removeRange(firstTokenOfStmt.line(), firstTokenOfStmt.column(), firstNextToken.line(), firstNextToken.column()); } else if (hasPreviousSiblingOnLine) { // Statement is last on the line and has one or more previous statement on the line // Remove from last token or separator of previous statement to avoid trailing white spaces // Keep the line break to ensure elements on the next line don't get pushed to the current line Token lastPreviousToken = TreeUtils.getTreeSeparatorOrLastToken(previous); - return PythonTextEdit.removeRange(lastPreviousToken.line(), getEndColumn(lastPreviousToken), lastPreviousToken.line(), getEndColumn(lastTokenOfStmt) - 1); + return removeRange(lastPreviousToken.line(), getEndColumn(lastPreviousToken), lastPreviousToken.line(), getEndColumn(lastTokenOfStmt) - 1); } else { // Statement is single on the line // Remove the entire line including indent and line break - return PythonTextEdit.removeRange(firstTokenOfStmt.line(), 0, lastTokenOfStmt.line(), getEndColumn(lastTokenOfStmt)); + return removeRange(firstTokenOfStmt.line(), 0, lastTokenOfStmt.line(), getEndColumn(lastTokenOfStmt)); } } @@ -196,54 +181,9 @@ public static List renameAllUsages(HasSymbol node, String newNam List usages = symbol != null ? symbol.usages() : Collections.emptyList(); List result = new LinkedList<>(); for(Usage usage: usages) { - PythonTextEdit text = PythonTextEdit.replace(usage.tree().firstToken(), newName); + PythonTextEdit text = replace(usage.tree().firstToken(), newName); result.add(text); } return result; } - - public String replacementText() { - return message; - } - - public int startLine() { - return startLine; - } - - public int startLineOffset() { - return startLineOffset; - } - - public int endLine() { - return endLine; - } - - public int endLineOffset() { - return endLineOffset; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PythonTextEdit that = (PythonTextEdit) o; - return startLine == that.startLine && startLineOffset == that.startLineOffset && endLine == that.endLine - && endLineOffset == that.endLineOffset && Objects.equals(message, that.message); - } - - @Override - public int hashCode() { - return Objects.hash(message, startLine, startLineOffset, endLine, endLineOffset); - } - - @Override - public String toString() { - return "PythonTextEdit{" + - "message='" + message + '\'' + - ", startLine=" + startLine + - ", startLineOffset=" + startLineOffset + - ", endLine=" + endLine + - ", endLineOffset=" + endLineOffset + - '}'; - } } diff --git a/python-frontend/src/test/java/org/sonar/python/quickfix/PythonQuickFixTest.java b/python-frontend/src/test/java/org/sonar/plugins/python/api/quickfix/PythonQuickFixTest.java similarity index 90% rename from python-frontend/src/test/java/org/sonar/python/quickfix/PythonQuickFixTest.java rename to python-frontend/src/test/java/org/sonar/plugins/python/api/quickfix/PythonQuickFixTest.java index b5eb233e0d..c7c24e5722 100644 --- a/python-frontend/src/test/java/org/sonar/python/quickfix/PythonQuickFixTest.java +++ b/python-frontend/src/test/java/org/sonar/plugins/python/api/quickfix/PythonQuickFixTest.java @@ -17,9 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.python.quickfix; +package org.sonar.plugins.python.api.quickfix; import org.junit.Test; +import org.sonar.plugins.python.api.quickfix.PythonQuickFix; +import org.sonar.plugins.python.api.quickfix.PythonTextEdit; import static org.assertj.core.api.Assertions.assertThat; diff --git a/python-frontend/src/test/java/org/sonar/plugins/python/api/quickfix/PythonTextEditTest.java b/python-frontend/src/test/java/org/sonar/plugins/python/api/quickfix/PythonTextEditTest.java new file mode 100644 index 0000000000..3a36d83a19 --- /dev/null +++ b/python-frontend/src/test/java/org/sonar/plugins/python/api/quickfix/PythonTextEditTest.java @@ -0,0 +1,58 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.python.api.quickfix; + +import org.junit.Test; +import org.sonar.plugins.python.api.quickfix.PythonTextEdit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PythonTextEditTest { + + @Test + public void equals() { + PythonTextEdit edit = new PythonTextEdit("", 0, 0, 1, 1); + assertThat(edit.equals(edit)).isTrue(); + assertThat(edit.equals(null)).isFalse(); + assertThat(edit.equals(new Object())).isFalse(); + + assertThat(edit.equals(new PythonTextEdit("", 0, 0, 1, 1))).isTrue(); + assertThat(edit.equals(new PythonTextEdit("",1, 0, 1, 1))).isFalse(); + assertThat(edit.equals(new PythonTextEdit("",0, 1, 1, 1))).isFalse(); + assertThat(edit.equals(new PythonTextEdit("",0, 0, 0, 1))).isFalse(); + assertThat(edit.equals(new PythonTextEdit("",0, 0, 1, 0))).isFalse(); + assertThat(edit.equals(new PythonTextEdit("a", 0, 0, 1, 1))).isFalse(); + } + + @Test + public void test_hashCode() { + PythonTextEdit edit = new PythonTextEdit("", 0, 0, 1, 1); + assertThat(edit) + .hasSameHashCodeAs(edit) + .hasSameHashCodeAs(new PythonTextEdit("", 0, 0, 1, 1)) + .doesNotHaveSameHashCodeAs(new Object()) + .doesNotHaveSameHashCodeAs(new PythonTextEdit("",1, 0, 1, 1)) + .doesNotHaveSameHashCodeAs(new PythonTextEdit("",0, 1, 1, 1)) + .doesNotHaveSameHashCodeAs(new PythonTextEdit("",0, 0, 0, 1)) + .doesNotHaveSameHashCodeAs(new PythonTextEdit("",0, 0, 1, 0)) + .doesNotHaveSameHashCodeAs(new PythonTextEdit("a", 0, 0, 1, 1)) + ; + } +} diff --git a/python-frontend/src/test/java/org/sonar/python/PythonCheckTest.java b/python-frontend/src/test/java/org/sonar/python/PythonCheckTest.java index 89f1e5643b..cbab9c8d59 100644 --- a/python-frontend/src/test/java/org/sonar/python/PythonCheckTest.java +++ b/python-frontend/src/test/java/org/sonar/python/PythonCheckTest.java @@ -22,7 +22,7 @@ import java.io.File; import java.util.List; import org.junit.Test; -import org.sonar.api.batch.fs.InputFile; +import org.mockito.Mockito; import org.sonar.plugins.python.api.IssueLocation; import org.sonar.plugins.python.api.PythonCheck; import org.sonar.plugins.python.api.PythonCheck.PreciseIssue; @@ -32,6 +32,8 @@ import org.sonar.plugins.python.api.symbols.FunctionSymbol; import org.sonar.plugins.python.api.tree.FunctionDef; import org.sonar.plugins.python.api.tree.ReturnStatement; +import org.sonar.plugins.python.api.quickfix.PythonQuickFix; +import org.sonar.plugins.python.api.quickfix.PythonTextEdit; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -73,6 +75,17 @@ public void visitFunctionDef(FunctionDef pyFunctionDefTree) { assertThat(primaryLocation.endLine()).isEqualTo(1); assertThat(primaryLocation.startLineOffset()).isEqualTo(4); assertThat(primaryLocation.endLineOffset()).isEqualTo(9); + + assertThat(firstIssue.quickFixes()).isEmpty(); + PythonTextEdit textEdit = Mockito.mock(PythonTextEdit.class); + PythonQuickFix quickFix = PythonQuickFix.newQuickFix("New Quickfix") + .addTextEdit(textEdit) + .build(); + + firstIssue.addQuickFix(quickFix); + firstIssue.addQuickFix(quickFix); + + assertThat(firstIssue.quickFixes()).containsExactly(quickFix, quickFix); } @Test diff --git a/python-frontend/src/test/java/org/sonar/python/quickfix/IssueWithQuickFixTest.java b/python-frontend/src/test/java/org/sonar/python/quickfix/IssueWithQuickFixTest.java deleted file mode 100644 index 0489f88db2..0000000000 --- a/python-frontend/src/test/java/org/sonar/python/quickfix/IssueWithQuickFixTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.python.quickfix; - -import org.junit.Test; -import org.mockito.Mockito; -import org.sonar.plugins.python.api.IssueLocation; -import org.sonar.plugins.python.api.LocationInFile; -import org.sonar.plugins.python.api.PythonCheck; - -import static org.assertj.core.api.Assertions.assertThat; - -public class IssueWithQuickFixTest { - - @Test - public void test() { - PythonCheck check = Mockito.mock(PythonCheck.class); - LocationInFile loc1 = new LocationInFile("null", 1, 7, 10, 10); - IssueLocation issueLocation = IssueLocation.preciseLocation(loc1, "location"); - - IssueWithQuickFix issue = new IssueWithQuickFix(check, issueLocation); - - assertThat(issue.getQuickFixes()).isEmpty(); - - PythonTextEdit textEdit = Mockito.mock(PythonTextEdit.class); - PythonQuickFix quickFix = PythonQuickFix.newQuickFix("New Quickfix") - .addTextEdit(textEdit) - .build(); - - issue.addQuickFix(quickFix); - issue.addQuickFix(quickFix); - - assertThat(issue.getQuickFixes()).containsExactly(quickFix, quickFix); - } - -} diff --git a/python-frontend/src/test/java/org/sonar/python/quickfix/PythonTextEditTest.java b/python-frontend/src/test/java/org/sonar/python/quickfix/TextEditUtilsTest.java similarity index 71% rename from python-frontend/src/test/java/org/sonar/python/quickfix/PythonTextEditTest.java rename to python-frontend/src/test/java/org/sonar/python/quickfix/TextEditUtilsTest.java index aea437e073..2f791b16b6 100644 --- a/python-frontend/src/test/java/org/sonar/python/quickfix/PythonTextEditTest.java +++ b/python-frontend/src/test/java/org/sonar/python/quickfix/TextEditUtilsTest.java @@ -22,6 +22,7 @@ import java.util.List; import org.junit.Test; import org.mockito.Mockito; +import org.sonar.plugins.python.api.quickfix.PythonTextEdit; import org.sonar.plugins.python.api.tree.CallExpression; import org.sonar.plugins.python.api.tree.FileInput; import org.sonar.plugins.python.api.tree.FunctionDef; @@ -37,14 +38,14 @@ import static org.mockito.Mockito.when; import static org.sonar.python.PythonTestUtils.parse; -public class PythonTextEditTest { +public class TextEditUtilsTest { @Test public void insertBefore() { String textToInsert = "This is a replacement text"; Token token = mockToken("token", 1, 7); - PythonTextEdit textEdit = PythonTextEdit.insertBefore(token, textToInsert); + PythonTextEdit textEdit = TextEditUtils.insertBefore(token, textToInsert); assertThat(textEdit.replacementText()).isEqualTo(textToInsert); assertTextEditLocation(textEdit, 1, 7, 1, 7); } @@ -54,7 +55,7 @@ public void insertAfter() { String textToInsert = "This is a replacement text"; Token token = mockToken("token", 1, 7); - PythonTextEdit textEdit = PythonTextEdit.insertAfter(token, textToInsert); + PythonTextEdit textEdit = TextEditUtils.insertAfter(token, textToInsert); assertThat(textEdit.replacementText()).isEqualTo(textToInsert); assertTextEditLocation(textEdit, 1, 12, 1, 12); } @@ -64,7 +65,7 @@ public void replace() { String replacementText = "This is a replacement text"; Token token = mockToken("token", 1, 7); - PythonTextEdit textEdit = PythonTextEdit.replace(token, replacementText); + PythonTextEdit textEdit = TextEditUtils.replace(token, replacementText); assertThat(textEdit.replacementText()).isEqualTo(replacementText); assertTextEditLocation(textEdit, 1, 7, 1, 12); } @@ -73,7 +74,7 @@ public void replace() { public void remove() { Token token = mockToken("token", 1, 7); - PythonTextEdit textEdit = PythonTextEdit.remove(token); + PythonTextEdit textEdit = TextEditUtils.remove(token); assertThat(textEdit.replacementText()).isEmpty(); assertTextEditLocation(textEdit, 1, 7, 1, 12); } @@ -84,7 +85,7 @@ public void replaceRange() { Token token1 = mockToken("(", 1, 4); Token token2 = mockToken(")", 1, 12); - PythonTextEdit textEdit = PythonTextEdit.replaceRange(token1, token2, "b and c"); + PythonTextEdit textEdit = TextEditUtils.replaceRange(token1, token2, "b and c"); assertThat(textEdit.replacementText()).isEqualTo("b and c"); assertTextEditLocation(textEdit, 1, 4, 1, 13); } @@ -93,7 +94,7 @@ public void replaceRange() { public void insertLineBefore() { Token token = mockToken("tree", 1, 4); - PythonTextEdit textEdit = PythonTextEdit.insertLineBefore(token, "firstLine\n secondLineWithIndent"); + PythonTextEdit textEdit = TextEditUtils.insertLineBefore(token, "firstLine\n secondLineWithIndent"); assertThat(textEdit.replacementText()).isEqualTo("firstLine\n secondLineWithIndent\n "); assertTextEditLocation(textEdit, 1, 4, 1, 4); } @@ -108,7 +109,7 @@ public void shiftLeft() { ); StatementList functionBody = getFunctionBody(file); - List textEdits = PythonTextEdit.shiftLeft(functionBody); + List textEdits = TextEditUtils.shiftLeft(functionBody); assertThat(textEdits).hasSize(2); textEdits.forEach(textEdit -> { assertThat(textEdit.startLineOffset()).isZero(); @@ -129,7 +130,7 @@ public void removeUntil() { StatementList functionBody = getFunctionBody(file); Statement lastStatement = ListUtils.getLast(functionBody.statements()); - PythonTextEdit textEdit = PythonTextEdit.removeUntil(functionBody, lastStatement); + PythonTextEdit textEdit = TextEditUtils.removeUntil(functionBody, lastStatement); assertThat(textEdit.replacementText()).isEmpty(); assertTextEditLocation(textEdit, 2, 4, 4, 4); } @@ -144,7 +145,7 @@ public void testRenameAllUsages() { ); Parameter parameter = PythonTestUtils.getFirstDescendant(file, descendant -> descendant.is(Tree.Kind.PARAMETER)); - List textEdits = PythonTextEdit.renameAllUsages(parameter.name(), "xxx"); + List textEdits = TextEditUtils.renameAllUsages(parameter.name(), "xxx"); assertThat(textEdits).containsExactly( new PythonTextEdit("xxx", 1, 8, 1, 11), @@ -157,7 +158,7 @@ public void test_insertLineAfter_without_indent() { FileInput file = parse("foo()"); CallExpression call = PythonTestUtils.getFirstDescendant(file, descendant -> descendant.is(Tree.Kind.CALL_EXPR)); - PythonTextEdit textEdit = PythonTextEdit.insertLineAfter(call, call, "bar"); + PythonTextEdit textEdit = TextEditUtils.insertLineAfter(call, call, "bar"); assertThat(textEdit).isEqualTo(new PythonTextEdit("\nbar", 1,3,1,3)); } @@ -170,7 +171,7 @@ public void test_insertLineAfter_with_indent() { FunctionDef functionDef = PythonTestUtils.getFirstDescendant(file, descendant -> descendant.is(Tree.Kind.FUNCDEF)); StatementList functionBody = functionDef.body(); - PythonTextEdit textEdit = PythonTextEdit.insertLineAfter(functionDef.colon(), functionBody, "bar"); + PythonTextEdit textEdit = TextEditUtils.insertLineAfter(functionDef.colon(), functionBody, "bar"); assertThat(textEdit).isEqualTo(new PythonTextEdit("\n bar", 1,10,1,10)); } @@ -180,7 +181,7 @@ public void removeStatement_with_single_statement_will_replace_with_pass() { " a = 1"); StatementList functionBody = getFunctionBody(file); - PythonTextEdit textEdit = PythonTextEdit.removeStatement(functionBody.statements().get(0)); + PythonTextEdit textEdit = TextEditUtils.removeStatement(functionBody.statements().get(0)); assertThat(textEdit).isEqualTo(new PythonTextEdit("pass", 2, 4, 2, 9)); } @@ -191,10 +192,10 @@ public void removeStatement_with_two_statement_will_remove_indent_and_line_break " b = 2"); StatementList functionBody = getFunctionBody(file); - PythonTextEdit firstTextEdit = PythonTextEdit.removeStatement(functionBody.statements().get(0)); + PythonTextEdit firstTextEdit = TextEditUtils.removeStatement(functionBody.statements().get(0)); assertThat(firstTextEdit).isEqualTo(new PythonTextEdit("", 2, 0, 2, 10)); - PythonTextEdit secondTextEdit = PythonTextEdit.removeStatement(functionBody.statements().get(1)); + PythonTextEdit secondTextEdit = TextEditUtils.removeStatement(functionBody.statements().get(1)); assertThat(secondTextEdit).isEqualTo(new PythonTextEdit("", 3, 0, 3, 9)); } @@ -205,7 +206,7 @@ public void removeStatement_with_statement_with_separator_will_remove_all() { " b = 2"); StatementList functionBody = getFunctionBody(file); - PythonTextEdit firstTextEdit = PythonTextEdit.removeStatement(functionBody.statements().get(0)); + PythonTextEdit firstTextEdit = TextEditUtils.removeStatement(functionBody.statements().get(0)); assertThat(firstTextEdit).isEqualTo(new PythonTextEdit("", 2, 0, 2, 11)); } @@ -215,46 +216,16 @@ public void removeStatement_with_tree_statement_on_same_line_will_not_remove_ind " a = 1; b = 2; c = 3;"); StatementList functionBody = getFunctionBody(file); - PythonTextEdit firstTextEdit = PythonTextEdit.removeStatement(functionBody.statements().get(0)); + PythonTextEdit firstTextEdit = TextEditUtils.removeStatement(functionBody.statements().get(0)); assertThat(firstTextEdit).isEqualTo(new PythonTextEdit("", 2, 4, 2, 11)); - PythonTextEdit secondTextEdit = PythonTextEdit.removeStatement(functionBody.statements().get(1)); + PythonTextEdit secondTextEdit = TextEditUtils.removeStatement(functionBody.statements().get(1)); assertThat(secondTextEdit).isEqualTo(new PythonTextEdit("", 2, 11, 2, 18)); - PythonTextEdit thirdTextEdit = PythonTextEdit.removeStatement(functionBody.statements().get(2)); + PythonTextEdit thirdTextEdit = TextEditUtils.removeStatement(functionBody.statements().get(2)); assertThat(thirdTextEdit).isEqualTo(new PythonTextEdit("", 2, 17, 2, 23)); } - @Test - public void equals() { - PythonTextEdit edit = new PythonTextEdit("", 0, 0, 1, 1); - assertThat(edit.equals(edit)).isTrue(); - assertThat(edit.equals(null)).isFalse(); - assertThat(edit.equals(new Object())).isFalse(); - - assertThat(edit.equals(new PythonTextEdit("", 0, 0, 1, 1))).isTrue(); - assertThat(edit.equals(new PythonTextEdit("",1, 0, 1, 1))).isFalse(); - assertThat(edit.equals(new PythonTextEdit("",0, 1, 1, 1))).isFalse(); - assertThat(edit.equals(new PythonTextEdit("",0, 0, 0, 1))).isFalse(); - assertThat(edit.equals(new PythonTextEdit("",0, 0, 1, 0))).isFalse(); - assertThat(edit.equals(new PythonTextEdit("a", 0, 0, 1, 1))).isFalse(); - } - - @Test - public void test_hashCode() { - PythonTextEdit edit = new PythonTextEdit("", 0, 0, 1, 1); - assertThat(edit) - .hasSameHashCodeAs(edit) - .hasSameHashCodeAs(new PythonTextEdit("", 0, 0, 1, 1)) - .doesNotHaveSameHashCodeAs(new Object()) - .doesNotHaveSameHashCodeAs(new PythonTextEdit("",1, 0, 1, 1)) - .doesNotHaveSameHashCodeAs(new PythonTextEdit("",0, 1, 1, 1)) - .doesNotHaveSameHashCodeAs(new PythonTextEdit("",0, 0, 0, 1)) - .doesNotHaveSameHashCodeAs(new PythonTextEdit("",0, 0, 1, 0)) - .doesNotHaveSameHashCodeAs(new PythonTextEdit("a", 0, 0, 1, 1)) - ; - } - private void assertTextEditLocation(PythonTextEdit textEdit, int startLine, int startLineOffset, int endLine, int endLineOffset) { assertThat(textEdit.startLine()).isEqualTo(startLine); assertThat(textEdit.startLineOffset()).isEqualTo(startLineOffset); diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java index 4d1eef9240..838222a2c7 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonScanner.java @@ -62,9 +62,8 @@ import org.sonar.python.metrics.FileLinesVisitor; import org.sonar.python.metrics.FileMetrics; import org.sonar.python.parser.PythonParser; -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.tree.PythonTreeMaker; import org.sonarsource.sonarlint.plugin.api.issue.NewInputFileEdit; import org.sonarsource.sonarlint.plugin.api.issue.NewQuickFix; @@ -338,8 +337,8 @@ private void saveMetricOnFile(InputFile inputFile, Metric metric, Integ } private void handleQuickFixes(InputFile inputFile, RuleKey ruleKey, NewIssue newIssue, PreciseIssue preciseIssue) { - if (isInSonarLint(context) && newIssue instanceof NewSonarLintIssue && preciseIssue instanceof IssueWithQuickFix) { - List quickFixes = ((IssueWithQuickFix) preciseIssue).getQuickFixes(); + if (isInSonarLint(context) && newIssue instanceof NewSonarLintIssue) { + List quickFixes = preciseIssue.quickFixes(); addQuickFixes(inputFile, ruleKey, quickFixes, (NewSonarLintIssue) newIssue); } }