mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-09 08:09:39 +07:00
[java-highlighting] checkMemberReferencedBeforeConstructorCalled migrated
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: a8d2dba7d30b49ac7bdcc3e704334139a5139c2a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
41c378f2d8
commit
7e1d0f2784
@@ -354,6 +354,7 @@ call.ambiguous.tooltip=\
|
||||
<tr>{3}<td>in <b>{4}</b>\\ match</td></tr>\
|
||||
</table></body></html>
|
||||
call.parsed.as.deconstruction.pattern=Constant expression, pattern or null is required
|
||||
call.member.before.constructor=Cannot call ''{0}'' before superclass constructor is called
|
||||
|
||||
array.illegal.initializer=Illegal initializer for ''{0}''
|
||||
array.initializer.not.allowed=Array initializer is not allowed here
|
||||
@@ -387,6 +388,7 @@ variable.already.assigned.initializer=Final field ''{0}'' is already initialized
|
||||
variable.assigned.in.loop=Variable ''{0}'' might be assigned in loop
|
||||
variable.already.defined=Variable ''{0}'' is already defined in the scope
|
||||
field.not.initialized=Field ''{0}'' might not have been initialized
|
||||
field.initialized.before.constructor.call=Cannot assign initialized field ''{0}'' before superclass constructor is called
|
||||
|
||||
instanceof.type.parameter=Class or array expected
|
||||
instanceof.illegal.generic.type=Illegal generic type for instanceof
|
||||
|
||||
@@ -808,13 +808,13 @@ final class ClassChecker {
|
||||
// 'this' can be used as an (implicit) super() qualifier
|
||||
PsiMethod[] constructors = aClass.getConstructors();
|
||||
if (constructors.length == 0) {
|
||||
myVisitor.report(JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR.create(aClass, aClass));
|
||||
myVisitor.report(JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR.create(aClass, aClass.getName() + ".this"));
|
||||
return;
|
||||
}
|
||||
for (PsiMethod constructor : constructors) {
|
||||
PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor);
|
||||
if (!JavaPsiConstructorUtil.isSuperConstructorCall(call)) {
|
||||
myVisitor.report(JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR.create(constructor, aClass));
|
||||
myVisitor.report(JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR.create(constructor, aClass.getName() + ".this"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
final class ExpressionChecker {
|
||||
private final @NotNull JavaErrorVisitor myVisitor;
|
||||
private final Map<PsiElement, PsiMethod> myInsideConstructorOfClassCache = new HashMap<>(); // null value means "cached but no corresponding ctr found"
|
||||
|
||||
ExpressionChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; }
|
||||
|
||||
@@ -1349,4 +1350,216 @@ final class ExpressionChecker {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// element -> a constructor inside which this element is contained
|
||||
private PsiMethod findSurroundingConstructor(@NotNull PsiElement entry) {
|
||||
PsiMethod result = null;
|
||||
PsiElement element;
|
||||
for (element = entry; element != null && !(element instanceof PsiFile); element = element.getParent()) {
|
||||
result = myInsideConstructorOfClassCache.get(element);
|
||||
if (result != null || myInsideConstructorOfClassCache.containsKey(element)) {
|
||||
break;
|
||||
}
|
||||
if (element instanceof PsiMethod method && method.isConstructor()) {
|
||||
result = method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (PsiElement e = entry; e != null && !(e instanceof PsiFile); e = e.getParent()) {
|
||||
myInsideConstructorOfClassCache.put(e, result);
|
||||
if (e == element) break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isOnSimpleAssignmentLeftHand(@NotNull PsiElement expr) {
|
||||
PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class);
|
||||
return parent instanceof PsiAssignmentExpression assignment &&
|
||||
JavaTokenType.EQ == assignment.getOperationTokenType() &&
|
||||
PsiTreeUtil.isAncestor(assignment.getLExpression(), expr, false);
|
||||
}
|
||||
|
||||
private static boolean isThisOrSuperReference(@Nullable PsiExpression qualifierExpression, @NotNull PsiClass aClass) {
|
||||
if (qualifierExpression == null) return true;
|
||||
if (!(qualifierExpression instanceof PsiQualifiedExpression expression)) return false;
|
||||
PsiJavaCodeReferenceElement qualifier = expression.getQualifier();
|
||||
if (qualifier == null) return true;
|
||||
PsiElement resolved = qualifier.resolve();
|
||||
return resolved instanceof PsiClass && InheritanceUtil.isInheritorOrSelf(aClass, (PsiClass)resolved, true);
|
||||
}
|
||||
|
||||
void checkMemberReferencedBeforeConstructorCalled(@NotNull PsiElement expression, @Nullable PsiElement resolved) {
|
||||
PsiMethod constructor = findSurroundingConstructor(expression);
|
||||
// not inside expression inside constructor
|
||||
if (constructor == null) return;
|
||||
PsiMethodCallExpression constructorCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor);
|
||||
if (constructorCall == null) return;
|
||||
if (expression.getTextOffset() > constructorCall.getTextOffset() + constructorCall.getTextLength()) return;
|
||||
// is in or before this() or super() call
|
||||
|
||||
PsiClass referencedClass;
|
||||
String resolvedName;
|
||||
PsiElement parent = expression.getParent();
|
||||
if (expression instanceof PsiJavaCodeReferenceElement referenceElement) {
|
||||
// redirected ctr
|
||||
if (PsiKeyword.THIS.equals(referenceElement.getReferenceName())
|
||||
&& resolved instanceof PsiMethod psiMethod
|
||||
&& psiMethod.isConstructor()) {
|
||||
return;
|
||||
}
|
||||
PsiElement qualifier = referenceElement.getQualifier();
|
||||
referencedClass = PsiUtil.resolveClassInType(qualifier instanceof PsiExpression psiExpression ? psiExpression.getType() : null);
|
||||
|
||||
boolean isSuperCall = JavaPsiConstructorUtil.isSuperConstructorCall(parent);
|
||||
if (resolved == null && isSuperCall) {
|
||||
if (qualifier instanceof PsiReferenceExpression referenceExpression) {
|
||||
resolved = referenceExpression.resolve();
|
||||
expression = qualifier;
|
||||
referencedClass = PsiUtil.resolveClassInType(referenceExpression.getType());
|
||||
}
|
||||
else if (qualifier == null) {
|
||||
resolved = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiMember.class);
|
||||
if (resolved instanceof PsiMethod psiMethod) {
|
||||
referencedClass = psiMethod.getContainingClass();
|
||||
}
|
||||
}
|
||||
else if (qualifier instanceof PsiThisExpression thisExpression) {
|
||||
referencedClass = PsiUtil.resolveClassInType(thisExpression.getType());
|
||||
}
|
||||
}
|
||||
if (resolved instanceof PsiField field) {
|
||||
if (field.hasModifierProperty(PsiModifier.STATIC)) return;
|
||||
if (myVisitor.isApplicable(JavaFeature.STATEMENTS_BEFORE_SUPER) &&
|
||||
myVisitor.languageLevel() != LanguageLevel.JDK_22_PREVIEW &&
|
||||
isOnSimpleAssignmentLeftHand(expression) &&
|
||||
field.getContainingClass() == PsiTreeUtil.getParentOfType(expression, PsiClass.class, PsiLambdaExpression.class)) {
|
||||
if (field.hasInitializer()) {
|
||||
myVisitor.report(JavaErrorKinds.FIELD_INITIALIZED_BEFORE_CONSTRUCTOR_CALL.create(expression, field));
|
||||
}
|
||||
return;
|
||||
}
|
||||
resolvedName =
|
||||
PsiFormatUtil.formatVariable(field, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY);
|
||||
referencedClass = field.getContainingClass();
|
||||
}
|
||||
else if (resolved instanceof PsiMethod method) {
|
||||
if (method.hasModifierProperty(PsiModifier.STATIC)) return;
|
||||
PsiElement nameElement =
|
||||
expression instanceof PsiThisExpression ? expression : ((PsiJavaCodeReferenceElement)expression).getReferenceNameElement();
|
||||
String name = nameElement == null ? null : nameElement.getText();
|
||||
if (isSuperCall) {
|
||||
if (referencedClass == null) return;
|
||||
if (qualifier == null) {
|
||||
PsiClass superClass = referencedClass.getSuperClass();
|
||||
if (superClass != null
|
||||
&& PsiUtil.isInnerClass(superClass)
|
||||
&& InheritanceUtil.isInheritorOrSelf(referencedClass, superClass.getContainingClass(), true)) {
|
||||
// by default super() is considered "this"-qualified
|
||||
resolvedName = PsiKeyword.THIS;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
resolvedName = qualifier.getText();
|
||||
}
|
||||
}
|
||||
else if (PsiKeyword.THIS.equals(name)) {
|
||||
resolvedName = PsiKeyword.THIS;
|
||||
}
|
||||
else {
|
||||
resolvedName = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY,
|
||||
PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, 0);
|
||||
if (referencedClass == null) referencedClass = method.getContainingClass();
|
||||
}
|
||||
}
|
||||
else if (resolved instanceof PsiClass aClass) {
|
||||
if (expression instanceof PsiReferenceExpression) return;
|
||||
if (aClass.hasModifierProperty(PsiModifier.STATIC)) return;
|
||||
referencedClass = aClass.getContainingClass();
|
||||
if (referencedClass == null) return;
|
||||
resolvedName = PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (expression instanceof PsiQualifiedExpression qualifiedExpression) {
|
||||
referencedClass = PsiUtil.resolveClassInType(qualifiedExpression.getType());
|
||||
String keyword = expression instanceof PsiThisExpression ? PsiKeyword.THIS : PsiKeyword.SUPER;
|
||||
PsiJavaCodeReferenceElement qualifier = qualifiedExpression.getQualifier();
|
||||
resolvedName = qualifier != null && qualifier.resolve() instanceof PsiClass aClass
|
||||
? PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME) + "." + keyword
|
||||
: keyword;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (referencedClass == null ||
|
||||
PsiTreeUtil.getParentOfType(expression, PsiReferenceParameterList.class, true, PsiExpression.class) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiClass parentClass = constructor.getContainingClass();
|
||||
if (parentClass == null) return;
|
||||
|
||||
// references to private methods from the outer class are not calls to super methods
|
||||
// even if the outer class is the superclass
|
||||
if (resolved instanceof PsiMember member && member.hasModifierProperty(PsiModifier.PRIVATE) && referencedClass != parentClass) return;
|
||||
// field or method should be declared in this class or super
|
||||
if (!InheritanceUtil.isInheritorOrSelf(parentClass, referencedClass, true)) return;
|
||||
// and point to our instance
|
||||
if (expression instanceof PsiReferenceExpression ref) {
|
||||
PsiExpression qualifier = ref.getQualifierExpression();
|
||||
if (!isThisOrSuperReference(qualifier, parentClass)) return;
|
||||
else if (qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) {
|
||||
if (((PsiQualifiedExpression)qualifier).getQualifier() != null) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (expression instanceof PsiThisExpression && referencedClass != parentClass) return;
|
||||
|
||||
if (expression instanceof PsiJavaCodeReferenceElement) {
|
||||
if (!parentClass.equals(PsiTreeUtil.getParentOfType(expression, PsiClass.class)) &&
|
||||
PsiTreeUtil.getParentOfType(expression, PsiTypeElement.class) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PsiTreeUtil.getParentOfType(expression, PsiClassObjectAccessExpression.class) != null) return;
|
||||
|
||||
if (parent instanceof PsiNewExpression newExpression &&
|
||||
newExpression.isArrayCreation() &&
|
||||
newExpression.getClassOrAnonymousClassReference() == expression) {
|
||||
return;
|
||||
}
|
||||
if (parent instanceof PsiThisExpression || parent instanceof PsiSuperExpression) return;
|
||||
}
|
||||
if (!(expression instanceof PsiThisExpression) && !(expression instanceof PsiSuperExpression) ||
|
||||
((PsiQualifiedExpression)expression).getQualifier() == null) {
|
||||
PsiClass expressionClass = PsiTreeUtil.getParentOfType(expression, PsiClass.class, true);
|
||||
while (expressionClass != null && parentClass != expressionClass) {
|
||||
if (InheritanceUtil.isInheritorOrSelf(expressionClass, referencedClass, true)) return;
|
||||
expressionClass = PsiTreeUtil.getParentOfType(expressionClass, PsiClass.class, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (expression instanceof PsiThisExpression) {
|
||||
LanguageLevel languageLevel = PsiUtil.getLanguageLevel(expression);
|
||||
if (JavaFeature.STATEMENTS_BEFORE_SUPER.isSufficient(languageLevel) && languageLevel != LanguageLevel.JDK_22_PREVIEW) {
|
||||
parent = PsiUtil.skipParenthesizedExprUp(parent);
|
||||
if (isOnSimpleAssignmentLeftHand(parent) &&
|
||||
parent instanceof PsiReferenceExpression ref &&
|
||||
ref.resolve() instanceof PsiField field &&
|
||||
field.getContainingClass() == PsiTreeUtil.getParentOfType(expression, PsiClass.class, PsiLambdaExpression.class)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var kind = resolved instanceof PsiMethod ?
|
||||
JavaErrorKinds.CALL_MEMBER_BEFORE_CONSTRUCTOR :
|
||||
JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR;
|
||||
myVisitor.report(kind.create(expression, resolvedName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -957,6 +957,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
if (!hasErrorResults()) myGenericsChecker.checkGenericCannotExtendException(psiAnonymousClass);
|
||||
}
|
||||
if (!hasErrorResults() && resolved instanceof PsiClass psiClass) myExpressionChecker.checkRestrictedIdentifierReference(ref, psiClass);
|
||||
if (!hasErrorResults()) myExpressionChecker.checkMemberReferencedBeforeConstructorCalled(ref, resolved);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1177,6 +1178,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
@Override
|
||||
public void visitSuperExpression(@NotNull PsiSuperExpression expr) {
|
||||
myExpressionChecker.checkSuperExpressionInIllegalContext(expr);
|
||||
if (!hasErrorResults()) myExpressionChecker.checkMemberReferencedBeforeConstructorCalled(expr, null);
|
||||
if (!hasErrorResults()) visitExpression(expr);
|
||||
}
|
||||
|
||||
@@ -1184,6 +1186,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
public void visitThisExpression(@NotNull PsiThisExpression expr) {
|
||||
if (!(expr.getParent() instanceof PsiReceiverParameter)) {
|
||||
myExpressionChecker.checkThisExpressionInIllegalContext(expr);
|
||||
if (!hasErrorResults()) myExpressionChecker.checkMemberReferencedBeforeConstructorCalled(expr, null);
|
||||
if (!hasErrorResults()) visitExpression(expr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,10 +936,10 @@ public final class JavaErrorKinds {
|
||||
parameterized(PsiJavaCodeReferenceElement.class, PsiTypeParameter.class, "new.expression.type.parameter")
|
||||
.withRawDescription((ref, typeParameter) -> message("new.expression.type.parameter", formatClass(typeParameter)));
|
||||
|
||||
public static final Parameterized<PsiMember, PsiClass> REFERENCE_MEMBER_BEFORE_CONSTRUCTOR =
|
||||
parameterized(PsiMember.class, PsiClass.class, "reference.member.before.constructor")
|
||||
.withRange((psi, cls) -> getMemberDeclarationTextRange(psi))
|
||||
.withRawDescription((psi, cls) -> message("reference.member.before.constructor", cls.getName() + ".this"));
|
||||
public static final Parameterized<PsiElement, String> REFERENCE_MEMBER_BEFORE_CONSTRUCTOR =
|
||||
parameterized(PsiElement.class, String.class, "reference.member.before.constructor")
|
||||
.withRange((psi, refName) -> getRange(psi))
|
||||
.withRawDescription((psi, refName) -> message("reference.member.before.constructor", refName));
|
||||
public static final Parameterized<PsiReferenceParameterList, PsiClass> REFERENCE_TYPE_ARGUMENT_STATIC_CLASS =
|
||||
parameterized(PsiReferenceParameterList.class, PsiClass.class, "reference.type.argument.static.class")
|
||||
.withRawDescription((list, cls) -> message("reference.type.argument.static.class", formatClass(cls)));
|
||||
@@ -1146,6 +1146,10 @@ public final class JavaErrorKinds {
|
||||
error(PsiMethodCallExpression.class, "call.constructor.recursive");
|
||||
public static final Simple<PsiMethodCallExpression> CALL_CONSTRUCTOR_RECORD_IN_CANONICAL =
|
||||
error(PsiMethodCallExpression.class, "call.constructor.record.in.canonical");
|
||||
public static final Parameterized<PsiElement, String> CALL_MEMBER_BEFORE_CONSTRUCTOR =
|
||||
parameterized(PsiElement.class, String.class, "call.member.before.constructor")
|
||||
.withRange((psi, refName) -> getRange(psi))
|
||||
.withRawDescription((psi, refName) -> message("call.member.before.constructor", refName));
|
||||
|
||||
public static final Simple<PsiExpression> STRING_TEMPLATE_VOID_NOT_ALLOWED_IN_EMBEDDED =
|
||||
error("string.template.void.not.allowed.in.embedded");
|
||||
@@ -1245,6 +1249,12 @@ public final class JavaErrorKinds {
|
||||
error(PsiField.class, "field.not.initialized")
|
||||
.withRange(JavaErrorFormatUtil::getFieldDeclarationTextRange)
|
||||
.withRawDescription(var -> message("field.not.initialized", var.getName()));
|
||||
public static final Parameterized<PsiElement, PsiField> FIELD_INITIALIZED_BEFORE_CONSTRUCTOR_CALL =
|
||||
parameterized(PsiElement.class, PsiField.class, "field.initialized.before.constructor.call")
|
||||
.withRawDescription((psi, field) -> message(
|
||||
"field.initialized.before.constructor.call",
|
||||
PsiFormatUtil.formatVariable(field, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY)));
|
||||
|
||||
public static final Parameterized<PsiReferenceExpression, PsiVariable> VARIABLE_NOT_INITIALIZED =
|
||||
parameterized(PsiReferenceExpression.class, PsiVariable.class, "variable.not.initialized")
|
||||
.withRawDescription((ref, var) -> message("variable.not.initialized", var.getName()));
|
||||
|
||||
Reference in New Issue
Block a user