[java] fix to move a class with unresolved reference on the test library from production to the test root (IDEA-278923)

GitOrigin-RevId: d7c792d91997dd2ab6311783ddefe75ef7d4c913
This commit is contained in:
Anna Kozlova
2021-10-04 06:20:59 +00:00
committed by intellij-monorepo-bot
parent b36789d9ee
commit ad3c351ee5
3 changed files with 183 additions and 7 deletions
@@ -6,7 +6,9 @@ import com.intellij.codeInsight.daemon.impl.actions.AddImportAction;
import com.intellij.codeInsight.daemon.quickFix.ExternalLibraryResolver;
import com.intellij.codeInsight.daemon.quickFix.ExternalLibraryResolver.ExternalClassResolveResult;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.PriorityAction;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.MoveToTestRootFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.editor.Editor;
@@ -141,6 +143,7 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
Map<Library, String> librariesToAdd = new HashMap<>();
Set<VirtualFile> jars = new HashSet<>();
Set<Library> excluded = new HashSet<>();
Set<Library> withTestScope = new HashSet<>();
ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(currentModule).getFileIndex();
for (PsiClass aClass : allowedDependencies) {
if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) continue;
@@ -166,11 +169,16 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
continue;
}
OrderEntry entryForFile = moduleFileIndex.getOrderEntryForFile(virtualFile);
if (entryForFile != null &&
!(entryForFile instanceof ExportableOrderEntry &&
((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST &&
!moduleFileIndex.isInTestSourceContent(refVFile))) {
excluded.add(library);
if (entryForFile != null) {
boolean testScopeLibraryInProduction = entryForFile instanceof ExportableOrderEntry &&
((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST &&
!moduleFileIndex.isInTestSourceContent(refVFile);
if (testScopeLibraryInProduction) {
withTestScope.add(library);
}
else {
excluded.add(library);
}
}
}
}
@@ -178,10 +186,30 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
excluded.forEach(librariesToAdd::remove);
if (!librariesToAdd.isEmpty()) {
OrderEntryFix fix = new AddLibraryDependencyFix(reference, currentModule, librariesToAdd, scope, false);
class AddLibraryFix extends AddLibraryDependencyFix implements PriorityAction {
AddLibraryFix(PsiReference reference,
Module currentModule,
Map<Library, String> libraries, DependencyScope scope, boolean exported) {
super(reference, currentModule, libraries, scope, exported);
}
@Override
public @NotNull Priority getPriority() {
return withTestScope.isEmpty() ? Priority.NORMAL : Priority.LOW;
}
}
OrderEntryFix fix = new AddLibraryFix(reference, currentModule, librariesToAdd, scope, false);
registrar.register(fix);
result.add(fix);
}
if (!withTestScope.isEmpty()) {
MoveToTestRootFix fix = new MoveToTestRootFix(containingFile);
if (fix.isAvailable(containingFile)) {
registrar.register(fix);
result.add(fix);
}
}
}
return result;
@@ -262,7 +290,8 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
facade.findClass(resolveResult.getQualifiedClassName(), currentModule.getModuleWithDependenciesAndLibrariesScope(true)) == null) {
fix = new AddExtLibraryDependencyFix(reference, currentModule, resolveResult.getLibrary(), scope, resolveResult.getQualifiedClassName());
}
else if (!fullReferenceText.equals(shortReferenceName)) {
else if (!fullReferenceText.equals(shortReferenceName) &&
facade.findClass(fullReferenceText, currentModule.getModuleWithDependenciesAndLibrariesScope(true)) == null) {
ExternalLibraryDescriptor descriptor = resolver.resolvePackage(fullReferenceText);
if (descriptor != null) {
fix = new AddExtLibraryDependencyFix(reference, currentModule, descriptor, scope, null);
@@ -0,0 +1,145 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection;
import com.intellij.CommonBundle;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.ide.util.DirectoryChooser;
import com.intellij.java.JavaBundle;
import com.intellij.java.refactoring.JavaRefactoringBundle;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ContentFolder;
import com.intellij.openapi.roots.ModuleFileIndex;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.SourceFolder;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.refactoring.PackageWrapper;
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesProcessor;
import com.intellij.refactoring.move.moveClassesOrPackages.SingleSourceRootMoveDestination;
import com.intellij.refactoring.util.RefactoringMessageUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;
public class MoveToTestRootFix extends LocalQuickFixAndIntentionActionOnPsiElement {
private static final Logger LOG = Logger.getInstance(MoveToTestRootFix.class);
public MoveToTestRootFix(PsiFile psiFile) {
super(psiFile);
}
@Override
public @IntentionName @NotNull String getText() {
PsiFile containingFile = getStartElement().getContainingFile();
if (containingFile instanceof PsiClassOwner) {
PsiClass[] classes = ((PsiClassOwner)containingFile).getClasses();
if (classes.length > 0) {
return JavaBundle.message("intention.name.move.class.to.test.root", classes[0].getName());
}
}
return getFamilyName();
}
@Override
@NotNull
public String getFamilyName() {
return JavaBundle.message("intention.family.name.move.class.to.test.root");
}
public boolean isAvailable(PsiFile file) {
if (file == null || !file.isValid()) return false;
VirtualFile virtualFile = PsiUtilCore.getVirtualFile(file);
if (virtualFile == null) return false;
Module module = ModuleUtilCore.findModuleForFile(virtualFile, file.getProject());
if (module == null) return false;
ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
ModuleFileIndex fileIndex = rootManager.getFileIndex();
if (!fileIndex.isInSourceContent(virtualFile)) return false;
return getTestRoots(rootManager).findFirst().isPresent();
}
@NotNull
private static Stream<SourceFolder> getTestRoots(ModuleRootManager rootManager) {
return Arrays.stream(rootManager.getContentEntries())
.flatMap(entry -> Arrays.stream(entry.getSourceFolders()))
.filter(SourceFolder::isTestSource);
}
@Override
public void invoke(@NotNull Project project,
@NotNull PsiFile file,
@Nullable Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
final PsiFile myFile = startElement.getContainingFile();
if (!FileModificationService.getInstance().prepareFileForWrite(myFile)) return;
Module module = ModuleUtilCore.findModuleForFile(myFile);
Stream<SourceFolder> testRoots = getTestRoots(ModuleRootManager.getInstance(Objects.requireNonNull(module)));
DirectoryChooser chooser = new DirectoryChooser(project);
chooser.setTitle(JavaRefactoringBundle.message("select.source.root.chooser.title"));
PsiManager manager = myFile.getManager();
PsiDirectory[] directories = testRoots.map(ContentFolder::getFile).filter(Objects::nonNull)
.map(vFile -> manager.findDirectory(vFile))
.filter(Objects::nonNull).toArray(PsiDirectory[]::new);
chooser.fillList(directories, null, project, "");
if (!chooser.showAndGet()) {
return;
}
PsiDirectory sourceRoot = chooser.getSelectedDirectory();
if (sourceRoot == null) return;
PsiPackage targetPackage = JavaDirectoryService.getInstance().getPackage(myFile.getContainingDirectory());
PackageWrapper wrapper = PackageWrapper.create(targetPackage);
PsiDirectory selectedDirectory = WriteAction.compute(() -> RefactoringUtil.createPackageDirectoryInSourceRoot(wrapper, sourceRoot.getVirtualFile()));
try {
String error;
try {
error = RefactoringMessageUtil.checkCanCreateFile(selectedDirectory, myFile.getName());
}
catch (IncorrectOperationException e) {
error = e.getLocalizedMessage();
}
if (error != null) {
Messages.showMessageDialog(project, error, CommonBundle.getErrorTitle(), Messages.getErrorIcon());
return;
}
new MoveClassesOrPackagesProcessor(
project,
((PsiJavaFile) myFile).getClasses(),
new SingleSourceRootMoveDestination(wrapper, selectedDirectory), false,
false,
null).run();
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
@Override
public boolean startInWriteAction() {
return false;
}
}
@@ -1687,3 +1687,5 @@ javadoc.documentation.url.checked=The following documentation {0, choice, 1#URL
javadoc.edit.api.docs.paths=Edit API docs paths
intention.family.name.edit.color.settings=Edit color settings for reassigned variable
intention.name.edit.color.settings=Edit ''{0}'' color settings
intention.family.name.move.class.to.test.root=Move class to test root
intention.name.move.class.to.test.root=Move ''{0}'' to test root