mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Error highlighting for records: record constructors (except compact) (8.10.4) IDEA-228460
Also ChangeModifierIntention: do not suggest to change canonical constructor access modifier Also AddExceptionToThrowsFix: do not suggest to add throws to canonical constructor GitOrigin-RevId: 43c21af324c35a6b65633ab8cc3882bb972f993f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
cc130f3daa
commit
811b8fca50
+62
-12
@@ -1271,17 +1271,22 @@ public class HighlightMethodUtil {
|
||||
return info;
|
||||
}
|
||||
|
||||
static HighlightInfo checkConstructorCallMustBeFirstStatement(@NotNull PsiMethodCallExpression methodCall) {
|
||||
static HighlightInfo checkConstructorCallProblems(@NotNull PsiMethodCallExpression methodCall) {
|
||||
if (!JavaPsiConstructorUtil.isConstructorCall(methodCall)) return null;
|
||||
PsiElement codeBlock = methodCall.getParent().getParent();
|
||||
if (codeBlock instanceof PsiCodeBlock
|
||||
&& codeBlock.getParent() instanceof PsiMethod
|
||||
&& ((PsiMethod)codeBlock.getParent()).isConstructor()) {
|
||||
PsiElement prevSibling = methodCall.getParent().getPrevSibling();
|
||||
while (true) {
|
||||
if (prevSibling == null) return null;
|
||||
if (prevSibling instanceof PsiStatement) break;
|
||||
prevSibling = prevSibling.getPrevSibling();
|
||||
if (codeBlock instanceof PsiCodeBlock) {
|
||||
PsiMethod ctor = ObjectUtils.tryCast(codeBlock.getParent(), PsiMethod.class);
|
||||
if (ctor != null && ctor.isConstructor()) {
|
||||
if (JavaPsiRecordUtil.isCanonicalConstructor(ctor)) {
|
||||
String message = JavaErrorMessages.message("record.constructor.call.in.canonical");
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(methodCall).descriptionAndTooltip(message).create();
|
||||
}
|
||||
PsiElement prevSibling = methodCall.getParent().getPrevSibling();
|
||||
while (true) {
|
||||
if (prevSibling == null) return null;
|
||||
if (prevSibling instanceof PsiStatement) break;
|
||||
prevSibling = prevSibling.getPrevSibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
PsiReferenceExpression expression = methodCall.getMethodExpression();
|
||||
@@ -1926,17 +1931,62 @@ public class HighlightMethodUtil {
|
||||
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMethodReturnFix(method, componentType, false));
|
||||
return info;
|
||||
}
|
||||
return checkRecordSpecialMethodDeclaration(method, JavaErrorMessages.message("record.accessor"));
|
||||
}
|
||||
|
||||
public static HighlightInfo checkRecordConstructorDeclaration(PsiMethod method) {
|
||||
if (!method.isConstructor()) return null;
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null || !aClass.isRecord()) return null;
|
||||
if (JavaPsiRecordUtil.isCanonicalConstructor(method)) {
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
PsiRecordComponent[] components = aClass.getRecordComponents();
|
||||
assert parameters.length == components.length;
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
PsiType componentType = components[i].getType();
|
||||
PsiType parameterType = parameters[i].getType();
|
||||
if (!parameterType.equals(componentType)) {
|
||||
String message =
|
||||
JavaErrorMessages.message("record.canonical.constructor.wrong.parameter.type", components[i].getName(),
|
||||
componentType.getPresentableText(), parameterType.getPresentableText());
|
||||
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(
|
||||
Objects.requireNonNull(parameters[i].getTypeElement())).descriptionAndTooltip(message).create();
|
||||
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMethodParameterTypeFix(method, i, componentType, false));
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return checkRecordSpecialMethodDeclaration(method, JavaErrorMessages.message("record.canonical.constructor"));
|
||||
}
|
||||
else {
|
||||
// Non-canonical constructor
|
||||
PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(method);
|
||||
if (call == null || JavaPsiConstructorUtil.isSuperConstructorCall(call)) {
|
||||
PsiIdentifier identifier = method.getNameIdentifier();
|
||||
if (identifier != null) {
|
||||
String message = JavaErrorMessages.message("record.no.constructor.call.in.non.canonical");
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier)
|
||||
.descriptionAndTooltip(message).create();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static HighlightInfo checkRecordSpecialMethodDeclaration(PsiMethod method, String methodTitle) {
|
||||
PsiIdentifier identifier = method.getNameIdentifier();
|
||||
if (identifier == null) return null;
|
||||
PsiTypeParameterList typeParameterList = method.getTypeParameterList();
|
||||
if (typeParameterList != null && typeParameterList.getTypeParameters().length > 0) {
|
||||
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeParameterList)
|
||||
.descriptionAndTooltip(JavaErrorMessages.message("record.accessor.type.parameters"))
|
||||
.descriptionAndTooltip(JavaErrorMessages.message("record.special.method.type.parameters", methodTitle))
|
||||
.create();
|
||||
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createDeleteFix(typeParameterList));
|
||||
return info;
|
||||
}
|
||||
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) {
|
||||
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier)
|
||||
.descriptionAndTooltip(JavaErrorMessages.message("record.accessor.non.public"))
|
||||
.descriptionAndTooltip(JavaErrorMessages.message("record.special.method.non.public", methodTitle))
|
||||
.create();
|
||||
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.PUBLIC, true, false));
|
||||
return info;
|
||||
@@ -1944,7 +1994,7 @@ public class HighlightMethodUtil {
|
||||
PsiReferenceList throwsList = method.getThrowsList();
|
||||
if (throwsList.getReferenceElements().length > 0) {
|
||||
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(throwsList)
|
||||
.descriptionAndTooltip(JavaErrorMessages.message("record.accessor.throws"))
|
||||
.descriptionAndTooltip(JavaErrorMessages.message("record.special.method.throws", methodTitle))
|
||||
.create();
|
||||
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createDeleteFix(throwsList));
|
||||
return info;
|
||||
|
||||
+2
-1
@@ -882,6 +882,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkRecursiveConstructorInvocation(method));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkSafeVarargsAnnotation(method, myLanguageLevel));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkRecordAccessorDeclaration(method));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkRecordConstructorDeclaration(method));
|
||||
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if (!myHolder.hasErrorResults() && method.isConstructor()) {
|
||||
@@ -962,7 +963,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
catch (IndexNotReadyException ignored) { }
|
||||
}
|
||||
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkConstructorCallMustBeFirstStatement(expression));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkConstructorCallProblems(expression));
|
||||
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkSuperAbstractMethodDirectCall(expression));
|
||||
|
||||
if (!myHolder.hasErrorResults()) visitExpression(expression);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.psi.util;
|
||||
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiRecordComponent;
|
||||
import com.intellij.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -29,4 +27,23 @@ public class JavaPsiRecordUtil {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param method to check
|
||||
* @return true if given method is a canonical constructor for a record class
|
||||
*/
|
||||
public static boolean isCanonicalConstructor(@NotNull PsiMethod method) {
|
||||
if (!method.isConstructor()) return false;
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null || !aClass.isRecord()) return false;
|
||||
PsiRecordComponent[] components = aClass.getRecordComponents();
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
if (components.length != parameters.length) return false;
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
PsiType componentType = components[i].getType();
|
||||
PsiType parameterType = parameters[i].getType();
|
||||
if (!TypeConversionUtil.erasure(componentType).equals(TypeConversionUtil.erasure(parameterType))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,6 +927,7 @@ public class ExceptionUtil {
|
||||
* @return true if given method can declare thrown exceptions, according to the specification; false otherwise
|
||||
*/
|
||||
public static boolean canDeclareThrownExceptions(@NotNull PsiMethod method) {
|
||||
return JavaPsiRecordUtil.getRecordComponentForAccessor(method) == null;
|
||||
return JavaPsiRecordUtil.getRecordComponentForAccessor(method) == null &&
|
||||
!JavaPsiRecordUtil.isCanonicalConstructor(method);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,9 +475,14 @@ record.component.restricted.name=Illegal record component name ''{0}''
|
||||
record.instance.initializer=Instance initializer is not allowed in record
|
||||
record.instance.field=Instance field is not allowed in record
|
||||
record.accessor.wrong.return.type=Incorrect component accessor return type. Expected: ''{0}'', found: ''{1}''
|
||||
record.accessor.type.parameters=Record component accessor cannot have type parameters
|
||||
record.accessor.non.public=Record component accessor must be 'public'
|
||||
record.accessor.throws=Record component accessor cannot declare thrown exceptions
|
||||
record.canonical.constructor.wrong.parameter.type=Incorrect parameter type for record component ''{0}''. Expected: ''{1}'', found: ''{2}''
|
||||
record.constructor.call.in.canonical=Canonical constructor cannot delegate to another constructor
|
||||
record.no.constructor.call.in.non.canonical=Non-canonical record constructor must delegate to another constructor
|
||||
record.special.method.type.parameters={0} cannot have type parameters
|
||||
record.special.method.non.public={0} must be ''public''
|
||||
record.special.method.throws={0} cannot declare thrown exceptions
|
||||
record.canonical.constructor=Canonical constructor
|
||||
record.accessor=Record component accessor
|
||||
|
||||
feature.generics=Generics
|
||||
feature.annotations=Annotations
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
record NotPublic(int x, int y) {
|
||||
<error descr="Canonical constructor must be 'public'">NotPublic</error>(int x, int y) {this.x = x; this.y = y;}
|
||||
NotPublic() {this(0,0);}
|
||||
}
|
||||
record Generic(String x) {
|
||||
public <error descr="Canonical constructor cannot have type parameters"><T></error> Generic(String x) {this.x = x;}
|
||||
public <T> Generic() {this("");}
|
||||
}
|
||||
record Throws() {
|
||||
public Throws() <error descr="Canonical constructor cannot declare thrown exceptions">throws Throwable</error> {}
|
||||
public Throws(int x) throws Throwable { this(); }
|
||||
}
|
||||
record TypeMismatch<T>(T t) {
|
||||
public TypeMismatch(<error descr="Incorrect parameter type for record component 't'. Expected: 'T', found: 'Object'">Object</error> t) {
|
||||
this.t = null;
|
||||
}
|
||||
}
|
||||
record Delegate(int x) {
|
||||
public Delegate(int x) {
|
||||
<error descr="Canonical constructor cannot delegate to another constructor">this()</error>;
|
||||
}
|
||||
|
||||
public <error descr="Non-canonical record constructor must delegate to another constructor">Delegate</error>() {
|
||||
}
|
||||
|
||||
public <error descr="Non-canonical record constructor must delegate to another constructor">Delegate</error>(int x, int y) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
+3
@@ -24,6 +24,9 @@ public class LightRecordsHighlightingTest extends LightJavaCodeInsightFixtureTes
|
||||
public void testRecordAccessors() {
|
||||
doTest();
|
||||
}
|
||||
public void testRecordConstructors() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTest() {
|
||||
myFixture.configureByFile(getTestName(false) + ".java");
|
||||
|
||||
@@ -130,7 +130,8 @@ public class ChangeModifierIntention extends BaseElementAtCaretIntentionAction {
|
||||
}
|
||||
if (member instanceof PsiMethod) {
|
||||
if (containingClass == null || containingClass.isEnum() && ((PsiMethod)member).isConstructor()) return Collections.emptyList();
|
||||
if (JavaPsiRecordUtil.getRecordComponentForAccessor((PsiMethod)member) != null) {
|
||||
if (JavaPsiRecordUtil.getRecordComponentForAccessor((PsiMethod)member) != null ||
|
||||
JavaPsiRecordUtil.isCanonicalConstructor((PsiMethod)member)) {
|
||||
return Collections.singletonList(AccessModifier.PUBLIC);
|
||||
}
|
||||
if (containingClass.isInterface()) {
|
||||
|
||||
Reference in New Issue
Block a user