Records error highlighting: well-formedness; components (part of IDEA-228460)

GitOrigin-RevId: f72bba51738ed7d89dbbebf43311a049c23d317f
This commit is contained in:
Tagir Valeev
2019-12-17 11:02:18 +00:00
committed by intellij-monorepo-bot
parent 2c22ee32a7
commit 3ff5ba7461
9 changed files with 130 additions and 5 deletions
@@ -404,11 +404,14 @@ public class HighlightClassUtil {
static HighlightInfo checkExtendsAllowed(@NotNull PsiReferenceList list) {
if (list.getParent() instanceof PsiClass) {
PsiClass aClass = (PsiClass)list.getParent();
if (aClass.isEnum()) {
if (aClass.isEnum() || aClass.isRecord()) {
boolean isExtends = list.equals(aClass.getExtendsList());
if (isExtends) {
String description = JavaErrorMessages.message("extends.after.enum");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create();
String description = JavaErrorMessages.message(aClass.isRecord() ? "record.extends" : "extends.after.enum");
HighlightInfo info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createDeleteFix(list));
return info;
}
}
}
@@ -873,4 +876,24 @@ public class HighlightClassUtil {
}
return null;
}
public static HighlightInfo checkWellFormedRecord(PsiClass psiClass) {
PsiRecordHeader header = psiClass.getRecordHeader();
if (!psiClass.isRecord()) {
if (header != null) {
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(header)
.descriptionAndTooltip(JavaErrorMessages.message("record.header.regular.class")).create();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createDeleteFix(header));
return info;
}
return null;
}
PsiIdentifier identifier = psiClass.getNameIdentifier();
if (identifier == null) return null;
if (header == null) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier)
.descriptionAndTooltip(JavaErrorMessages.message("record.no.header")).create();
}
return null;
}
}
@@ -86,6 +86,8 @@ public class HighlightUtil extends HighlightUtilBase {
ContainerUtil.newTroveSet(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.NATIVE, PsiModifier.FINAL, PsiModifier.STRICTFP, PsiModifier.SYNCHRONIZED);
private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields";
private static final Set<String> RESTRICTED_RECORD_COMPONENT_NAMES = ContainerUtil.immutableSet(
"clone", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait");
static {
ourClassIncompatibleModifiers.put(PsiModifier.ABSTRACT, ContainerUtil.newTroveSet(PsiModifier.FINAL));
@@ -915,6 +917,10 @@ public class HighlightUtil extends HighlightUtilBase {
isAllowed &= !(PsiModifier.FINAL.equals(modifier) || PsiModifier.ABSTRACT.equals(modifier));
}
if (aClass.isRecord()) {
isAllowed &= !PsiModifier.ABSTRACT.equals(modifier);
}
if (aClass.getContainingClass() instanceof PsiAnonymousClass) {
isAllowed &= !privateOrProtected;
}
@@ -958,7 +964,8 @@ public class HighlightUtil extends HighlightUtilBase {
else if (modifierOwner instanceof PsiClassInitializer) {
isAllowed = PsiModifier.STATIC.equals(modifier);
}
else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) {
else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter ||
modifierOwner instanceof PsiRecordComponent) {
isAllowed = PsiModifier.FINAL.equals(modifier);
}
else if (modifierOwner instanceof PsiReceiverParameter) {
@@ -1464,6 +1471,26 @@ public class HighlightUtil extends HighlightUtilBase {
return infos;
}
static HighlightInfo checkRecordComponentName(PsiRecordComponent component) {
PsiIdentifier identifier = component.getNameIdentifier();
if (identifier != null) {
String name = identifier.getText();
if (RESTRICTED_RECORD_COMPONENT_NAMES.contains(name)) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier)
.descriptionAndTooltip(JavaErrorMessages.message("record.component.restricted.name", name)).create();
}
}
return null;
}
static HighlightInfo checkRecordComponentVarArg(PsiRecordComponent recordComponent) {
if (recordComponent.isVarArgs() && PsiTreeUtil.getNextSiblingOfType(recordComponent, PsiRecordComponent.class) != null) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(recordComponent)
.descriptionAndTooltip(JavaErrorMessages.message("record.component.vararg.not.last")).create();
}
return null;
}
private enum SelectorKind { INT, ENUM, STRING }
private static SelectorKind getSwitchSelectorKind(@NotNull PsiType type) {
@@ -469,6 +469,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkImplicitThisReferenceBeforeSuper(aClass, myJavaSdkVersion));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkClassAndPackageConflict(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkPublicClassInRightFile(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkWellFormedRecord(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkTypeParameterOverrideEquivalentMethods(aClass, myLanguageLevel));
}
@@ -1062,6 +1063,13 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
}
@Override
public void visitRecordComponent(PsiRecordComponent recordComponent) {
super.visitRecordComponent(recordComponent);
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkRecordComponentVarArg(recordComponent));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkRecordComponentName(recordComponent));
}
@Override
public void visitParameter(PsiParameter parameter) {
super.visitParameter(parameter);
@@ -33,6 +33,7 @@ public class AnnotationTargetUtil {
private static final TargetType[] CONSTRUCTOR_TARGETS = {TargetType.CONSTRUCTOR, TargetType.TYPE_USE};
private static final TargetType[] METHOD_TARGETS = {TargetType.METHOD, TargetType.TYPE_USE};
private static final TargetType[] FIELD_TARGETS = {TargetType.FIELD, TargetType.TYPE_USE};
private static final TargetType[] RECORD_COMPONENT_TARGETS = {TargetType.RECORD_COMPONENT, TargetType.FIELD, TargetType.METHOD, TargetType.TYPE_USE};
private static final TargetType[] PARAMETER_TARGETS = {TargetType.PARAMETER, TargetType.TYPE_USE};
private static final TargetType[] LOCAL_VARIABLE_TARGETS = {TargetType.LOCAL_VARIABLE, TargetType.TYPE_USE};
private static final TargetType[] MODULE_TARGETS = {TargetType.MODULE};
@@ -67,6 +68,9 @@ public class AnnotationTargetUtil {
return TYPE_TARGETS;
}
}
if (element instanceof PsiRecordComponent) {
return RECORD_COMPONENT_TARGETS;
}
if (element instanceof PsiMethod) {
if (((PsiMethod)element).isConstructor()) {
return CONSTRUCTOR_TARGETS;
@@ -32,7 +32,7 @@ public interface PsiAnnotation extends PsiAnnotationMemberValue, JvmAnnotation {
*/
enum TargetType {
// see java.lang.annotation.ElementType
TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_USE, TYPE_PARAMETER, MODULE,
TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_USE, TYPE_PARAMETER, MODULE, RECORD_COMPONENT,
// auxiliary value, used when it's impossible to determine annotation's targets
UNKNOWN;
@@ -3,4 +3,13 @@ package com.intellij.psi;
public interface PsiRecordComponent extends PsiMember, PsiVariable {
PsiRecordComponent[] EMPTY_ARRAY = new PsiRecordComponent[]{};
/**
* Checks if the record component accepts a variable number of arguments in canonical constructor.
*
* @return true if the record component is a vararg, false otherwise
*/
default boolean isVarArgs() {
return getType() instanceof PsiEllipsisType;
}
}
@@ -120,6 +120,12 @@ public class PsiModifierListImpl extends JavaStubPsiElement<PsiModifierListStub>
implicitModifiers.add(STATIC);
}
}
if (((PsiClass)parent).isRecord()) {
if (!(grandParent instanceof PsiFile)) {
implicitModifiers.add(STATIC);
}
implicitModifiers.add(FINAL);
}
if (((PsiClass)parent).isEnum()) {
if (!(grandParent instanceof PsiFile)) {
implicitModifiers.add(STATIC);
@@ -155,6 +161,9 @@ public class PsiModifierListImpl extends JavaStubPsiElement<PsiModifierListStub>
implicitModifiers.add(PRIVATE);
}
}
else if (parent instanceof PsiRecordComponent) {
implicitModifiers.add(FINAL);
}
else if (parent instanceof PsiField) {
if (parent instanceof PsiEnumConstant) {
Collections.addAll(implicitModifiers, PUBLIC, STATIC, FINAL);
@@ -59,6 +59,8 @@ annotation.target.LOCAL_VARIABLE=local variable
annotation.target.PACKAGE=package
# suppress inspection "UnusedProperty"
annotation.target.MODULE=module
# suppress inspection "UnusedProperty"
annotation.target.RECORD_COMPONENT=record component
# generics related messages
generics.holder.type=Type
@@ -465,6 +467,12 @@ lvti.null=Cannot infer type: variable initializer is 'null'
lvti.void=Cannot infer type: variable initializer is 'void'
lvti.selfReferenced=Cannot infer type: variable initializer is self-referencing
record.no.header=Record has no header declared
record.header.regular.class=Record header declared for non-record
record.extends=No extends clause allowed for record
record.component.vararg.not.last=Vararg record component must be the last in the list
record.component.restricted.name=Illegal record component name ''{0}''
feature.generics=Generics
feature.annotations=Annotations
feature.static.imports=Static imports
@@ -0,0 +1,37 @@
import java.lang.annotation.*;
record <error descr="Record has no header declared">NoComponentList</error> {}
record NoComponents() {}
class ClassWithComponents<error descr="Record header declared for non-record">(int x)</error> {}
class ClassWithComponents2<error descr="Record header declared for non-record">(int x, int y)</error> {}
<error descr="Modifier 'abstract' not allowed here">abstract</error> record AbstractRecord() {}
record ExtendsObject() <error descr="No extends clause allowed for record">extends Object</error> {}
class ExtendsRecord extends <error descr="Cannot inherit from final 'NoComponents'">NoComponents</error> {}
record ComponentModifiers(
<error descr="Modifier 'public' not allowed here">public</error> int x,
<error descr="Modifier 'static' not allowed here">static</error> int y,
final int z) {}
record ComponentDuplicateName(int <error descr="Variable 'x' is already defined in the scope">x</error>, int <error descr="Variable 'x' is already defined in the scope">x</error>) {}
record VarArgOk(int... x) {}
record VarArgOk2(int x, int... y) {}
record VarArgNotOk(<error descr="Vararg record component must be the last in the list">int... x</error>, int y) {}
record IllegalComponentName(
int <error descr="Illegal record component name 'clone'">clone</error>,
int <error descr="Illegal record component name 'finalize'">finalize</error>,
int <error descr="Illegal record component name 'getClass'">getClass</error>,
int <error descr="Illegal record component name 'hashCode'">hashCode</error>,
int <error descr="Illegal record component name 'notify'">notify</error>,
int <error descr="Illegal record component name 'notifyAll'">notifyAll</error>,
int <error descr="Illegal record component name 'toString'">toString</error>,
int <error descr="Illegal record component name 'wait'">wait</error>) {}
@interface SimpleAnno {}
@Target(ElementType.CONSTRUCTOR)
@interface ConstructorAnno {}
@Target(ElementType.METHOD)
@interface MethodAnno {}
record AnnotatedComponents(
@SimpleAnno int x,
<error descr="'@ConstructorAnno' not applicable to record component">@ConstructorAnno</error> int y,
@MethodAnno int z) {}