Java: Convert intention "Join Declaration And Assignment" into inspection (IDEA-178919)

This commit is contained in:
Pavel Dolgov
2018-10-04 16:46:00 +03:00
parent f90397afe1
commit 799b769b2f
13 changed files with 176 additions and 34 deletions
@@ -5,18 +5,25 @@ import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.editorActions.DeclarationJoinLinesHandler;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
import static com.intellij.codeInspection.ProblemHighlightType.INFORMATION;
@@ -57,12 +64,12 @@ public class JoinDeclarationAndAssignmentJavaInspection extends AbstractBaseJava
String message = InspectionsBundle.message("inspection.join.declaration.and.assignment.message", context.myName);
JoinDeclarationAndAssignmentFix fix = new JoinDeclarationAndAssignmentFix();
ProblemHighlightType highlightType = context.myIsUpdate ? INFORMATION : GENERIC_ERROR_OR_WARNING;
if (isOnTheFly && (context.myIsUpdate || isInformationLevel(location))) {
ProblemHighlightType highlightType = context.myIsUpdate ? INFORMATION : GENERIC_ERROR_OR_WARNING;
holder.registerProblem(location, message, highlightType, fix);
}
else if (location == assignment) {
holder.registerProblem(assignment.getLExpression(), message, highlightType, fix);
else if (location == assignment && !context.myIsUpdate) {
holder.registerProblem(assignment.getLExpression(), message, fix);
}
}
}
@@ -131,19 +138,18 @@ public class JoinDeclarationAndAssignmentJavaInspection extends AbstractBaseJava
@Nullable
private static PsiAssignmentExpression findAssignment(@NotNull PsiVariable variable) {
PsiDeclarationStatement statement = ObjectUtils.tryCast(variable.getParent(), PsiDeclarationStatement.class);
PsiElement candidate = PsiTreeUtil.skipWhitespacesAndCommentsForward(statement);
return findNextAssignment(variable.getParent(), variable);
}
@Nullable
private static PsiAssignmentExpression findNextAssignment(@Nullable PsiElement element, @NotNull PsiVariable variable) {
PsiElement candidate = PsiTreeUtil.skipWhitespacesAndCommentsForward(element);
if (candidate instanceof PsiExpressionStatement) {
PsiExpression expression = ((PsiExpressionStatement)candidate).getExpression();
if (expression instanceof PsiAssignmentExpression) {
PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression;
PsiExpression lExpression = PsiUtil.skipParenthesizedExprDown(assignmentExpression.getLExpression());
if (lExpression instanceof PsiReferenceExpression) {
PsiReferenceExpression reference = (PsiReferenceExpression)lExpression;
if (!reference.isQualified() && // optimization: locals aren't qualified
reference.isReferenceTo(variable)) {
return assignmentExpression;
}
if (ExpressionUtils.isReferenceTo(assignmentExpression.getLExpression(), variable)) {
return assignmentExpression;
}
}
}
@@ -176,13 +182,80 @@ public class JoinDeclarationAndAssignmentJavaInspection extends AbstractBaseJava
}
if (!FileModificationService.getInstance().prepareFileForWrite(assignmentExpression.getContainingFile())) return;
WriteAction.run(() -> {
PsiExpression initializerExpression = DeclarationJoinLinesHandler.getInitializerExpression(variable, assignmentExpression);
if (initializerExpression != null) {
variable.setInitializer(initializerExpression);
new CommentTracker().deleteAndRestoreComments(assignmentExpression);
}
});
WriteAction.run(() -> applyFixImpl(context));
}
}
public void applyFixImpl(@NotNull Context context) {
PsiExpression initializerExpression = DeclarationJoinLinesHandler.getInitializerExpression(context.myVariable, context.myAssignment);
PsiElement elementToReplace = context.myAssignment.getParent();
if (initializerExpression != null && elementToReplace != null) {
List<String> commentTexts = collectCommentTexts(elementToReplace);
List<String> reverseTrailingCommentTexts = collectReverseTrailingCommentTexts(elementToReplace);
PsiElement declaration = replaceWithDeclaration(context, elementToReplace, initializerExpression);
restoreComments(commentTexts, reverseTrailingCommentTexts, declaration);
new CommentTracker().deleteAndRestoreComments(context.myVariable);
}
}
@NotNull
private static PsiElement replaceWithDeclaration(@NotNull Context context,
@NotNull PsiElement elementToReplace,
@NotNull PsiExpression initializerExpression) {
Project project = elementToReplace.getProject();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiAnnotation[] annotations = context.myVariable.getAnnotations();
String text = (annotations.length != 0 ? StringUtil.join(annotations, PsiElement::getText, " ") + " " : "") +
context.myVariable.getTypeElement().getText() + " " + context.myName + "=" + initializerExpression.getText() + ";";
PsiStatement statement = factory.createStatementFromText(text, context.myAssignment);
PsiElement replaced = elementToReplace.replace(statement);
return CodeStyleManager.getInstance(project).reformat(replaced);
}
@NotNull
public List<String> collectCommentTexts(@NotNull PsiElement element) {
return ContainerUtil.map(PsiTreeUtil.collectElementsOfType(element, PsiComment.class), PsiElement::getText);
}
@NotNull
private static List<String> collectReverseTrailingCommentTexts(@NotNull PsiElement element) {
List<String> result = new ArrayList<>();
PsiElement child = element.getLastChild();
while (child instanceof PsiComment ||
child instanceof PsiWhiteSpace ||
child instanceof PsiJavaToken && JavaTokenType.SEMICOLON.equals(((PsiJavaToken)child).getTokenType())) {
if (child instanceof PsiComment) {
result.add(child.getText());
}
child = child.getPrevSibling();
}
return result;
}
private void restoreComments(@NotNull List<String> commentTexts,
@NotNull List<String> reverseTrailingCommentTexts,
@NotNull PsiElement target) {
if (commentTexts.isEmpty()) return;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(target.getProject());
List<String> newCommentTexts = collectCommentTexts(target);
PsiElement parent = target.getParent();
for (String commentText : commentTexts) {
if (!newCommentTexts.contains(commentText) && !reverseTrailingCommentTexts.contains(commentText)) {
PsiComment comment = factory.createCommentFromText(commentText, target);
parent.addBefore(comment, target);
}
}
if (reverseTrailingCommentTexts.isEmpty()) return;
for (String commentText : reverseTrailingCommentTexts) {
if (!newCommentTexts.contains(commentText)) {
PsiComment comment = factory.createCommentFromText(commentText, target);
parent.addAfter(comment, target); // adding immediately after the target restores the original order
}
}
}
}
@@ -197,7 +270,8 @@ public class JoinDeclarationAndAssignmentJavaInspection extends AbstractBaseJava
myAssignment = assignment;
myVariable = variable;
myName = name;
myIsUpdate = !JavaTokenType.EQ.equals(myAssignment.getOperationTokenType());
myIsUpdate = !JavaTokenType.EQ.equals(myAssignment.getOperationTokenType()) ||
findNextAssignment(myAssignment.getParent(), myVariable) != null;
}
}
}
@@ -0,0 +1,10 @@
// "Join declaration and assignment" "GENERIC_ERROR_OR_WARNING"
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
class C {
void foo() {
@A int n = 1;
}
@Target(ElementType.LOCAL_VARIABLE) @interface A {}
}
@@ -1,8 +1,13 @@
// "Join declaration and assignment" "GENERIC_ERROR_OR_WARNING"
class Test {
{
String ss = "hello"; // comment A
// comment B
/*comment C*/ /*comment D*/ // comment E
}
{
/*comment 1*/
/*comment 2*/
/*comment 3*/
// comment 4
// comment A
/*comment B*/
/*comment C*/
String ss = "hello" + /*comment D*/ " world";/*comment E*/// comment F
}
}
@@ -1,6 +1,6 @@
// "Join declaration and assignment" "GENERIC_ERROR_OR_WARNING"
class Test {
{
String ss = "hello";
String ss = "hello";
}
}
@@ -1,6 +1,7 @@
// "Join declaration and assignment" "GENERIC_ERROR_OR_WARNING"
class Test {
{
String ss = "hello", a = "";
String a = "";
String ss = "hello";
}
}
@@ -1,6 +1,6 @@
// "Join declaration and assignment" "INFORMATION"
class Test {
{
int i = 4 * (2 + 3);
int i = 4 * (2 + 3);
}
}
@@ -1,6 +1,6 @@
// "Join declaration and assignment" "INFORMATION"
class Test {
{
String ss = "hello" + "world";
String ss = "hello" + "world";
}
}
@@ -0,0 +1,12 @@
// "Join declaration and assignment" "GENERIC_ERROR_OR_WARNING"
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
class C {
void foo() {
@A @B int k, m;
@A @B int n = 1;
}
@Target(ElementType.LOCAL_VARIABLE) @interface A {}
@Target(ElementType.LOCAL_VARIABLE) @interface B {}
}
@@ -0,0 +1,8 @@
// "Join declaration and assignment" "INFORMATION"
class C {
int foo (int a, int b){
int x = a;
x = x * 31 + b;
return x;
}
}
@@ -0,0 +1,11 @@
// "Join declaration and assignment" "GENERIC_ERROR_OR_WARNING"
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
class C {
void foo() {
@A int n;
<caret>n = 1;
}
@Target(ElementType.LOCAL_VARIABLE) @interface A {}
}
@@ -1,8 +1,8 @@
// "Join declaration and assignment" "GENERIC_ERROR_OR_WARNING"
class Test {
{
String s<caret>s = ""; // comment A
// comment B
/*comment C*/ ss /*comment D*/ = "hello"; // comment E
}
{
String /*comment 1*/ s<caret>s /*comment 2*/ = "" /*comment 3*/; // comment 4
// comment A
/*comment B*/ ss /*comment C*/ = "hello" + /*comment D*/ " world" /*comment E*/; // comment F
}
}
@@ -0,0 +1,12 @@
// "Join declaration and assignment" "GENERIC_ERROR_OR_WARNING"
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
class C {
void foo() {
@A @B int k, n, m;
<caret>n = 1;
}
@Target(ElementType.LOCAL_VARIABLE) @interface A {}
@Target(ElementType.LOCAL_VARIABLE) @interface B {}
}
@@ -0,0 +1,9 @@
// "Join declaration and assignment" "INFORMATION"
class C {
int foo (int a, int b){
int x;
<caret>x = a;
x = x * 31 + b;
return x;
}
}