diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index d2aa1bad16c9..f15f3b97cace 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -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}'' diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ClassChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ClassChecker.java index 9ca08281836a..bbe2b34dce1d 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ClassChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ClassChecker.java @@ -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 */ diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java index c0f2f5b74dfc..add2f7c45bba 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java @@ -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); diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorHighlightType.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorHighlightType.java index 5ec93fa0ca62..878321610f90 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorHighlightType.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorHighlightType.java @@ -10,6 +10,11 @@ public enum JavaErrorHighlightType { */ ERROR, + /** + * Error highlighting applied for the whole file + */ + FILE_LEVEL_ERROR, + /** * Error highlighting for unresolved/unknown reference */ diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKind.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKind.java index 351e05275934..d33e5b777df1 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKind.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKind.java @@ -305,6 +305,17 @@ public sealed interface JavaErrorKind { 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 withRange(@NotNull BiFunction range) { + return new Parameterized<>(myKey, myDescription, myAnchor, range, myHighlightType, myValidator); + } + /** * Creates a new instance of Parameterized with a specified validator function. * diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java index 1298e1a1051d..3ae2909b511d 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java @@ -202,6 +202,16 @@ public final class JavaErrorKinds { return message(messageKey, referenceName, formatMethod(abstractMethod), formatClass(requireNonNull(abstractMethod.getContainingClass()), false)); }); + public static final Parameterized 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 CLASS_CYCLIC_INHERITANCE = + error(PsiClass.class, "class.cyclic.inheritance") + .withRange(JavaErrorFormatUtil::getClassDeclarationTextRange).withContext() + .withRawDescription((aClass, circularClass) -> message("class.cyclic.inheritance", formatClass(circularClass))); public static final Parameterized CLASS_REFERENCE_LIST_DUPLICATE = parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.duplicate") .withRawDescription( diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java index 57bf228cd201..5323b9fc8434 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java @@ -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)) diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index b2dbaea00787..1b5494574a44 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -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) { } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java index 9dfbdb88d420..109938fb9483 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java @@ -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) { diff --git a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties index 95685fbdc44a..941c36a0a464 100644 --- a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties @@ -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'