mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-07 22:09:38 +07:00
[java-highlighting] Finish moving module access errors
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 0f89a7cd9609aea2780e132118aae892a6b0bafd
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f976c0b04c
commit
2250bb64ee
@@ -3,11 +3,11 @@ package com.intellij.java.codeserver.core
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.psi.JavaModuleSystem
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFileSystemItem
|
||||
import com.intellij.psi.PsiJavaModule
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.pom.java.JavaFeature
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.light.LightJavaModule
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
|
||||
/**
|
||||
* Represents a JPMS module and the corresponding module in IntelliJ project model
|
||||
@@ -55,4 +55,49 @@ sealed interface JpmsModuleInfo {
|
||||
return JpmsModuleAccessInfo(current, this)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Find module info structures when accessing a given location.
|
||||
*
|
||||
* @param targetPackageName package name which about to be accessed
|
||||
* @param targetFile concrete target file which is about to be accessed; null if not known (in this case,
|
||||
* multiple results could be returned, as multiple source roots may define a given package)
|
||||
* @param place source place from where the access is requested
|
||||
* @return list of TargetModuleInfo structures that describe the possible target; empty list if the target package is empty
|
||||
* (which is generally an error), null if not applicable (e.g., modules are not supported at place;
|
||||
* target does not belong to any module; etc.). In this case, no access problem should be reported.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun findTargetModuleInfos(targetPackageName: String, targetFile: PsiFile?, place: PsiFile): List<TargetModuleInfo>? {
|
||||
val originalTargetFile = targetFile?.originalFile
|
||||
if (!PsiUtil.isAvailable(JavaFeature.MODULES, place)) return null
|
||||
|
||||
val useVFile = place.virtualFile
|
||||
val project = place.project
|
||||
val index = ProjectFileIndex.getInstance(project)
|
||||
if (useVFile != null && index.isInLibrarySource(useVFile)) return null
|
||||
if (originalTargetFile != null && originalTargetFile.isPhysical) {
|
||||
return listOf(TargetModuleInfo(originalTargetFile, targetPackageName))
|
||||
}
|
||||
if (useVFile == null) return null
|
||||
|
||||
val target = JavaPsiFacade.getInstance(project).findPackage(targetPackageName) ?: return null
|
||||
val module = index.getModuleForFile(useVFile) ?: return null
|
||||
val test = index.isInTestSourceContent(useVFile)
|
||||
val moduleScope = module.getModuleWithDependenciesAndLibrariesScope(test)
|
||||
val dirs = target.getDirectories(moduleScope)
|
||||
val packageName = target.qualifiedName
|
||||
if (dirs.isEmpty()) {
|
||||
return if (target.getFiles(moduleScope).isEmpty()) {
|
||||
listOf()
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return dirs.map { dir -> TargetModuleInfo(dir, packageName) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,6 +303,7 @@ reference.class.in.default.package=Class ''{0}'' is in the default package
|
||||
reference.non.static.from.static.context=Non-static {0} ''{1}'' cannot be referenced from a static context
|
||||
reference.outer.type.parameter.from.static.context=''{0}'' cannot be referenced from a static context
|
||||
reference.select.from.type.parameter=Cannot select from a type parameter
|
||||
reference.package.not.found=Package not found: {0}
|
||||
|
||||
statement.case.outside.switch=Case statement outside switch
|
||||
statement.invalid=Invalid statement
|
||||
|
||||
@@ -923,6 +923,9 @@ final class ExpressionChecker {
|
||||
if (!result.isStaticsScopeCorrect()) {
|
||||
myVisitor.report(JavaErrorKinds.REFERENCE_NON_STATIC_FROM_STATIC_CONTEXT.create(ref, resolved));
|
||||
}
|
||||
if (resolved instanceof PsiModifierListOwner owner) {
|
||||
myVisitor.myModuleChecker.checkModuleAccess(owner, ref);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkUnresolvedReference(@NotNull PsiJavaCodeReferenceElement ref, @NotNull JavaResolveResult result) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErro
|
||||
import com.intellij.lang.jvm.JvmModifier;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.IncompleteModelUtil;
|
||||
import com.intellij.psi.impl.PsiClassImplUtil;
|
||||
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
|
||||
import com.intellij.psi.infos.MethodCandidateInfo;
|
||||
import com.intellij.psi.util.*;
|
||||
@@ -389,4 +390,51 @@ final class FunctionChecker {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 15.13 | 15.27
|
||||
// It is a compile-time error if any class or interface mentioned by either U or the function type of U
|
||||
// is not accessible from the class or interface in which the method reference expression appears.
|
||||
void checkFunctionalInterfaceTypeAccessible(@NotNull PsiFunctionalExpression expression,
|
||||
@NotNull PsiType functionalInterfaceType) {
|
||||
checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType, true);
|
||||
}
|
||||
|
||||
private void checkFunctionalInterfaceTypeAccessible(@NotNull PsiFunctionalExpression expression,
|
||||
@NotNull PsiType functionalInterfaceType,
|
||||
boolean checkFunctionalTypeSignature) {
|
||||
PsiClassType.ClassResolveResult resolveResult =
|
||||
PsiUtil.resolveGenericsClassInType(PsiClassImplUtil.correctType(functionalInterfaceType, expression.getResolveScope()));
|
||||
PsiClass psiClass = resolveResult.getElement();
|
||||
if (psiClass == null) return;
|
||||
if (!PsiUtil.isAccessible(myVisitor.project(), psiClass, expression, null)) {
|
||||
myVisitor.myModifierChecker.reportAccessProblem(expression, psiClass, resolveResult);
|
||||
return;
|
||||
}
|
||||
for (PsiType type : resolveResult.getSubstitutor().getSubstitutionMap().values()) {
|
||||
if (type != null) {
|
||||
checkFunctionalInterfaceTypeAccessible(expression, type, false);
|
||||
if (myVisitor.hasErrorResults()) return;
|
||||
}
|
||||
}
|
||||
|
||||
PsiMethod psiMethod = checkFunctionalTypeSignature ? LambdaUtil.getFunctionalInterfaceMethod(resolveResult) : null;
|
||||
if (psiMethod != null) {
|
||||
PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(psiMethod, resolveResult);
|
||||
for (PsiParameter parameter : psiMethod.getParameterList().getParameters()) {
|
||||
PsiType substitute = substitutor.substitute(parameter.getType());
|
||||
if (substitute != null) {
|
||||
checkFunctionalInterfaceTypeAccessible(expression, substitute, false);
|
||||
if (myVisitor.hasErrorResults()) return;
|
||||
}
|
||||
}
|
||||
|
||||
PsiType substitute = substitutor.substitute(psiMethod.getReturnType());
|
||||
if (substitute != null) {
|
||||
checkFunctionalInterfaceTypeAccessible(expression, substitute, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
myVisitor.myModuleChecker.checkModuleAccess(psiClass, expression);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
final @NotNull ControlFlowChecker myControlFlowChecker = new ControlFlowChecker(this);
|
||||
private final @NotNull FunctionChecker myFunctionChecker = new FunctionChecker(this);
|
||||
final @NotNull PatternChecker myPatternChecker = new PatternChecker(this);
|
||||
private final @NotNull ModuleChecker myModuleChecker = new ModuleChecker(this);
|
||||
final @NotNull ModuleChecker myModuleChecker = new ModuleChecker(this);
|
||||
final @NotNull ModifierChecker myModifierChecker = new ModifierChecker(this);
|
||||
final @NotNull ExpressionChecker myExpressionChecker = new ExpressionChecker(this);
|
||||
private final @NotNull SwitchChecker mySwitchChecker = new SwitchChecker(this);
|
||||
@@ -426,6 +426,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
report(JavaErrorKinds.LAMBDA_NOT_FUNCTIONAL_INTERFACE.create(expression, functionalInterfaceType));
|
||||
}
|
||||
if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceContext(expression, functionalInterfaceType);
|
||||
if (!hasErrorResults()) myFunctionChecker.checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType);
|
||||
}
|
||||
if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceResolve(expression, results, functionalInterfaceType);
|
||||
if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceReturnType(expression, result, functionalInterfaceType);
|
||||
@@ -472,7 +473,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
else if (parent instanceof PsiClass aClass) {
|
||||
if (!hasErrorResults()) myClassChecker.checkDuplicateNestedClass(aClass);
|
||||
if (!hasErrorResults() && !(aClass instanceof PsiAnonymousClass)) {
|
||||
/* anonymous class is highlighted in HighlightClassUtil.checkAbstractInstantiation()*/
|
||||
/* an anonymous class is highlighted in HighlightClassUtil.checkAbstractInstantiation()*/
|
||||
myClassChecker.checkClassMustBeAbstract(aClass);
|
||||
}
|
||||
if (!hasErrorResults()) {
|
||||
@@ -933,6 +934,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
if (functionalInterfaceType != null) {
|
||||
myFunctionChecker.checkExtendsSealedClass(expression, functionalInterfaceType);
|
||||
if (!hasErrorResults()) myFunctionChecker.checkInterfaceFunctional(expression, functionalInterfaceType);
|
||||
if (!hasErrorResults()) myFunctionChecker.checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType);
|
||||
}
|
||||
else if (LambdaUtil.getFunctionalInterfaceType(expression, true) != null) {
|
||||
report(JavaErrorKinds.LAMBDA_TYPE_INFERENCE_FAILURE.create(expression));
|
||||
|
||||
@@ -178,9 +178,7 @@ final class ModifierChecker {
|
||||
}
|
||||
}
|
||||
|
||||
void reportAccessProblem(@NotNull PsiJavaCodeReferenceElement ref,
|
||||
@NotNull PsiModifierListOwner resolved,
|
||||
@NotNull JavaResolveResult result) {
|
||||
void reportAccessProblem(@NotNull PsiElement ref, @NotNull PsiModifierListOwner resolved, @NotNull JavaResolveResult result) {
|
||||
result = withElement(result, resolved);
|
||||
if (resolved.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
myVisitor.report(JavaErrorKinds.ACCESS_PRIVATE.create(ref, result));
|
||||
@@ -202,15 +200,11 @@ final class ModifierChecker {
|
||||
return;
|
||||
}
|
||||
|
||||
checkModuleAccess(resolved, ref);
|
||||
myVisitor.myModuleChecker.checkModuleAccess(resolved, ref);
|
||||
if (myVisitor.hasErrorResults()) return;
|
||||
myVisitor.report(JavaErrorKinds.ACCESS_GENERIC_PROBLEM.create(ref, result));
|
||||
}
|
||||
|
||||
private void checkModuleAccess(@NotNull PsiModifierListOwner resolved, @NotNull PsiElement ref) {
|
||||
// TODO: JPMS
|
||||
}
|
||||
|
||||
private static @NotNull JavaResolveResult withElement(@NotNull JavaResolveResult original, @NotNull PsiElement newElement) {
|
||||
if (newElement == original.getElement()) return original;
|
||||
return new JavaResolveResult() {
|
||||
|
||||
@@ -4,7 +4,9 @@ package com.intellij.java.codeserver.highlighting;
|
||||
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
|
||||
import com.intellij.java.codeserver.core.JavaServiceProviderUtil;
|
||||
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo;
|
||||
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo.JpmsModuleAccessProblem;
|
||||
import com.intellij.java.codeserver.core.JpmsModuleInfo;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
|
||||
import com.intellij.openapi.module.Module;
|
||||
@@ -24,6 +26,7 @@ import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -299,7 +302,7 @@ final class ModuleChecker {
|
||||
myVisitor.report(kind.create(statement));
|
||||
}
|
||||
|
||||
JavaErrorKind.Parameterized<PsiElement, JpmsModuleAccessInfo> accessError(@NotNull JpmsModuleAccessInfo.JpmsModuleAccessProblem problem) {
|
||||
JavaErrorKind.Parameterized<PsiElement, JpmsModuleAccessInfo> accessError(@NotNull JpmsModuleAccessProblem problem) {
|
||||
return switch (problem) {
|
||||
case FROM_NAMED -> JavaErrorKinds.MODULE_ACCESS_FROM_NAMED;
|
||||
case FROM_UNNAMED -> JavaErrorKinds.MODULE_ACCESS_FROM_UNNAMED;
|
||||
@@ -325,10 +328,51 @@ final class ModuleChecker {
|
||||
return;
|
||||
}
|
||||
JpmsModuleAccessInfo moduleAccess = new JpmsModuleInfo.TargetModuleInfo(target, "").accessAt(myVisitor.file().getOriginalFile());
|
||||
JpmsModuleAccessInfo.JpmsModuleAccessProblem problem = moduleAccess.checkModuleAccess(statement);
|
||||
JpmsModuleAccessProblem problem = moduleAccess.checkModuleAccess(statement);
|
||||
if (problem != null) {
|
||||
myVisitor.report(accessError(problem).create(statement, moduleAccess));
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull PsiElement findPackagePrefix(@NotNull PsiJavaCodeReferenceElement ref) {
|
||||
PsiElement candidate = ref;
|
||||
while (candidate instanceof PsiJavaCodeReferenceElement element) {
|
||||
if (element.resolve() instanceof PsiPackage) return candidate;
|
||||
candidate = element.getQualifier();
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
void checkModuleAccess(@NotNull PsiModifierListOwner target, @NotNull PsiElement ref) {
|
||||
if (target instanceof PsiClass targetClass && !(target instanceof PsiTypeParameter)) {
|
||||
String packageName = PsiUtil.getPackageName(targetClass);
|
||||
if (packageName != null) {
|
||||
checkAccess(packageName, target.getContainingFile(), ref);
|
||||
}
|
||||
}
|
||||
else if (target instanceof PsiPackage targetPackage) {
|
||||
checkAccess(targetPackage.getQualifiedName(), null, ref);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAccess(@NotNull String targetPackageName, @Nullable PsiFile targetFile, @NotNull PsiElement place) {
|
||||
PsiFile file = myVisitor.file().getOriginalFile();
|
||||
List<JpmsModuleInfo.@NotNull TargetModuleInfo> infos = JpmsModuleInfo.findTargetModuleInfos(targetPackageName, targetFile, file);
|
||||
if (infos == null) return;
|
||||
if (infos.isEmpty()) {
|
||||
myVisitor.report(JavaErrorKinds.REFERENCE_PACKAGE_NOT_FOUND.create(place, targetPackageName));
|
||||
return;
|
||||
}
|
||||
JavaCompilationError<PsiElement, JpmsModuleAccessInfo> error = null;
|
||||
for (JpmsModuleInfo.TargetModuleInfo info : infos) {
|
||||
JpmsModuleAccessInfo moduleAccessInfo = info.accessAt(file);
|
||||
JpmsModuleAccessProblem problem = moduleAccessInfo.checkAccess(file, JpmsModuleAccessInfo.JpmsModuleAccessMode.READ);
|
||||
if (problem == null) return;
|
||||
if (error == null) {
|
||||
PsiElement anchor = place instanceof PsiJavaCodeReferenceElement ref ? findPackagePrefix(ref) : place;
|
||||
error = accessError(problem).create(anchor, moduleAccessInfo);
|
||||
}
|
||||
}
|
||||
myVisitor.report(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,8 +117,8 @@ final class JavaErrorFormatUtil {
|
||||
return nameElement.getTextRangeInParent();
|
||||
}
|
||||
}
|
||||
if (element instanceof PsiReferenceExpression refExpression) {
|
||||
PsiElement nameElement = refExpression.getReferenceNameElement();
|
||||
if (element instanceof PsiJavaCodeReferenceElement ref) {
|
||||
PsiElement nameElement = ref.getReferenceNameElement();
|
||||
if (nameElement != null) {
|
||||
return nameElement.getTextRangeInParent();
|
||||
}
|
||||
|
||||
@@ -1157,6 +1157,9 @@ public final class JavaErrorKinds {
|
||||
.withRawDescription((ref, refElement) -> message("reference.outer.type.parameter.from.static.context", refElement.getName()));
|
||||
public static final Simple<PsiJavaCodeReferenceElement> REFERENCE_SELECT_FROM_TYPE_PARAMETER =
|
||||
error(PsiJavaCodeReferenceElement.class, "reference.select.from.type.parameter");
|
||||
public static final Parameterized<PsiElement, String> REFERENCE_PACKAGE_NOT_FOUND =
|
||||
parameterized(PsiElement.class, String.class, "reference.package.not.found")
|
||||
.withRawDescription((psi, packageName) -> message("reference.package.not.found", packageName));
|
||||
|
||||
public static final Simple<PsiSwitchLabelStatementBase> STATEMENT_CASE_OUTSIDE_SWITCH = error("statement.case.outside.switch");
|
||||
public static final Simple<PsiStatement> STATEMENT_INVALID = error("statement.invalid");
|
||||
@@ -1307,23 +1310,23 @@ public final class JavaErrorKinds {
|
||||
parameterized(PsiExpression.class, PsiType.class, "string.template.raw.processor")
|
||||
.withRawDescription((psi, type) -> message("string.template.raw.processor", type.getPresentableText()));
|
||||
|
||||
public static final Parameterized<PsiJavaCodeReferenceElement, JavaResolveResult> ACCESS_PRIVATE =
|
||||
parameterized(PsiJavaCodeReferenceElement.class, JavaResolveResult.class, "access.private")
|
||||
.withAnchor(psi -> requireNonNullElse(psi.getReferenceNameElement(), psi))
|
||||
public static final Parameterized<PsiElement, JavaResolveResult> ACCESS_PRIVATE =
|
||||
parameterized(PsiElement.class, JavaResolveResult.class, "access.private")
|
||||
.withRange((psi, cls) -> getRange(psi))
|
||||
.withRawDescription((psi, result) -> message("access.private", formatResolvedSymbol(result), formatResolvedSymbolContainer(result)));
|
||||
public static final Parameterized<PsiJavaCodeReferenceElement, JavaResolveResult> ACCESS_PROTECTED =
|
||||
parameterized(PsiJavaCodeReferenceElement.class, JavaResolveResult.class, "access.protected")
|
||||
.withAnchor(psi -> requireNonNullElse(psi.getReferenceNameElement(), psi))
|
||||
public static final Parameterized<PsiElement, JavaResolveResult> ACCESS_PROTECTED =
|
||||
parameterized(PsiElement.class, JavaResolveResult.class, "access.protected")
|
||||
.withRange((psi, cls) -> getRange(psi))
|
||||
.withRawDescription(
|
||||
(psi, result) -> message("access.protected", formatResolvedSymbol(result), formatResolvedSymbolContainer(result)));
|
||||
public static final Parameterized<PsiJavaCodeReferenceElement, JavaResolveResult> ACCESS_PACKAGE_LOCAL =
|
||||
parameterized(PsiJavaCodeReferenceElement.class, JavaResolveResult.class, "access.package.local")
|
||||
.withAnchor(psi -> requireNonNullElse(psi.getReferenceNameElement(), psi))
|
||||
public static final Parameterized<PsiElement, JavaResolveResult> ACCESS_PACKAGE_LOCAL =
|
||||
parameterized(PsiElement.class, JavaResolveResult.class, "access.package.local")
|
||||
.withRange((psi, cls) -> getRange(psi))
|
||||
.withRawDescription(
|
||||
(psi, result) -> message("access.package.local", formatResolvedSymbol(result), formatResolvedSymbolContainer(result)));
|
||||
public static final Parameterized<PsiJavaCodeReferenceElement, JavaResolveResult> ACCESS_GENERIC_PROBLEM =
|
||||
parameterized(PsiJavaCodeReferenceElement.class, JavaResolveResult.class, "access.generic.problem")
|
||||
.withAnchor(psi -> requireNonNullElse(psi.getReferenceNameElement(), psi))
|
||||
public static final Parameterized<PsiElement, JavaResolveResult> ACCESS_GENERIC_PROBLEM =
|
||||
parameterized(PsiElement.class, JavaResolveResult.class, "access.generic.problem")
|
||||
.withRange((psi, cls) -> getRange(psi))
|
||||
.withRawDescription(
|
||||
(psi, result) -> message("access.generic.problem", formatResolvedSymbol(result), formatResolvedSymbolContainer(result)));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user