mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-13 09:19:13 +07:00
Merge duplicate statements in module-info.java - merge all duplicates at once, preserve comments, set caret to the resulting statement (IDEA-169211)
This commit is contained in:
+38
-31
@@ -21,23 +21,22 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import gnu.trove.THashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public abstract class MergeModuleStatementsFix<T extends PsiElement> extends LocalQuickFixAndIntentionActionOnPsiElement {
|
||||
protected final SmartPsiElementPointer<T> myOtherStatement;
|
||||
|
||||
protected MergeModuleStatementsFix(@NotNull T thisStatement, @NotNull T otherStatement) {
|
||||
super(thisStatement);
|
||||
final PsiFile file = otherStatement.getContainingFile();
|
||||
myOtherStatement = SmartPointerManager.getInstance(otherStatement.getProject()).createSmartPsiElementPointer(otherStatement, file);
|
||||
protected MergeModuleStatementsFix(@NotNull PsiJavaModule javaModule) {
|
||||
super(javaModule);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,49 +44,57 @@ public abstract class MergeModuleStatementsFix<T extends PsiElement> extends Loc
|
||||
@NotNull PsiFile file,
|
||||
@NotNull PsiElement startElement,
|
||||
@NotNull PsiElement endElement) {
|
||||
final T otherStatement = myOtherStatement.getElement();
|
||||
return otherStatement != null && otherStatement.isValid() && PsiUtil.isLanguageLevel9OrHigher(file);
|
||||
return PsiUtil.isLanguageLevel9OrHigher(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project,
|
||||
@NotNull PsiFile file,
|
||||
@Nullable Editor editor,
|
||||
@NotNull PsiElement thisStatement,
|
||||
@NotNull PsiElement startElement,
|
||||
@NotNull PsiElement endElement) {
|
||||
final T otherStatement = myOtherStatement.getElement();
|
||||
if (startElement instanceof PsiJavaModule) {
|
||||
final PsiJavaModule javaModule = (PsiJavaModule)startElement;
|
||||
final List<T> statementsToMerge = getStatementsToMerge(javaModule);
|
||||
LOG.assertTrue(!statementsToMerge.isEmpty());
|
||||
|
||||
if (otherStatement != null) {
|
||||
final PsiElement parent = otherStatement.getParent();
|
||||
if (parent instanceof PsiJavaModule) {
|
||||
final String moduleName = ((PsiJavaModule)parent).getName();
|
||||
final String moduleText = PsiKeyword.MODULE + " " + moduleName + " {" + getReplacementText(otherStatement) + "}";
|
||||
final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
|
||||
final PsiJavaModule tempModule = factory.createModuleFromText(moduleText);
|
||||
final String tempModuleText = PsiKeyword.MODULE + " " + javaModule.getName() + " {" + getReplacementText(statementsToMerge) + "}";
|
||||
final PsiJavaModule tempModule = JavaPsiFacade.getInstance(project).getElementFactory().createModuleFromText(tempModuleText);
|
||||
|
||||
final Iterator<T> statementIterator = getStatements(tempModule).iterator();
|
||||
LOG.assertTrue(statementIterator.hasNext());
|
||||
final T replacement = statementIterator.next();
|
||||
final List<T> tempStatements = getStatementsToMerge(tempModule);
|
||||
LOG.assertTrue(!tempStatements.isEmpty());
|
||||
final T replacement = tempStatements.get(0);
|
||||
|
||||
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
|
||||
codeStyleManager.reformat(otherStatement.replace(replacement));
|
||||
thisStatement.delete();
|
||||
final T firstStatement = statementsToMerge.get(0);
|
||||
final CommentTracker commentTracker = new CommentTracker();
|
||||
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
|
||||
final PsiElement resultingStatement = codeStyleManager.reformat(commentTracker.replace(firstStatement, replacement));
|
||||
|
||||
for (int i = 1; i < statementsToMerge.size(); i++) {
|
||||
T statement = statementsToMerge.get(i);
|
||||
commentTracker.delete(statement);
|
||||
}
|
||||
commentTracker.insertCommentsBefore(resultingStatement);
|
||||
|
||||
if (editor != null) {
|
||||
final int offset = resultingStatement.getTextRange().getEndOffset();
|
||||
editor.getCaretModel().moveToOffset(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract String getReplacementText(@NotNull T otherStatement);
|
||||
protected abstract String getReplacementText(List<T> statementsToMerge);
|
||||
|
||||
@NotNull
|
||||
protected abstract Iterable<T> getStatements(@NotNull PsiJavaModule javaModule);
|
||||
protected abstract List<T> getStatementsToMerge(@NotNull PsiJavaModule javaModule);
|
||||
|
||||
@NotNull
|
||||
protected static String joinNames(@NotNull List<String> oldNames, @NotNull List<String> newNames) {
|
||||
final StringJoiner joiner = new StringJoiner(",");
|
||||
oldNames.forEach(joiner::add);
|
||||
newNames.stream().filter(name -> !oldNames.contains(name)).forEach(joiner::add);
|
||||
return joiner.toString();
|
||||
protected static String joinUniqueNames(@NotNull List<String> names) {
|
||||
final Set<String> unique = new THashSet<>();
|
||||
return names.stream()
|
||||
.filter(name -> unique.add(name))
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+29
-33
@@ -22,10 +22,12 @@ import com.intellij.psi.PsiJavaModule;
|
||||
import com.intellij.psi.PsiKeyword;
|
||||
import com.intellij.psi.PsiPackageAccessibilityStatement;
|
||||
import com.intellij.psi.PsiPackageAccessibilityStatement.Role;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -37,17 +39,12 @@ public class MergePackageAccessibilityStatementsFix
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(MergePackageAccessibilityStatementsFix.class);
|
||||
private final String myPackageName;
|
||||
private final List<String> myModuleNames;
|
||||
private final Role myRole;
|
||||
|
||||
protected MergePackageAccessibilityStatementsFix(@NotNull PsiPackageAccessibilityStatement thisStatement,
|
||||
@NotNull String packageName,
|
||||
@NotNull List<String> moduleNames,
|
||||
@NotNull PsiPackageAccessibilityStatement otherStatement) {
|
||||
super(thisStatement, otherStatement);
|
||||
protected MergePackageAccessibilityStatementsFix(@NotNull PsiJavaModule javaModule, @NotNull String packageName, @NotNull Role role) {
|
||||
super(javaModule);
|
||||
myPackageName = packageName;
|
||||
myModuleNames = moduleNames;
|
||||
myRole = thisStatement.getRole();
|
||||
myRole = role;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@@ -66,15 +63,33 @@ public class MergePackageAccessibilityStatementsFix
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getReplacementText(@NotNull PsiPackageAccessibilityStatement otherStatement) {
|
||||
return getKeyword() + " " + myPackageName + " " + PsiKeyword.TO + " " +
|
||||
joinNames(otherStatement.getModuleNames(), myModuleNames) + ";";
|
||||
protected String getReplacementText(@NotNull List<PsiPackageAccessibilityStatement> statementsToMerge) {
|
||||
final List<String> moduleNames = getModuleNames(statementsToMerge);
|
||||
if (!moduleNames.isEmpty()) {
|
||||
return getKeyword() + " " + myPackageName + " " + PsiKeyword.TO + " " + joinUniqueNames(moduleNames) + ";";
|
||||
}
|
||||
return getKeyword() + " " + myPackageName + ";";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> getModuleNames(@NotNull List<PsiPackageAccessibilityStatement> statements) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
for (PsiPackageAccessibilityStatement statement : statements) {
|
||||
final List<String> moduleNames = statement.getModuleNames();
|
||||
if (moduleNames.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
result.addAll(moduleNames);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Iterable<PsiPackageAccessibilityStatement> getStatements(@NotNull PsiJavaModule javaModule) {
|
||||
return getStatements(javaModule, myRole);
|
||||
protected List<PsiPackageAccessibilityStatement> getStatementsToMerge(@NotNull PsiJavaModule javaModule) {
|
||||
return StreamEx.of(getStatements(javaModule, myRole).iterator())
|
||||
.filter(statement -> myPackageName.equals(statement.getPackageName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -82,28 +97,9 @@ public class MergePackageAccessibilityStatementsFix
|
||||
if (statement != null) {
|
||||
final PsiElement parent = statement.getParent();
|
||||
if (parent instanceof PsiJavaModule) {
|
||||
final PsiJavaModule javaModule = (PsiJavaModule)parent;
|
||||
|
||||
final String packageName = statement.getPackageName();
|
||||
if (packageName != null) {
|
||||
final List<String> moduleNames = statement.getModuleNames();
|
||||
if (!moduleNames.isEmpty()) {
|
||||
PsiPackageAccessibilityStatement targetStatement = null;
|
||||
for (PsiPackageAccessibilityStatement candidate : getStatements(javaModule, statement.getRole())) {
|
||||
if (candidate != statement && packageName.equals(candidate.getPackageName())) {
|
||||
if (candidate.getModuleNames().isEmpty()) {
|
||||
// merging with a statement that has no target modules is equivalent to deletion; deletion is a different fix
|
||||
return null;
|
||||
}
|
||||
if (targetStatement == null) {
|
||||
targetStatement = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetStatement != null) {
|
||||
return new MergePackageAccessibilityStatementsFix(statement, packageName, moduleNames, targetStatement);
|
||||
}
|
||||
}
|
||||
return new MergePackageAccessibilityStatementsFix((PsiJavaModule)parent, packageName, statement.getRole());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-40
@@ -17,30 +17,23 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.psi.*;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class MergeProvidesStatementsFix extends MergeModuleStatementsFix<PsiProvidesStatement> {
|
||||
private final String myInterfaceName;
|
||||
private final List<String> myImplementationNames;
|
||||
|
||||
MergeProvidesStatementsFix(@NotNull PsiProvidesStatement thisStatement,
|
||||
@NotNull String interfaceName,
|
||||
@NotNull List<String> implementationNames,
|
||||
@NotNull PsiProvidesStatement otherStatement) {
|
||||
super(thisStatement, otherStatement);
|
||||
MergeProvidesStatementsFix(@NotNull PsiJavaModule javaModule, @NotNull String interfaceName) {
|
||||
super(javaModule);
|
||||
myInterfaceName = interfaceName;
|
||||
myImplementationNames = implementationNames;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -58,29 +51,33 @@ public class MergeProvidesStatementsFix extends MergeModuleStatementsFix<PsiProv
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getReplacementText(@NotNull PsiProvidesStatement otherStatement) {
|
||||
return PsiKeyword.PROVIDES + " " + myInterfaceName + " " + PsiKeyword.WITH + " " +
|
||||
joinNames(getImplementationNames(otherStatement), myImplementationNames) + ";";
|
||||
protected String getReplacementText(@NotNull List<PsiProvidesStatement> statementsToMerge) {
|
||||
final List<String> implementationNames = getImplementationNames(statementsToMerge);
|
||||
LOG.assertTrue(!implementationNames.isEmpty());
|
||||
return PsiKeyword.PROVIDES + " " + myInterfaceName + " " + PsiKeyword.WITH + " " + joinUniqueNames(implementationNames) + ";";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> getImplementationNames(@NotNull List<PsiProvidesStatement> statements) {
|
||||
return StreamEx.of(statements)
|
||||
.map(PsiProvidesStatement::getImplementationList)
|
||||
.nonNull()
|
||||
.flatMap(implementationList -> Arrays.stream(implementationList.getReferenceElements()))
|
||||
.nonNull()
|
||||
.map(PsiJavaCodeReferenceElement::getQualifiedName)
|
||||
.nonNull()
|
||||
.toList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Iterable<PsiProvidesStatement> getStatements(@NotNull PsiJavaModule javaModule) {
|
||||
return javaModule.getProvides();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> getImplementationNames(@Nullable PsiProvidesStatement statement) {
|
||||
if (statement != null) {
|
||||
final PsiReferenceList implementationList = statement.getImplementationList();
|
||||
if (implementationList != null) {
|
||||
return Arrays.stream(implementationList.getReferenceElements())
|
||||
.map(PsiJavaCodeReferenceElement::getQualifiedName)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
protected List<PsiProvidesStatement> getStatementsToMerge(@NotNull PsiJavaModule javaModule) {
|
||||
return StreamEx.of(javaModule.getProvides().iterator())
|
||||
.filter(statement -> {
|
||||
final PsiJavaCodeReferenceElement reference = statement.getInterfaceReference();
|
||||
return reference != null && myInterfaceName.equals(reference.getQualifiedName());
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -88,21 +85,11 @@ public class MergeProvidesStatementsFix extends MergeModuleStatementsFix<PsiProv
|
||||
if (statement != null) {
|
||||
final PsiElement parent = statement.getParent();
|
||||
if (parent instanceof PsiJavaModule) {
|
||||
final PsiJavaModule javaModule = (PsiJavaModule)parent;
|
||||
|
||||
final PsiJavaCodeReferenceElement interfaceReference = statement.getInterfaceReference();
|
||||
if (interfaceReference != null) {
|
||||
final String interfaceName = interfaceReference.getQualifiedName();
|
||||
if (interfaceName != null) {
|
||||
final List<String> implementationNames = getImplementationNames(statement);
|
||||
if (!implementationNames.isEmpty()) {
|
||||
for (PsiProvidesStatement candidate : javaModule.getProvides()) {
|
||||
final PsiJavaCodeReferenceElement candidateInterfaceReference = candidate.getInterfaceReference();
|
||||
if (candidateInterfaceReference != null && interfaceName.equals(candidateInterfaceReference.getQualifiedName())) {
|
||||
return new MergeProvidesStatementsFix(statement, interfaceName, implementationNames, candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new MergeProvidesStatementsFix((PsiJavaModule)parent, interfaceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
module M {
|
||||
exports my.api to M4, M6;
|
||||
exports my.api to M4, M6;<caret>
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
module M {
|
||||
exports <caret>my.api to M2, M4;
|
||||
exports my.api to M6;
|
||||
exports my.api to M2, M4;
|
||||
exports <caret>my.api to M6;
|
||||
exports my.api;
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module M {
|
||||
exports my.api;<caret>
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
module M {
|
||||
// first
|
||||
exports my.api to M2;
|
||||
// second
|
||||
exports <caret>my.api to M6;
|
||||
// third
|
||||
exports my.api to M4;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
module M {
|
||||
// first
|
||||
exports my.api to M2, M6, M4;<caret>
|
||||
// second
|
||||
// third
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
module M {
|
||||
opens my.api to M4, M6;
|
||||
opens my.api to M4, M6;<caret>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module M {
|
||||
opens my.api;<caret>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module M {
|
||||
opens /*first*/ my.api to M4;
|
||||
opens /*second*/ <caret>my.api to M2;
|
||||
opens /*third*/ my.api to M6;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
module M {
|
||||
/*first*/
|
||||
/*second*/
|
||||
/*third*/
|
||||
opens my.api to M4, M2, M6;<caret>
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
module M {
|
||||
provides my.api.MyService with my.impl.MyServiceImpl,my.impl.MyServiceImpl1;
|
||||
provides my.api.MyService with my.impl.MyServiceImpl,my.impl.MyServiceImpl1;<caret>
|
||||
}
|
||||
+1
-1
@@ -3,5 +3,5 @@ import my.impl.MyServiceImpl1;
|
||||
import my.impl.MyServiceImpl2;
|
||||
|
||||
module M {
|
||||
provides my.api.MyService with MyServiceImpl,MyServiceImpl2,MyServiceImpl1;
|
||||
provides my.api.MyService with MyServiceImpl,MyServiceImpl2,MyServiceImpl1;<caret>
|
||||
}
|
||||
+1
-1
@@ -3,5 +3,5 @@ import my.impl.MyServiceImpl1;
|
||||
import my.impl.MyServiceImpl2;
|
||||
|
||||
module M {
|
||||
provides my.api.MyService with MyServiceImpl,MyServiceImpl1,MyServiceImpl2;
|
||||
provides my.api.MyService with MyServiceImpl,MyServiceImpl1,MyServiceImpl2;<caret>
|
||||
}
|
||||
+4
-2
@@ -28,14 +28,16 @@ class MergeModuleStatementsFixTest : LightJava9ModulesCodeInsightFixtureTestCase
|
||||
override fun getBasePath() = getRelativeJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/quickFix/mergeModuleStatementsFix"
|
||||
|
||||
fun testExports1() = doTest("exports", "my.api")
|
||||
fun testExports2() = doTest("exports", "my.api", false)
|
||||
fun testExports2() = doTest("exports", "my.api")
|
||||
fun testExports3() = doTest("exports", "my.api")
|
||||
|
||||
fun testProvides1() = doTest("provides", "my.api.MyService")
|
||||
fun testProvides2() = doTest("provides", "my.api.MyService")
|
||||
fun testProvides3() = doTest("provides", "my.api.MyService")
|
||||
|
||||
fun testOpens1() = doTest("opens", "my.api")
|
||||
fun testOpens2() = doTest("opens", "my.api", false)
|
||||
fun testOpens2() = doTest("opens", "my.api")
|
||||
fun testOpens3() = doTest("opens", "my.api")
|
||||
|
||||
|
||||
override fun setUp() {
|
||||
|
||||
Reference in New Issue
Block a user