Record constructors access level for Java 15 (IDEA-239088)

GitOrigin-RevId: b8adab8548bcea21d2053606e058495e888199b7
This commit is contained in:
Tagir Valeev
2020-06-19 14:11:10 +03:00
committed by intellij-monorepo-bot
parent c074d04b51
commit 547ebddae3
15 changed files with 175 additions and 8 deletions
@@ -1366,7 +1366,7 @@ public class HighlightMethodUtil {
* instance method overrides static. see JLS 8.4.6.1, 8.4.6.2
*/
static HighlightInfo checkStaticMethodOverride(@NotNull PsiMethod method, @NotNull PsiFile containingFile) {
// constructors are not members and therefor don't override class methods
// constructors are not members and therefore don't override class methods
if (method.isConstructor()) {
return null;
}
@@ -2038,7 +2038,21 @@ public class HighlightMethodUtil {
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createDeleteFix(typeParameterList));
return info;
}
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) {
if (method.isConstructor() && PsiUtil.getLanguageLevel(method) != LanguageLevel.JDK_14_PREVIEW) {
AccessModifier modifier = AccessModifier.fromModifierList(method.getModifierList());
PsiModifierList classModifierList = Objects.requireNonNull(method.getContainingClass()).getModifierList();
if (classModifierList != null) {
AccessModifier classModifier = AccessModifier.fromModifierList(classModifierList);
if (classModifier.isWeaker(modifier)) {
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier)
.descriptionAndTooltip(JavaErrorBundle.message("record.special.method.stronger.access", methodTitle, classModifier))
.create();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(
method, classModifier.toPsiModifier(), true, false));
return info;
}
}
} else if (!method.hasModifierProperty(PsiModifier.PUBLIC)) {
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier)
.descriptionAndTooltip(JavaErrorBundle.message("record.special.method.non.public", methodTitle))
.create();
@@ -3,11 +3,14 @@ package com.intellij.codeInsight.generation;
import com.intellij.codeInsight.AnnotationTargetUtil;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.util.AccessModifier;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.util.PsiFormatUtilBase;
import com.intellij.psi.util.PsiUtil;
import com.intellij.ui.SimpleColoredComponent;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
@@ -47,18 +50,26 @@ public class RecordConstructorMember implements ClassMember {
@NotNull
public PsiMethod generateRecordConstructor() {
String constructor;
AccessModifier accessModifier = AccessModifier.PUBLIC;
if (PsiUtil.getLanguageLevel(myRecord) != LanguageLevel.JDK_14_PREVIEW) {
PsiModifierList list = myRecord.getModifierList();
if (list != null) {
accessModifier = AccessModifier.fromModifierList(list);
}
}
if (myCompact) {
constructor = "public " + myRecord.getName() + "{\n}";
constructor = myRecord.getName() + "{\n}";
}
else {
PsiRecordComponent[] components = myRecord.getRecordComponents();
String parameters = StreamEx.of(components).map(PsiRecordComponent::getText).joining(",", "(", ")");
String body =
StreamEx.of(components).map(PsiRecordComponent::getName).map(name -> "this." + name + "=" + name + ";\n").joining("", "{", "}");
constructor = "public " + myRecord.getName() + parameters + body;
constructor = myRecord.getName() + parameters + body;
}
Project project = myRecord.getProject();
PsiMethod ctor = JavaPsiFacade.getElementFactory(project).createMethodFromText(constructor, myRecord);
ctor.getModifierList().setModifierProperty(accessModifier.toPsiModifier(), true);
if (!myCompact) {
JavaCodeStyleSettings settings = JavaCodeStyleSettings.getInstance(myRecord.getContainingFile());
boolean finalParameters = settings.isGenerateFinalParameters();
@@ -1,6 +1,7 @@
// Copyright 2000-2020 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.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightRecordCanonicalConstructor;
import com.intellij.util.containers.ContainerUtil;
@@ -115,10 +116,19 @@ public enum AccessModifier {
if (member instanceof PsiMethod) {
PsiMethod method = (PsiMethod)member;
if (containingClass == null || containingClass.isEnum() && method.isConstructor()) return Collections.emptyList();
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) != null ||
JavaPsiRecordUtil.isCompactConstructor(method) ||
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) != null) {
return Collections.singletonList(PUBLIC);
}
if (JavaPsiRecordUtil.isCompactConstructor(method) ||
JavaPsiRecordUtil.isExplicitCanonicalConstructor(method) ||
method instanceof LightRecordCanonicalConstructor) {
if (PsiUtil.getLanguageLevel(member) != LanguageLevel.JDK_14_PREVIEW) {
PsiModifierList list = containingClass.getModifierList();
if (list != null) {
AccessModifier classModifier = fromModifierList(list);
return ContainerUtil.filter(ALL_MODIFIERS, m -> !classModifier.isWeaker(m));
}
}
return Collections.singletonList(PUBLIC);
}
if (containingClass.isInterface()) {
@@ -423,6 +423,7 @@ record.constructor.call.in.canonical=Canonical constructor cannot delegate to an
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.stronger.access={0} access level cannot be stronger than the record access level (''{1}'')
record.special.method.throws={0} cannot declare thrown exceptions
record.canonical.constructor=Canonical constructor
record.compact.constructor=Compact constructor
@@ -0,0 +1,65 @@
class Outer {
public record PublicRecord1() {
public PublicRecord1 {}
}
public record PublicRecord2() {
protected <error descr="Canonical constructor access level cannot be stronger than the record access level ('public')">PublicRecord2</error>() {}
}
public record PublicRecord3() {
<error descr="Canonical constructor access level cannot be stronger than the record access level ('public')">PublicRecord3</error>() {}
}
public record PublicRecord4() {
private <error descr="Compact constructor access level cannot be stronger than the record access level ('public')">PublicRecord4</error> {}
}
protected record ProtectedRecord1() {
public ProtectedRecord1 {}
}
protected record ProtectedRecord2() {
protected ProtectedRecord2() {}
}
protected record ProtectedRecord3() {
<error descr="Canonical constructor access level cannot be stronger than the record access level ('protected')">ProtectedRecord3</error>() {}
}
protected record ProtectedRecord4() {
private <error descr="Compact constructor access level cannot be stronger than the record access level ('protected')">ProtectedRecord4</error> {}
}
record PackageRecord1() {
public PackageRecord1 {}
}
record PackageRecord2() {
protected PackageRecord2() {}
}
record PackageRecord3() {
PackageRecord3() {}
}
record PackageRecord4() {
private <error descr="Compact constructor access level cannot be stronger than the record access level ('package-private')">PackageRecord4</error> {}
}
private record PrivateRecord1() {
public PrivateRecord1 {}
}
private record PrivateRecord2() {
protected PrivateRecord2() {}
}
private record PrivateRecord3() {
PrivateRecord3() {}
}
private record PrivateRecord4() {
private PrivateRecord4 {}
}
void test() {
record LocalRecord1() {
public LocalRecord1 {}
}
record LocalRecord2() {
protected LocalRecord2() {}
}
record LocalRecord3() {
LocalRecord3() {}
}
record LocalRecord4() {
private <error descr="Compact constructor access level cannot be stronger than the record access level ('package-private')">LocalRecord4</error> {}
}
}
}
@@ -32,6 +32,9 @@ public class LightRecordsHighlightingTest extends LightJavaCodeInsightFixtureTes
public void testRecordConstructors() {
doTest();
}
public void testRecordConstructorAccessJava15() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_15_PREVIEW, this::doTest);
}
public void testRecordCompactConstructors() {
doTest();
}
@@ -17,8 +17,10 @@ package com.siyeh.ig.classlayout;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
@@ -104,9 +106,10 @@ public class PublicConstructorInNonPublicClassInspection extends BaseInspection
if (containingClass == null) {
return;
}
if (containingClass.isRecord() &&
if (containingClass.isRecord() && PsiUtil.getLanguageLevel(containingClass) == LanguageLevel.JDK_14_PREVIEW &&
(JavaPsiRecordUtil.isCompactConstructor(method) || JavaPsiRecordUtil.isExplicitCanonicalConstructor(method))) {
// compact and canonical constructors in record must be public, according to spec
// compact and canonical constructors in record must be public, according to Java 14-preview spec
// this restriction is relaxed in Java 15-preview, so the inspection makes sense again
return;
}
if (containingClass.hasModifierProperty(PsiModifier.PUBLIC) ||
@@ -0,0 +1,18 @@
package com.siyeh.igtest.classlayout.public_constructor_in_non_public_class;
record Rec() {
<warning descr="Constructor is declared 'public' in non-public class 'Rec'">public</warning> Rec {}
<warning descr="Constructor is declared 'public' in non-public class 'Rec'">public</warning> Rec(int x) {
this();
System.out.println(x);
}
}
record Rec2(int x) {
<warning descr="Constructor is declared 'public' in non-public class 'Rec2'">public</warning> Rec2() {
this(0);
}
<warning descr="Constructor is declared 'public' in non-public class 'Rec2'">public</warning> Rec2(int x) {
System.out.println(x);
this.x = x;
}
}
@@ -2,6 +2,8 @@
package com.siyeh.ig.classlayout;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import com.siyeh.ig.LightJavaInspectionTestCase;
@@ -27,6 +29,9 @@ public class PublicConstructorInNonPublicClassInspectionTest extends LightJavaCo
public void testPublicConstructorInNonPublicClass() {
doTest();
}
public void testRecordsJava15() {
IdeaTestUtil.withLevel(myFixture.getModule(), LanguageLevel.JDK_15_PREVIEW, this::doTest);
}
public void testQuickfix() {
doTest();
@@ -0,0 +1,5 @@
class Outer {
protected record Foo() {
<caret>public Foo {}
}
}
@@ -0,0 +1,5 @@
class Outer {
protected record Foo() {
<caret>public Foo {}
}
}
@@ -0,0 +1,5 @@
class Outer {
protected record Foo() {
<caret>protected Foo {}
}
}
@@ -0,0 +1,5 @@
class Outer {
private record Foo() {
<caret>protected Foo {}
}
}
@@ -0,0 +1,5 @@
class Outer {
private record Foo() {
<caret>public Foo {}
}
}
@@ -81,6 +81,18 @@ public class ChangeModifierIntentionTest extends IPPTestCase {
BaseRefactoringProcessor.ConflictsInTestsException.withIgnoredConflicts(() -> doTestWithChooser("protected"));
}
public void testRecordConstructor1() {
IdeaTestUtil.withLevel(myFixture.getModule(), LanguageLevel.JDK_15_PREVIEW, () -> doTest("Make 'Foo' protected"));
}
public void testRecordConstructor1Java14() {
IdeaTestUtil.withLevel(myFixture.getModule(), LanguageLevel.JDK_14_PREVIEW, () -> assertIntentionNotAvailable("Make 'Foo' protected"));
}
public void testRecordConstructor2() {
IdeaTestUtil.withLevel(myFixture.getModule(), LanguageLevel.JDK_15_PREVIEW, () -> doTestWithChooser("public"));
}
void doTestWithChooser(String wanted) {
UiInterceptors
.register(new ChooserInterceptor(Arrays.asList("public", "protected", "package-private", "private"), Pattern.quote(wanted)));