[java-import] IDEA-366927 and IDEA-366926 improve auto-import

- use code style for on-demand imports
- support members for auto-import, not the whole class

GitOrigin-RevId: 32d14c06819a1b1062b081756f614113e22c9858
This commit is contained in:
Mikhail Pyltsin
2025-02-04 20:23:25 +00:00
committed by intellij-monorepo-bot
parent cf99cef8c7
commit 3402d737d7
27 changed files with 260 additions and 37 deletions
@@ -77,12 +77,14 @@ abstract class ImportTable extends ListTableWithButtons<ImportTable.Item> implem
@Override
public TableCellRenderer getRenderer(Item pair) {
return new ComboBoxTableRenderer<>(Scope.values());
return new ComboBoxTableRenderer<>(Scope.values())
.withClickCount(1);
}
@Override
public TableCellEditor getEditor(Item pair) {
return new ComboBoxTableRenderer<>(Scope.values());
return new ComboBoxTableRenderer<>(Scope.values())
.withClickCount(1);
}
@Override
@@ -142,6 +142,7 @@ class JavaAutoImportOptions(val project: Project) : UiDslUnnamedConfigurable.Sim
cell(autoStaticImportTable.component)
.align(AlignX.FILL)
.label(JavaBundle.message("auto.static.import.completion.group"), LabelPosition.TOP)
.comment(JavaBundle.message("auto.static.import.example"))
.onApply { autoStaticImportTable.apply() }
.onReset { autoStaticImportTable.reset() }
.onIsModified { autoStaticImportTable.isModified }
@@ -12,6 +12,7 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.PatternUtil;
import com.intellij.util.containers.ConcurrentFactoryMap;
@@ -47,9 +48,17 @@ public class JavaProjectCodeInsightSettings implements PersistentStateComponent<
return names;
}
public boolean isStaticAutoImportClass(@Nullable String name) {
/**
* Determines whether the given name should be considered as a static auto-import name.
* Can be a fully qualified name of a class or member of a class
*
* @param name the name to check, which can be null
* @return true if the name is included in the list of static auto-import names, false otherwise
*/
public boolean isStaticAutoImportName(@Nullable String name) {
if (name == null) return false;
return getAllIncludedAutoStaticNames().contains(name);
List<String> names = getAllIncludedAutoStaticNames();
return names.contains(name) || names.contains(StringUtil.getPackageName(name));
}
public boolean isExcluded(@NotNull String name) {
@@ -7,6 +7,7 @@ import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.VariableLookupItem;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
@@ -41,11 +42,28 @@ public class JavaStaticMemberProcessor extends StaticMemberProcessor {
JavaProjectCodeInsightSettings codeInsightSettings = JavaProjectCodeInsightSettings.getSettings(project);
JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project);
GlobalSearchScope resolveScope = parameters.getOriginalFile().getResolveScope();
for (String classNames : codeInsightSettings.getAllIncludedAutoStaticNames()) {
PsiClass aClass = javaPsiFacade.findClass(classNames, resolveScope);
for (String name : codeInsightSettings.getAllIncludedAutoStaticNames()) {
PsiClass aClass = javaPsiFacade.findClass(name, resolveScope);
if (aClass != null) {
importMembersOf(aClass);
}
else {
String shortMemberName = StringUtil.getShortName(name);
String containingMemberName = StringUtil.getPackageName(name);
if (containingMemberName.isEmpty() || shortMemberName.isEmpty()) continue;
PsiClass containingClass = javaPsiFacade.findClass(containingMemberName, resolveScope);
if (containingClass != null) {
for (PsiMethod method : containingClass.findMethodsByName(shortMemberName, true)) {
if (method.hasModifierProperty(PsiModifier.STATIC)) {
importMember(method);
}
}
PsiField psiField = containingClass.findFieldByName(shortMemberName, true);
if (psiField != null && psiField.hasModifierProperty(PsiModifier.STATIC)) {
importMember(psiField);
}
}
}
}
}
@@ -66,14 +84,17 @@ public class JavaStaticMemberProcessor extends StaticMemberProcessor {
PsiClass memberContainingClass = member.getContainingClass();
boolean shouldBeAutoImported = memberContainingClass != null &&
member.hasModifierProperty(PsiModifier.STATIC) &&
member.getName() != null &&
JavaCodeStyleManager.getInstance(member.getProject())
.isStaticAutoImportClass(memberContainingClass.getQualifiedName()) &&
member.getName() != null;
.isStaticAutoImportName(memberContainingClass.getQualifiedName() + "." + member.getName());
if (shouldBeAutoImported) {
shouldImport = !ContainerUtil.exists(results, result -> result.getElement() instanceof PsiModifierListOwner modifierListOwner &&
((modifierListOwner).hasModifierProperty(PsiModifier.STATIC) ||
modifierListOwner instanceof PsiMember psiMember &&
member.getName().equals(psiMember.getName())));
shouldImport = !ContainerUtil.exists(results, result -> {
PsiElement element = result.getElement();
return element instanceof PsiModifierListOwner modifierListOwner &&
modifierListOwner.hasModifierProperty(PsiModifier.STATIC) ||
element instanceof PsiMember psiMember &&
member.getName().equals(psiMember.getName());
});
}
else {
shouldImport = false;
@@ -23,6 +23,7 @@ import java.util.*;
public abstract class StaticMemberProcessor {
private final Set<PsiClass> myStaticImportedClasses = new HashSet<>();
private final Set<PsiMember> myStaticImportedMembers = new HashSet<>();
private final PsiElement myPosition;
private final Project myProject;
private final PsiResolveHelper myResolveHelper;
@@ -41,6 +42,10 @@ public abstract class StaticMemberProcessor {
myStaticImportedClasses.add(psiClass);
}
public void importMember(@NotNull PsiMember member) {
myStaticImportedMembers.add(member);
}
public void processStaticMethodsGlobally(@NotNull PrefixMatcher matcher, @NotNull Consumer<? super LookupElement> consumer) {
GlobalSearchScope scope = myPosition.getResolveScope();
Collection<String> memberNames = JavaStaticMemberNameIndex.getInstance().getAllKeys(myProject);
@@ -60,11 +65,8 @@ public abstract class StaticMemberProcessor {
if (JavaCompletionUtil.isSourceLevelAccessible(myPosition, containingClass, myPackagedContext)) {
if (member instanceof PsiMethod && !classesToSkip.add(containingClass)) return;
if(!additionalFilter(member)) return;
boolean shouldImport = myStaticImportedClasses.contains(containingClass);
if (!shouldImport && codeInsightSettings.isStaticAutoImportClass(containingClass.getQualifiedName())) {
shouldImport = true;
}
else {
boolean shouldImport = myStaticImportedClasses.contains(containingClass) || myStaticImportedMembers.contains(member);
if (!codeInsightSettings.isStaticAutoImportName(containingClass.getQualifiedName() + "." + member.getName())) {
showHint(shouldImport);
}
LookupElement item = member instanceof PsiMethod ? createItemWithOverloads((PsiMethod)member, containingClass, shouldImport) :
@@ -118,6 +120,7 @@ public abstract class StaticMemberProcessor {
}
}
}
for (PsiField field : psiClass.getAllFields()) {
if (nameCondition.value(field. getName())) {
if (isStaticallyImportable(field)) {
@@ -126,6 +129,14 @@ public abstract class StaticMemberProcessor {
}
}
}
for (PsiMember member : myStaticImportedMembers) {
if (nameCondition.value(member. getName())) {
if (isStaticallyImportable(member)) {
consumer.consume(member, member.getContainingClass());
}
}
}
}
private boolean isStaticallyImportable(@NotNull PsiMember member) {
@@ -120,7 +120,7 @@ public final class AddOnDemandStaticImportAction extends PsiUpdateModCommandActi
}
Project project = element.getProject();
JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
if (codeStyleManager.isStaticAutoImportClass(classToImport.getQualifiedName())) {
if (codeStyleManager.isStaticAutoImportName(classToImport.getQualifiedName())) {
return null;
}
return Presentation.of(JavaBundle.message("intention.add.on.demand.static.import.text", classToImport.getQualifiedName()));
@@ -80,14 +80,14 @@ public final class AddOnDemandStaticImportToAutoImportAction extends PsiBasedMod
}
if (!staticStatement.isOnDemand() &&
(classQualifiedName + "." + methodExpression.getReferenceName()).equals(importReference.getQualifiedName())) {
name = classQualifiedName;
name = importReference.getQualifiedName();
break;
}
}
}
}
if (name == null) return null;
if (JavaCodeStyleManager.getInstance(element.getProject()).isStaticAutoImportClass(name)) return null;
if (JavaCodeStyleManager.getInstance(element.getProject()).isStaticAutoImportName(name)) return null;
return name;
}
}
@@ -87,7 +87,7 @@ public final class StaticImportCanBeUsedInspection extends AbstractBaseJavaLocal
if (PsiTreeUtil.getParentOfType(reference, PsiImportStatementBase.class) != null) return null;
if (!(qualifier.resolve() instanceof PsiClass psiClass)) return null;
String classQualifiedName = psiClass.getQualifiedName();
if (!javaCodeStyleManager.isStaticAutoImportClass(classQualifiedName)) return null;
if (!javaCodeStyleManager.isStaticAutoImportName(classQualifiedName + "." + memberName)) return null;
PsiElement referenceNameElement = qualifier.getReferenceNameElement();
if (referenceNameElement == null) return null;
PsiClass anImport = AddOnDemandStaticImportAction.getClassToPerformStaticImport(referenceNameElement);
@@ -228,8 +228,7 @@ public final class ImportHelper {
}
classToCountMap.forEach((className, count) -> {
if (isToUseImportOnDemand(className, count, true, javaCodeStyleSettings) ||
javaProjectCodeInsightSettings.isStaticAutoImportClass(className)) {
if (isToUseImportOnDemand(className, count, true, javaCodeStyleSettings)) {
outClassesOrPackagesToImportOnDemand.put(className, true);
}
});
@@ -135,10 +135,10 @@ public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager {
}
@Override
public boolean isStaticAutoImportClass(@Nullable String fqn) {
public boolean isStaticAutoImportName(@Nullable String fqn) {
if (fqn == null) return false;
JavaProjectCodeInsightSettings settings = JavaProjectCodeInsightSettings.getSettings(myProject);
return settings.isStaticAutoImportClass(fqn);
return settings.isStaticAutoImportName(fqn);
}
@Override
@@ -26,12 +26,12 @@ public abstract class JavaCodeStyleManager {
/**
* Determines whether the specified fully qualified name (FQN) represents
* a class that should be statically auto-imported in the context.
* a class or member of a class that should be statically auto-imported in the context.
*
* @param fqn the fully qualified name of the target class, must not be null
* @return true if the class is configured to be statically auto-imported, false otherwise
*/
public abstract boolean isStaticAutoImportClass(@Nullable String fqn);
public abstract boolean isStaticAutoImportName(@Nullable String fqn);
public abstract boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass);
public abstract @NotNull PsiElement shortenClassReferences(@NotNull PsiElement element,
@@ -23,7 +23,7 @@ import java.util.function.Predicate;
public class CoreJavaCodeStyleManager extends JavaCodeStyleManager {
@Override
public boolean isStaticAutoImportClass(@Nullable String fqn) {
public boolean isStaticAutoImportName(@Nullable String fqn) {
return false;
}
@@ -108,8 +108,7 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(qualifierClass.getProject());
boolean hasToBeImportedBySettings =
(javaCodeStyleSettingsFacade.isToImportOnDemand(qualifiedName) ||
refs.size() + 1 >= javaCodeStyleSettingsFacade.getNamesCountToUseImportOnDemand()) ||
javaCodeStyleManager.isStaticAutoImportClass(qualifiedName);
refs.size() + 1 >= javaCodeStyleSettingsFacade.getNamesCountToUseImportOnDemand());
if (!hasToBeImportedBySettings ||
javaCodeStyleManager.hasConflictingOnDemandImport((PsiJavaFile)importList.getContainingFile(), qualifierClass, staticName)) {
importList.add(JavaPsiFacade.getElementFactory(qualifierClass.getProject()).createImportStaticStatement(qualifierClass, staticName));
@@ -0,0 +1,8 @@
import java.util.List;
class Foo {
void test(List<String> baz) {
s<caret>
}
}
@@ -0,0 +1,8 @@
import java.util.List;
class Foo {
void test(List<String> baz) {
s<caret>
}
}
@@ -0,0 +1,8 @@
import java.util.List;
class Foo {
void test(List<String> baz) {
s<caret>
}
}
@@ -0,0 +1,10 @@
import java.util.List;
import static java.util.Arrays.*;
class Foo {
void test(List<String> baz) {
sort(<caret>);
}
}
@@ -0,0 +1,10 @@
import java.util.List;
import static java.util.Arrays.sort;
class Foo {
void test(List<String> baz) {
sort(<caret>);
}
}
@@ -0,0 +1,8 @@
import java.util.List;
class Foo {
void test(List<String> baz) {
so<caret>
}
}
@@ -0,0 +1,10 @@
import java.util.List;
import static java.util.Arrays.*;
class Foo {
void test(List<String> baz) {
sort(<caret>);
}
}
@@ -1,6 +1,6 @@
import java.util.List;
import static java.util.Arrays.*;
import static java.util.Arrays.sort;
class Foo {
@@ -0,0 +1,5 @@
class Foo {
void m() {
<caret>
}
}
@@ -0,0 +1,7 @@
import static java.util.Objects.*;
class Foo {
void m() {
requireNonNull(<caret>)
}
}
@@ -1,4 +1,4 @@
import static java.util.Objects.*;
import static java.util.Objects.requireNonNull;
class Foo {
void m() {
@@ -6,6 +6,9 @@ import com.intellij.codeInsight.JavaProjectCodeInsightSettings;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.codeStyle.PackageEntry;
import com.intellij.psi.codeStyle.PackageEntryTable;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.NeedsIndex;
@@ -53,6 +56,80 @@ public class AutoStaticImportCompletionTest extends NormalCompletionTestCase {
checkResult();
}
public void testSimpleMethodStaticAutoImport() {
addStaticAutoImport("java.util.Arrays.sort");
configure();
type("o");
LookupElement[] elements = myFixture.getLookupElements();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("sort") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Arrays"));
assertNotNull(element);
selectItem(element);
checkResult();
}
public void testSimpleMethodStaticAutoImportOnDemand() {
addStaticAutoImport("java.util.Arrays.sort");
JavaCodeStyleSettings javaSettings = JavaCodeStyleSettings.getInstance(getProject());
PackageEntry entry = new PackageEntry(true, "java.util.Arrays", false);
PackageEntryTable onDemand = javaSettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND;
int onDemandLength = onDemand.getEntries().length;
onDemand.addEntry(entry);
try {
configure();
type("o");
LookupElement[] elements = myFixture.getLookupElements();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("sort") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Arrays"));
assertNotNull(element);
selectItem(element);
checkResult();
}
finally {
onDemand.removeEntryAt(onDemandLength);
}
}
public void testSimpleMethodStaticAutoImportNotFound() {
addStaticAutoImport("java.util.Arrays.binarySearch");
configure();
type("o");
LookupElement[] elements = myFixture.getLookupElements();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("sort") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Arrays"));
assertNull(element);
}
public void testSimpleStaticAutoImportWithEnabledOnDemand() {
addStaticAutoImport("java.util.Arrays");
JavaCodeStyleSettings javaSettings = JavaCodeStyleSettings.getInstance(getProject());
PackageEntry entry = new PackageEntry(true, "java.util.Arrays", false);
PackageEntryTable onDemand = javaSettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND;
int onDemandLength = onDemand.getEntries().length;
onDemand.addEntry(entry);
try {
configure();
type("r");
LookupElement[] elements = myFixture.getLookupElements();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("sort") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Arrays"));
assertNotNull(element);
selectItem(element);
checkResult();
}
finally {
onDemand.removeEntryAt(onDemandLength);
}
}
@NeedsIndex.Full
public void testSimpleStaticAutoImportWithConflict() {
addStaticAutoImport("java.util.Arrays");
@@ -160,6 +237,35 @@ public class AutoStaticImportCompletionTest extends NormalCompletionTestCase {
checkResult();
}
@NeedsIndex.Full
public void testWithPackageConflictsWithOnDemand() {
addStaticAutoImport("java.util.Objects");
JavaCodeStyleSettings javaSettings = JavaCodeStyleSettings.getInstance(getProject());
PackageEntry entry = new PackageEntry(true, "java.util.Objects", false);
PackageEntryTable onDemand = javaSettings.PACKAGES_TO_USE_IMPORT_ON_DEMAND;
int onDemandLength = onDemand.getEntries().length;
onDemand.addEntry(entry);
try {
myFixture.addClass("""
package requireNonNull;
public final class Dummy {}
""");
configure();
type("requireN");
LookupElement[] elements = myFixture.getLookupElements();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("requireNonNull") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Objects"));
assertNotNull(element);
selectItem(element);
checkResult();
}
finally {
onDemand.removeEntryAt(onDemandLength);
}
}
@NeedsIndex.Full
public void testWithMemberConflicts() {
addStaticAutoImport("a.Objects");
@@ -54,8 +54,8 @@ public class AddOnDemandStaticImportToAutoImportActionTest extends LightJavaCode
}
}
""");
assertTrue(tryToAddToAutoImport("java.util.Objects"));
assertTrue(tableContains("java.util.Objects"));
assertTrue(tryToAddToAutoImport("java.util.Objects.requireNonNull"));
assertTrue(tableContains("java.util.Objects.requireNonNull"));
});
}
@@ -82,7 +82,7 @@ public class AddOnDemandStaticImportToAutoImportActionTest extends LightJavaCode
private boolean tableContains(@NotNull String name) {
JavaProjectCodeInsightSettings codeInsightSettings = JavaProjectCodeInsightSettings.getSettings(getProject());
return codeInsightSettings.isStaticAutoImportClass(name);
return codeInsightSettings.isStaticAutoImportName(name);
}
private void doTest(@NotNull Runnable runnable) {
@@ -237,9 +237,10 @@ exclude.0.from.completion=Exclude ''{0}'' from completion
exclude.accessors=&Exclude Accessors
exclude.from.completion.group=Exclude from auto-import and completion:
auto.static.import.completion.group=Include auto-import of static members in completion:
auto.static.import.comment=Add a class to include its static members in auto-import and completion
auto.static.import.class=Class
auto.static.import.comment=Add a class or member of a class to include in auto-import and completion
auto.static.import.class=Class or static members
auto.static.import.scope=Scope
auto.static.import.example=Examples: 'java.util.Objects' or 'java.util.Objects.requireNonNull'
exclude.from.imports.no.exclusions=Add a class, package, or member to exclude from auto-import and completion
exclude.table.mask=Class, Package, or Member
exclude.table.scope.column=Scope