[java] IDEA-355777 Support implicit imports

- Support implicitly imported modules

GitOrigin-RevId: dbf10158847f652936c405a1ad20ce4dd04dbba7
This commit is contained in:
Mikhail Pyltsin
2024-07-17 18:18:57 +00:00
committed by intellij-monorepo-bot
parent 8ce06c83bb
commit eee416a4e8
9 changed files with 138 additions and 4 deletions
@@ -0,0 +1,29 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Represents a module that is implicitly imported.
*
*/
@ApiStatus.Experimental
public final class ImplicitlyImportedModule {
public static final @NotNull ImplicitlyImportedModule @NotNull [] EMPTY_ARRAY = new ImplicitlyImportedModule[0];
private final @NotNull String myModuleName;
private ImplicitlyImportedModule(@NotNull String moduleName) {
myModuleName = moduleName;
}
public @NotNull String getModuleName() {
return myModuleName;
}
@NotNull
public static ImplicitlyImportedModule create(@NotNull String moduleName) {
return new ImplicitlyImportedModule(moduleName);
}
}
@@ -426,6 +426,12 @@ public interface PsiElementFactory extends PsiJavaParserFacade, JVMElementFactor
*/
@NotNull PsiImportStaticStatement createImportStaticStatementFromText(@NotNull String classFullyQualifiedName, @NotNull String memberName) throws IncorrectOperationException;
/**
* Creates an {@code import static} statement for importing the specified member
* from the specified class.
*/
@NotNull PsiImportModuleStatement createImportModuleStatementFromText(@NotNull String moduleName) throws IncorrectOperationException;
/**
* Creates a parameter list from the specified parameter names and types.
*
@@ -100,4 +100,12 @@ public interface PsiJavaFile extends PsiImportHolder, PsiClassOwner, AbstractBas
default @NotNull ImplicitlyImportedStaticMember @NotNull [] getImplicitlyImportedStaticMembers() {
return ImplicitlyImportedStaticMember.EMPTY_ARRAY;
}
/**
* @return the array of implicitly imported modules.
*/
@ApiStatus.Experimental
default @NotNull ImplicitlyImportedModule @NotNull [] getImplicitlyImportedModules(){
return ImplicitlyImportedModule.EMPTY_ARRAY;
}
}
@@ -449,6 +449,17 @@ public final class PsiElementFactoryImpl extends PsiJavaParserFacadeImpl impleme
return (PsiImportStaticStatement)CodeStyleManager.getInstance(myManager.getProject()).reformat(statement);
}
@Override
public @NotNull PsiImportModuleStatement createImportModuleStatementFromText(@NotNull String moduleName)
throws IncorrectOperationException {
PsiJavaFile aFile = createDummyJavaFile("import module " + moduleName + ";");
PsiImportList importList = aFile.getImportList();
if (importList == null) throw new IncorrectOperationException("Can't create module with name: " + moduleName);
PsiImportModuleStatement[] statements = importList.getImportModuleStatements();
if (statements.length != 1) throw new IncorrectOperationException("Created more than one module with name: " + moduleName);
return (PsiImportModuleStatement)CodeStyleManager.getInstance(myManager.getProject()).reformat(statements[0]);
}
@Override
public @NotNull PsiParameterList createParameterList(String @NotNull [] names, PsiType @NotNull [] types) throws IncorrectOperationException {
StringBuilder builder = new StringBuilder();
@@ -45,6 +45,7 @@ import java.util.*;
public final class PsiImplUtil {
private static final Logger LOG = Logger.getInstance(PsiImplUtil.class);
private static final String JAVA_IO_IO = "java.io.IO";
private static final String JAVA_BASE = "java.base";
private PsiImplUtil() { }
@@ -871,4 +872,25 @@ public final class PsiImplUtil {
return staticImports.toArray(ImplicitlyImportedStaticMember.EMPTY_ARRAY);
}
/**
* Retrieves the implicit static imports for the given file.
*
* @param file the file for which to retrieve implicit static imports
* @return an array of static members representing the implicit static imports
*/
@ApiStatus.Experimental
public static @NotNull ImplicitlyImportedModule @NotNull[] getImplicitlyImportedModules(@NotNull PsiFile file) {
List<ImplicitlyImportedModule> modules = new ArrayList<>();
// import module java.base; for implicit classes
if (PsiUtil.isAvailable(JavaFeature.IMPLICIT_IMPORT_IN_IMPLICIT_CLASSES, file) && file instanceof PsiJavaFile) {
PsiClass[] classes = ((PsiJavaFile)file).getClasses();
if (classes.length == 1 && classes[0] instanceof PsiImplicitClass) {
modules.add(ImplicitlyImportedModule.create(JAVA_BASE));
}
}
return modules.toArray(ImplicitlyImportedModule.EMPTY_ARRAY);
}
}
@@ -51,6 +51,7 @@ public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJava
private final CachedValue<MostlySingularMultiMap<String, ResultWithContext>> myResolveCache;
private final CachedValue<Map<String, Iterable<ResultWithContext>>> myCachedDeclarations;
private final CachedValue<List<PsiImportStaticStatement>> myCachedImplicitImportStaticStatements;
private final CachedValue<List<PsiImportModuleStatement>> myCachedImplicitImportModuleStatements;
private volatile String myPackageName;
protected PsiJavaFileBaseImpl(@NotNull IElementType elementType, @NotNull IElementType contentElementType, @NotNull FileViewProvider viewProvider) {
@@ -69,6 +70,10 @@ public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJava
List<PsiImportStaticStatement> statements = createImplicitImportStaticStatements();
return Result.create(statements, this.getContainingFile());
}, false);
myCachedImplicitImportModuleStatements = cachedValuesManager.createCachedValue(() -> {
List<PsiImportModuleStatement> statements = createImplicitImportModuleStatements();
return Result.create(statements, this.getContainingFile());
}, false);
}
@Override
@@ -405,7 +410,7 @@ public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJava
}
private boolean processModules(@NotNull ResolveState state, @NotNull PsiElement place, @NotNull PsiScopeProcessor processor) {
for (PsiImportModuleStatement statement : getImportModuleStatements()) {
for (PsiImportModuleStatement statement : ContainerUtil.append(getImplicitImportModuleStatements(), getImportModuleStatements())) {
PsiElement resolved = statement.resolve();
if (resolved != null) {
processor.handleEvent(JavaScopeProcessorEvent.SET_CURRENT_FILE_CONTEXT, statement);
@@ -572,6 +577,11 @@ public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJava
return PsiImplUtil.getImplicitStaticImports(this);
}
@Override
public @NotNull ImplicitlyImportedModule @NotNull [] getImplicitlyImportedModules() {
return PsiImplUtil.getImplicitlyImportedModules(this);
}
@Override
public void clearCaches() {
super.clearCaches();
@@ -589,6 +599,11 @@ public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJava
return myCachedImplicitImportStaticStatements.getValue();
}
@NotNull
private List<PsiImportModuleStatement> getImplicitImportModuleStatements() {
return myCachedImplicitImportModuleStatements.getValue();
}
@NotNull
private List<PsiImportStaticStatement> createImplicitImportStaticStatements() {
PsiElementFactory factory = PsiElementFactory.getInstance(getProject());
@@ -596,6 +611,12 @@ public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJava
member -> factory.createImportStaticStatementFromText(member.getContainingClass(), member.getMemberName()));
}
private List<PsiImportModuleStatement> createImplicitImportModuleStatements() {
PsiElementFactory factory = PsiElementFactory.getInstance(getProject());
return ContainerUtil.map(getImplicitlyImportedModules(),
mod -> factory.createImportModuleStatementFromText(mod.getModuleName()));
}
private static final Key<String> SHEBANG_SOURCE_LEVEL = Key.create("SHEBANG_SOURCE_LEVEL");
private @NotNull LanguageLevel getLanguageLevelInner() {
@@ -0,0 +1,3 @@
void main() {
List<String> a = new ArrayList<>();
}
@@ -2,10 +2,13 @@
package com.intellij.java.codeInsight.completion;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.NeedsIndex;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class NormalImplicitClassCompletionTest extends NormalCompletionTestCase {
@NotNull
@Override
@@ -33,4 +36,22 @@ public class NormalImplicitClassCompletionTest extends NormalCompletionTestCase
println(<caret>);
}""");
}
@NeedsIndex.ForStandardLibrary
public void testImplicitModuleImport() {
myFixture.configureByText("a.java", """
void main(){
List<String> a = new ArrayLis<caret>
}""");
myFixture.complete(CompletionType.BASIC, 0);
assertEquals(List.of("ArrayList", "CopyOnWriteArrayList"), myFixture.getLookupElementStrings());
LookupElement[] elements = myFixture.getLookupElements();
assertNotNull(elements);
myFixture.getLookup().setCurrentItem(elements[0]);
myFixture.type('\n');
myFixture.checkResult("""
void main(){
List<String> a = new ArrayList<>(<caret>)
}""");
}
}
@@ -4,9 +4,8 @@ package com.intellij.java.codeInsight.daemon
import com.intellij.JavaTestUtil
import com.intellij.pom.java.JavaFeature
import com.intellij.pom.java.LanguageLevel
import com.intellij.psi.PsiCallExpression
import com.intellij.psi.PsiExpressionStatement
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.*
import com.intellij.psi.util.PsiUtil
import com.intellij.testFramework.IdeaTestUtil
import com.intellij.testFramework.UsefulTestCase
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
@@ -88,6 +87,20 @@ class ImplicitClassHighlightingTest : LightJavaCodeInsightFixtureTestCase() {
})
}
fun testImplicitModuleImport() {
IdeaTestUtil.withLevel(module, JavaFeature.IMPLICIT_IMPORT_IN_IMPLICIT_CLASSES.minimumLevel, Runnable {
val psiFile = myFixture.configureByFile(getTestName(false) + ".java")
myFixture.checkHighlighting()
val statement = (psiFile as PsiJavaFile).classes[0].methods[0].body!!.statements[0] as PsiDeclarationStatement
val variable = (statement.declaredElements[0] as PsiVariable)
val variableType = variable.type
assertNotNull(variableType)
val psiClass = PsiUtil.resolveClassInClassTypeOnly(variableType)
assertNotNull(psiClass)
assertEquals(CommonClassNames.JAVA_UTIL_LIST, psiClass!!.qualifiedName)
})
}
private fun doTest() {
myFixture.configureByFile(getTestName(false) + ".java")
myFixture.checkHighlighting()