mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] checkClassMustBeAbstract, checkDuplicateNestedClass, checkCyclicInheritance -> ClassChecker
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: ac39c3d4a1198d9ea32c27fc9d3dfe6eee4ed251
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6035671a2a
commit
388f3deac0
@@ -61,6 +61,8 @@ override.on.non-overriding.method=Method does not override method from its super
|
||||
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.duplicate=Duplicate class: ''{0}''
|
||||
class.cyclic.inheritance=Cyclic inheritance involving ''{0}''
|
||||
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}''
|
||||
|
||||
+68
-3
@@ -3,7 +3,6 @@ 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;
|
||||
@@ -23,11 +22,11 @@ final class ClassChecker {
|
||||
if (parent instanceof PsiAnonymousClass aClass
|
||||
&& parent.getParent() instanceof PsiNewExpression
|
||||
&& !PsiUtilCore.hasErrorElementChild(parent.getParent())) {
|
||||
checkClassWithAbstractMethods(aClass, aClass, ref.getTextRange());
|
||||
checkClassWithAbstractMethods(aClass, aClass);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkClassWithAbstractMethods(@NotNull PsiClass aClass, @NotNull PsiMember implementsFixElement, @NotNull TextRange range) {
|
||||
private void checkClassWithAbstractMethods(@NotNull PsiClass aClass, @NotNull PsiMember implementsFixElement) {
|
||||
PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass);
|
||||
if (abstractMethod == null) {
|
||||
return;
|
||||
@@ -108,6 +107,72 @@ final class ClassChecker {
|
||||
});
|
||||
}
|
||||
|
||||
void checkClassMustBeAbstract(@NotNull PsiClass aClass) {
|
||||
boolean mustCheck = aClass.isEnum() ? !hasEnumConstantsWithInitializer(aClass) :
|
||||
!aClass.hasModifierProperty(PsiModifier.ABSTRACT) && aClass.getRBrace() != null;
|
||||
if (mustCheck) {
|
||||
checkClassWithAbstractMethods(aClass, aClass);
|
||||
}
|
||||
}
|
||||
|
||||
void checkDuplicateNestedClass(PsiClass aClass) {
|
||||
String name = aClass.getName();
|
||||
if (name == null) return;
|
||||
PsiElement parent = aClass.getParent();
|
||||
boolean checkSiblings;
|
||||
if (parent instanceof PsiClass psiClass && !PsiUtil.isLocalOrAnonymousClass(psiClass) && !PsiUtil.isLocalOrAnonymousClass(aClass)) {
|
||||
// optimization: instead of iterating PsiClass children manually we can get'em all from caches
|
||||
PsiClass innerClass = psiClass.findInnerClassByName(name, false);
|
||||
if (innerClass != null && innerClass != aClass) {
|
||||
if (innerClass.getTextOffset() > aClass.getTextOffset()) {
|
||||
// report duplicate lower in text
|
||||
PsiClass c = innerClass;
|
||||
innerClass = aClass;
|
||||
aClass = c;
|
||||
}
|
||||
myVisitor.report(JavaErrorKinds.CLASS_DUPLICATE.create(aClass, innerClass));
|
||||
return;
|
||||
}
|
||||
checkSiblings = false; // there still might be duplicates in parents
|
||||
}
|
||||
else {
|
||||
checkSiblings = true;
|
||||
}
|
||||
if (!(parent instanceof PsiDeclarationStatement)) {
|
||||
parent = aClass;
|
||||
}
|
||||
while (parent != null) {
|
||||
if (parent instanceof PsiFile) break;
|
||||
PsiElement element = checkSiblings ? parent.getPrevSibling() : null;
|
||||
if (element == null) {
|
||||
element = parent.getParent();
|
||||
// JLS 14.3:
|
||||
// The name of a local class C may not be redeclared
|
||||
// as a local class of the directly enclosing method, constructor, or initializer block within the scope of C, or a compile-time
|
||||
// error occurs. However, a local class declaration may be shadowed (6.3.1)
|
||||
// anywhere inside a class declaration nested within the local class declaration's scope.
|
||||
if (element instanceof PsiMethod ||
|
||||
element instanceof PsiClass ||
|
||||
element instanceof PsiCodeBlock && element.getParent() instanceof PsiClassInitializer) {
|
||||
checkSiblings = false;
|
||||
}
|
||||
}
|
||||
parent = element;
|
||||
|
||||
if (element instanceof PsiDeclarationStatement) element = PsiTreeUtil.getChildOfType(element, PsiClass.class);
|
||||
if (element instanceof PsiClass psiClass && name.equals(psiClass.getName())) {
|
||||
myVisitor.report(JavaErrorKinds.CLASS_DUPLICATE.create(aClass, psiClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkCyclicInheritance(@NotNull PsiClass aClass) {
|
||||
PsiClass circularClass = InheritanceUtil.getCircularClass(aClass);
|
||||
if (circularClass != null) {
|
||||
myVisitor.report(JavaErrorKinds.CLASS_CYCLIC_INHERITANCE.create(aClass, circularClass));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 15.9 Class Instance Creation Expressions | 15.9.2 Determining Enclosing Instances
|
||||
*/
|
||||
|
||||
+31
@@ -89,6 +89,37 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
myAnnotationChecker.checkNameValuePair(pair);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnumConstantInitializer(@NotNull PsiEnumConstantInitializer enumConstantInitializer) {
|
||||
super.visitEnumConstantInitializer(enumConstantInitializer);
|
||||
if (!hasErrorResults()) myClassChecker.checkClassMustBeAbstract(enumConstantInitializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitModifierList(@NotNull PsiModifierList list) {
|
||||
super.visitModifierList(list);
|
||||
PsiElement parent = list.getParent();
|
||||
if (parent instanceof PsiMethod method) {
|
||||
|
||||
}
|
||||
else if (parent instanceof PsiClass aClass) {
|
||||
if (!hasErrorResults()) myClassChecker.checkDuplicateNestedClass(aClass);
|
||||
if (!hasErrorResults() && !(aClass instanceof PsiAnonymousClass)) {
|
||||
/* anonymous class is highlighted in HighlightClassUtil.checkAbstractInstantiation()*/
|
||||
myClassChecker.checkClassMustBeAbstract(aClass);
|
||||
}
|
||||
if (!hasErrorResults()) {
|
||||
//myClassChecker.checkClassDoesNotCallSuperConstructorOrHandleExceptions(aClass, getResolveHelper(getProject()));
|
||||
}
|
||||
//if (!hasErrorResults()) add(HighlightMethodUtil.checkOverrideEquivalentInheritedMethods(aClass, myFile, myLanguageLevel));
|
||||
if (!hasErrorResults()) {
|
||||
//GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(aClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
|
||||
//myErrorSink.accept(myOverrideEquivalentMethodsErrors.get(aClass));
|
||||
}
|
||||
if (!hasErrorResults()) myClassChecker.checkCyclicInheritance(aClass);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
|
||||
JavaResolveResult resultForIncompleteCode = doVisitReferenceElement(expression);
|
||||
|
||||
+5
@@ -10,6 +10,11 @@ public enum JavaErrorHighlightType {
|
||||
*/
|
||||
ERROR,
|
||||
|
||||
/**
|
||||
* Error highlighting applied for the whole file
|
||||
*/
|
||||
FILE_LEVEL_ERROR,
|
||||
|
||||
/**
|
||||
* Error highlighting for unresolved/unknown reference
|
||||
*/
|
||||
|
||||
+11
@@ -305,6 +305,17 @@ public sealed interface JavaErrorKind<Psi extends PsiElement, Context> {
|
||||
return new Parameterized<>(myKey, myDescription, anchor, myRange, myHighlightType, myValidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of Parameterized with the specified range function.
|
||||
*
|
||||
* @param range a BiFunction that determines the {@link TextRange} for a given Psi object.
|
||||
* The range is relative to anchor returned from {@link #anchor(PsiElement, Object)}
|
||||
* @return a new Parameterized instance with the updated range function.
|
||||
*/
|
||||
public Parameterized<Psi, Context> withRange(@NotNull BiFunction<? super Psi, ? super Context, ? extends TextRange> range) {
|
||||
return new Parameterized<>(myKey, myDescription, myAnchor, range, myHighlightType, myValidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of Parameterized with a specified validator function.
|
||||
*
|
||||
|
||||
+10
@@ -202,6 +202,16 @@ public final class JavaErrorKinds {
|
||||
return message(messageKey, referenceName, formatMethod(abstractMethod),
|
||||
formatClass(requireNonNull(abstractMethod.getContainingClass()), false));
|
||||
});
|
||||
public static final Parameterized<PsiClass, PsiClass> CLASS_DUPLICATE =
|
||||
error(PsiClass.class, "class.duplicate")
|
||||
.withAnchor(cls -> requireNonNullElse(cls.getNameIdentifier(), cls))
|
||||
.withHighlightType(cls -> cls instanceof PsiImplicitClass ? JavaErrorHighlightType.FILE_LEVEL_ERROR : JavaErrorHighlightType.ERROR)
|
||||
.withRawDescription(cls -> message("class.duplicate", cls.getName()))
|
||||
.withContext();
|
||||
public static final Parameterized<PsiClass, PsiClass> CLASS_CYCLIC_INHERITANCE =
|
||||
error(PsiClass.class, "class.cyclic.inheritance")
|
||||
.withRange(JavaErrorFormatUtil::getClassDeclarationTextRange).<PsiClass>withContext()
|
||||
.withRawDescription((aClass, circularClass) -> message("class.cyclic.inheritance", formatClass(circularClass)));
|
||||
public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_REFERENCE_LIST_DUPLICATE =
|
||||
parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.duplicate")
|
||||
.withRawDescription(
|
||||
|
||||
-72
@@ -109,16 +109,6 @@ public final class HighlightClassUtil {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkClassMustBeAbstract(@NotNull PsiClass aClass, @NotNull TextRange textRange) {
|
||||
if (aClass.isEnum()) {
|
||||
if (hasEnumConstantsWithInitializer(aClass)) return null;
|
||||
}
|
||||
else if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) || aClass.getRBrace() == null) {
|
||||
return null;
|
||||
}
|
||||
return checkClassWithAbstractMethods(aClass, aClass, textRange);
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkInstantiationOfAbstractClass(@NotNull PsiClass aClass, @NotNull PsiElement highlightElement) {
|
||||
HighlightInfo.Builder errorResult = null;
|
||||
if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) &&
|
||||
@@ -219,68 +209,6 @@ public final class HighlightClassUtil {
|
||||
return info;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkDuplicateNestedClass(@NotNull PsiClass aClass) {
|
||||
String name = aClass.getName();
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
PsiElement parent = aClass.getParent();
|
||||
boolean checkSiblings;
|
||||
if (parent instanceof PsiClass psiClass && !PsiUtil.isLocalOrAnonymousClass(psiClass) && !PsiUtil.isLocalOrAnonymousClass(aClass)) {
|
||||
// optimization: instead of iterating PsiClass children manually we can get'em all from caches
|
||||
PsiClass innerClass = psiClass.findInnerClassByName(name, false);
|
||||
if (innerClass != null && innerClass != aClass) {
|
||||
if (innerClass.getTextOffset() > aClass.getTextOffset()) {
|
||||
// report duplicate lower in text
|
||||
PsiClass c = innerClass;innerClass=aClass;aClass=c;
|
||||
}
|
||||
HighlightInfo.Builder info = createInfoAndRegisterRenameFix(aClass, name, "duplicate.class");
|
||||
IntentionAction action = QuickFixFactory.getInstance().createNavigateToDuplicateElementFix(innerClass);
|
||||
if (info != null) {
|
||||
info.registerFix(action, null, null, null, null);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
checkSiblings = false; // there still might be duplicates in parents
|
||||
}
|
||||
else {
|
||||
checkSiblings = true;
|
||||
}
|
||||
if (!(parent instanceof PsiDeclarationStatement)) {
|
||||
parent = aClass;
|
||||
}
|
||||
while (parent != null) {
|
||||
if (parent instanceof PsiFile) break;
|
||||
PsiElement element = checkSiblings ? parent.getPrevSibling() : null;
|
||||
if (element == null) {
|
||||
element = parent.getParent();
|
||||
// JLS 14.3:
|
||||
// The name of a local class C may not be redeclared
|
||||
// as a local class of the directly enclosing method, constructor, or initializer block within the scope of C, or a compile-time
|
||||
// error occurs. However, a local class declaration may be shadowed (6.3.1)
|
||||
// anywhere inside a class declaration nested within the local class declaration's scope.
|
||||
if (element instanceof PsiMethod ||
|
||||
element instanceof PsiClass ||
|
||||
element instanceof PsiCodeBlock && element.getParent() instanceof PsiClassInitializer) {
|
||||
checkSiblings = false;
|
||||
}
|
||||
}
|
||||
parent = element;
|
||||
|
||||
if (element instanceof PsiDeclarationStatement) element = PsiTreeUtil.getChildOfType(element, PsiClass.class);
|
||||
if (element instanceof PsiClass psiClass && name.equals(psiClass.getName())) {
|
||||
HighlightInfo.Builder info = createInfoAndRegisterRenameFix(aClass, name, "duplicate.class");
|
||||
IntentionAction action = QuickFixFactory.getInstance().createNavigateToDuplicateElementFix(psiClass);
|
||||
if (info != null) {
|
||||
info.registerFix(action, null, null, null, null);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkPublicClassInRightFile(@NotNull PsiClass aClass) {
|
||||
PsiFile containingFile = aClass.getContainingFile();
|
||||
if (aClass.getParent() != containingFile || !aClass.hasModifierProperty(PsiModifier.PUBLIC) || !(containingFile instanceof PsiJavaFile file))
|
||||
|
||||
+7
-17
@@ -14,6 +14,7 @@ import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.codeInspection.ex.GlobalInspectionContextBase;
|
||||
import com.intellij.java.codeserver.highlighting.JavaErrorCollector;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorHighlightType;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.lang.jvm.JvmModifier;
|
||||
@@ -204,13 +205,17 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
myJavaModule = JavaFeature.MODULES.isSufficient(myLanguageLevel) ? JavaModuleGraphUtil.findDescriptorByElement(file) : null;
|
||||
myPreviewFeatureVisitor = myLanguageLevel.isPreview() ? null : new PreviewFeatureUtil.PreviewFeatureVisitor(myLanguageLevel, myErrorSink);
|
||||
myCollector = new JavaErrorCollector(myFile, error -> {
|
||||
HighlightInfoType type = switch (error.highlightType()) {
|
||||
case ERROR -> HighlightInfoType.ERROR;
|
||||
JavaErrorHighlightType javaHighlightType = error.highlightType();
|
||||
HighlightInfoType type = switch (javaHighlightType) {
|
||||
case ERROR, FILE_LEVEL_ERROR -> HighlightInfoType.ERROR;
|
||||
case WRONG_REF -> HighlightInfoType.WRONG_REF;
|
||||
};
|
||||
TextRange range = error.range();
|
||||
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(type)
|
||||
.descriptionAndTooltip(error.description().toString());
|
||||
if (javaHighlightType == JavaErrorHighlightType.FILE_LEVEL_ERROR) {
|
||||
info.fileLevelAnnotation();
|
||||
}
|
||||
if (range != null) {
|
||||
info.range(error.anchor(), range);
|
||||
} else {
|
||||
@@ -509,15 +514,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
if (!hasErrorResults()) add(HighlightUtil.checkUnhandledExceptions(enumConstant));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnumConstantInitializer(@NotNull PsiEnumConstantInitializer enumConstantInitializer) {
|
||||
super.visitEnumConstantInitializer(enumConstantInitializer);
|
||||
if (!hasErrorResults()) {
|
||||
TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(enumConstantInitializer);
|
||||
add(HighlightClassUtil.checkClassMustBeAbstract(enumConstantInitializer, textRange));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitExpression(@NotNull PsiExpression expression) {
|
||||
ProgressManager.checkCanceled(); // visitLiteralExpression is invoked very often in array initializers
|
||||
@@ -937,11 +933,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
}
|
||||
else if (parent instanceof PsiClass aClass) {
|
||||
try {
|
||||
if (!hasErrorResults()) add(HighlightClassUtil.checkDuplicateNestedClass(aClass));
|
||||
if (!hasErrorResults() && !(aClass instanceof PsiAnonymousClass)/* anonymous class is highlighted in HighlightClassUtil.checkAbstractInstantiation()*/) {
|
||||
TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
|
||||
add(HighlightClassUtil.checkClassMustBeAbstract(aClass, textRange));
|
||||
}
|
||||
if (!hasErrorResults()) {
|
||||
add(HighlightClassUtil.checkClassDoesNotCallSuperConstructorOrHandleExceptions(aClass, getResolveHelper(getProject())));
|
||||
}
|
||||
@@ -950,7 +941,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(aClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
|
||||
myErrorSink.accept(myOverrideEquivalentMethodsErrors.get(aClass));
|
||||
}
|
||||
if (!hasErrorResults()) add(HighlightClassUtil.checkCyclicInheritance(aClass));
|
||||
}
|
||||
catch (IndexNotReadyException ignored) {
|
||||
}
|
||||
|
||||
+2
@@ -96,6 +96,8 @@ final class JavaErrorFixProvider {
|
||||
ContainerUtil.map(List.of(PsiModifier.PUBLIC, PsiModifier.PROTECTED),
|
||||
(@PsiModifier.ModifierConstant String modifier) ->
|
||||
factory.createModifierListFix(error.context(), modifier, true, false)));
|
||||
single(CLASS_DUPLICATE, error -> factory.createRenameFix(Objects.requireNonNullElse(error.psi().getNameIdentifier(), error.psi())));
|
||||
single(CLASS_DUPLICATE, error -> factory.createNavigateToDuplicateElementFix(error.context()));
|
||||
}
|
||||
|
||||
private static void createReceiverParameterFixes(@NotNull QuickFixFactory factory) {
|
||||
|
||||
@@ -80,8 +80,6 @@ enum.constant.must.implement.method=Enum constant ''{0}'' must implement abstrac
|
||||
class.must.implement.method=Class ''{0}'' must implement abstract method ''{1}'' in ''{2}''
|
||||
abstract.cannot.be.instantiated=''{0}'' is abstract; cannot be instantiated
|
||||
duplicate.class.in.other.file=Duplicate class found in the file ''{0}''
|
||||
duplicate.class=Duplicate class: ''{0}''
|
||||
duplicate.reference.in.list=Duplicate reference to ''{0}'' in ''{1}'' list
|
||||
public.class.should.be.named.after.file=Class ''{0}'' is public, should be declared in a file named ''{0}.java''
|
||||
inheritance.from.final.class=Cannot inherit from {1, choice, 1#final class|2#enum|3#record|4#non-abstract value class} ''{0}''
|
||||
value.class.can.only.inherit=Value classes may only extend abstract value classes or 'java.lang.Object'
|
||||
|
||||
Reference in New Issue
Block a user