[java-inspection] IDEA-363979 Conflicts with on-demand and module imports

(cherry picked from commit 0857df90e3f9cc450b48b56186573a9a89b5b041)

GitOrigin-RevId: 8e1474a59c687561ba5f00a13fecd4d2a9057b7a
This commit is contained in:
Mikhail Pyltsin
2024-12-05 17:59:11 +01:00
committed by intellij-monorepo-bot
parent 6113015d06
commit 3348e7291f
23 changed files with 700 additions and 151 deletions

View File

@@ -1,6 +1,7 @@
// 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.codeInspection.unusedImport;
import com.intellij.psi.PsiImportModuleStatement;
import com.intellij.psi.PsiImportStatementBase;
import com.siyeh.ig.psiutils.PsiElementOrderComparator;
@@ -20,11 +21,19 @@ final class ImportStatementComparator implements Comparator<PsiImportStatementBa
@Override
public int compare(PsiImportStatementBase importStatementBase1, PsiImportStatementBase importStatementBase2) {
final boolean onDemand = importStatementBase1.isOnDemand();
if (onDemand != importStatementBase2.isOnDemand()) {
return onDemand ? -1 : 1;
final boolean onDemand1 = importStatementBase1.isOnDemand();
final boolean onDemand2 = importStatementBase2.isOnDemand();
if (onDemand1 != onDemand2) {
return onDemand1 ? -1 : 1;
}
// just sort on demand imports first, and sort the rest in reverse file order.
if (onDemand1) {
boolean isModule1 = importStatementBase1 instanceof PsiImportModuleStatement;
boolean isModule2 = importStatementBase2 instanceof PsiImportModuleStatement;
if (isModule1 != isModule2) {
return isModule1 ? -1 : 1;
}
}
// just sort on module import first, then on demand imports, and sort the rest in reverse file order.
return -PsiElementOrderComparator.getInstance().compare(importStatementBase1, importStatementBase2);
}
}

View File

