When "Add unambiguous imports on the fly" option is on, try to add these imports during "Optimize Imports" batch action to fix IDEABKL-6064 'Optimize Imports' should add missing imports

GitOrigin-RevId: fc37b70918b9a065670a668f5c27306f7ece0a86
This commit is contained in:
Alexey Kudravtsev
2020-05-06 21:05:05 +00:00
committed by intellij-monorepo-bot
parent dc5ebd15c8
commit b560663f08
10 changed files with 178 additions and 45 deletions
@@ -944,7 +944,7 @@ public class JavaCompletionContributor extends CompletionContributor {
element = qualifier;
}
if (!(element.getParent() instanceof PsiMethodCallExpression) && element.multiResolve(true).length == 0) {
new ImportClassFix(element).doFix(editor, false, false);
new ImportClassFix(element).fixSilently(editor);
PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument());
}
}
@@ -51,7 +51,7 @@ public class JavaReferenceImporter implements ReferenceImporter {
if (element instanceof PsiJavaCodeReferenceElement) {
PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)element;
if (ref.multiResolve(true).length == 0) {
new ImportClassFix(ref).doFix(editor, false, allowCaretNearRef);
new ImportClassFix(ref).doFix(editor, false, allowCaretNearRef, true);
return true;
}
}
@@ -2,14 +2,12 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.ImportFilter;
import com.intellij.codeInsight.completion.JavaCompletionUtil;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.DaemonListeners;
import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass;
import com.intellij.codeInsight.daemon.impl.actions.AddImportAction;
import com.intellij.codeInsight.hint.HintManager;
@@ -18,12 +16,10 @@ import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.codeInspection.HintAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.packageDependencies.DependencyRule;
import com.intellij.packageDependencies.DependencyValidationManager;
@@ -31,7 +27,6 @@ import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.util.FileTypeUtils;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
@@ -293,12 +288,13 @@ public abstract class ImportClassFixBase<T extends PsiElement, R extends PsiRefe
@Override
public boolean fixSilently(@NotNull Editor editor) {
return mayAutoImportNow(myElement.getContainingFile()) &&
doFix(editor, false, false) == Result.CLASS_AUTO_IMPORTED;
PsiFile file = myElement.isValid() ? myElement.getContainingFile() : null;
if (file == null || !ShowAutoImportPass.isAddUnambiguousImportsOnTheFlyEnabled(file)) return false;
return doFix(editor, false, false, true) == Result.CLASS_AUTO_IMPORTED;
}
@NotNull
public Result doFix(@NotNull final Editor editor, boolean allowPopup, final boolean allowCaretNearRef) {
public Result doFix(@NotNull final Editor editor, boolean allowPopup, final boolean allowCaretNearRef, boolean mayAddUnambiguousImportsSilently) {
ApplicationManager.getApplication().assertIsDispatchThread();
List<PsiClass> classesToImport = getClassesToImport();
//do not show popups for already imported classes when library is missing (show them for explicit action)
@@ -332,7 +328,7 @@ public abstract class ImportClassFixBase<T extends PsiElement, R extends PsiRefe
if (classes.length == 1 &&
(canImportHere = canImportHere(allowCaretNearRef, editor, psiFile, classes[0].getName())) &&
mayAutoImportNow(psiFile) &&
mayAddUnambiguousImportsSilently &&
!autoImportWillInsertUnexpectedCharacters(classes[0])) {
CommandProcessor.getInstance().runUndoTransparentAction(() -> action.execute());
return Result.CLASS_AUTO_IMPORTED;
@@ -349,24 +345,6 @@ public abstract class ImportClassFixBase<T extends PsiElement, R extends PsiRefe
return Result.POPUP_NOT_SHOWN;
}
private static boolean mayAutoImportNow(@NotNull PsiFile psiFile) {
return isAddUnambiguousImportsOnTheFlyEnabled(psiFile) &&
(ApplicationManager.getApplication().isUnitTestMode() || DaemonListeners.canChangeFileSilently(psiFile)) &&
isInModelessContext(psiFile.getProject());
}
private static boolean isInModelessContext(@NotNull Project project) {
return Registry.is("ide.perProjectModality") ?
!LaterInvocator.isInModalContextForProject(project) :
!LaterInvocator.isInModalContext();
}
public static boolean isAddUnambiguousImportsOnTheFlyEnabled(@NotNull PsiFile psiFile) {
return FileTypeUtils.isInServerPageFile(psiFile) ?
CodeInsightSettings.getInstance().JSP_ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY :
CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY;
}
protected int getStartOffset(@NotNull T element, @NotNull R ref) {
return element.getTextOffset();
}
@@ -394,7 +372,10 @@ public abstract class ImportClassFixBase<T extends PsiElement, R extends PsiRefe
if (isQualified(myRef)) {
return false;
}
Result result = doFix(editor, true, false);
PsiFile file = myElement.isValid() ? myElement.getContainingFile() : null;
if (file == null) return false;
Result result = doFix(editor, true, false, ShowAutoImportPass.mayAutoImportNow(file));
return result == Result.POPUP_SHOWN || result == Result.CLASS_AUTO_IMPORTED;
}
@@ -1,8 +1,8 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass;
import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix;
import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFixBase;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.util.TextRange;
@@ -116,7 +116,7 @@ public class JavaCopyPasteReferenceProcessor extends CopyPasteReferenceProcessor
}
}
if (ImportClassFixBase.isAddUnambiguousImportsOnTheFlyEnabled(file)) {
if (ShowAutoImportPass.isAddUnambiguousImportsOnTheFlyEnabled(file)) {
for (int i = 0; i < refs.length; i++) {
if (isUnambiguous(refs[i])) {
refs[i] = null;
@@ -0,0 +1,5 @@
package p;
class X1 {
ArrayList l;
}
@@ -0,0 +1,5 @@
package p;
class X2 {
ArrayList l;
}
@@ -218,7 +218,6 @@ public class ImportHelperTest extends LightDaemonAnalyzerTestCase {
public void testAutoImportCaretLocation() {
String text = "class X { ArrayList<caret> c; }";
configureByText(text);
CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true;
type(" ");
backspace();
@@ -228,12 +227,12 @@ public class ImportHelperTest extends LightDaemonAnalyzerTestCase {
PsiReference ref = getFile().findReferenceAt(offset - 1);
assertTrue(ref instanceof PsiJavaCodeReferenceElement);
ImportClassFixBase.Result result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false);
ImportClassFixBase.Result result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false, true);
assertEquals(ImportClassFixBase.Result.POPUP_NOT_SHOWN, result);
UIUtil.dispatchAllInvocationEvents();
getEditor().getCaretModel().moveToOffset(offset - 1);
result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false);
result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false, true);
assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result);
UIUtil.dispatchAllInvocationEvents();
@@ -243,7 +242,6 @@ public class ImportHelperTest extends LightDaemonAnalyzerTestCase {
public void testAutoImportCaretLocation2() {
String text = "class X { <caret>ArrayList c = null; }";
configureByText(text);
CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true;
type(" ");
backspace();
@@ -254,7 +252,7 @@ public class ImportHelperTest extends LightDaemonAnalyzerTestCase {
PsiReference ref = getFile().findReferenceAt(offset);
assertTrue(ref instanceof PsiJavaCodeReferenceElement);
ImportClassFixBase.Result result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false);
ImportClassFixBase.Result result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false, true);
assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result);
UIUtil.dispatchAllInvocationEvents();
@@ -265,8 +263,6 @@ public class ImportHelperTest extends LightDaemonAnalyzerTestCase {
@NonNls String text = "class S { ArrayList<caret> }";
configureByText(text);
CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true;
doHighlighting();
//caret is too close
assertNoImportsAdded();
@@ -276,7 +272,7 @@ public class ImportHelperTest extends LightDaemonAnalyzerTestCase {
PsiJavaCodeReferenceElement element =
(PsiJavaCodeReferenceElement)getFile().findReferenceAt(getEditor().getCaretModel().getOffset() - 2);
ImportClassFix fix = new ImportClassFix(element);
ImportClassFixBase.Result result = fix.doFix(getEditor(), false, false);
ImportClassFixBase.Result result = fix.doFix(getEditor(), false, false, true);
assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result);
assertOneImportAdded("java.util.ArrayList");
@@ -15,6 +15,7 @@
*/
package com.intellij.java.psi;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.actions.OptimizeImportsProcessor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
@@ -47,4 +48,51 @@ public class OptimizeImportsMultiFileTest extends JavaPsiTestCase {
String textAfter = VfsUtilCore.loadText(x);
assertEquals(textBefore, textAfter);
}
public void testOptimizeImportsMustAddUnambiguousImportsIfTheCorrespondingSettingIsOn() throws Exception {
boolean importsOnTheFly = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY;
CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = true;
try {
VirtualFile root = PsiTestUtil.createTestProjectStructure(myProject, myModule, OptimizeImportsTest.BASE_PATH + "/src1", myFilesToDelete, false);
PsiTestUtil.addSourceRoot(getModule(), root);
PsiDirectory directory = myPsiManager.findDirectory(root);
assertNotNull(directory);
new OptimizeImportsProcessor(getProject(), directory, true).run();
WriteCommandAction.runWriteCommandAction(null, () -> {
PostprocessReformattingAspect.getInstance(getProject()).doPostponedFormatting();
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
ApplicationManager.getApplication().saveAll();
});
String text1After = VfsUtilCore.loadText(root.findFileByRelativePath("p/X1.java"));
assertTrue(text1After, text1After.contains("import java.util.ArrayList;"));
String text2After = VfsUtilCore.loadText(root.findFileByRelativePath("p/X2.java"));
assertTrue(text2After, text2After.contains("import java.util.ArrayList;"));
}
finally {
CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = importsOnTheFly;
}
}
public void testOptimizeImportsMustNotAddUnambiguousImportsIfTheCorrespondingSettingIsOff() throws Exception {
boolean importsOnTheFly = CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY;
CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = false;
try {
VirtualFile root = PsiTestUtil.createTestProjectStructure(myProject, myModule, OptimizeImportsTest.BASE_PATH + "/src1", myFilesToDelete, false);
PsiTestUtil.addSourceRoot(getModule(), root);
PsiDirectory directory = myPsiManager.findDirectory(root);
assertNotNull(directory);
new OptimizeImportsProcessor(getProject(), directory, true).run();
WriteCommandAction.runWriteCommandAction(null, () -> {
PostprocessReformattingAspect.getInstance(getProject()).doPostponedFormatting();
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
ApplicationManager.getApplication().saveAll();
});
String text1After = VfsUtilCore.loadText(root.findFileByRelativePath("p/X1.java"));
assertFalse(text1After, text1After.contains("import java.util.ArrayList;"));
String text2After = VfsUtilCore.loadText(root.findFileByRelativePath("p/X2.java"));
assertFalse(text2After, text2After.contains("import java.util.ArrayList;"));
}
finally {
CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY = importsOnTheFly;
}
}
}
@@ -3,8 +3,11 @@
package com.intellij.codeInsight.actions;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass;
import com.intellij.codeInspection.HintAction;
import com.intellij.lang.ImportOptimizer;
import com.intellij.lang.LanguageImportStatements;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
@@ -83,7 +86,10 @@ public class OptimizeImportsProcessor extends AbstractLayoutCodeProcessor {
}
}
Runnable runnable = runnables.isEmpty() ? EmptyRunnable.getInstance() : () -> {
List<HintAction> hints = ShowAutoImportPass.getImportHints(file);
Runnable writeTask = runnables.isEmpty() ? EmptyRunnable.getInstance() : () -> {
ApplicationManager.getApplication().assertIsDispatchThread();
CodeStyleManagerImpl.setSequentialProcessingAllowed(false);
try {
for (Runnable runnable1 : runnables) {
@@ -91,13 +97,14 @@ public class OptimizeImportsProcessor extends AbstractLayoutCodeProcessor {
myOptimizerNotifications.add(getNotificationInfo(runnable1));
}
putNotificationInfoIntoCollector();
ShowAutoImportPass.fixAllImportsSilently(file, hints);
}
finally {
CodeStyleManagerImpl.setSequentialProcessingAllowed(true);
}
};
return new FutureTask<>(runnable, true);
return new FutureTask<>(writeTask, true);
}
@NotNull
@@ -4,6 +4,7 @@ package com.intellij.codeInsight.daemon.impl;
import com.intellij.codeHighlighting.Pass;
import com.intellij.codeHighlighting.TextEditorHighlightingPass;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.daemon.DaemonBundle;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
@@ -15,23 +16,30 @@ import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.impl.ApplicationImpl;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorActivityManager;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public class ShowAutoImportPass extends TextEditorHighlightingPass {
private final Editor myEditor;
@@ -101,13 +109,36 @@ public class ShowAutoImportPass extends TextEditorHighlightingPass {
for (HighlightInfo info : infos) {
for (HintAction action : extractHints(info)) {
if (action.isAvailable(myProject, myEditor, myFile) && action.fixSilently(myEditor)) {
if (action.isAvailable(myProject, myEditor, myFile)
&& mayAutoImportNow(myFile)
&& action.fixSilently(myEditor)) {
break;
}
}
}
}
public static boolean mayAutoImportNow(@NotNull PsiFile psiFile) {
return isAddUnambiguousImportsOnTheFlyEnabled(psiFile) &&
(ApplicationManager.getApplication().isUnitTestMode() || DaemonListeners.canChangeFileSilently(psiFile)) &&
isInModelessContext(psiFile.getProject());
}
private static boolean isInModelessContext(@NotNull Project project) {
return Registry.is("ide.perProjectModality") ?
!LaterInvocator.isInModalContextForProject(project) :
!LaterInvocator.isInModalContext();
}
public static boolean isAddUnambiguousImportsOnTheFlyEnabled(@NotNull PsiFile psiFile) {
PsiFile templateFile = PsiUtilCore.getTemplateLanguageFile(psiFile);
if (templateFile == null) return false;
boolean isJsp = templateFile.getFileType().getName().equals("JSP") || templateFile.getFileType().getName().equals("JSPX");
return isJsp ?
CodeInsightSettings.getInstance().JSP_ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY :
CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY;
}
@NotNull
private static List<HighlightInfo> getVisibleHighlights(final int startOffset,
final int endOffset,
@@ -141,6 +172,66 @@ public class ShowAutoImportPass extends TextEditorHighlightingPass {
return false;
}
public static void fixAllImportsSilently(@NotNull PsiFile file, @NotNull List<? extends HintAction> actions) {
if (actions.isEmpty()) return;
Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
if (document == null) return;
Editor editor = EditorFactory.getInstance().createEditor(document, file.getProject());
try {
for (HintAction action : actions) {
action.fixSilently(editor);
}
}
finally {
EditorFactory.getInstance().releaseEditor(editor);
}
}
@NotNull
public static List<HintAction> getImportHints(@NotNull PsiFile file) {
if (ApplicationManager.getApplication().isDispatchThread()) {
// really can't run highlighting from within EDT
// also, guard against recursive call optimize imports->add imports->optimize imports (in AddImportAction.doAddImport())
return Collections.emptyList();
}
Project project = file.getProject();
Document document = PsiDocumentManager.getInstance(project).getDocument(file);
if (document == null || !hasUnresolvedReferences(file)) return Collections.emptyList();
DaemonProgressIndicator progress = new DaemonProgressIndicator();
AtomicReference<List<HighlightInfo>> infos = new AtomicReference<>(Collections.emptyList());
((ApplicationImpl)ApplicationManager.getApplication()).executeByImpatientReader(() -> {
ProgressManager.getInstance().executeProcessUnderProgress(() -> {
infos.set(DaemonCodeAnalyzerEx.getInstanceEx(project).runMainPasses(file, document, progress));
}, progress);
});
List<HintAction> result = new ArrayList<>(infos.get().size());
Editor editor = null;
for (HighlightInfo info : infos.get()) {
for (HintAction action : extractHints(info)) {
if (action.isAvailable(project, null, file)) {
result.add(action);
}
}
}
return result;
}
private static boolean hasUnresolvedReferences(@NotNull PsiFile file) {
Ref<Boolean> result = new Ref<>(false);
file.accept(new PsiRecursiveElementWalkingVisitor() {
@Override
public void visitElement(@NotNull PsiElement element) {
if (element instanceof PsiReference && ((PsiReference)element).resolve() == null) {
result.set(true);
stopWalking();
}
super.visitElement(element);
}
});
return result.get();
}
private boolean isImportHintEnabled() {
return DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled() &&
DaemonCodeAnalyzer.getInstance(myProject).isImportHintsEnabled(myFile);