From c964a598ebf0f2734f63b14ab3e14153a219d5db Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Thu, 29 Jan 2026 10:32:48 +0100 Subject: [PATCH] [java-inspection] IDEA-382122 Convert from compact source file GitOrigin-RevId: 765719579aa0128639b69b55cc72e0967712aa45 --- .../AdditionalJavaErrorFixProvider.java | 41 ++++++++++++- ...licitClassBackwardMigrationInspection.java | 32 +++++----- .../MigrateFromJavaLangIoInspection.java | 60 ++++++++++--------- .../implicitClass21/IOToSystemOut.java | 5 ++ .../implicitClass21/IOToSystemOut_after.java | 5 ++ .../ImplicitClassToExplicit.java | 3 + .../ImplicitClassToExplicit_after.java | 5 ++ .../afterPrintUnresolved.java | 4 -- .../daemon/ImplicitClassHighlighting21Test.kt | 41 +++++++++++++ ...MigrateFromJavaLangIoInspection21Test.java | 5 +- .../MigrateFromJavaLangIoInspectionTest.java | 2 +- .../resources/messages/JavaBundle.properties | 2 +- 12 files changed, 148 insertions(+), 57 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/IOToSystemOut.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/IOToSystemOut_after.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/ImplicitClassToExplicit.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/ImplicitClassToExplicit_after.java delete mode 100644 java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintUnresolved.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlighting21Test.kt diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/AdditionalJavaErrorFixProvider.java b/java/java-impl-inspections/src/com/intellij/codeInspection/AdditionalJavaErrorFixProvider.java index 8ffe60e671c1..8adcaebb65f5 100644 --- a/java/java-impl-inspections/src/com/intellij/codeInspection/AdditionalJavaErrorFixProvider.java +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/AdditionalJavaErrorFixProvider.java @@ -5,17 +5,19 @@ import com.intellij.codeInsight.daemon.impl.analysis.AbstractJavaErrorFixProvide import com.intellij.codeInsight.daemon.impl.analysis.HighlightFixUtil; import com.intellij.codeInsight.daemon.impl.quickfix.*; import com.intellij.codeInsight.intention.CommonIntentionAction; +import com.intellij.codeInsight.intention.PriorityAction; import com.intellij.codeInspection.streamMigration.SimplifyForEachInspection; import com.intellij.core.JavaPsiBundle; import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds; +import com.intellij.pom.java.JavaFeature; import com.intellij.psi.*; +import com.intellij.psi.util.JvmMainMethodSearcher; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NotNull; import java.util.function.Consumer; -import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.UNDERSCORE_IDENTIFIER_UNNAMED; - /** * Some quick-fixes not accessible from the java.analysis module are registered here. */ @@ -25,9 +27,42 @@ public final class AdditionalJavaErrorFixProvider extends AbstractJavaErrorFixPr fix(JavaErrorKinds.VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA, error -> new VariableAccessFromInnerClassJava10Fix(error.psi())); fix(JavaErrorKinds.VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD, error -> new VariableAccessFromInnerClassJava10Fix(error.psi())); fixes(JavaErrorKinds.SYNTAX_ERROR, (error, info) -> registerErrorElementFixes(info, error.psi())); - fix(UNDERSCORE_IDENTIFIER_UNNAMED, error -> error.psi().getParent() instanceof PsiReferenceExpression ref && + fix(JavaErrorKinds.UNDERSCORE_IDENTIFIER_UNNAMED, error -> error.psi().getParent() instanceof PsiReferenceExpression ref && "_".equals(ref.getReferenceName()) ? new RenameUnderscoreFix(ref) : null); + fix(JavaErrorKinds.UNSUPPORTED_FEATURE, error -> { + if (error.context() != JavaFeature.IMPLICIT_CLASSES) return null; + PsiMember member = PsiTreeUtil.getNonStrictParentOfType(error.psi(), PsiMember.class); + if (!(member instanceof PsiMethod)) return null; + if (!(member.getContainingClass() instanceof PsiImplicitClass implicitClass)) return null; + boolean hasMainMethod = new JvmMainMethodSearcher() { + + @Override + public boolean instanceMainMethodsEnabled(@NotNull PsiElement psiElement) { + return true; + } + + @Override + protected boolean inheritedStaticMainEnabled(@NotNull PsiElement psiElement) { + return true; + } + }.hasMainMethod(implicitClass); + if (!hasMainMethod) return null; + if (PsiTreeUtil.hasErrorElements(implicitClass)) { + return null; + } + return new ImplicitToExplicitClassBackwardMigrationInspection.ReplaceWithExplicitClassFix(implicitClass); + }); + fix(JavaErrorKinds.REFERENCE_UNRESOLVED, error -> { + PsiJavaCodeReferenceElement psi = error.psi(); + if (PsiUtil.isAvailable(JavaFeature.IMPLICIT_CLASSES, psi)) return null; + if (!(psi instanceof PsiReferenceExpression)) return null; + if (!(psi.getParent() instanceof PsiReferenceExpression parentReference)) return null; + if (!(parentReference.getParent() instanceof PsiMethodCallExpression methodCallExpression)) return null; + if (!MigrateFromJavaLangIoInspection.canBeIOPrint(methodCallExpression)) return null; + return new MigrateFromJavaLangIoInspection.ConvertIOToSystemOutFix(methodCallExpression) + .withPresentation(presentation -> presentation.withPriority(PriorityAction.Priority.HIGH)); + }); } private static void registerErrorElementFixes(@NotNull Consumer info, diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/ImplicitToExplicitClassBackwardMigrationInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/ImplicitToExplicitClassBackwardMigrationInspection.java index dae201333087..88801d17c132 100644 --- a/java/java-impl-inspections/src/com/intellij/codeInspection/ImplicitToExplicitClassBackwardMigrationInspection.java +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/ImplicitToExplicitClassBackwardMigrationInspection.java @@ -3,8 +3,10 @@ package com.intellij.codeInspection; import com.intellij.codeInspection.wrongPackageStatement.AdjustPackageNameFix; import com.intellij.java.JavaBundle; +import com.intellij.modcommand.ActionContext; +import com.intellij.modcommand.ModCommandService; import com.intellij.modcommand.ModPsiUpdater; -import com.intellij.modcommand.PsiUpdateModCommandQuickFix; +import com.intellij.modcommand.PsiUpdateModCommandAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.SingleFileSourcesTracker; import com.intellij.openapi.util.Predicates; @@ -55,20 +57,25 @@ public final class ImplicitToExplicitClassBackwardMigrationInspection extends Ab if (identifier == null) { return; } + ReplaceWithExplicitClassFix fix = new ReplaceWithExplicitClassFix(aClass); + LocalQuickFix localQuickFix = ModCommandService.getInstance().wrapToQuickFix(fix); if (InspectionProjectProfileManager.isInformationLevel(getShortName(), identifier)) { TextRange textRange = TextRange.create(0, method.getParameterList().getTextRange().getEndOffset() - method.getTextRange().getStartOffset()); - holder.registerProblem(method, textRange, message, new ReplaceWithExplicitClassFix()); + holder.registerProblem(method, textRange, message, localQuickFix); } else { - holder.registerProblem(identifier, message, new ReplaceWithExplicitClassFix()); + holder.registerProblem(identifier, message, localQuickFix); } } }; } + public static class ReplaceWithExplicitClassFix extends PsiUpdateModCommandAction { - private static class ReplaceWithExplicitClassFix extends PsiUpdateModCommandQuickFix { + protected ReplaceWithExplicitClassFix(@NotNull PsiImplicitClass element) { + super(element); + } @Override public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getFamilyName() { @@ -76,24 +83,15 @@ public final class ImplicitToExplicitClassBackwardMigrationInspection extends Ab } @Override - protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) { - PsiImplicitClass implicitClass; - PsiFile originalFile = updater.getOriginalFile(element.getContainingFile()); - if (element instanceof PsiImplicitClass elementAsClass) { - implicitClass = elementAsClass; - } - else { - implicitClass = PsiTreeUtil.getParentOfType(element, PsiImplicitClass.class); - } - if (implicitClass == null) { - return; - } + protected void invoke(@NotNull ActionContext context, @NotNull PsiImplicitClass implicitClass, @NotNull ModPsiUpdater updater) { + PsiFile originalFile = updater.getOriginalFile(implicitClass.getContainingFile()); String text = implicitClass.getText(); String qualifiedName = implicitClass.getQualifiedName(); if (qualifiedName == null) { return; } - PsiClass newClass = PsiElementFactory.getInstance(element.getProject()).createClassFromText(text, implicitClass); + Project project = implicitClass.getProject(); + PsiClass newClass = PsiElementFactory.getInstance(project).createClassFromText(text, implicitClass); newClass.setName(qualifiedName); //user probably mostly wants to use it somewhere PsiModifierList modifierList = newClass.getModifierList(); diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateFromJavaLangIoInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateFromJavaLangIoInspection.java index 460bd157c127..8bc6facf337f 100644 --- a/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateFromJavaLangIoInspection.java +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateFromJavaLangIoInspection.java @@ -2,9 +2,10 @@ package com.intellij.codeInspection; import com.intellij.java.JavaBundle; +import com.intellij.modcommand.ActionContext; +import com.intellij.modcommand.ModCommandService; import com.intellij.modcommand.ModPsiUpdater; -import com.intellij.modcommand.PsiUpdateModCommandQuickFix; -import com.intellij.openapi.project.Project; +import com.intellij.modcommand.PsiUpdateModCommandAction; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.siyeh.ig.callMatcher.CallMatcher; @@ -15,15 +16,19 @@ import static com.intellij.psi.CommonClassNames.JAVA_LANG_IO; public final class MigrateFromJavaLangIoInspection extends AbstractBaseJavaLocalInspectionTool { + private static final CallMatcher CAN_BE_IO_PRINT = + CallMatcher.anyOf(CallMatcher.staticCall(JAVA_LANG_IO, "println") + .parameterCount(0) + .allowStaticUnresolved(), + CallMatcher.staticCall(JAVA_LANG_IO, "println", "print") + .parameterCount(1) + .allowStaticUnresolved()); + private static final CallMatcher IO_PRINT = - CallMatcher.anyOf( - CallMatcher.staticCall(JAVA_LANG_IO, "println") - .parameterCount(0) - .allowStaticUnresolved(), - CallMatcher.staticCall(JAVA_LANG_IO, "println", "print") - .parameterCount(1) - .allowStaticUnresolved() - ); + CallMatcher.anyOf(CallMatcher.staticCall(JAVA_LANG_IO, "println") + .parameterCount(0), + CallMatcher.staticCall(JAVA_LANG_IO, "println", "print") + .parameterCount(1)); @Override public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { @@ -38,19 +43,18 @@ public final class MigrateFromJavaLangIoInspection extends AbstractBaseJavaLocal if (!isIOPrint(expression)) return; PsiReferenceExpression methodExpression = expression.getMethodExpression(); - holder.registerProblem(methodExpression, - JavaBundle.message("inspection.migrate.from.java.lang.io.name"), - new ConvertIOToSystemOutFix(referenceName)); + ConvertIOToSystemOutFix fix = new ConvertIOToSystemOutFix(expression); + LocalQuickFix localQuickFix = ModCommandService.getInstance().wrapToQuickFix(fix); + holder.registerProblem(methodExpression, JavaBundle.message("inspection.migrate.from.java.lang.io.name"), localQuickFix); } }; } - private static class ConvertIOToSystemOutFix extends PsiUpdateModCommandQuickFix { + public static class ConvertIOToSystemOutFix extends PsiUpdateModCommandAction { - @NotNull - private final String methodName; - - private ConvertIOToSystemOutFix(@NotNull String name) { methodName = name; } + public ConvertIOToSystemOutFix(@NotNull PsiMethodCallExpression expression) { + super(expression); + } @Override public @NotNull String getFamilyName() { @@ -58,19 +62,12 @@ public final class MigrateFromJavaLangIoInspection extends AbstractBaseJavaLocal } @Override - public @NotNull String getName() { - return JavaBundle.message("inspection.migrate.from.java.lang.io.fix.name", "System.out." + methodName + "()"); - } - - @Override - protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) { - PsiElement parent = element.getParent(); - if (!(parent instanceof PsiMethodCallExpression methodCall)) return; - replaceToSystemOut(methodCall); + protected void invoke(@NotNull ActionContext context, @NotNull PsiMethodCallExpression element, @NotNull ModPsiUpdater updater) { + replaceToSystemOut(element); } } - static void replaceToSystemOut(@NotNull PsiMethodCallExpression methodCall) { + private static void replaceToSystemOut(@NotNull PsiMethodCallExpression methodCall) { PsiReferenceExpression methodExpr = methodCall.getMethodExpression(); String methodName = methodExpr.getReferenceName(); if (methodName == null) return; @@ -80,8 +77,13 @@ public final class MigrateFromJavaLangIoInspection extends AbstractBaseJavaLocal } } - private static boolean isIOPrint(@NotNull PsiMethodCallExpression expression) { + public static boolean isIOPrint(@NotNull PsiMethodCallExpression expression) { if (!IO_PRINT.test(expression)) return false; return MigrateToJavaLangIoInspection.callIOAndSystemIdentical(expression.getArgumentList()); } + + public static boolean canBeIOPrint(@NotNull PsiMethodCallExpression expression) { + if (!CAN_BE_IO_PRINT.test(expression)) return false; + return MigrateToJavaLangIoInspection.callIOAndSystemIdentical(expression.getArgumentList()); + } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/IOToSystemOut.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/IOToSystemOut.java new file mode 100644 index 000000000000..935a1a1c60da --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/IOToSystemOut.java @@ -0,0 +1,5 @@ +public class ImplicitClassToExplicit { + void main() { + IO.println("Hello World!"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/IOToSystemOut_after.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/IOToSystemOut_after.java new file mode 100644 index 000000000000..4c5612445d9b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/IOToSystemOut_after.java @@ -0,0 +1,5 @@ +public class ImplicitClassToExplicit { + void main() { + System.out.println("Hello World!"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/ImplicitClassToExplicit.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/ImplicitClassToExplicit.java new file mode 100644 index 000000000000..b149e0b22789 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/ImplicitClassToExplicit.java @@ -0,0 +1,3 @@ +void main() { + IO.println("Hello World!"); +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/ImplicitClassToExplicit_after.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/ImplicitClassToExplicit_after.java new file mode 100644 index 000000000000..4a2e26ae7e30 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass21/ImplicitClassToExplicit_after.java @@ -0,0 +1,5 @@ +public class ImplicitClassToExplicit { + void main() { + IO.println("Hello World!"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintUnresolved.java b/java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintUnresolved.java deleted file mode 100644 index 0f5ed3feb43f..000000000000 --- a/java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintUnresolved.java +++ /dev/null @@ -1,4 +0,0 @@ -void main() { - /*some*/ - System.out.print(/*some2*/"Hello"); -} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlighting21Test.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlighting21Test.kt new file mode 100644 index 000000000000..1fd8e496bd0a --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlighting21Test.kt @@ -0,0 +1,41 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.codeInsight.daemon + +import com.intellij.JavaTestUtil +import com.intellij.codeInsight.daemon.impl.quickfix.InsertMissingTokenFix +import com.intellij.codeInspection.ImplicitToExplicitClassBackwardMigrationInspection +import com.intellij.codeInspection.MigrateFromJavaLangIoInspection +import com.intellij.openapi.diagnostic.ReportingClassSubstitutor +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase + +class ImplicitClassHighlighting21Test : LightJavaCodeInsightFixtureTestCase() { + override fun getProjectDescriptor() = JAVA_11 + override fun getBasePath() = JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/implicitClass21" + + fun testImplicitClassToExplicit() { + doTest() + val availableIntentions = myFixture.availableIntentions + .mapNotNull { it.asModCommandAction() } + .filterIsInstance() + assertEquals(1, availableIntentions.size) + myFixture.launchAction(availableIntentions.first().asIntention()) + myFixture.checkResultByFile("${getTestName(false)}_after.java") + } + + + fun testIOToSystemOut() { + doTest() + val availableIntentions = myFixture.availableIntentions + .mapNotNull { it.asModCommandAction() } + .filter { (it as? ReportingClassSubstitutor)?.substitutedClass == MigrateFromJavaLangIoInspection.ConvertIOToSystemOutFix::class.java } + assertEquals(1, availableIntentions.size) + myFixture.launchAction(availableIntentions.first().asIntention()) + myFixture.checkResultByFile("${getTestName(false)}_after.java") + } + + + private fun doTest() { + myFixture.configureByFile(getTestName(false) + ".java") + myFixture.checkHighlighting() + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspection21Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspection21Test.java index 2d988ed80835..47abf084b8f2 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspection21Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspection21Test.java @@ -2,6 +2,7 @@ package com.intellij.java.codeInspection; import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.MigrateFromJavaLangIoInspection; import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; @@ -23,7 +24,7 @@ public class MigrateFromJavaLangIoInspection21Test extends LightJavaCodeInsightF public void testPrintUnresolved() { myFixture.enableInspections(new MigrateFromJavaLangIoInspection()); myFixture.testHighlighting(true, true, true, "before" + getTestName(false) + ".java"); - myFixture.checkPreviewAndLaunchAction(myFixture.findSingleIntention("Replace with 'System.out.print()'")); - myFixture.checkResultByFile("after" + getTestName(false) + ".java"); + IntentionAction intention = myFixture.getAvailableIntention("Replace with 'System.out.print()'"); + assertNull(intention); } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspectionTest.java index bbf66a64d07b..55f89024bd14 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspectionTest.java @@ -25,7 +25,7 @@ public class MigrateFromJavaLangIoInspectionTest extends LightJavaCodeInsightFix } public void testPrintlnResolved() { - doTest("Replace with 'System.out.println()'"); + doTest("Replace with 'System.out' method"); } public void testPrintArrayChar() { diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index 82a25f05d619..732597025048 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -747,7 +747,7 @@ inspection.explicit.to.implicit.move.to.root.move=Move under the project root inspection.explicit.to.implicit.move.to.root.delete.package=Don't move, only delete package statement inspection.explicit.to.implicit.convert.to.io=Replace 'System.out.print()' and 'System.out.println()' with methods from 'java.lang.IO' class inspection.migrate.from.java.lang.io.name=Method from 'System.out' can be used instead of 'java.lang.IO' -inspection.migrate.from.java.lang.io.fix.family=Replace with 'System.out' methods +inspection.migrate.from.java.lang.io.fix.family=Replace with 'System.out' method inspection.migrate.from.java.lang.io.fix.name=Replace with ''{0}'' inspection.migrate.to.java.lang.io.name=Method from 'java.lang.IO' class can be used inspection.migrate.to.java.lang.io.fix.name=Replace with ''{0}''