@@ -118,7 +118,7 @@ class ImportsAreUsedVisitor extends JavaRecursiveElementWalkingVisitor {
if (memberPackageName == null) {
return null;
}
final boolean hasOnDemandImportConflict = ImportUtils.hasOnDemandImportConflict(memberQualifiedName, myFile);
ImportUtils.OnDemandImportConflicts conflicts = ImportUtils.findOnDemandImportConflict(memberQualifiedName, myFile);
for (PsiImportStatementBase importStatement : importStatements) {
if (!importStatement.isOnDemand()) {
final PsiJavaCodeReferenceElement reference = importStatement.getImportReference();
@@ -133,7 +133,12 @@ class ImportsAreUsedVisitor extends JavaRecursiveElementWalkingVisitor {
}
}
else {
if (hasOnDemandImportConflict) {
if (importStatement instanceof PsiImportModuleStatement && conflicts.conflictForModules()) {
continue;
}
if (!(importStatement instanceof PsiImportModuleStatement) &&
importStatement.isOnDemand() &&
conflicts.conflictForOnDemand()) {
continue;
}
if (importStatement instanceof PsiImportModuleStatement psiImportModuleStatement &&

View File

@@ -25,7 +25,6 @@ import com.siyeh.HardcodedMethodConstants;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.InspectionGadgetsFix;
import com.siyeh.ig.fixes.DeleteImportFix;
import com.siyeh.ig.psiutils.ImportUtils;
import org.jetbrains.annotations.NotNull;
@@ -81,7 +80,7 @@ public final class JavaLangImportInspection extends BaseInspection implements Cl
if (!HardcodedMethodConstants.JAVA_LANG.equals(parentName)) {
return;
}
if (ImportUtils.hasOnDemandImportConflict(text, statement)) {
if (ImportUtils.findOnDemandImportConflict(text, statement).conflictForOnDemand()) {
return;
}
registerError(statement);

View File

@@ -20,16 +20,20 @@ import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaFileCodeStyleFacade;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -67,12 +71,26 @@ public final class ImportUtils {
if (containingPackageName.equals(packageName) || importList.findSingleClassImportStatement(qualifiedName) != null) {
return;
}
if ((createImplicitImportChecker(javaFile).isImplicitlyImported(qualifiedName, false) ||
importList.findOnDemandImportStatement(packageName) != null ||
ContainerUtil.exists(importList.getImportModuleStatements(),
moduleStatement -> moduleStatement.findImportedPackage(packageName) != null))
&& !hasOnDemandImportConflict(qualifiedName, javaFile)) {
return;
OnDemandImportConflicts conflict = null;
ImplicitImportChecker implicitImportChecker = createImplicitImportChecker(javaFile);
if (implicitImportChecker.isImplicitlyImported(qualifiedName, false)) {
conflict = findOnDemandImportConflict(qualifiedName, javaFile);
if (implicitImportChecker.isImplicitlyImported(qualifiedName, false, conflict)) {
return;
}
}
if (importList.findOnDemandImportStatement(packageName) != null) {
if (conflict == null) {
conflict = findOnDemandImportConflict(qualifiedName, javaFile);
}
if (!conflict.conflictForOnDemand()) return;
}
if (ContainerUtil.exists(importList.getImportModuleStatements(),
moduleStatement -> moduleStatement.findImportedPackage(packageName) != null)) {
if (conflict == null) {
conflict = findOnDemandImportConflict(qualifiedName, javaFile);
}
if (!conflict.conflictForModules() && !conflict.conflictForOnDemand()) return;
}
if (hasExactImportConflict(qualifiedName, javaFile)) {
return;
@@ -131,7 +149,8 @@ public final class ImportUtils {
if (hasExactImportConflict(fqName, file)) {
return false;
}
if (hasOnDemandImportConflict(fqName, file, true) && !isAlreadyImported(file, fqName)
if (findOnDemandImportConflict(fqName, file, true, true, true) &&
!isAlreadyImported(file, fqName)
) {
return false;
}
@@ -222,17 +241,59 @@ public final class ImportUtils {
return false;
}
public static boolean hasOnDemandImportConflict(@NotNull String fqName, @NotNull PsiElement context) {
return hasOnDemandImportConflict(fqName, context, false);
/**
* Represents the conflicts that can occur with on-demand imports in a Java file.
* This record captures whether there is a conflict with on-demand imports involving
* class names and module imports.
* The `conflictForOnDemand` indicates if there is a conflict when importing classes
* using on-demand imports, such as `import package.*;`.
* The `conflictForModules` indicates if there is a conflict when considering module-related
* imports.
*
* @param conflictForOnDemand true if there is a conflict with on-demand imports, false otherwise
* @param conflictForModules true if there is a conflict related to modules, false otherwise
*/
public record OnDemandImportConflicts(boolean conflictForOnDemand, boolean conflictForModules) {}
/**
* Finds conflicts related to on-demand imports in the context of a given fully qualified name.
* This method checks for potential conflicts with existing imports in the specified context,
* considering both class-level on-demand imports and module-related imports.
*
* @param fqName the fully qualified name of the class or package to check for import conflicts
* @param context the PSI element representing the context within which to check for import conflicts
* @return an OnDemandImportConflicts object that contains information about whether there are
* conflicts for on-demand imports and module imports
*/
public static OnDemandImportConflicts findOnDemandImportConflict(@NotNull String fqName, @NotNull PsiElement context) {
if(PsiUtil.isAvailable(JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS, context)) {
boolean onDemandConflict = findOnDemandImportConflict(fqName, context, false, true, false);
boolean moduleConflict = findOnDemandImportConflict(fqName, context, false, false, true);
return new OnDemandImportConflicts(onDemandConflict, moduleConflict);
}
boolean demandImportConflict = findOnDemandImportConflict(fqName, context, false, true, true);
return new OnDemandImportConflicts(demandImportConflict, demandImportConflict) ;
}
/**
* @deprecated use {@link ImportUtils#findOnDemandImportConflict(String, PsiElement)}
*/
@Deprecated(forRemoval = true)
public static boolean hasOnDemandImportConflict(@NotNull String fqName, @NotNull PsiElement context) {
OnDemandImportConflicts conflict = findOnDemandImportConflict(fqName, context);
return conflict.conflictForOnDemand() || conflict.conflictForModules();
}
/**
* @param strict if strict is true this method checks if the conflicting
* class which is imported is actually used in the file. If it isn't the
* on demand import can be overridden with an exact import for the fqName
* without breaking stuff.
*/
private static boolean hasOnDemandImportConflict(@NotNull String fqName, @NotNull PsiElement context, boolean strict) {
private static boolean findOnDemandImportConflict(@NotNull String fqName,
@NotNull PsiElement context,
boolean strict,
boolean considerOnDemand,
boolean considerModules) {
final PsiFile containingFile = context.getContainingFile();
if (!(containingFile instanceof PsiJavaFile javaFile)) {
return false;
@@ -243,9 +304,9 @@ public final class ImportUtils {
}
final List<PsiImportStatementBase> importStatements =
ContainerUtil.append(getAllImplicitImports(javaFile), imports.getAllImportStatements());
ThreeState state = hasOnDemandImportConflictWithImports(javaFile, importStatements, fqName, strict);
ThreeState state = hasOnDemandImportConflictWithImports(javaFile, importStatements, fqName, strict, considerOnDemand, considerModules);
if (state != ThreeState.UNSURE) return state.toBoolean();
return hasDefaultImportConflict(fqName, javaFile);
return considerOnDemand && hasDefaultImportConflict(fqName, javaFile);
}
/**
@@ -255,25 +316,31 @@ public final class ImportUtils {
* @param javaFile the Java file to check for import conflicts.
* @param importStatements the list of import statements to check against.
* @param fqName the fully qualified name to check for conflicts.
* @param checkOnDemand check conflicts for all on-demand statements, excluding module import statements.
* @param checkModules check conflicts for module imports.
* @return true if there is an on-demand import conflict, false otherwise.
*/
public static boolean hasOnDemandImportConflictWithImports(@NotNull PsiJavaFile javaFile,
@NotNull List<? extends PsiImportStatementBase> importStatements,
@NotNull String fqName) {
return hasOnDemandImportConflictWithImports(javaFile, importStatements, fqName, false) == ThreeState.YES;
@NotNull String fqName,
boolean checkOnDemand,
boolean checkModules) {
return hasOnDemandImportConflictWithImports(javaFile, importStatements, fqName, false, checkOnDemand, checkModules) == ThreeState.YES;
}
private static ThreeState hasOnDemandImportConflictWithImports(@NotNull PsiJavaFile javaFile,
@NotNull List<? extends PsiImportStatementBase> importStatements,
@NotNull String fqName,
boolean strict) {
boolean strict,
boolean checkOnDemand,
boolean checkModules) {
final String shortName = ClassUtil.extractClassName(fqName);
final String packageName = ClassUtil.extractPackageName(fqName);
for (final PsiImportStatementBase importStatement : importStatements) {
if (!importStatement.isOnDemand()) {
continue;
}
if (importStatement instanceof PsiImportModuleStatement moduleStatement) {
if (checkModules && importStatement instanceof PsiImportModuleStatement moduleStatement) {
//can't process, let's assume that we have conflict because it is safe
if (DumbService.isDumb(javaFile.getProject())) return ThreeState.YES;
Ref<Boolean> result = new Ref<>(null);
@@ -303,6 +370,7 @@ public final class ImportUtils {
}, scope, null);
if (result.get() != null) return ThreeState.fromBoolean(result.get());
}
if (!checkOnDemand) continue;
final PsiJavaCodeReferenceElement importReference = importStatement.getImportReference();
if (importReference == null) {
continue;
@@ -434,7 +502,9 @@ public final class ImportUtils {
return false;
}
final PsiImportStaticStatement onDemandImportStatement = findOnDemandImportStaticStatement(importList, qualifierClass);
if (onDemandImportStatement != null && !hasOnDemandImportConflict(qualifierClass + '.' + memberName, javaFile)) {
if (onDemandImportStatement != null &&
//check only on demands
!findOnDemandImportConflict(qualifierClass + '.' + memberName, javaFile, false, true, false)) {
return true;
}
final Project project = context.getProject();
@@ -542,7 +612,7 @@ public final class ImportUtils {
}
final PsiImportStaticStatement onDemandImportStatement = findOnDemandImportStaticStatement(importList, memberClassName);
if (onDemandImportStatement != null) {
if (!hasOnDemandImportConflict(memberClassName + '.' + memberName, javaFile)) {
if (!findOnDemandImportConflict(memberClassName + '.' + memberName, javaFile, false, true, false)) {
return true;
}
}
@@ -593,29 +663,38 @@ public final class ImportUtils {
}
public boolean isImplicitlyImported(String qName, boolean isStatic) {
return isImplicitlyImported(qName, isStatic, null);
}
public boolean isImplicitlyImported(String qName, boolean isStatic,
@Nullable ImportUtils.OnDemandImportConflicts conflicts) {
String packageOrClassName = StringUtil.getPackageName(qName);
String className = ClassUtil.extractClassName(qName);
if (!isStatic) {
for (PsiImportModuleStatement psiImportModuleStatement : myModulesStatements) {
PsiPackageAccessibilityStatement importedPackage = psiImportModuleStatement.findImportedPackage(packageOrClassName);
if (importedPackage == null) continue;
PsiJavaCodeReferenceElement reference = importedPackage.getPackageReference();
if (reference == null) continue;
PsiElement resolved = reference.resolve();
if (resolved instanceof PsiPackage psiPackage) {
if (psiPackage.containsClassNamed(className)) return true;
if(conflicts==null || (!conflicts.conflictForModules() && !conflicts.conflictForOnDemand())){
for (PsiImportModuleStatement psiImportModuleStatement : myModulesStatements) {
PsiPackageAccessibilityStatement importedPackage = psiImportModuleStatement.findImportedPackage(packageOrClassName);
if (importedPackage == null) continue;
PsiJavaCodeReferenceElement reference = importedPackage.getPackageReference();
if (reference == null) continue;
PsiElement resolved = reference.resolve();
if (resolved instanceof PsiPackage psiPackage) {
if (psiPackage.containsClassNamed(className)) return true;
}
}
}
if (myPackageStatements.containsKey(packageOrClassName)) return true;
if ((conflicts == null || !conflicts.conflictForOnDemand()) && myPackageStatements.containsKey(packageOrClassName)) return true;
}
else {
PsiImportStaticStatement psiImportStaticStatement = myStaticImportStatements.get(packageOrClassName);
if (psiImportStaticStatement != null) {
if (psiImportStaticStatement.isOnDemand()) return true;
PsiJavaCodeReferenceElement reference = psiImportStaticStatement.getImportReference();
if (reference == null) return false;
String qualifiedName = reference.getQualifiedName();
return qName.equals(qualifiedName);
if (conflicts == null || !conflicts.conflictForOnDemand()) {
PsiImportStaticStatement psiImportStaticStatement = myStaticImportStatements.get(packageOrClassName);
if (psiImportStaticStatement != null) {
if (psiImportStaticStatement.isOnDemand()) return true;
PsiJavaCodeReferenceElement reference = psiImportStaticStatement.getImportReference();
if (reference == null) return false;
String qualifiedName = reference.getQualifiedName();
return qName.equals(qualifiedName);
}
}
}
return false;