mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
[java-highlighting] More class-related stuff and implicit class stuff -> ClassChecker
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: aa4e9711952175cbd0677b50d7486efaa19113fc
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c6e3aee5d6
commit
9450c1bf37
@@ -76,6 +76,16 @@ class.sealed.incomplete.permits=Sealed class permits clause must contain all sub
|
||||
class.sealed.inheritor.expected.modifiers.can.be.final=Modifier 'sealed', 'non-sealed' or 'final' expected
|
||||
class.sealed.inheritor.expected.modifiers=Modifier 'sealed' or 'non-sealed' expected
|
||||
|
||||
class.implicit.no.main.method=Implicitly declared class contains no 'main' method
|
||||
class.implicit.invalid.file.name=Implicitly declared class's file name is not a valid identifier
|
||||
class.implicit.initializer=Initializers are not allowed in implicitly declared classes
|
||||
class.implicit.package.statement=Package statement is not allowed for implicitly declared class
|
||||
|
||||
interface.constructor=Constructor is not allowed in interface
|
||||
interface.class.initializer=Class initializer is not allowed in interface
|
||||
|
||||
record.instance.initializer=Instance initializer is not allowed in record
|
||||
record.instance.field=Instance field is not allowed in record
|
||||
record.no.header=Record has no header declared
|
||||
record.header.regular.class=Record header declared for non-record
|
||||
|
||||
|
||||
@@ -241,7 +241,22 @@ final class ClassChecker {
|
||||
checkDuplicateClasses(aClass, classes);
|
||||
}
|
||||
|
||||
void checkDuplicateClasses(@NotNull PsiClass aClass, @NotNull PsiClass @NotNull[] classes) {
|
||||
void checkDuplicateClassesWithImplicit(@NotNull PsiJavaFile file) {
|
||||
if (!myVisitor.isApplicable(JavaFeature.IMPLICIT_CLASSES)) return;
|
||||
PsiImplicitClass implicitClass = JavaImplicitClassUtil.getImplicitClassFor(file);
|
||||
if (implicitClass == null) return;
|
||||
|
||||
Module module = ModuleUtilCore.findModuleForPsiElement(file);
|
||||
if (module == null) return;
|
||||
|
||||
GlobalSearchScope scope = GlobalSearchScope.moduleScope(module).intersectWith(implicitClass.getResolveScope());
|
||||
String qualifiedName = implicitClass.getQualifiedName();
|
||||
if (qualifiedName == null) return;
|
||||
PsiClass[] classes = JavaPsiFacade.getInstance(implicitClass.getProject()).findClasses(qualifiedName, scope);
|
||||
checkDuplicateClasses(implicitClass, classes);
|
||||
}
|
||||
|
||||
private void checkDuplicateClasses(@NotNull PsiClass aClass, @NotNull PsiClass @NotNull [] classes) {
|
||||
PsiManager manager = aClass.getManager();
|
||||
Module module = ModuleUtilCore.findModuleForPsiElement(aClass);
|
||||
if (module == null) return;
|
||||
@@ -380,6 +395,69 @@ final class ClassChecker {
|
||||
}
|
||||
}
|
||||
|
||||
void checkImplicitClassWellFormed(@NotNull PsiJavaFile file) {
|
||||
if (!myVisitor.isApplicable(JavaFeature.IMPLICIT_CLASSES)) return;
|
||||
PsiImplicitClass implicitClass = JavaImplicitClassUtil.getImplicitClassFor(file);
|
||||
if (implicitClass == null) return;
|
||||
String name = implicitClass.getQualifiedName();
|
||||
if (!PsiNameHelper.getInstance(file.getProject()).isIdentifier(name)) {
|
||||
myVisitor.report(JavaErrorKinds.CLASS_IMPLICIT_INVALID_FILE_NAME.create(file, implicitClass));
|
||||
return;
|
||||
}
|
||||
PsiMethod[] methods = implicitClass.getMethods();
|
||||
boolean hasMainMethod = ContainerUtil.exists(methods, method -> "main".equals(method.getName()) && PsiMethodUtil.isMainMethod(method));
|
||||
if (!hasMainMethod) {
|
||||
myVisitor.report(JavaErrorKinds.CLASS_IMPLICIT_NO_MAIN_METHOD.create(file, implicitClass));
|
||||
}
|
||||
}
|
||||
|
||||
void checkImplicitClassMember(@NotNull PsiMember member) {
|
||||
if (!(member.getContainingClass() instanceof PsiImplicitClass)) return;
|
||||
|
||||
PsiElement anchor = member;
|
||||
if (member instanceof PsiNameIdentifierOwner owner) {
|
||||
PsiElement nameIdentifier = owner.getNameIdentifier();
|
||||
if (nameIdentifier != null) {
|
||||
anchor = nameIdentifier;
|
||||
}
|
||||
}
|
||||
myVisitor.checkFeature(anchor, JavaFeature.IMPLICIT_CLASSES);
|
||||
}
|
||||
|
||||
void checkIllegalInstanceMemberInRecord(@NotNull PsiMember member) {
|
||||
if (!member.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
PsiClass aClass = member.getContainingClass();
|
||||
if (aClass != null && aClass.isRecord()) {
|
||||
var error = member instanceof PsiClassInitializer initializer ?
|
||||
JavaErrorKinds.RECORD_INSTANCE_INITIALIZER.create(initializer) :
|
||||
JavaErrorKinds.RECORD_INSTANCE_FIELD.create((PsiField)member);
|
||||
myVisitor.report(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkThingNotAllowedInInterface(@NotNull PsiMember member) {
|
||||
PsiClass aClass = member.getContainingClass();
|
||||
if (aClass == null || !aClass.isInterface()) return;
|
||||
if (member instanceof PsiMethod method && method.isConstructor()) {
|
||||
myVisitor.report(JavaErrorKinds.INTERFACE_CONSTRUCTOR.create(method));
|
||||
} else if (member instanceof PsiClassInitializer initializer) {
|
||||
myVisitor.report(JavaErrorKinds.INTERFACE_CLASS_INITIALIZER.create(initializer));
|
||||
}
|
||||
}
|
||||
|
||||
void checkInitializersInImplicitClass(@NotNull PsiClassInitializer initializer) {
|
||||
if (initializer.getContainingClass() instanceof PsiImplicitClass && myVisitor.isApplicable(JavaFeature.IMPLICIT_CLASSES)) {
|
||||
myVisitor.report(JavaErrorKinds.CLASS_IMPLICIT_INITIALIZER.create(initializer));
|
||||
}
|
||||
}
|
||||
|
||||
void checkPackageNotAllowedInImplicitClass(@NotNull PsiPackageStatement statement) {
|
||||
if (myVisitor.isApplicable(JavaFeature.IMPLICIT_CLASSES) && JavaImplicitClassUtil.isFileWithImplicitClass(myVisitor.file())) {
|
||||
myVisitor.report(JavaErrorKinds.CLASS_IMPLICIT_PACKAGE.create(statement));
|
||||
}
|
||||
}
|
||||
|
||||
private static @Unmodifiable @NotNull Map<PsiJavaCodeReferenceElement, PsiClass> getPermittedClassesRefs(@NotNull PsiClass psiClass) {
|
||||
PsiReferenceList permitsList = psiClass.getPermitsList();
|
||||
if (permitsList == null) return Collections.emptyMap();
|
||||
|
||||
@@ -76,7 +76,8 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
@Override
|
||||
public void visitPackageStatement(@NotNull PsiPackageStatement statement) {
|
||||
super.visitPackageStatement(statement);
|
||||
myAnnotationChecker.checkPackageAnnotationContainingFile(statement);
|
||||
if (!hasErrorResults()) myAnnotationChecker.checkPackageAnnotationContainingFile(statement);
|
||||
if (!hasErrorResults()) myClassChecker.checkPackageNotAllowedInImplicitClass(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,6 +163,41 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJavaFile(@NotNull PsiJavaFile file) {
|
||||
super.visitJavaFile(file);
|
||||
if (!hasErrorResults()) myClassChecker.checkImplicitClassWellFormed(file);
|
||||
if (!hasErrorResults()) myClassChecker.checkDuplicateClassesWithImplicit(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClassInitializer(@NotNull PsiClassInitializer initializer) {
|
||||
super.visitClassInitializer(initializer);
|
||||
if (!hasErrorResults()) myClassChecker.checkImplicitClassMember(initializer);
|
||||
if (!hasErrorResults()) myClassChecker.checkIllegalInstanceMemberInRecord(initializer);
|
||||
if (!hasErrorResults()) myClassChecker.checkThingNotAllowedInInterface(initializer);
|
||||
if (!hasErrorResults()) myClassChecker.checkInitializersInImplicitClass(initializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
super.visitField(field);
|
||||
if (!hasErrorResults()) myClassChecker.checkIllegalInstanceMemberInRecord(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIdentifier(@NotNull PsiIdentifier identifier) {
|
||||
PsiElement parent = identifier.getParent();
|
||||
if (parent instanceof PsiVariable variable) {
|
||||
if (variable instanceof PsiField field) {
|
||||
myClassChecker.checkImplicitClassMember(field);
|
||||
}
|
||||
}
|
||||
else if (parent instanceof PsiMethod method) {
|
||||
myClassChecker.checkImplicitClassMember(method);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
super.visitClass(aClass);
|
||||
@@ -252,6 +288,9 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
if (!hasErrorResults() && aClass != null) {
|
||||
myMethodChecker.checkDuplicateMethod(aClass, method);
|
||||
}
|
||||
if (!hasErrorResults() && method.isConstructor()) {
|
||||
myClassChecker.checkThingNotAllowedInInterface(method);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -261,6 +261,20 @@ public final class JavaErrorKinds {
|
||||
public static final Simple<PsiClass> RECORD_NO_HEADER = error(PsiClass.class, "record.no.header")
|
||||
.withAnchor(PsiClass::getNameIdentifier);
|
||||
public static final Simple<PsiRecordHeader> RECORD_HEADER_REGULAR_CLASS = error("record.header.regular.class");
|
||||
public static final Simple<PsiClassInitializer> RECORD_INSTANCE_INITIALIZER = error("record.instance.initializer");
|
||||
public static final Simple<PsiField> RECORD_INSTANCE_FIELD = error("record.instance.field");
|
||||
|
||||
public static final Simple<PsiClassInitializer> INTERFACE_CLASS_INITIALIZER = error("interface.class.initializer");
|
||||
public static final Simple<PsiMethod> INTERFACE_CONSTRUCTOR = error("interface.constructor");
|
||||
|
||||
public static final Parameterized<PsiJavaFile, PsiImplicitClass> CLASS_IMPLICIT_NO_MAIN_METHOD =
|
||||
error(PsiJavaFile.class, "class.implicit.no.main.method")
|
||||
.withHighlightType(psi -> JavaErrorHighlightType.FILE_LEVEL_ERROR).parameterized();
|
||||
public static final Parameterized<PsiJavaFile, PsiImplicitClass> CLASS_IMPLICIT_INVALID_FILE_NAME =
|
||||
error(PsiJavaFile.class, "class.implicit.invalid.file.name")
|
||||
.withHighlightType(psi -> JavaErrorHighlightType.FILE_LEVEL_ERROR).parameterized();
|
||||
public static final Simple<PsiClassInitializer> CLASS_IMPLICIT_INITIALIZER = error("class.implicit.initializer");
|
||||
public static final Simple<PsiPackageStatement> CLASS_IMPLICIT_PACKAGE = error("class.implicit.package.statement");
|
||||
|
||||
private static @NotNull <Psi extends PsiElement> Simple<Psi> error(@NotNull String key) {
|
||||
return new Simple<>(key);
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.intellij.codeInsight.daemon.JavaErrorBundle;
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.MoveMembersIntoClassFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
@@ -15,15 +14,10 @@ import com.intellij.lang.jvm.JvmModifier;
|
||||
import com.intellij.lang.jvm.actions.ChangeModifierRequest;
|
||||
import com.intellij.lang.jvm.actions.JvmElementActionFactories;
|
||||
import com.intellij.lang.jvm.actions.MemberRequestsKt;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ModuleFileIndex;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
@@ -36,7 +30,6 @@ import com.intellij.util.JavaPsiConstructorUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
@@ -47,36 +40,6 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public final class HighlightClassUtil {
|
||||
|
||||
static @Nullable HighlightInfo.Builder checkDuplicateClasses(@NotNull PsiClass aClass, @NotNull PsiClass @NotNull[] classes) {
|
||||
PsiManager manager = aClass.getManager();
|
||||
Module module = ModuleUtilCore.findModuleForPsiElement(aClass);
|
||||
if (module == null) return null;
|
||||
ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex();
|
||||
VirtualFile virtualFile = PsiUtilCore.getVirtualFile(aClass);
|
||||
if (virtualFile == null) return null;
|
||||
boolean isTestSourceRoot = fileIndex.isInTestSourceContent(virtualFile);
|
||||
String dupFileName = null;
|
||||
PsiClass dupClass = null;
|
||||
for (PsiClass dupClassCandidate : classes) {
|
||||
// do not use equals
|
||||
if (dupClassCandidate != aClass) {
|
||||
VirtualFile file = dupClassCandidate.getContainingFile().getVirtualFile();
|
||||
if (file != null && manager.isInProject(dupClassCandidate) && fileIndex.isInTestSourceContent(file) == isTestSourceRoot) {
|
||||
dupClass = dupClassCandidate;
|
||||
dupFileName = FileUtil.toSystemDependentName(file.getPath());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dupFileName == null) return null;
|
||||
HighlightInfo.Builder info = createInfoAndRegisterRenameFix(aClass, dupFileName, "duplicate.class.in.other.file");
|
||||
IntentionAction action = QuickFixFactory.getInstance().createNavigateToDuplicateElementFix(dupClass);
|
||||
if (info != null) {
|
||||
info.registerFix(action, null, null, null, null);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkClassRestrictedKeyword(@NotNull LanguageLevel level, @NotNull PsiIdentifier identifier) {
|
||||
String className = identifier.getText();
|
||||
if (isRestrictedIdentifier(className, level)) {
|
||||
@@ -101,36 +64,6 @@ public final class HighlightClassUtil {
|
||||
PsiKeyword.VALUE.equals(typeName) && JavaFeature.VALHALLA_VALUE_CLASSES.isSufficient(level);
|
||||
}
|
||||
|
||||
private static @Nullable HighlightInfo.Builder createInfoAndRegisterRenameFix(@NotNull PsiClass aClass,
|
||||
@NotNull String name,
|
||||
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String key) {
|
||||
String message = JavaErrorBundle.message(key, name);
|
||||
PsiIdentifier identifier = aClass.getNameIdentifier();
|
||||
HighlightInfo.Builder info;
|
||||
if (aClass instanceof PsiImplicitClass) {
|
||||
info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(aClass)
|
||||
.fileLevelAnnotation()
|
||||
.description(message);
|
||||
IntentionAction action = QuickFixFactory.getInstance().createRenameFix(aClass);
|
||||
if (action != null) {
|
||||
info.registerFix(action, null, null, null, null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (identifier == null) return null;
|
||||
TextRange textRange = identifier.getTextRange();
|
||||
info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(textRange)
|
||||
.descriptionAndTooltip(message);
|
||||
IntentionAction action = QuickFixFactory.getInstance().createRenameFix(identifier);
|
||||
if (action != null) {
|
||||
info.registerFix(action, null, null, null, null);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private static HighlightInfo.Builder checkStaticFieldDeclarationInInnerClass(@NotNull PsiKeyword keyword) {
|
||||
if (getEnclosingStaticClass(keyword, PsiField.class) == null) {
|
||||
return null;
|
||||
@@ -471,17 +404,6 @@ public final class HighlightClassUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkThingNotAllowedInInterface(@NotNull PsiElement element, @Nullable PsiClass aClass) {
|
||||
if (aClass == null || !aClass.isInterface()) return null;
|
||||
String description = JavaErrorBundle.message("not.allowed.in.interface");
|
||||
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description);
|
||||
IntentionAction action1 = QuickFixFactory.getInstance().createDeleteFix(element);
|
||||
info.registerFix(action1, null, null, null, null);
|
||||
IntentionAction action = QuickFixFactory.getInstance().createConvertInterfaceToClassFix(aClass);
|
||||
info.registerFix(action, null, null, null, null);
|
||||
return info;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkQualifiedNew(@NotNull PsiNewExpression expression, @Nullable PsiType type, @Nullable PsiClass aClass) {
|
||||
PsiExpression qualifier = expression.getQualifier();
|
||||
if (qualifier == null) return null;
|
||||
@@ -644,21 +566,6 @@ public final class HighlightClassUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkIllegalInstanceMemberInRecord(@NotNull PsiMember member) {
|
||||
if (!member.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
PsiClass aClass = member.getContainingClass();
|
||||
if (aClass != null && aClass.isRecord()) {
|
||||
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(member)
|
||||
.descriptionAndTooltip(JavaErrorBundle.message(member instanceof PsiClassInitializer ?
|
||||
"record.instance.initializer" : "record.instance.field"));
|
||||
IntentionAction action = QuickFixFactory.getInstance().createModifierListFix(member, PsiModifier.STATIC, true, false);
|
||||
info.registerFix(action, null, null, null, null);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkValueClassExtends(@NotNull PsiClass superClass,
|
||||
@NotNull PsiClass psiClass,
|
||||
@NotNull PsiElement elementToHighlight) {
|
||||
@@ -915,31 +822,4 @@ public final class HighlightClassUtil {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkImplicitClassMember(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel,
|
||||
@NotNull PsiFile psiFile) {
|
||||
if (!(member.getContainingClass() instanceof PsiImplicitClass implicitClass)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PsiElement anchor = member;
|
||||
if (member instanceof PsiNameIdentifierOwner owner) {
|
||||
PsiElement nameIdentifier = owner.getNameIdentifier();
|
||||
if (nameIdentifier != null) {
|
||||
anchor = nameIdentifier;
|
||||
}
|
||||
}
|
||||
HighlightInfo.Builder builder = HighlightUtil.checkFeature(anchor, JavaFeature.IMPLICIT_CLASSES, languageLevel, psiFile);
|
||||
if (builder == null) return null;
|
||||
|
||||
if (!(member instanceof PsiClass)) {
|
||||
boolean hasClassToRelocate = PsiTreeUtil.findChildOfType(implicitClass, PsiClass.class) != null;
|
||||
if (hasClassToRelocate) {
|
||||
MoveMembersIntoClassFix fix = new MoveMembersIntoClassFix(implicitClass);
|
||||
builder.registerFix(fix, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +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.QuickFixFactory;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.JavaImplicitClassUtil;
|
||||
import com.intellij.psi.util.PsiMethodUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Checks and reports errors for implicitly declared classes {@link PsiImplicitClass}.
|
||||
*/
|
||||
public final class HighlightImplicitClassUtil {
|
||||
|
||||
static HighlightInfo.@Nullable Builder checkImplicitClassHasMainMethod(@NotNull PsiJavaFile file) {
|
||||
if (!PsiUtil.isAvailable(JavaFeature.IMPLICIT_CLASSES, file)) return null;
|
||||
PsiImplicitClass implicitClass = JavaImplicitClassUtil.getImplicitClassFor(file);
|
||||
if (implicitClass == null) return null;
|
||||
PsiMethod[] methods = implicitClass.getMethods();
|
||||
boolean hasMainMethod = ContainerUtil.exists(methods, method -> "main".equals(method.getName()) && PsiMethodUtil.isMainMethod(method));
|
||||
if (!hasMainMethod) {
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(file)
|
||||
.fileLevelAnnotation()
|
||||
.registerFix(QuickFixFactory.getInstance().createAddMainMethodFix(implicitClass), null, null, null, null)
|
||||
.description(JavaErrorBundle.message("error.implicit.class.contains.no.main.method"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.@Nullable Builder checkImplicitClassFileIsValidIdentifier(@NotNull PsiJavaFile file) {
|
||||
if (!PsiUtil.isAvailable(JavaFeature.IMPLICIT_CLASSES, file)) return null;
|
||||
PsiImplicitClass implicitClass = JavaImplicitClassUtil.getImplicitClassFor(file);
|
||||
if (implicitClass == null) return null;
|
||||
String name = implicitClass.getQualifiedName();
|
||||
if (!PsiNameHelper.getInstance(file.getProject()).isIdentifier(name)) {
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(file)
|
||||
.fileLevelAnnotation()
|
||||
.description(JavaErrorBundle.message("error.implicit.class.has.invalid.file.name"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.@Nullable Builder checkInitializersInImplicitClass(@NotNull PsiClassInitializer initializer) {
|
||||
if (initializer.getContainingClass() instanceof PsiImplicitClass && PsiUtil.isAvailable(JavaFeature.IMPLICIT_CLASSES, initializer)) {
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(initializer)
|
||||
.descriptionAndTooltip(JavaErrorBundle.message("error.initializers.are.not.allowed.in.implicit.classes"))
|
||||
.registerFix(QuickFixFactory.getInstance().createDeleteFix(initializer), null, null, null, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static HighlightInfo.@Nullable Builder checkPackageNotAllowedInImplicitClass(@NotNull PsiPackageStatement statement,
|
||||
@NotNull PsiFile file) {
|
||||
if (PsiUtil.isAvailable(JavaFeature.IMPLICIT_CLASSES, file) && JavaImplicitClassUtil.isFileWithImplicitClass(file)) {
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
|
||||
.range(statement)
|
||||
.descriptionAndTooltip(JavaErrorBundle.message("error.package.statement.not.allowed.for.implicit.class"))
|
||||
.registerFix(QuickFixFactory.getInstance().createDeleteFix(statement), null, null, null, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static @Nullable HighlightInfo.Builder checkDuplicateClasses(@NotNull PsiJavaFile file) {
|
||||
if (!PsiUtil.isAvailable(JavaFeature.IMPLICIT_CLASSES, file)) return null;
|
||||
PsiImplicitClass implicitClass = JavaImplicitClassUtil.getImplicitClassFor(file);
|
||||
if (implicitClass == null) return null;
|
||||
|
||||
Module module = ModuleUtilCore.findModuleForPsiElement(file);
|
||||
if (module == null) return null;
|
||||
|
||||
GlobalSearchScope scope = GlobalSearchScope.moduleScope(module).intersectWith(implicitClass.getResolveScope());
|
||||
String qualifiedName = implicitClass.getQualifiedName();
|
||||
if (qualifiedName == null) {
|
||||
return null;
|
||||
}
|
||||
PsiClass[] classes = JavaPsiFacade.getInstance(implicitClass.getProject()).findClasses(qualifiedName, scope);
|
||||
return HighlightClassUtil.checkDuplicateClasses(implicitClass, classes);
|
||||
}
|
||||
}
|
||||
@@ -252,14 +252,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJavaFile(@NotNull PsiJavaFile file) {
|
||||
super.visitJavaFile(file);
|
||||
if (!hasErrorResults()) add(HighlightImplicitClassUtil.checkImplicitClassHasMainMethod(file));
|
||||
if (!hasErrorResults()) add(HighlightImplicitClassUtil.checkImplicitClassFileIsValidIdentifier(file));
|
||||
if (!hasErrorResults()) add(HighlightImplicitClassUtil.checkDuplicateClasses(file));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitArrayInitializerExpression(@NotNull PsiArrayInitializerExpression expression) {
|
||||
super.visitArrayInitializerExpression(expression);
|
||||
@@ -429,14 +421,8 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
@Override
|
||||
public void visitClassInitializer(@NotNull PsiClassInitializer initializer) {
|
||||
super.visitClassInitializer(initializer);
|
||||
if (!hasErrorResults()) add(HighlightClassUtil.checkImplicitClassMember(initializer, myLanguageLevel, myFile));
|
||||
if (!hasErrorResults()) add(HighlightClassUtil.checkIllegalInstanceMemberInRecord(initializer));
|
||||
if (!hasErrorResults()) add(HighlightControlFlowUtil.checkInitializerCompleteNormally(initializer));
|
||||
if (!hasErrorResults()) add(HighlightControlFlowUtil.checkUnreachableStatement(initializer.getBody()));
|
||||
if (!hasErrorResults()) {
|
||||
add(HighlightClassUtil.checkThingNotAllowedInInterface(initializer, initializer.getContainingClass()));
|
||||
}
|
||||
if (!hasErrorResults()) add(HighlightImplicitClassUtil.checkInitializersInImplicitClass(initializer));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -591,7 +577,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
@Override
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
super.visitField(field);
|
||||
if (!hasErrorResults()) add(HighlightClassUtil.checkIllegalInstanceMemberInRecord(field));
|
||||
if (!hasErrorResults()) add(HighlightControlFlowUtil.checkFinalFieldInitialized(field));
|
||||
}
|
||||
|
||||
@@ -623,9 +608,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
public void visitIdentifier(@NotNull PsiIdentifier identifier) {
|
||||
PsiElement parent = identifier.getParent();
|
||||
if (parent instanceof PsiVariable variable) {
|
||||
if (variable instanceof PsiField field) {
|
||||
add(HighlightClassUtil.checkImplicitClassMember(field, myLanguageLevel, myFile));
|
||||
}
|
||||
add(HighlightUtil.checkVariableAlreadyDefined(variable));
|
||||
if (variable.isUnnamed()) {
|
||||
HighlightInfo.Builder notAvailable = checkFeature(variable, JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES);
|
||||
@@ -635,11 +617,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
add(HighlightUtil.checkUnnamedVariableDeclaration(variable));
|
||||
}
|
||||
}
|
||||
|
||||
if (variable.getInitializer() == null) {
|
||||
PsiElement child = variable.getLastChild();
|
||||
if (child instanceof PsiErrorElement && child.getPrevSibling() == identifier) return;
|
||||
}
|
||||
}
|
||||
else if (parent instanceof PsiClass aClass) {
|
||||
if (aClass.isAnnotationType()) {
|
||||
@@ -659,7 +636,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
}
|
||||
}
|
||||
else if (parent instanceof PsiMethod method) {
|
||||
add(HighlightClassUtil.checkImplicitClassMember(method, myLanguageLevel, myFile));
|
||||
if (method.isConstructor()) {
|
||||
HighlightInfo.Builder info = HighlightMethodUtil.checkConstructorName(method);
|
||||
if (info != null) {
|
||||
@@ -859,11 +835,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
if (!hasErrorResults()) add(HighlightMethodUtil.checkConstructorHandleSuperClassExceptions(method));
|
||||
if (!hasErrorResults()) add(HighlightMethodUtil.checkRecordAccessorDeclaration(method));
|
||||
if (!hasErrorResults()) HighlightMethodUtil.checkRecordConstructorDeclaration(method, myErrorSink);
|
||||
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if (!hasErrorResults() && method.isConstructor()) {
|
||||
add(HighlightClassUtil.checkThingNotAllowedInInterface(method, aClass));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -977,7 +948,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
if (JavaFeature.MODULES.isSufficient(myLanguageLevel)) {
|
||||
if (!hasErrorResults()) add(ModuleHighlightUtil.checkPackageStatement(statement, myFile, myJavaModule));
|
||||
}
|
||||
if (!hasErrorResults()) add(HighlightImplicitClassUtil.checkPackageNotAllowedInImplicitClass(statement, myFile));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,6 +4,7 @@ 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.MoveMembersIntoClassFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.ReplaceVarWithExplicitTypeFix;
|
||||
import com.intellij.codeInsight.intention.CommonIntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
@@ -17,6 +18,7 @@ import com.intellij.lang.jvm.actions.JvmElementActionFactories;
|
||||
import com.intellij.lang.jvm.actions.MemberRequestsKt;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.Service;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
@@ -68,11 +70,13 @@ final class JavaErrorFixProvider {
|
||||
JavaFixProvider<PsiElement, Object> genericRemover = error -> myFactory.createDeleteFix(error.psi());
|
||||
for (JavaErrorKind<?, ?> kind : List.of(ANNOTATION_MEMBER_THROWS_NOT_ALLOWED, ANNOTATION_ATTRIBUTE_DUPLICATE,
|
||||
ANNOTATION_NOT_ALLOWED_EXTENDS, RECEIVER_STATIC_CONTEXT, RECEIVER_WRONG_POSITION,
|
||||
RECORD_HEADER_REGULAR_CLASS)) {
|
||||
RECORD_HEADER_REGULAR_CLASS, INTERFACE_CLASS_INITIALIZER, INTERFACE_CONSTRUCTOR,
|
||||
CLASS_IMPLICIT_INITIALIZER, CLASS_IMPLICIT_PACKAGE)) {
|
||||
fix(kind, genericRemover);
|
||||
}
|
||||
|
||||
createClassFixes();
|
||||
createRecordFixes();
|
||||
createAnnotationFixes();
|
||||
createReceiverParameterFixes();
|
||||
}
|
||||
@@ -152,7 +156,6 @@ final class JavaErrorFixProvider {
|
||||
}
|
||||
return registrar;
|
||||
});
|
||||
fix(RECORD_NO_HEADER, error -> myFactory.createAddEmptyRecordHeaderFix(error.psi()));
|
||||
fix(CLASS_SEALED_INCOMPLETE_PERMITS, error -> myFactory.createFillPermitsListFix(requireNonNull(error.psi().getNameIdentifier())));
|
||||
multi(CLASS_SEALED_INHERITOR_EXPECTED_MODIFIERS_CAN_BE_FINAL, error -> List.of(
|
||||
addModifierFix(error.psi(), PsiModifier.FINAL),
|
||||
@@ -161,6 +164,23 @@ final class JavaErrorFixProvider {
|
||||
multi(CLASS_SEALED_INHERITOR_EXPECTED_MODIFIERS, error -> List.of(
|
||||
addModifierFix(error.psi(), PsiModifier.SEALED),
|
||||
addModifierFix(error.psi(), PsiModifier.NON_SEALED)));
|
||||
fix(CLASS_IMPLICIT_NO_MAIN_METHOD, error -> myFactory.createAddMainMethodFix(error.context()));
|
||||
fix(UNSUPPORTED_FEATURE, error -> {
|
||||
if (error.context() != JavaFeature.IMPLICIT_CLASSES) return null;
|
||||
PsiMember member = PsiTreeUtil.getNonStrictParentOfType(error.psi(), PsiMember.class);
|
||||
if (member == null || member instanceof PsiClass) return null;
|
||||
if (!(member.getContainingClass() instanceof PsiImplicitClass implicitClass)) return null;
|
||||
boolean hasClassToRelocate = PsiTreeUtil.findChildOfType(implicitClass, PsiClass.class) != null;
|
||||
return hasClassToRelocate ? new MoveMembersIntoClassFix(implicitClass) : null;
|
||||
});
|
||||
fix(INTERFACE_CONSTRUCTOR, error -> myFactory.createConvertInterfaceToClassFix(requireNonNull(error.psi().getContainingClass())));
|
||||
fix(INTERFACE_CLASS_INITIALIZER, error -> myFactory.createConvertInterfaceToClassFix(requireNonNull(error.psi().getContainingClass())));
|
||||
}
|
||||
|
||||
private void createRecordFixes() {
|
||||
fix(RECORD_NO_HEADER, error -> myFactory.createAddEmptyRecordHeaderFix(error.psi()));
|
||||
fix(RECORD_INSTANCE_FIELD, error -> addModifierFix(error.psi(), PsiModifier.STATIC));
|
||||
fix(RECORD_INSTANCE_INITIALIZER, error -> addModifierFix(error.psi(), PsiModifier.STATIC));
|
||||
}
|
||||
|
||||
private void createReceiverParameterFixes() {
|
||||
|
||||
@@ -546,9 +546,7 @@ too.many.array.dimensions=Too many array dimensions
|
||||
error.cannot.infer.pattern.type=Cannot infer pattern type: {0}
|
||||
error.extra.semicolons.between.import.statements.not.allowed=Extra semicolons between import statements are not allowed
|
||||
error.guard.allowed.after.patterns.only=Guard is allowed after patterns only
|
||||
error.implicit.class.contains.no.main.method=Implicitly declared class contains no 'main' method
|
||||
error.implicit.class.has.invalid.file.name=Implicitly declared class's file name is not a valid identifier
|
||||
error.package.statement.not.allowed.for.implicit.class=Package statement is not allowed for implicitly declared class
|
||||
error.initializers.are.not.allowed.in.implicit.classes=Initializers are not allowed in implicitly declared classes
|
||||
remove.unused.imports.quickfix.text=Remove unused imports
|
||||
incomplete.project.state.pending.reference=Not resolved until the project is fully loaded
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
interface A {
|
||||
<error descr="Not allowed in interface">A();</error>
|
||||
<error descr="Not allowed in interface">static {}</error>
|
||||
<error descr="Not allowed in interface">{}</error>
|
||||
<error descr="Constructor is not allowed in interface">A();</error>
|
||||
<error descr="Class initializer is not allowed in interface">static {}</error>
|
||||
<error descr="Class initializer is not allowed in interface">{}</error>
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class D {
|
||||
}
|
||||
|
||||
interface IllegalMods {
|
||||
void m1()<error descr="'{' or ';' expected"> </error>default<error descr="Identifier or type expected">;</error> <error descr="Not allowed in interface">{ }</error>
|
||||
void m1()<error descr="'{' or ';' expected"> </error>default<error descr="Identifier or type expected">;</error> <error descr="Class initializer is not allowed in interface">{ }</error>
|
||||
|
||||
<error descr="Static methods in interfaces should have a body">static void m2()</error>;
|
||||
<error descr="Illegal combination of modifiers 'static' and 'default'">static</error> <error descr="Illegal combination of modifiers 'default' and 'static'">default</error> void m3() { }
|
||||
|
||||
Reference in New Issue
Block a user