mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
[java-highlighting] Unrelated defaults checks moved to MethodChecker
GenericsHighlightUtil.java is removed completely Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: de2bddb49469c21efe1088e86f6a1ec5b9d35b4a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5014925e91
commit
96bc4480b1
@@ -129,6 +129,8 @@ class.initializer.must.complete.normally=Initializer must be able to complete no
|
||||
class.permitted.not.direct.subclass=Invalid ''permits'' clause: ''{0}'' must directly {1, choice, 1#extend|2#implement} ''{2}''
|
||||
class.permitted.must.have.modifier=All sealed class subclasses must either be final, sealed or non-sealed
|
||||
class.or.package.expected=Expected class or package
|
||||
class.inherits.abstract.and.default={0} inherits abstract and default for {1} from types {2} and {3}
|
||||
class.inherits.unrelated.defaults={0} inherits unrelated defaults for {1} from types {2} and {3}
|
||||
|
||||
class.implicit.no.main.method=Implicitly declared class contains no 'main' method
|
||||
class.implicit.invalid.file.name=The file name of an implicitly declared class is not a valid identifier
|
||||
|
||||
@@ -582,6 +582,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
if (!hasErrorResults()) myClassChecker.checkClassAlreadyImported(aClass);
|
||||
if (!hasErrorResults()) myClassChecker.checkClassRestrictedKeyword(identifier);
|
||||
if (!hasErrorResults()) myGenericsChecker.checkUnrelatedConcrete(aClass);
|
||||
if (!hasErrorResults() && isApplicable(JavaFeature.EXTENSION_METHODS)) myMethodChecker.checkUnrelatedDefaultMethods(aClass);
|
||||
}
|
||||
else if (parent instanceof PsiMethod method) {
|
||||
myClassChecker.checkImplicitClassMember(method);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// 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.codeInsight.ExceptionUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
|
||||
import com.intellij.java.codeserver.core.JavaPsiMethodUtil;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
|
||||
import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Couple;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
@@ -549,4 +552,33 @@ final class MethodChecker {
|
||||
myVisitor.report(JavaErrorKinds.METHOD_DUPLICATE.create(method));
|
||||
}
|
||||
}
|
||||
|
||||
void checkUnrelatedDefaultMethods(@NotNull PsiClass aClass) {
|
||||
Map<? extends MethodSignature, Set<PsiMethod>> overrideEquivalent = PsiSuperMethodUtil.collectOverrideEquivalents(aClass);
|
||||
|
||||
for (Set<PsiMethod> overrideEquivalentMethods : overrideEquivalent.values()) {
|
||||
PsiMethod abstractMethod = JavaPsiMethodUtil.getAbstractMethodToImplementWhenDefaultPresent(aClass, overrideEquivalentMethods);
|
||||
if (abstractMethod != null) {
|
||||
PsiMethod anyAbstractMethod = ClassUtil.getAnyAbstractMethod(aClass);
|
||||
if (anyAbstractMethod != null) {
|
||||
PsiClass containingClass = anyAbstractMethod.getContainingClass();
|
||||
if (containingClass != null && containingClass != aClass) {
|
||||
// Already reported inside ClassChecker.checkClassWithAbstractMethods
|
||||
continue;
|
||||
}
|
||||
}
|
||||
myVisitor.report(JavaErrorKinds.CLASS_NO_ABSTRACT_METHOD.create(aClass, abstractMethod));
|
||||
continue;
|
||||
}
|
||||
Couple<@NotNull PsiMethod> pair = JavaPsiMethodUtil.getUnrelatedSuperMethods(aClass, overrideEquivalentMethods);
|
||||
if (pair == null ||
|
||||
MethodSignatureUtil.findMethodBySuperMethod(aClass, overrideEquivalentMethods.iterator().next(), false) != null) {
|
||||
continue;
|
||||
}
|
||||
var kind = pair.getSecond().hasModifierProperty(PsiModifier.ABSTRACT)
|
||||
? JavaErrorKinds.CLASS_INHERITS_ABSTRACT_AND_DEFAULT
|
||||
: JavaErrorKinds.CLASS_INHERITS_UNRELATED_DEFAULTS;
|
||||
myVisitor.report(kind.create(aClass, new JavaErrorKinds.OverrideClashContext(pair.getFirst(), pair.getSecond())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +308,24 @@ public final class JavaErrorKinds {
|
||||
return message(messageKey, referenceName, formatMethod(abstractMethod),
|
||||
formatClass(requireNonNull(abstractMethod.getContainingClass()), false));
|
||||
});
|
||||
public static final Parameterized<PsiClass, OverrideClashContext> CLASS_INHERITS_ABSTRACT_AND_DEFAULT =
|
||||
parameterized(PsiClass.class, OverrideClashContext.class, "class.inherits.abstract.and.default")
|
||||
.withAnchor(PsiClass::getNameIdentifier)
|
||||
.withRawDescription((cls, ctx) -> {
|
||||
return message("class.inherits.abstract.and.default", formatClass(cls),
|
||||
formatMethod(ctx.method()),
|
||||
formatClass(requireNonNull(ctx.method().getContainingClass())),
|
||||
formatClass(requireNonNull(ctx.superMethod().getContainingClass())));
|
||||
});
|
||||
public static final Parameterized<PsiClass, OverrideClashContext> CLASS_INHERITS_UNRELATED_DEFAULTS =
|
||||
parameterized(PsiClass.class, OverrideClashContext.class, "class.inherits.unrelated.defaults")
|
||||
.withAnchor(PsiClass::getNameIdentifier)
|
||||
.withRawDescription((cls, ctx) -> {
|
||||
return message("class.inherits.unrelated.defaults", formatClass(cls),
|
||||
formatMethod(ctx.method()),
|
||||
formatClass(requireNonNull(ctx.method().getContainingClass())),
|
||||
formatClass(requireNonNull(ctx.superMethod().getContainingClass())));
|
||||
});
|
||||
public static final Simple<PsiClass> CLASS_ALREADY_IMPORTED =
|
||||
error(PsiClass.class, "class.already.imported").withAnchor(PsiClass::getNameIdentifier)
|
||||
.withRawDescription(cls -> message("class.already.imported", formatClass(cls, false)));
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.daemon.impl.analysis;
|
||||
|
||||
import com.intellij.codeInsight.daemon.JavaErrorBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.java.codeserver.core.JavaPsiMethodUtil;
|
||||
import com.intellij.openapi.util.Couple;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.MethodSignature;
|
||||
import com.intellij.psi.util.MethodSignatureUtil;
|
||||
import com.intellij.psi.util.PsiSuperMethodUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public final class GenericsHighlightUtil {
|
||||
|
||||
private GenericsHighlightUtil() { }
|
||||
|
||||
static HighlightInfo.Builder checkUnrelatedDefaultMethods(@NotNull PsiClass aClass, @NotNull PsiIdentifier classIdentifier) {
|
||||
Map<? extends MethodSignature, Set<PsiMethod>> overrideEquivalent = PsiSuperMethodUtil.collectOverrideEquivalents(aClass);
|
||||
|
||||
for (Set<PsiMethod> overrideEquivalentMethods : overrideEquivalent.values()) {
|
||||
String errorMessage = getUnrelatedDefaultsMessage(aClass, overrideEquivalentMethods);
|
||||
if (errorMessage != null &&
|
||||
MethodSignatureUtil.findMethodBySuperMethod(aClass, overrideEquivalentMethods.iterator().next(), false) == null) {
|
||||
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(classIdentifier)
|
||||
.descriptionAndTooltip(errorMessage);
|
||||
IntentionAction action = QuickFixFactory.getInstance().createImplementMethodsFix(aClass);
|
||||
info.registerFix(action, null, null, null, null);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return error message if class inherits 2 unrelated default methods or abstract and default methods which do not belong to one hierarchy
|
||||
*/
|
||||
public static @Nullable @NlsContexts.DetailedDescription String getUnrelatedDefaultsMessage(
|
||||
@NotNull PsiClass aClass, @NotNull Collection<? extends PsiMethod> overrideEquivalentSuperMethods) {
|
||||
PsiMethod abstractMethod = JavaPsiMethodUtil.getAbstractMethodToImplementWhenDefaultPresent(aClass, overrideEquivalentSuperMethods);
|
||||
if (abstractMethod != null) {
|
||||
String key = aClass instanceof PsiEnumConstantInitializer || aClass.isRecord() || aClass.isEnum() ?
|
||||
"class.must.implement.method" : "class.must.be.abstract";
|
||||
return JavaErrorBundle.message(key,
|
||||
HighlightUtil.formatClass(aClass, false),
|
||||
JavaHighlightUtil.formatMethod(abstractMethod),
|
||||
HighlightUtil.formatClass(requireNonNull(abstractMethod.getContainingClass()), false));
|
||||
}
|
||||
|
||||
Couple<@NotNull PsiMethod> pair = JavaPsiMethodUtil.getUnrelatedSuperMethods(aClass, overrideEquivalentSuperMethods);
|
||||
if (pair == null) return null;
|
||||
String key = pair.getSecond().hasModifierProperty(PsiModifier.ABSTRACT) ?
|
||||
"text.class.inherits.abstract.and.default" :
|
||||
"text.class.inherits.unrelated.defaults";
|
||||
return JavaErrorBundle.message(key, HighlightUtil.formatClass(aClass),
|
||||
JavaHighlightUtil.formatMethod(pair.getFirst()),
|
||||
HighlightUtil.formatClass(requireNonNull(pair.getFirst().getContainingClass())),
|
||||
HighlightUtil.formatClass(requireNonNull(pair.getSecond().getContainingClass())));
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import com.intellij.openapi.project.IndexNotReadyException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
|
||||
@@ -221,18 +220,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIdentifier(@NotNull PsiIdentifier identifier) {
|
||||
PsiElement parent = identifier.getParent();
|
||||
if (parent instanceof PsiClass aClass) {
|
||||
if (!hasErrorResults() && JavaFeature.EXTENSION_METHODS.isSufficient(myLanguageLevel)) {
|
||||
add(GenericsHighlightUtil.checkUnrelatedDefaultMethods(aClass, identifier));
|
||||
}
|
||||
}
|
||||
|
||||
super.visitIdentifier(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitImportStaticReferenceElement(@NotNull PsiImportStaticReferenceElement ref) {
|
||||
super.visitImportStaticReferenceElement(ref);
|
||||
|
||||
@@ -801,6 +801,8 @@ final class JavaErrorFixProvider {
|
||||
}
|
||||
|
||||
private void createClassFixes() {
|
||||
fix(CLASS_INHERITS_UNRELATED_DEFAULTS, error -> myFactory.createImplementMethodsFix(error.psi()));
|
||||
fix(CLASS_INHERITS_ABSTRACT_AND_DEFAULT, error -> myFactory.createImplementMethodsFix(error.psi()));
|
||||
fix(CLASS_NO_ABSTRACT_METHOD, error -> {
|
||||
if (error.psi() instanceof PsiClass aClass && !(aClass instanceof PsiAnonymousClass) && !aClass.isEnum()) {
|
||||
return maybeAddModifierFix(aClass, PsiModifier.ABSTRACT);
|
||||
|
||||
@@ -186,8 +186,6 @@ record.canonical.constructor=Canonical constructor
|
||||
record.compact.constructor=Compact constructor
|
||||
annotation.on.static.member.qualifying.type.family.name=Move type annotation
|
||||
create.class.action.this.not.valid.java.qualified.name=This is not a valid Java qualified name
|
||||
text.class.inherits.abstract.and.default={0} inherits abstract and default for {1} from types {2} and {3}
|
||||
text.class.inherits.unrelated.defaults={0} inherits unrelated defaults for {1} from types {2} and {3}
|
||||
text.class.is.not.accessible={0} is not accessible in current context
|
||||
text.class.cannot.access=Cannot access {0}
|
||||
remove.unused.imports.quickfix.text=Remove unused imports
|
||||
|
||||
@@ -22,8 +22,8 @@ class AnonymousExtendsJLR {
|
||||
<error descr="Class 'SuperInterface' must implement abstract method 'run()' in 'Runnable'">record SuperInterface() implements Runnable</error> {}
|
||||
interface I1 { default void run() {}}
|
||||
interface I2 { void run();}
|
||||
record <error descr="Class 'UnrelatedDefaults' must implement abstract method 'run()' in 'I2'">UnrelatedDefaults</error>() implements I1, I2 {}
|
||||
enum <error descr="Class 'UnrelatedDefaults2' must implement abstract method 'run()' in 'I2'">UnrelatedDefaults2</error> implements I1, I2 {}
|
||||
<error descr="Class 'UnrelatedDefaults' must implement abstract method 'run()' in 'I2'">record UnrelatedDefaults() implements I1, I2</error> {}
|
||||
<error descr="Class 'UnrelatedDefaults2' must implement abstract method 'run()' in 'I2'">enum UnrelatedDefaults2 implements I1, I2</error> {}
|
||||
|
||||
record ComponentModifiers(
|
||||
<error descr="Modifier 'public' not allowed here">public</error> int x,
|
||||
|
||||
@@ -5,7 +5,7 @@ class Test1 {
|
||||
default void foo(String x) { }
|
||||
}
|
||||
|
||||
class <error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'A'">C</error> implements A<String> { }
|
||||
<error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'A'">class C implements A<String></error> { }
|
||||
abstract class <error descr="Test1.D inherits abstract and default for foo(String) from types Test1.A and Test1.A">D</error> implements A<String> {}
|
||||
interface <error descr="Test1.E inherits abstract and default for foo(String) from types Test1.A and Test1.A">E</error> extends A<String> {}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ class Test2 {
|
||||
void foo(T x);
|
||||
}
|
||||
|
||||
<error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'B'">class <error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'B'">C</error> implements B<String></error> { }
|
||||
<error descr="Class 'C' must either be declared abstract or implement abstract method 'foo(T)' in 'B'">class C implements B<String></error> { }
|
||||
abstract class D implements B<String> {}
|
||||
interface E extends B<String> {}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class Test4 {
|
||||
void accept();
|
||||
}
|
||||
|
||||
<error descr="Class 'D' must either be declared abstract or implement abstract method 'accept()' in 'C'">class <error descr="Class 'D' must either be declared abstract or implement abstract method 'accept()' in 'C'">D</error> implements B, C</error> {}
|
||||
<error descr="Class 'D' must either be declared abstract or implement abstract method 'accept()' in 'C'">class D implements B, C</error> {}
|
||||
abstract class E implements B, C {}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ interface SecondParent {
|
||||
int doSomething();
|
||||
}
|
||||
|
||||
class <error descr="Class 'FirstSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">FirstSon</error> implements FirstParent, SecondParent {}
|
||||
<error descr="Class 'FirstSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">class FirstSon implements FirstParent, SecondParent</error> {}
|
||||
|
||||
<error descr="Class 'SecondSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">class <error descr="Class 'SecondSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">SecondSon</error> implements SecondParent, FirstParent</error> {}
|
||||
<error descr="Class 'SecondSon' must either be declared abstract or implement abstract method 'doSomething()' in 'SecondParent'">class SecondSon implements SecondParent, FirstParent</error> {}
|
||||
|
||||
interface A {
|
||||
default int foo() {
|
||||
|
||||
@@ -10,4 +10,4 @@ interface C {
|
||||
void m();
|
||||
}
|
||||
|
||||
class <error descr="Class 'D' must either be declared abstract or implement abstract method 'm()' in 'C'">D</error> implements A, B, C { }
|
||||
<error descr="Class 'D' must either be declared abstract or implement abstract method 'm()' in 'C'">class D implements A, B, C</error> { }
|
||||
@@ -9,7 +9,7 @@ class Test1 {
|
||||
default void foo(T x) { }
|
||||
}
|
||||
|
||||
class <error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(String)' in 'A'">D</error> implements C<String> { }
|
||||
<error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(String)' in 'A'">class D implements C<String></error> { }
|
||||
interface E extends C<String> { }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class Test2 {
|
||||
void foo(T x);
|
||||
}
|
||||
|
||||
<error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(T)' in 'C'">class <error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(T)' in 'C'">D</error> implements C<String></error> {}
|
||||
<error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(T)' in 'C'">class D implements C<String></error> {}
|
||||
interface E extends C<String> {}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class Test5 {
|
||||
default void foo(T x) { }
|
||||
}
|
||||
|
||||
class <error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(String)' in 'A'">D</error> extends B implements C<String> { }
|
||||
<error descr="Class 'D' must either be declared abstract or implement abstract method 'foo(String)' in 'A'">class D extends B implements C<String></error> { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,4 +13,4 @@ class C<K> extends AC<K> implements B<K> {
|
||||
public void replace(K k) {}
|
||||
}
|
||||
|
||||
<error descr="Class 'D' must either be declared abstract or implement abstract method 'replace(K)' in 'B'">class <error descr="Class 'D' must either be declared abstract or implement abstract method 'replace(K)' in 'B'">D</error><K> extends AC<K> implements B<K></error> {}
|
||||
<error descr="Class 'D' must either be declared abstract or implement abstract method 'replace(K)' in 'B'">class D<K> extends AC<K> implements B<K></error> {}
|
||||
Reference in New Issue
Block a user