mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] checkAbstractInstantiation, checkExtendsDuplicate, checkClassExtendsForeignInnerClass -> ClassChecker
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 2a8fd18d616644166ac14c31f1abdbf10c074c8e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7103151e05
commit
6035671a2a
@@ -57,3 +57,11 @@ receiver.name.mismatch=The receiver name does not match the enclosing class type
|
||||
|
||||
override.on.static.method=Static methods cannot be annotated with @Override
|
||||
override.on.non-overriding.method=Method does not override method from its superclass
|
||||
|
||||
class.must.implement.method=Class ''{0}'' must implement abstract method ''{1}'' in ''{2}''
|
||||
class.must.implement.method.or.abstract=Class ''{0}'' must either be declared abstract or implement abstract method ''{1}'' in ''{2}''
|
||||
class.must.implement.method.enum.constant=Enum constant ''{0}'' must implement abstract method ''{1}'' in ''{2}''
|
||||
class.reference.list.duplicate=Duplicate reference to ''{0}'' in ''{1}'' list
|
||||
class.reference.list.name.expected=Class name expected
|
||||
class.reference.list.inner.private=''{0}'' has private access in ''{1}''
|
||||
class.reference.list.no.enclosing.instance=No enclosing instance of type ''{0}'' is in scope
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeserver.highlighting;
|
||||
|
||||
import com.intellij.codeInsight.ClassUtil;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.JavaPsiConstructorUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
final class ClassChecker {
|
||||
private final @NotNull JavaErrorVisitor myVisitor;
|
||||
|
||||
ClassChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; }
|
||||
|
||||
/**
|
||||
* new ref(...) or new ref(...) { ... } where ref is abstract class
|
||||
*/
|
||||
void checkAbstractInstantiation(@NotNull PsiJavaCodeReferenceElement ref) {
|
||||
PsiElement parent = ref.getParent();
|
||||
if (parent instanceof PsiAnonymousClass aClass
|
||||
&& parent.getParent() instanceof PsiNewExpression
|
||||
&& !PsiUtilCore.hasErrorElementChild(parent.getParent())) {
|
||||
checkClassWithAbstractMethods(aClass, aClass, ref.getTextRange());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkClassWithAbstractMethods(@NotNull PsiClass aClass, @NotNull PsiMember implementsFixElement, @NotNull TextRange range) {
|
||||
PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass);
|
||||
if (abstractMethod == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiClass containingClass = abstractMethod.getContainingClass();
|
||||
if (containingClass == null ||
|
||||
containingClass == aClass ||
|
||||
implementsFixElement instanceof PsiEnumConstant && !hasEnumConstantsWithInitializer(aClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
myVisitor.report(JavaErrorKinds.CLASS_NO_ABSTRACT_METHOD.create(implementsFixElement, abstractMethod));
|
||||
}
|
||||
|
||||
void checkExtendsDuplicate(@NotNull PsiJavaCodeReferenceElement element, PsiElement resolved) {
|
||||
if (!(element.getParent() instanceof PsiReferenceList list)) return;
|
||||
if (!(list.getParent() instanceof PsiClass)) return;
|
||||
if (!(resolved instanceof PsiClass aClass)) return;
|
||||
PsiManager manager = myVisitor.file().getManager();
|
||||
PsiJavaCodeReferenceElement sibling = PsiTreeUtil.getPrevSiblingOfType(element, PsiJavaCodeReferenceElement.class);
|
||||
while (true) {
|
||||
if (sibling == null) return;
|
||||
PsiElement target = sibling.resolve();
|
||||
if (manager.areElementsEquivalent(target, aClass)) break;
|
||||
sibling = PsiTreeUtil.getPrevSiblingOfType(sibling, PsiJavaCodeReferenceElement.class);
|
||||
}
|
||||
myVisitor.report(JavaErrorKinds.CLASS_REFERENCE_LIST_DUPLICATE.create(element, aClass));
|
||||
}
|
||||
|
||||
void checkClassExtendsForeignInnerClass(@NotNull PsiJavaCodeReferenceElement extendRef, @Nullable PsiElement resolved) {
|
||||
PsiElement parent = extendRef.getParent();
|
||||
if (!(parent instanceof PsiReferenceList)) return;
|
||||
PsiElement grand = parent.getParent();
|
||||
if (!(grand instanceof PsiClass aClass)) return;
|
||||
PsiClass containerClass;
|
||||
if (aClass instanceof PsiTypeParameter typeParameter) {
|
||||
if (!(typeParameter.getOwner() instanceof PsiClass cls)) return;
|
||||
containerClass = cls;
|
||||
}
|
||||
else {
|
||||
containerClass = aClass;
|
||||
}
|
||||
if (aClass.getExtendsList() != parent && aClass.getImplementsList() != parent) return;
|
||||
if (resolved != null && !(resolved instanceof PsiClass)) {
|
||||
myVisitor.report(JavaErrorKinds.CLASS_REFERENCE_LIST_NAME_EXPECTED.create(extendRef));
|
||||
return;
|
||||
}
|
||||
extendRef.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement reference) {
|
||||
super.visitReferenceElement(reference);
|
||||
PsiElement resolve = reference.resolve();
|
||||
if (resolve instanceof PsiClass base) {
|
||||
PsiClass baseClass = base.getContainingClass();
|
||||
if (baseClass != null &&
|
||||
base.hasModifierProperty(PsiModifier.PRIVATE) &&
|
||||
baseClass == containerClass &&
|
||||
baseClass.getContainingClass() == null) {
|
||||
myVisitor.report(JavaErrorKinds.CLASS_REFERENCE_LIST_INNER_PRIVATE.create(reference, base));
|
||||
stopWalking();
|
||||
return;
|
||||
}
|
||||
|
||||
// must be inner class
|
||||
if (!PsiUtil.isInnerClass(base)) return;
|
||||
|
||||
if (resolve == resolved &&
|
||||
baseClass != null &&
|
||||
(!PsiTreeUtil.isAncestor(baseClass, extendRef, true) || aClass.hasModifierProperty(PsiModifier.STATIC)) &&
|
||||
!InheritanceUtil.hasEnclosingInstanceInScope(baseClass, extendRef, psiClass -> psiClass != aClass, true) &&
|
||||
!qualifiedNewCalledInConstructors(aClass)) {
|
||||
myVisitor.report(JavaErrorKinds.CLASS_REFERENCE_LIST_NO_ENCLOSING_INSTANCE.create(extendRef, baseClass));
|
||||
stopWalking();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 15.9 Class Instance Creation Expressions | 15.9.2 Determining Enclosing Instances
|
||||
*/
|
||||
private static boolean qualifiedNewCalledInConstructors(@NotNull PsiClass aClass) {
|
||||
PsiMethod[] constructors = aClass.getConstructors();
|
||||
if (constructors.length == 0) return false;
|
||||
for (PsiMethod constructor : constructors) {
|
||||
PsiMethodCallExpression methodCallExpression = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor);
|
||||
if (methodCallExpression == null) return false;
|
||||
if (JavaPsiConstructorUtil.isChainedConstructorCall(methodCallExpression)) continue;
|
||||
PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression();
|
||||
PsiExpression qualifierExpression = PsiUtil.skipParenthesizedExprDown(referenceExpression.getQualifierExpression());
|
||||
//If the class instance creation expression is qualified, then the immediately
|
||||
//enclosing instance of i is the object that is the value of the Primary expression or the ExpressionName,
|
||||
//otherwise aClass needs to be a member of a class enclosing the class in which the class instance creation expression appears
|
||||
//already excluded by InheritanceUtil.hasEnclosingInstanceInScope
|
||||
if (qualifierExpression == null) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasEnumConstantsWithInitializer(@NotNull PsiClass aClass) {
|
||||
return CachedValuesManager.getCachedValue(aClass, () -> {
|
||||
PsiField[] fields = aClass.getFields();
|
||||
for (PsiField field : fields) {
|
||||
if (field instanceof PsiEnumConstant constant && constant.getInitializingClass() != null) {
|
||||
return new CachedValueProvider.Result<>(true, PsiModificationTracker.MODIFICATION_COUNT);
|
||||
}
|
||||
}
|
||||
return new CachedValueProvider.Result<>(false, PsiModificationTracker.MODIFICATION_COUNT);
|
||||
});
|
||||
}
|
||||
}
|
||||
+40
-1
@@ -3,13 +3,17 @@ package com.intellij.java.codeserver.highlighting;
|
||||
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
|
||||
import com.intellij.openapi.project.IndexNotReadyException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
|
||||
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -23,6 +27,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
private final @NotNull PsiFile myFile;
|
||||
private final @NotNull LanguageLevel myLanguageLevel;
|
||||
private final @NotNull AnnotationChecker myAnnotationChecker = new AnnotationChecker(this);
|
||||
private final @NotNull ClassChecker myClassChecker = new ClassChecker(this);
|
||||
private final @NotNull MethodChecker myMethodChecker = new MethodChecker(this);
|
||||
private final @NotNull ReceiverChecker myReceiverChecker = new ReceiverChecker(this);
|
||||
private boolean myHasError; // true if myHolder.add() was called with HighlightInfo of >=ERROR severity. On each .visit(PsiElement) call this flag is reset. Useful to determine whether the error was already reported while visiting this PsiElement.
|
||||
@@ -86,13 +91,47 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
|
||||
super.visitReferenceExpression(expression);
|
||||
JavaResolveResult resultForIncompleteCode = doVisitReferenceElement(expression);
|
||||
if (!hasErrorResults()) {
|
||||
visitExpression(expression);
|
||||
if (hasErrorResults()) return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) {
|
||||
JavaResolveResult result = ref instanceof PsiExpression ? resolveOptimised(ref, myFile) : doVisitReferenceElement(ref);
|
||||
if (result != null) {
|
||||
PsiElement resolved = result.getElement();
|
||||
}
|
||||
}
|
||||
|
||||
private JavaResolveResult doVisitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) {
|
||||
JavaResolveResult result = resolveOptimised(ref, myFile);
|
||||
if (result == null) return null;
|
||||
|
||||
PsiElement resolved = result.getElement();
|
||||
|
||||
if (!hasErrorResults()) myClassChecker.checkAbstractInstantiation(ref);
|
||||
if (!hasErrorResults()) myClassChecker.checkExtendsDuplicate(ref, resolved);
|
||||
if (!hasErrorResults()) myClassChecker.checkClassExtendsForeignInnerClass(ref, resolved);
|
||||
return result;
|
||||
}
|
||||
|
||||
static @Nullable JavaResolveResult resolveOptimised(@NotNull PsiJavaCodeReferenceElement ref, @NotNull PsiFile containingFile) {
|
||||
try {
|
||||
if (ref instanceof PsiReferenceExpressionImpl) {
|
||||
PsiReferenceExpressionImpl.OurGenericsResolver resolver = PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE;
|
||||
JavaResolveResult[] results = JavaResolveUtil.resolveWithContainingFile(ref, resolver, true, true, containingFile);
|
||||
return results.length == 1 ? results[0] : JavaResolveResult.EMPTY;
|
||||
}
|
||||
return ref.advancedResolve(true);
|
||||
}
|
||||
catch (IndexNotReadyException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceList(@NotNull PsiReferenceList list) {
|
||||
super.visitReferenceList(list);
|
||||
|
||||
+26
@@ -38,6 +38,32 @@ final class JavaErrorFormatUtil {
|
||||
return new TextRange(start, end).shiftLeft(method.getTextRange().getStartOffset());
|
||||
}
|
||||
|
||||
static @NotNull TextRange getFieldDeclarationTextRange(@NotNull PsiField field) {
|
||||
PsiModifierList modifierList = field.getModifierList();
|
||||
TextRange range = field.getTextRange();
|
||||
int start = modifierList == null ? range.getStartOffset() : stripAnnotationsFromModifierList(modifierList);
|
||||
int end = field.getNameIdentifier().getTextRange().getEndOffset();
|
||||
return new TextRange(start, end).shiftLeft(range.getStartOffset());
|
||||
}
|
||||
|
||||
static @NotNull TextRange getClassDeclarationTextRange(@NotNull PsiClass aClass) {
|
||||
if (aClass instanceof PsiEnumConstantInitializer) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
PsiElement psiElement = aClass instanceof PsiAnonymousClass anonymousClass
|
||||
? anonymousClass.getBaseClassReference()
|
||||
: aClass.getModifierList() == null ? aClass.getNameIdentifier() : aClass.getModifierList();
|
||||
if(psiElement == null) return new TextRange(0, 0);
|
||||
int start = stripAnnotationsFromModifierList(psiElement);
|
||||
PsiElement endElement = aClass instanceof PsiAnonymousClass anonymousClass ?
|
||||
anonymousClass.getBaseClassReference() :
|
||||
aClass.getImplementsList();
|
||||
if (endElement == null) endElement = aClass.getNameIdentifier();
|
||||
TextRange endTextRange = endElement == null ? null : endElement.getTextRange();
|
||||
int end = endTextRange == null ? start : endTextRange.getEndOffset();
|
||||
return new TextRange(start, end).shiftLeft(aClass.getTextRange().getStartOffset());
|
||||
}
|
||||
|
||||
private static int stripAnnotationsFromModifierList(@NotNull PsiElement element) {
|
||||
TextRange textRange = element.getTextRange();
|
||||
if (textRange == null) return 0;
|
||||
|
||||
+39
@@ -3,6 +3,7 @@ package com.intellij.java.codeserver.highlighting.errors;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationTargetUtil;
|
||||
import com.intellij.core.JavaPsiBundle;
|
||||
import com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind.Parameterized;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind.Simple;
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion;
|
||||
@@ -13,6 +14,7 @@ import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -176,6 +178,43 @@ public final class JavaErrorKinds {
|
||||
.withAnchor(parameter -> requireNonNullElse(parameter.getTypeElement(), parameter)).withContext();
|
||||
public static final Parameterized<PsiReceiverParameter, @Nullable String> RECEIVER_NAME_MISMATCH =
|
||||
error(PsiReceiverParameter.class, "receiver.name.mismatch").withAnchor(PsiReceiverParameter::getIdentifier).withContext();
|
||||
// PsiMember = PsiClass | PsiEnumConstant
|
||||
public static final Parameterized<PsiMember, PsiMethod> CLASS_NO_ABSTRACT_METHOD =
|
||||
error(PsiMember.class, "class.must.implement.method")
|
||||
.withRange(member ->
|
||||
member instanceof PsiEnumConstant enumConstant ? enumConstant.getNameIdentifier().getTextRange() :
|
||||
member instanceof PsiClass aClass ? JavaErrorFormatUtil.getClassDeclarationTextRange(aClass) : null)
|
||||
.<PsiMethod>withContext()
|
||||
.withRawDescription((member, abstractMethod) -> {
|
||||
PsiClass aClass = member instanceof PsiClass cls ? cls : requireNonNull(member.getContainingClass());
|
||||
@PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String messageKey;
|
||||
String referenceName;
|
||||
if (aClass instanceof PsiEnumConstantInitializer enumConstant) {
|
||||
messageKey = "class.must.implement.method.enum.constant";
|
||||
referenceName = enumConstant.getEnumConstant().getName();
|
||||
}
|
||||
else {
|
||||
messageKey = aClass.isEnum() || aClass.isRecord() || aClass instanceof PsiAnonymousClass
|
||||
? "class.must.implement.method"
|
||||
: "class.must.implement.method.or.abstract";
|
||||
referenceName = formatClass(aClass, false);
|
||||
}
|
||||
return message(messageKey, referenceName, formatMethod(abstractMethod),
|
||||
formatClass(requireNonNull(abstractMethod.getContainingClass()), false));
|
||||
});
|
||||
public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_REFERENCE_LIST_DUPLICATE =
|
||||
parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.duplicate")
|
||||
.withRawDescription(
|
||||
(ref, target) -> message("class.reference.list.duplicate", formatClass(target), ref.getParent().getFirstChild().getText()));
|
||||
public static final Simple<PsiJavaCodeReferenceElement> CLASS_REFERENCE_LIST_NAME_EXPECTED =
|
||||
error("class.reference.list.name.expected");
|
||||
public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_REFERENCE_LIST_INNER_PRIVATE =
|
||||
parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.inner.private")
|
||||
.withRawDescription((ref, target) -> message("class.reference.list.inner.private",
|
||||
formatClass(target), formatClass(requireNonNull(target.getContainingClass()))));
|
||||
public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_REFERENCE_LIST_NO_ENCLOSING_INSTANCE =
|
||||
parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.no.enclosing.instance")
|
||||
.withRawDescription((ref, target) -> message("class.reference.list.no.enclosing.instance", formatClass(target)));
|
||||
|
||||
private static @NotNull <Psi extends PsiElement> Simple<Psi> error(@NotNull String key) {
|
||||
return new Simple<>(key);
|
||||
|
||||
-136
@@ -55,18 +55,6 @@ import java.util.function.Consumer;
|
||||
* Generates HighlightInfoType.ERROR-only HighlightInfos at PsiClass level.
|
||||
*/
|
||||
public final class HighlightClassUtil {
|
||||
/**
|
||||
* new ref(...) or new ref(...) { ... } where ref is abstract class
|
||||
*/
|
||||
static HighlightInfo.Builder checkAbstractInstantiation(@NotNull PsiJavaCodeReferenceElement ref) {
|
||||
PsiElement parent = ref.getParent();
|
||||
if (parent instanceof PsiAnonymousClass aClass
|
||||
&& parent.getParent() instanceof PsiNewExpression
|
||||
&& !PsiUtilCore.hasErrorElementChild(parent.getParent())) {
|
||||
return checkClassWithAbstractMethods(aClass, aClass, ref.getTextRange());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkClassWithAbstractMethods(@NotNull PsiClass aClass, @NotNull PsiElement implementsFixElement, @NotNull TextRange range) {
|
||||
PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass);
|
||||
@@ -739,29 +727,6 @@ public final class HighlightClassUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkExtendsDuplicate(@NotNull PsiJavaCodeReferenceElement element,
|
||||
@Nullable PsiElement resolved,
|
||||
@NotNull PsiFile containingFile) {
|
||||
if (!(element.getParent() instanceof PsiReferenceList list)) return null;
|
||||
if (!(list.getParent() instanceof PsiClass)) return null;
|
||||
if (!(resolved instanceof PsiClass aClass)) return null;
|
||||
PsiManager manager = containingFile.getManager();
|
||||
PsiJavaCodeReferenceElement sibling = PsiTreeUtil.getPrevSiblingOfType(element, PsiJavaCodeReferenceElement.class);
|
||||
while (true) {
|
||||
if (sibling == null) return null;
|
||||
PsiElement target = sibling.resolve();
|
||||
if (manager.areElementsEquivalent(target, aClass)) break;
|
||||
sibling = PsiTreeUtil.getPrevSiblingOfType(sibling, PsiJavaCodeReferenceElement.class);
|
||||
}
|
||||
String name = HighlightUtil.formatClass(aClass);
|
||||
String description = JavaErrorBundle.message("duplicate.reference.in.list", name, list.getFirstChild().getText());
|
||||
HighlightInfo.Builder info =
|
||||
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description);
|
||||
IntentionAction action = QuickFixFactory.getInstance().createRemoveDuplicateExtendsAction(name);
|
||||
info.registerFix(action, null, null, null, null);
|
||||
return info;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkClassAlreadyImported(@NotNull PsiClass aClass, @NotNull PsiElement elementToHighlight) {
|
||||
PsiFile file = aClass.getContainingFile();
|
||||
if (!(file instanceof PsiJavaFile javaFile)) return null;
|
||||
@@ -861,107 +826,6 @@ public final class HighlightClassUtil {
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* class c extends foreign.inner {}
|
||||
*
|
||||
* @param extendRef points to the class in the extends list
|
||||
* @param resolved extendRef resolved
|
||||
*/
|
||||
static HighlightInfo.Builder checkClassExtendsForeignInnerClass(@NotNull PsiJavaCodeReferenceElement extendRef, @Nullable PsiElement resolved) {
|
||||
PsiElement parent = extendRef.getParent();
|
||||
if (!(parent instanceof PsiReferenceList)) {
|
||||
return null;
|
||||
}
|
||||
PsiElement grand = parent.getParent();
|
||||
if (!(grand instanceof PsiClass aClass)) {
|
||||
return null;
|
||||
}
|
||||
PsiClass containerClass;
|
||||
if (aClass instanceof PsiTypeParameter typeParameter) {
|
||||
if (!(typeParameter.getOwner() instanceof PsiClass cls)) {
|
||||
return null;
|
||||
}
|
||||
containerClass = cls;
|
||||
}
|
||||
else {
|
||||
containerClass = aClass;
|
||||
}
|
||||
if (aClass.getExtendsList() != parent && aClass.getImplementsList() != parent) {
|
||||
return null;
|
||||
}
|
||||
if (resolved != null && !(resolved instanceof PsiClass)) {
|
||||
String description = JavaErrorBundle.message("class.name.expected");
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef).descriptionAndTooltip(description);
|
||||
}
|
||||
HighlightInfo.Builder[] infos = new HighlightInfo.Builder[1];
|
||||
extendRef.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitElement(@NotNull PsiElement element) {
|
||||
if (infos[0] != null) return;
|
||||
super.visitElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement reference) {
|
||||
super.visitReferenceElement(reference);
|
||||
PsiElement resolve = reference.resolve();
|
||||
if (resolve instanceof PsiClass base) {
|
||||
PsiClass baseClass = base.getContainingClass();
|
||||
if (baseClass != null && base.hasModifierProperty(PsiModifier.PRIVATE) && baseClass == containerClass && baseClass.getContainingClass() == null) {
|
||||
String description = JavaErrorBundle.message("private.symbol",
|
||||
HighlightUtil.formatClass(base),
|
||||
HighlightUtil.formatClass(baseClass));
|
||||
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(extendRef)
|
||||
.descriptionAndTooltip(description);
|
||||
|
||||
IntentionAction action1 = QuickFixFactory.getInstance().createModifierListFix(base, PsiModifier.PUBLIC, true, false);
|
||||
info.registerFix(action1, null, null, null, null);
|
||||
IntentionAction action = QuickFixFactory.getInstance().createModifierListFix(base, PsiModifier.PROTECTED, true, false);
|
||||
info.registerFix(action, null, null, null, null);
|
||||
|
||||
infos[0] = info;
|
||||
return;
|
||||
}
|
||||
|
||||
// must be inner class
|
||||
if (!PsiUtil.isInnerClass(base)) return;
|
||||
|
||||
if (resolve == resolved && baseClass != null && (!PsiTreeUtil.isAncestor(baseClass, extendRef, true) || aClass.hasModifierProperty(PsiModifier.STATIC)) &&
|
||||
!InheritanceUtil.hasEnclosingInstanceInScope(baseClass, extendRef, psiClass -> psiClass != aClass, true) &&
|
||||
!qualifiedNewCalledInConstructors(aClass)) {
|
||||
String description = JavaErrorBundle.message("no.enclosing.instance.in.scope", HighlightUtil.formatClass(baseClass));
|
||||
infos[0] = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef).descriptionAndTooltip(description);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return infos[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 15.9 Class Instance Creation Expressions | 15.9.2 Determining Enclosing Instances
|
||||
*/
|
||||
private static boolean qualifiedNewCalledInConstructors(@NotNull PsiClass aClass) {
|
||||
PsiMethod[] constructors = aClass.getConstructors();
|
||||
if (constructors.length == 0) return false;
|
||||
for (PsiMethod constructor : constructors) {
|
||||
PsiMethodCallExpression methodCallExpression = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor);
|
||||
if (methodCallExpression == null) return false;
|
||||
if (JavaPsiConstructorUtil.isChainedConstructorCall(methodCallExpression)) continue;
|
||||
PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression();
|
||||
PsiExpression qualifierExpression = PsiUtil.skipParenthesizedExprDown(referenceExpression.getQualifierExpression());
|
||||
//If the class instance creation expression is qualified, then the immediately
|
||||
//enclosing instance of i is the object that is the value of the Primary expression or the ExpressionName,
|
||||
//otherwise aClass needs to be a member of a class enclosing the class in which the class instance creation expression appears
|
||||
//already excluded by InheritanceUtil.hasEnclosingInstanceInScope
|
||||
if (qualifierExpression == null) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkCreateInnerClassFromStaticContext(@NotNull PsiNewExpression expression, @NotNull PsiType type, @NotNull PsiClass aClass) {
|
||||
if (type instanceof PsiArrayType || type instanceof PsiPrimitiveType) return null;
|
||||
if (aClass instanceof PsiAnonymousClass anonymousClass) {
|
||||
|
||||
+1
-3
@@ -1046,6 +1046,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
|
||||
@Override
|
||||
public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) {
|
||||
super.visitReferenceElement(ref);
|
||||
JavaResolveResult result = ref instanceof PsiExpression ? resolveOptimised(ref, myFile) : doVisitReferenceElement(ref);
|
||||
if (result != null) {
|
||||
PsiElement resolved = result.getElement();
|
||||
@@ -1090,9 +1091,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasErrorResults()) add(HighlightClassUtil.checkAbstractInstantiation(ref));
|
||||
if (!hasErrorResults()) add(HighlightClassUtil.checkExtendsDuplicate(ref, resolved, myFile));
|
||||
if (!hasErrorResults()) add(HighlightClassUtil.checkClassExtendsForeignInnerClass(ref, resolved));
|
||||
if (!hasErrorResults()) add(GenericsHighlightUtil.checkSelectStaticClassFromParameterizedType(resolved, ref));
|
||||
if (!hasErrorResults() && parent instanceof PsiNewExpression newExpression) {
|
||||
add(GenericsHighlightUtil.checkDiamondTypeNotAllowed(newExpression));
|
||||
|
||||
+41
-1
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.daemon.impl.analysis;
|
||||
|
||||
import com.intellij.codeInsight.ClassUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.MoveAnnotationOnStaticMemberQualifyingTypeFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.MoveAnnotationToPackageInfoFileFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.ReplaceVarWithExplicitTypeFix;
|
||||
@@ -10,16 +11,21 @@ import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind;
|
||||
import com.intellij.lang.jvm.JvmModifier;
|
||||
import com.intellij.lang.jvm.actions.JvmElementActionFactories;
|
||||
import com.intellij.lang.jvm.actions.MemberRequestsKt;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.*;
|
||||
|
||||
@@ -53,11 +59,45 @@ final class JavaErrorFixProvider {
|
||||
ANNOTATION_NOT_ALLOWED_EXTENDS, RECEIVER_STATIC_CONTEXT, RECEIVER_WRONG_POSITION)) {
|
||||
single(kind, genericRemover);
|
||||
}
|
||||
|
||||
|
||||
createClassFixes(factory);
|
||||
createAnnotationFixes(factory);
|
||||
createReceiverParameterFixes(factory);
|
||||
}
|
||||
|
||||
private static void createClassFixes(QuickFixFactory factory) {
|
||||
single(CLASS_NO_ABSTRACT_METHOD, error -> {
|
||||
if (error.psi() instanceof PsiClass aClass && !(aClass instanceof PsiAnonymousClass) && !aClass.isEnum()
|
||||
&& aClass.getModifierList() != null
|
||||
&& HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, aClass.getModifierList()) == null) {
|
||||
return factory.createModifierListFix(aClass, PsiModifier.ABSTRACT, true, false);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
multi(CLASS_NO_ABSTRACT_METHOD, error -> {
|
||||
PsiClass aClass = error.psi() instanceof PsiClass cls ? cls : Objects.requireNonNull(error.psi().getContainingClass());
|
||||
PsiClass containingClass = Objects.requireNonNull(error.context().getContainingClass());
|
||||
PsiMethod anyMethodToImplement = ClassUtil.getAnyMethodToImplement(aClass);
|
||||
if (anyMethodToImplement == null) return List.of();
|
||||
if (!anyMethodToImplement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) ||
|
||||
JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, containingClass)) {
|
||||
return List.of(factory.createImplementMethodsFix(error.psi()));
|
||||
}
|
||||
else {
|
||||
return StreamEx.of(JvmModifier.PROTECTED, JvmModifier.PUBLIC)
|
||||
.flatCollection(modifier ->
|
||||
JvmElementActionFactories.createModifierActions(anyMethodToImplement, MemberRequestsKt.modifierRequest(modifier, true)))
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
});
|
||||
single(CLASS_REFERENCE_LIST_DUPLICATE,
|
||||
error -> factory.createRemoveDuplicateExtendsAction(HighlightUtil.formatClass(error.context())));
|
||||
multi(CLASS_REFERENCE_LIST_INNER_PRIVATE, error ->
|
||||
ContainerUtil.map(List.of(PsiModifier.PUBLIC, PsiModifier.PROTECTED),
|
||||
(@PsiModifier.ModifierConstant String modifier) ->
|
||||
factory.createModifierListFix(error.context(), modifier, true, false)));
|
||||
}
|
||||
|
||||
private static void createReceiverParameterFixes(@NotNull QuickFixFactory factory) {
|
||||
single(RECEIVER_TYPE_MISMATCH, error -> factory.createReceiverParameterTypeFix(error.psi(), error.context()));
|
||||
single(RECEIVER_NAME_MISMATCH,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ class A<T extends <error descr="'A.B' has private access in 'A'">A<T>.B</error>>
|
||||
private class B {}
|
||||
}
|
||||
|
||||
abstract class Outer implements <error descr="'Outer.Inner' has private access in 'Outer'">List<Outer.Inner></error> {
|
||||
abstract class Outer implements List<<error descr="'Outer.Inner' has private access in 'Outer'">Outer.Inner</error>> {
|
||||
private static abstract class Inner implements List<Inner.Key> {
|
||||
private static final class Key {}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import java.util.Comparator;
|
||||
|
||||
abstract class A implements <error descr="'A.B' has private access in 'A'">Comparator<A.B></error> {
|
||||
abstract class A implements Comparator<<error descr="'A.B' has private access in 'A'">A.B</error>> {
|
||||
|
||||
private static class B {
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import java.util.Comparator;
|
||||
|
||||
abstract class A implements <error descr="'A.B' has private access in 'A'">Comparator<A.B></error> {
|
||||
abstract class A implements Comparator<<error descr="'A.B' has private access in 'A'">A.B</error>> {
|
||||
|
||||
private static class B {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user