[java] IDEA-327241 Enable compilation and running Java projects with configured unsupported preview versions

GitOrigin-RevId: df85f51ac1d4249ec3baf34f5ce7d150e06d2a13
This commit is contained in:
Tagir Valeev
2023-12-09 13:20:33 +00:00
committed by intellij-monorepo-bot
parent 3d075de8e5
commit 2a4fc312fb
18 changed files with 237 additions and 34 deletions
@@ -664,6 +664,7 @@ label.select.project.type.to.configure=Select project type to configure
language.level.combo.lts.versions=LTS Versions
language.level.combo.other.versions=Other Versions
language.level.combo.experimental.versions=Experimental Versions
language.level.combo.unsupported.versions=Unsupported Versions
project.or.module.jdk.misconfigured={0, choice, 0#Project|1#Module} JDK is misconfigured
notification.group.build.script.found=Build script found
@@ -11,12 +11,16 @@ import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.popup.ListSeparator
import com.intellij.pom.java.AcceptedLanguageLevelsSettings
import com.intellij.pom.java.LanguageLevel
import com.intellij.ui.ColoredText
import com.intellij.ui.GroupedComboBoxRenderer
import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.ArrayUtil
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
import javax.swing.DefaultComboBoxModel
@OptIn(ExperimentalStdlibApi::class)
abstract class LanguageLevelCombo @JvmOverloads constructor(
defaultItem: @Nls String?,
// It could be an open method but since we use it in constructor we have a leaking this problem
@@ -37,13 +41,22 @@ abstract class LanguageLevelCombo @JvmOverloads constructor(
val otherItems = items.filterAndAdd(buildList {
LanguageLevel.entries
.sortedBy { it.toJavaVersion().feature }
.filter { level: LanguageLevel -> level <= highestWithPreview && (level.isPreview || !ArrayUtil.contains(level, *LTS)) }
.filter { level: LanguageLevel -> level <= highestWithPreview && !level.isUnsupported &&
(level.isPreview || !ArrayUtil.contains(level, *LTS)) }
.forEach { level: LanguageLevel -> add(level) }
})
val experimentalItems = items.filterAndAdd(buildList {
for (level in LanguageLevel.entries) {
if (level > highestWithPreview) {
if (level > highestWithPreview && !level.isUnsupported) {
add(level)
}
}
})
val unsupportedItems = items.filterAndAdd(buildList {
for (level in LanguageLevel.entries) {
if (level.isUnsupported) {
add(level)
}
}
@@ -52,7 +65,8 @@ abstract class LanguageLevelCombo @JvmOverloads constructor(
val separatorsMap: Map<Any?, ListSeparator> = listOf(
ltsItems.firstOrNull() to ListSeparator(JavaUiBundle.message("language.level.combo.lts.versions")),
otherItems.firstOrNull() to ListSeparator(JavaUiBundle.message("language.level.combo.other.versions")),
experimentalItems.firstOrNull() to ListSeparator(JavaUiBundle.message("language.level.combo.experimental.versions"))
experimentalItems.firstOrNull() to ListSeparator(JavaUiBundle.message("language.level.combo.experimental.versions")),
unsupportedItems.firstOrNull() to ListSeparator(JavaUiBundle.message("language.level.combo.unsupported.versions")),
).filter { it.first != null }.toMap()
isSwingPopup = false
@@ -64,6 +78,14 @@ abstract class LanguageLevelCombo @JvmOverloads constructor(
else -> ""
}
override fun customize(item: SimpleColoredComponent, value: Any, index: Int, isSelected: Boolean, cellHasFocus: Boolean) {
if (value is LanguageLevel && value.isUnsupported) {
item.append(ColoredText.singleFragment(value.presentableText, SimpleTextAttributes.ERROR_ATTRIBUTES))
} else {
super.customize(item, value, index, isSelected, cellHasFocus)
}
}
override fun separatorFor(value: Any): ListSeparator? = separatorsMap[value]
}
}
@@ -25,13 +25,20 @@ import static com.intellij.reference.SoftReference.dereference;
public final class LanguageLevelUtil {
/**
* Returns explicitly specified custom language level for {@code module}, or {@code null} if the module uses 'Project default' language level
* Returns explicitly specified custom language level for {@code module}, or {@code null} if the module uses 'Project default' language level.
* May return {@linkplain LanguageLevel#isUnsupported() unsupported} language level.
* @param module to get the language level for.
*/
public static @Nullable LanguageLevel getCustomLanguageLevel(@NotNull Module module) {
LanguageLevelModuleExtension moduleExtension = ModuleRootManager.getInstance(module).getModuleExtension(LanguageLevelModuleExtension.class);
return moduleExtension != null ? moduleExtension.getLanguageLevel() : null;
}
/**
* Returns effective language level for the module (either custom module level, or project level if module level is not specified).
* May return {@linkplain LanguageLevel#isUnsupported() unsupported} language level.
* @param module to get the language level for.
*/
@NotNull
public static LanguageLevel getEffectiveLanguageLevel(@NotNull final Module module) {
ApplicationManager.getApplication().assertReadAccessAllowed();
@@ -73,7 +80,7 @@ public final class LanguageLevelUtil {
@Nullable
public static String getShortMessage(@NotNull LanguageLevel languageLevel) {
return ourPresentableShortMessage.get(languageLevel);
return ourPresentableShortMessage.get(languageLevel.getSupportedLevel());
}
/**
@@ -136,7 +143,9 @@ public final class LanguageLevelUtil {
if (forbiddenApi == null) return null;
if (forbiddenApi.contains(signature)) return languageLevel;
if (languageLevel.compareTo(LanguageLevel.HIGHEST) == 0) return null;
LanguageLevel nextLanguageLevel = LanguageLevel.values()[languageLevel.ordinal() + 1];
LanguageLevel[] values = LanguageLevel.values();
if (languageLevel.ordinal() == values.length - 1) return null;
LanguageLevel nextLanguageLevel = values[languageLevel.ordinal() + 1];
return getLastIncompatibleLanguageLevelForSignature(signature, nextLanguageLevel);
}
@@ -121,6 +121,7 @@ public enum HighlightingFeature {
}
boolean isSufficient(@NotNull LanguageLevel useSiteLevel) {
useSiteLevel = useSiteLevel.getSupportedLevel();
return useSiteLevel.isAtLeast(level) &&
(!level.isPreview() || useSiteLevel.isPreview());
}
@@ -128,6 +129,7 @@ public enum HighlightingFeature {
boolean isLimited() {
return false;
}
/**
* Override if feature was preview and then accepted as standard
*/
@@ -138,6 +138,7 @@ jdk.21.preview.language.level.description=21 (Preview) - String templates, unnam
jdk.22.language.level.description=22 - Unnamed variables and patterns
jdk.22.preview.language.level.description=22 (Preview) - Statements before super(), string templates (2nd preview) etc.
jdk.X.language.level.description=X - Experimental features
jdk.unsupported.preview.language.level.description={0} (Preview) - Unsupported
local.class.preposition=local
method.context.display={0}() in {1}
@@ -18,6 +18,10 @@ public abstract class LanguageLevelProjectExtension {
return project.getService(LanguageLevelProjectExtension.class);
}
/**
* @return configured project language level.
* May return {@linkplain LanguageLevel#isUnsupported() unsupported} language level.
*/
@NotNull
public abstract LanguageLevel getLanguageLevel();
@@ -4,7 +4,9 @@ package com.intellij.pom.java;
import com.intellij.core.JavaPsiBundle;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.lang.JavaVersion;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -43,7 +45,19 @@ public enum LanguageLevel {
JDK_21_PREVIEW(JavaPsiBundle.messagePointer("jdk.21.preview.language.level.description"), 21),
JDK_22(JavaPsiBundle.messagePointer("jdk.22.language.level.description"), 22),
JDK_22_PREVIEW(JavaPsiBundle.messagePointer("jdk.22.preview.language.level.description"), 22),
JDK_X(JavaPsiBundle.messagePointer("jdk.X.language.level.description"), 23),;
JDK_X(JavaPsiBundle.messagePointer("jdk.X.language.level.description"), 23),
// Unsupported
// Marked as obsolete to draw attention, as they should not be normally used in code or in tests,
// except the tests that explicitly test the obsolete levels
@ApiStatus.Obsolete
JDK_17_PREVIEW(17, JDK_20_PREVIEW),
@ApiStatus.Obsolete
JDK_18_PREVIEW(18, JDK_20_PREVIEW),
@ApiStatus.Obsolete
JDK_19_PREVIEW(19, JDK_20_PREVIEW),
;
/**
* Should point to the latest released JDK.
@@ -53,10 +67,23 @@ public enum LanguageLevel {
private final Supplier<@Nls String> myPresentableText;
private final JavaVersion myVersion;
private final boolean myPreview;
private final @Nullable LanguageLevel myAlias;
LanguageLevel(Supplier<@Nls String> presentableTextSupplier, int major) {
this(presentableTextSupplier, major, null);
}
LanguageLevel(int major, @NotNull LanguageLevel alias) {
this(JavaPsiBundle.messagePointer("jdk.unsupported.preview.language.level.description", major), major, alias);
}
LanguageLevel(Supplier<@Nls String> presentableTextSupplier, int major, @Nullable LanguageLevel alias) {
myPresentableText = presentableTextSupplier;
myVersion = JavaVersion.compose(major);
if (alias != null && alias.isUnsupported()) {
throw new IllegalArgumentException("Cannot alias to unsupported version");
}
myAlias = alias;
myPreview = name().endsWith("_PREVIEW") || name().endsWith("_X");
}
@@ -64,6 +91,24 @@ public enum LanguageLevel {
return myPreview;
}
/**
* @return true if this language level is not supported anymore. It's still possible to invoke compiler or launch the program
* using this language level. However, it's not guaranteed that the code insight features will work correctly.
* All the code insight features will use the {@linkplain #getSupportedLevel() alias level} instead
*/
public boolean isUnsupported() {
return myAlias != null;
}
/**
* @return the closest supported language level for the unsupported level;
* returns this for supported level
*/
@ApiStatus.Experimental
public @NotNull LanguageLevel getSupportedLevel() {
return myAlias == null ? this : myAlias;
}
/**
* @return corresponding preview level, or {@code null} if level has no paired preview level
*/
@@ -77,6 +122,14 @@ public enum LanguageLevel {
}
}
/**
* @return corresponding non-preview level; this if this level is non-preview already
*/
public @NotNull LanguageLevel getNonPreviewLevel() {
if (!myPreview) return this;
return valueOf(StringUtil.substringBefore(name(), "_PREVIEW"));
}
@NotNull
@Nls
public String getPresentableText() {
@@ -2,6 +2,7 @@
package com.intellij.codeInspection;
import com.intellij.java.JavaBundle;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
@@ -94,7 +95,7 @@ public class CapturingCleanerInspection extends AbstractBaseJavaLocalInspectionT
}
if (runnableExpr instanceof PsiNewExpression newExpression) {
if (newExpression.getAnonymousClass() != null) {
if (PsiUtil.isLanguageLevel18OrHigher(trackedClass)) {
if (PsiUtil.getDeclaredLanguageLevel(trackedClass).isAtLeast(LanguageLevel.JDK_18)) {
PsiElement elementCapturingThis = getLambdaOrInnerClassElementCapturingThis(newExpression, trackedClass);
if (elementCapturingThis != null) {
return elementCapturingThis;
@@ -109,7 +110,8 @@ public class CapturingCleanerInspection extends AbstractBaseJavaLocalInspectionT
if (aClass == null) return null;
if (aClass.getContainingClass() != trackedClass) return null;
if (aClass.hasModifierProperty(PsiModifier.STATIC)) return null;
if (PsiUtil.isLanguageLevel18OrHigher(trackedClass) && getLambdaOrInnerClassElementCapturingThis(newExpression, trackedClass) == null) return null;
if (PsiUtil.getDeclaredLanguageLevel(trackedClass).isAtLeast(LanguageLevel.JDK_18) &&
getLambdaOrInnerClassElementCapturingThis(newExpression, trackedClass) == null) return null;
return classReference;
}
return null;
@@ -63,25 +63,38 @@ public final class AcceptedLanguageLevelsSettings implements PersistentStateComp
showNotificationToAccept(project, unacceptedLevels, level);
}
}
if (!previewLevels.isEmpty() && !PropertiesComponent.getInstance(project).getBoolean(IGNORE_USED_PREVIEW_FEATURES, false)) {
if (!previewLevels.isEmpty()) {
LanguageLevel languageLevel = previewLevels.first();
if (languageLevel == LanguageLevel.JDK_X) {
return;
}
int previewFeature = languageLevel.toJavaVersion().feature;
PREVIEW_NOTIFICATION_GROUP.createNotification(
JavaBundle.message("java.preview.features.notification.title"),
JavaBundle.message("java.preview.features.warning", previewFeature + 1, previewFeature),
NotificationType.WARNING)
.addAction(new NotificationAction(IdeBundle.message("action.Anonymous.text.do.not.show.again")) {
@Override
public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
PropertiesComponent.getInstance(project).setValue(IGNORE_USED_PREVIEW_FEATURES, true);
notification.expire();
}
})
.notify(project);
if (languageLevel.isUnsupported()) {
LanguageLevel supportedLevel = languageLevel.getSupportedLevel();
String supportedLevelPresentation = supportedLevel.isPreview()
? JavaBundle.message("java.preview.level", supportedLevel.toJavaVersion().feature)
: String.valueOf(supportedLevel.toJavaVersion().feature);
PREVIEW_NOTIFICATION_GROUP.createNotification(
JavaBundle.message("java.preview.features.unsupported.title"),
JavaBundle.message("java.preview.features.unsupported", previewFeature, supportedLevelPresentation),
NotificationType.ERROR)
.notify(project);
}
else if (!PropertiesComponent.getInstance(project).getBoolean(IGNORE_USED_PREVIEW_FEATURES, false)) {
PREVIEW_NOTIFICATION_GROUP.createNotification(
JavaBundle.message("java.preview.features.notification.title"),
JavaBundle.message("java.preview.features.warning", previewFeature + 1, previewFeature),
NotificationType.WARNING)
.addAction(new NotificationAction(IdeBundle.message("action.Anonymous.text.do.not.show.again")) {
@Override
public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
PropertiesComponent.getInstance(project).setValue(IGNORE_USED_PREVIEW_FEATURES, true);
notification.expire();
}
})
.notify(project);
}
}
}
@@ -16,6 +16,7 @@
package com.siyeh.ig.memory;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
@@ -55,7 +56,7 @@ public class AnonymousInnerClassMayBeStaticInspection extends BaseInspection {
if (anonymousClass instanceof PsiEnumConstantInitializer) {
return;
}
if (PsiUtil.isLanguageLevel18OrHigher(anonymousClass) &&
if (PsiUtil.getDeclaredLanguageLevel(anonymousClass).isAtLeast(LanguageLevel.JDK_18) &&
!InheritanceUtil.isInheritor(anonymousClass, CommonClassNames.JAVA_IO_SERIALIZABLE)) {
// Since Java 18, non-serializable anonymous classes don't capture 'this' reference
return;
@@ -20,6 +20,11 @@ import org.jetbrains.annotations.Nullable;
public interface LanguageLevelModuleExtension {
void setLanguageLevel(LanguageLevel languageLevel);
/**
* Returns explicitly specified custom language level for {@code module}, or {@code null} if the module uses 'Project default' language level.
* May return {@linkplain LanguageLevel#isUnsupported() unsupported} language level.
*/
@Nullable
LanguageLevel getLanguageLevel();
}
@@ -32,10 +32,7 @@ import com.intellij.util.TimeoutUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.jetbrains.annotations.*;
import java.util.*;
import java.util.function.Predicate;
@@ -1078,12 +1075,23 @@ public final class PsiUtil extends PsiUtilCore {
return getLanguageLevel(element).isAtLeast(LanguageLevel.JDK_17);
}
public static boolean isLanguageLevel18OrHigher(@NotNull PsiElement element) {
return getLanguageLevel(element).isAtLeast(LanguageLevel.JDK_18);
}
/**
* @param element element to get Java language level for
* @return the language level. If {@linkplain LanguageLevel#isUnsupported() unsupported} language level
* is selected for project or module, this method returns the supported alias.
*/
@NotNull
public static LanguageLevel getLanguageLevel(@NotNull PsiElement element) {
return getDeclaredLanguageLevel(element).getSupportedLevel();
}
/**
* @param element element to get Java language level for
* @return the language level. May return {@linkplain LanguageLevel#isUnsupported() unsupported} level.
*/
@NotNull
@ApiStatus.Experimental
public static LanguageLevel getDeclaredLanguageLevel(@NotNull PsiElement element) {
if (element instanceof PsiDirectory) {
return JavaDirectoryService.getInstance().getLanguageLevel((PsiDirectory)element);
}
@@ -0,0 +1,19 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java;
import com.intellij.pom.java.LanguageLevel;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class LanguageLevelTest {
@Test
public void unsupported() {
@SuppressWarnings("UsagesOfObsoleteApi")
LanguageLevel jdk17Preview = LanguageLevel.JDK_17_PREVIEW;
assertTrue(jdk17Preview.isUnsupported());
assertEquals(LanguageLevel.JDK_20_PREVIEW, jdk17Preview.getSupportedLevel());
assertEquals(LanguageLevel.JDK_17, jdk17Preview.getNonPreviewLevel());
}
}
@@ -0,0 +1,49 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight;
import com.intellij.execution.CantRunException;
import com.intellij.execution.configurations.JavaParameters;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public final class UnsupportedVersionHighlightingTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_17;
}
public void testPatternInSwitch() {
//noinspection UsagesOfObsoleteApi
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_17_PREVIEW, () -> {
doTestPatternInSwitch();
});
}
private void doTestPatternInSwitch() {
myFixture.configureByText("Test.java", """
class X {
void test(Object obj) {
switch(obj) {
// Simple patterns are supported due to aliasing
case String s -> System.out.println("hello");
// guards with && are not supported anymore
case Integer i<error descr="':' or '->' expected"> </error><error descr="Unexpected token">&&</error> <error descr="Not a statement">i > 0</error> <error descr="Unexpected token">-></error> System.out.println("oops");
default -> {}
}
}
}
""");
myFixture.checkHighlighting();
JavaParameters javaParameters = new JavaParameters();
try {
javaParameters.configureByModule(getModule(), JavaParameters.CLASSES_ONLY);
}
catch (CantRunException e) {
throw new RuntimeException(e);
}
assertEquals("--enable-preview", javaParameters.getVMParametersList().getParametersString());
}
}
@@ -37,7 +37,7 @@ public class JavaElementFactoryTest extends LightJavaCodeInsightFixtureTestCase
public void testArrayClassLanguageLevel() {
for (LanguageLevel level : LanguageLevel.values()) {
assertEquals(level, PsiUtil.getLanguageLevel(myFactory.getArrayClass(level)));
assertEquals(level, PsiUtil.getDeclaredLanguageLevel(myFactory.getArrayClass(level)));
}
}
}
@@ -941,8 +941,13 @@ java.preview.features.accept.notification.link=Accept
java.preview.features.alert.title=Experimental Feature Alert
java.preview.features.legal.notice=You must accept the terms of legal notice of the beta Java specification to enable support for "{0}".<br/><br/>\
<b>The implementation of an early-draft specification developed under the Java Community Process (JCP) is made available for testing and evaluation purposes only and is not compatible with any specification of the JCP.</b>{1}
java.preview.level={0} (Preview)
java.preview.features.notification.title=Java preview features
java.preview.features.warning=Newer IDE versions may discontinue support for Java preview features. When Java {0} is released, support for the {1} (Preview) language level may be dropped.
java.preview.features.unsupported.title=Unsupported Java preview features
java.preview.features.unsupported=Java language level <b>{0} (Preview)</b> used in this project is not supported anymore. \
The code insight features will assume language level {1} instead.<br>\
It''s strongly encouraged to migrate to newer Java version or stop using preview features.
java.terms.exception=exception
java.terms.region=region
java.terms.instance.initializer=instance initializer
@@ -28,7 +28,13 @@ public enum LanguageLevel {
JDK_20(20), JDK_20_PREVIEW(20),
JDK_21(21), JDK_21_PREVIEW(21),
JDK_22(22), JDK_22_PREVIEW(22),
JDK_X(23);
JDK_X(23),
// Unsupported in IDE
JDK_17_PREVIEW(17),
JDK_18_PREVIEW(18),
JDK_19_PREVIEW(19),
;
public static final LanguageLevel HIGHEST = JDK_21;
@@ -167,7 +167,10 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
val sourcePsi = sourceNode.sourcePsi ?: return
if (target !is PsiMember) return
val module = ModuleUtilCore.findModuleForPsiElement(sourcePsi) ?: return
val languageLevel = getEffectiveLanguageLevel(module)
var languageLevel = getEffectiveLanguageLevel(module)
if (languageLevel.isUnsupported) {
languageLevel = languageLevel.nonPreviewLevel
}
val lastIncompatibleLevel = LanguageLevelUtil.getLastIncompatibleLanguageLevel(target, languageLevel)
if (lastIncompatibleLevel != null) {
if (shouldReportSinceLevelForElement(lastIncompatibleLevel, sourcePsi) == true) return