From 3a52f8601c32163afcba8b6ee35c59ccf99305f8 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Wed, 2 Jul 2025 14:56:41 +0200 Subject: [PATCH] [java] IDEA-371865 Inspection to convert 'System.out'<->'IO' GitOrigin-RevId: fdb87ffc06388bd022224000c7b4e8d9e2013e9d --- ...citToImplicitClassMigrationInspection.java | 58 +++++-- .../MigrateFromJavaLangIoInspection.java | 104 +++++++++++++ .../MigrateToJavaLangIoInspection.java | 143 ++++++++++++++++++ .../resources/META-INF/JavaPlugin.xml | 12 ++ .../ExplicitToImplicitClassMigration.html | 8 +- .../MigrateFromJavaLangIo.html | 18 +++ .../MigrateToJavaLangIo.html | 20 +++ .../afterCaretAtClass.java | 2 +- .../afterExtendsObject.java | 2 +- .../afterSimple.java | 2 +- .../afterSimpleWithComments.java | 2 +- .../afterWithEnhancedMain.java | 2 +- .../afterWithEnhancedMain2.java | 2 +- .../afterWithImportConflict.java | 2 +- ...erWithImportConflictDemandsOverModule.java | 2 +- .../afterWithPackage.java | 2 +- .../afterWithSeveralIO.java | 7 + .../beforeWithSeveralIO.java | 9 ++ .../afterPrintUnresolved.java | 3 + .../afterPrintlnResolved.java | 5 + .../beforePrintUnresolved.java | 3 + .../beforePrintlnResolved.java | 5 + .../afterPrintRegularClass.java | 5 + .../afterPrintlnImplicitClass.java | 3 + .../beforePrintRegularClass.java | 5 + .../migrateToJavaLangIo/beforePrintf.java | 3 + .../beforePrintlnImplicitClass.java | 3 + ...lassMigrationInspectionInspectionTest.java | 3 + .../MigrateFromJavaLangIoInspectionTest.java | 36 +++++ .../MigrateToJavaLangIoInspectionTest.java | 62 ++++++++ .../resources/messages/JavaBundle.properties | 9 ++ 31 files changed, 522 insertions(+), 20 deletions(-) create mode 100644 java/java-impl-inspections/src/com/intellij/codeInspection/MigrateFromJavaLangIoInspection.java create mode 100644 java/java-impl-inspections/src/com/intellij/codeInspection/MigrateToJavaLangIoInspection.java create mode 100644 java/java-impl/resources/inspectionDescriptions/MigrateFromJavaLangIo.html create mode 100644 java/java-impl/resources/inspectionDescriptions/MigrateToJavaLangIo.html create mode 100644 java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithSeveralIO.java create mode 100644 java/java-tests/testData/inspection/explicitToImplicitClassMigration/beforeWithSeveralIO.java create mode 100644 java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintUnresolved.java create mode 100644 java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintlnResolved.java create mode 100644 java/java-tests/testData/inspection/migrateFromJavaLangIo/beforePrintUnresolved.java create mode 100644 java/java-tests/testData/inspection/migrateFromJavaLangIo/beforePrintlnResolved.java create mode 100644 java/java-tests/testData/inspection/migrateToJavaLangIo/afterPrintRegularClass.java create mode 100644 java/java-tests/testData/inspection/migrateToJavaLangIo/afterPrintlnImplicitClass.java create mode 100644 java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintRegularClass.java create mode 100644 java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintf.java create mode 100644 java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintlnImplicitClass.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspectionTest.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateToJavaLangIoInspectionTest.java diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/ExplicitToImplicitClassMigrationInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/ExplicitToImplicitClassMigrationInspection.java index 4163454459a0..c95bee259ada 100644 --- a/java/java-impl-inspections/src/com/intellij/codeInspection/ExplicitToImplicitClassMigrationInspection.java +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/ExplicitToImplicitClassMigrationInspection.java @@ -3,6 +3,7 @@ package com.intellij.codeInspection; import com.intellij.codeInsight.TestFrameworks; import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil; +import com.intellij.codeInspection.options.OptPane; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.java.JavaBundle; import com.intellij.java.codeserver.core.JavaPsiModuleUtil; @@ -22,6 +23,7 @@ import com.intellij.psi.search.SearchScope; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.PsiMethodUtil; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; import com.intellij.util.ObjectUtils; import com.siyeh.ig.psiutils.CommentTracker; import com.siyeh.ig.psiutils.VariableAccessUtils; @@ -35,11 +37,21 @@ import java.util.Set; public final class ExplicitToImplicitClassMigrationInspection extends AbstractBaseJavaLocalInspectionTool { + public boolean convertToIo = true; + @Override public @NotNull Set<@NotNull JavaFeature> requiredFeatures() { return Set.of(JavaFeature.IMPLICIT_CLASSES); } + @Override + public @NotNull OptPane getOptionsPane() { + return OptPane.pane( + OptPane.checkbox("convertToIo", + JavaBundle.message("inspection.explicit.to.implicit.convert.to.io")) + ); + } + @Override public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { return new JavaElementVisitor() { @@ -159,14 +171,14 @@ public final class ExplicitToImplicitClassMigrationInspection extends AbstractBa if (underRoot) { holder.registerProblem(aClass, new TextRange(0, classIdentifier.getTextRangeInParent().getEndOffset()), JavaBundle.message("inspection.explicit.to.implicit.class.migration.name"), - new ReplaceWithImplicitClassFix()); + new ReplaceWithImplicitClassFix(convertToIo)); } else { holder.registerProblem(aClass, JavaBundle.message("inspection.explicit.to.implicit.class.migration.name"), ProblemHighlightType.INFORMATION, new TextRange(0, classIdentifier.getTextRangeInParent().getEndOffset()), - new ReplaceWithImplicitClassFix()); + new ReplaceWithImplicitClassFix(convertToIo)); } } @@ -181,6 +193,10 @@ public final class ExplicitToImplicitClassMigrationInspection extends AbstractBa private static class ReplaceWithImplicitClassFix extends ModCommandQuickFix { + private final boolean myConvertToIo; + + private ReplaceWithImplicitClassFix(boolean io) { myConvertToIo = io; } + @Override public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getFamilyName() { return JavaBundle.message("inspection.explicit.to.implicit.class.migration.fix.name"); @@ -206,7 +222,7 @@ public final class ExplicitToImplicitClassMigrationInspection extends AbstractBa ); } - private static @NotNull ModCommandAction getCommandActionWithMovingToRoot(@NotNull Project project) { + private @NotNull ModCommandAction getCommandActionWithMovingToRoot(@NotNull Project project) { return new ModCommandAction() { @Override public Presentation getPresentation(@NotNull ActionContext context) { @@ -243,7 +259,7 @@ public final class ExplicitToImplicitClassMigrationInspection extends AbstractBa }; } - private static void applyFix(@NotNull Project project, @NotNull PsiElement element) { + private void applyFix(@NotNull Project project, @NotNull PsiElement element) { PsiFile containingFile = element.getContainingFile(); if (!(containingFile instanceof PsiJavaFile javaFile)) { return; @@ -281,9 +297,9 @@ public final class ExplicitToImplicitClassMigrationInspection extends AbstractBa CommentTracker tracker = new CommentTracker(); String body = tracker.rangeText(lBrace.getNextSibling(), rBrace.getPrevSibling()); PsiImplicitClass newClass = PsiElementFactory.getInstance(project).createImplicitClassFromText(body, psiClass); - if(!(newClass.getContainingFile() instanceof PsiJavaFile dummyFile) || - dummyFile.getImportList() == null || - javaFile.getImportList() == null) { + if (!(newClass.getContainingFile() instanceof PsiJavaFile dummyFile) || + dummyFile.getImportList() == null || + javaFile.getImportList() == null) { return; } //it is necessary to resolve accurately inside a new implicit class @@ -299,8 +315,32 @@ public final class ExplicitToImplicitClassMigrationInspection extends AbstractBa cleanMainMethod(implicitClass); PsiFile replacedContainingFile = replaced.getContainingFile(); - if (replacedContainingFile != null) { - JavaCodeStyleManager.getInstance(project).optimizeImports(replacedContainingFile); + if (replacedContainingFile == null) return; + + if (myConvertToIo && PsiUtil.isAvailable(JavaFeature.JAVA_LANG_IO, replacedContainingFile)) { + convertToIOMethods(replacedContainingFile); + } + + JavaCodeStyleManager.getInstance(project).optimizeImports(replacedContainingFile); + } + + private static void convertToIOMethods(@NotNull PsiFile file) { + if (!(file instanceof PsiJavaFile javaFile)) { + return; + } + List systemOutPrints = new ArrayList<>(); + javaFile.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) { + if (MigrateToJavaLangIoInspection.isSystemOutPrintln(expression)) { + systemOutPrints.add(expression); + return; + } + super.visitMethodCallExpression(expression); + } + }); + for (PsiMethodCallExpression print : systemOutPrints) { + MigrateToJavaLangIoInspection.replaceToIO(print); } } diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateFromJavaLangIoInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateFromJavaLangIoInspection.java new file mode 100644 index 000000000000..3690c8ad0494 --- /dev/null +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateFromJavaLangIoInspection.java @@ -0,0 +1,104 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInspection; + +import com.intellij.java.JavaBundle; +import com.intellij.modcommand.ModPsiUpdater; +import com.intellij.modcommand.PsiUpdateModCommandQuickFix; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.CommentTracker; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; +import java.util.stream.Collectors; + +public final class MigrateFromJavaLangIoInspection extends AbstractBaseJavaLocalInspectionTool { + + private static final CallMatcher IO_PRINT = + CallMatcher.anyOf( + CallMatcher.staticCall("java.lang.IO", "println").parameterCount(0), + CallMatcher.staticCall("java.lang.IO", "println", "print").parameterCount(1) + ); + + private static final Set IO_PRINT_NAMES = IO_PRINT.names().collect(Collectors.toSet()); + + @Override + public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + + @Override + public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) { + super.visitMethodCallExpression(expression); + + String referenceName = expression.getMethodExpression().getReferenceName(); + if (referenceName == null) return; + if (!isIOPrint(expression)) return; + + PsiReferenceExpression methodExpression = expression.getMethodExpression(); + holder.registerProblem(methodExpression, + JavaBundle.message("inspection.migrate.from.java.lang.io.name"), + new ConvertIOToSystemOutFix(referenceName)); + } + }; + } + + private static class ConvertIOToSystemOutFix extends PsiUpdateModCommandQuickFix { + + @NotNull + private final String methodName; + + private ConvertIOToSystemOutFix(@NotNull String name) { methodName = name; } + + @Override + public @NotNull String getFamilyName() { + return JavaBundle.message("inspection.migrate.from.java.lang.io.fix.family"); + } + + @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); + } + } + + static void replaceToSystemOut(@NotNull PsiMethodCallExpression methodCall) { + PsiReferenceExpression methodExpr = methodCall.getMethodExpression(); + String methodName = methodExpr.getReferenceName(); + if (methodName == null) return; + PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + StringBuilder replacement = new StringBuilder("System.out.").append(methodName).append("("); + if (arguments.length == 1) { + replacement.append(arguments[0].getText()); + } + replacement.append(')'); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(methodCall.getProject()); + PsiExpression expr = factory.createExpressionFromText(replacement.toString(), methodCall); + new CommentTracker().replace(methodCall, expr); + } + + private static boolean isIOPrint(@NotNull PsiMethodCallExpression expression) { + boolean isResolvedIO = IO_PRINT.test(expression); + if (isResolvedIO) return true; + String name = expression.getMethodExpression().getReferenceName(); + if (!IO_PRINT_NAMES.contains(name)) return false; + PsiExpression[] args = expression.getArgumentList().getExpressions(); + if (!(args.length == 0 || args.length == 1)) return false; + PsiMethod method = expression.resolveMethod(); + if (method != null) return false; + PsiExpression qualifierExpression = expression.getMethodExpression().getQualifierExpression(); + if (!(qualifierExpression instanceof PsiReferenceExpression qualifierRefExpression)) return false; + if (qualifierRefExpression.getQualifierExpression() != null) return false; + String referenceName = qualifierRefExpression.getReferenceName(); + if (!"IO".equals(referenceName)) return false; + PsiElement resolvedQualifier = qualifierRefExpression.resolve(); + if (resolvedQualifier != null) return false; + return true; + } +} diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateToJavaLangIoInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateToJavaLangIoInspection.java new file mode 100644 index 000000000000..d369e233ba7b --- /dev/null +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/MigrateToJavaLangIoInspection.java @@ -0,0 +1,143 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInspection; + +import com.intellij.codeInspection.options.OptPane; +import com.intellij.java.JavaBundle; +import com.intellij.modcommand.ModPsiUpdater; +import com.intellij.modcommand.PsiUpdateModCommandQuickFix; +import com.intellij.openapi.project.Project; +import com.intellij.pom.java.JavaFeature; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.CommentTracker; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +public final class MigrateToJavaLangIoInspection extends AbstractBaseJavaLocalInspectionTool { + + public boolean processImplicitClasses = true; + public boolean processOrdinaryClasses = true; + + private static final CallMatcher PRINT_STREAM_PRINT = + CallMatcher.anyOf( + CallMatcher.instanceCall("java.io.PrintStream", "println").parameterCount(0), + CallMatcher.instanceCall("java.io.PrintStream", "println", "print").parameterCount(1) + ); + + + @Override + public @NotNull Set<@NotNull JavaFeature> requiredFeatures() { + return Set.of(JavaFeature.JAVA_LANG_IO); + } + + @Override + public @NotNull OptPane getOptionsPane() { + return OptPane.pane( + OptPane.checkbox("processOrdinaryClasses", + JavaBundle.message("inspection.migrate.to.java.lang.io.option.ordinary")), + OptPane.checkbox("processImplicitClasses", + JavaBundle.message("inspection.migrate.to.java.lang.io.option.implicit")) + ); + } + + @Override + public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + @Override + public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) { + super.visitMethodCallExpression(expression); + + String referenceName = expression.getMethodExpression().getReferenceName(); + if (referenceName == null) return; + if (!isSystemOutPrintln(expression)) return; + + PsiClass topClass = PsiTreeUtil.getTopmostParentOfType(expression, PsiClass.class); + if (topClass == null) return; + boolean applicableForClass = isApplicableForClass(topClass); + + PsiReferenceExpression methodExpression = expression.getMethodExpression(); + if (applicableForClass) { + holder.registerProblem(methodExpression, + JavaBundle.message("inspection.migrate.to.java.lang.io.name"), + new ConvertSystemOutPrintlnFix(referenceName)); + } + else { + holder.registerProblem(methodExpression, + JavaBundle.message("inspection.migrate.to.java.lang.io.name"), + ProblemHighlightType.INFORMATION, + new ConvertSystemOutPrintlnFix(referenceName)); + } + } + + private boolean isApplicableForClass(@Nullable PsiClass psiClass) { + if (psiClass == null) { + return false; + } + if (PsiTreeUtil.getParentOfType(psiClass, PsiClass.class, true) != null) { + return false; + } + if (psiClass instanceof PsiImplicitClass) { + return processImplicitClasses; + } + return processOrdinaryClasses; + } + }; + } + + private static class ConvertSystemOutPrintlnFix extends PsiUpdateModCommandQuickFix { + + @NotNull + private final String methodName; + + private ConvertSystemOutPrintlnFix(@NotNull String name) { methodName = name; } + + @Override + public @NotNull String getFamilyName() { + return JavaBundle.message("inspection.migrate.to.java.lang.io.fix.family"); + } + + @Override + public @NotNull String getName() { + return JavaBundle.message("inspection.migrate.to.java.lang.io.fix.name", "IO." + methodName + "()"); + } + + @Override + protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) { + PsiElement parent = element.getParent(); + if (!(parent instanceof PsiMethodCallExpression methodCall)) return; + replaceToIO(methodCall); + } + } + + static void replaceToIO(@NotNull PsiMethodCallExpression methodCall) { + PsiReferenceExpression methodExpr = methodCall.getMethodExpression(); + String methodName = methodExpr.getReferenceName(); + if (methodName == null) return; + PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + StringBuilder replacement = new StringBuilder("IO.").append(methodName).append("("); + if (arguments.length == 1) { + replacement.append(arguments[0].getText()); + } + replacement.append(')'); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(methodCall.getProject()); + PsiExpression expr = factory.createExpressionFromText(replacement.toString(), methodCall); + new CommentTracker().replace(methodCall, expr); + } + + static boolean isSystemOutPrintln(@NotNull PsiMethodCallExpression expression) { + if (!PRINT_STREAM_PRINT.test(expression)) return false; + PsiReferenceExpression methodExpression = expression.getMethodExpression(); + PsiExpression qualifier = methodExpression.getQualifierExpression(); + if (!(qualifier instanceof PsiReferenceExpression ref)) return false; + PsiElement resolved = ref.resolve(); + if (!(resolved instanceof PsiField field)) return false; + if (!field.getName().equals("out")) return false; + PsiClass containingClass = field.getContainingClass(); + if (containingClass == null) return false; + if (!CommonClassNames.JAVA_LANG_SYSTEM.equals(containingClass.getQualifiedName())) return false; + return true; + } +} diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index d0d8dc34b378..7818d40b0957 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -1705,6 +1705,18 @@ bundle="messages.JavaBundle" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES" key="inspection.explicit.to.implicit.class.migration.name"/> + + public class Sample { public static void main(String[] args) { - String a = "Hello, world!"; + System.out.println("Hello, world!"); } }

After the quick-fix is applied:


   void main() {
-      String a = "Hello, world!";
+      IO.println("Hello, world!");
   }
 
+

Configure the inspection:

+
    +
  • Select Replace 'System.out.print()' and 'System.out.println()' with methods from 'java.lang.IO' class to convert these calls to IO methods.
  • +

New in 2024.1

\ No newline at end of file diff --git a/java/java-impl/resources/inspectionDescriptions/MigrateFromJavaLangIo.html b/java/java-impl/resources/inspectionDescriptions/MigrateFromJavaLangIo.html new file mode 100644 index 000000000000..e8b3bd7ef635 --- /dev/null +++ b/java/java-impl/resources/inspectionDescriptions/MigrateFromJavaLangIo.html @@ -0,0 +1,18 @@ + + +Reports IO.println() and IO.print() calls and suggests replacing them with System.out methods. +

+ This inspection helps migrate code from java.lang.IO class back to traditional System.out calls.

+

+ Example: +

+
IO.println("Hello, World!");
+

After the quick-fix is applied:

+
System.out.println("Hello, World!");
+ +

+ This inspection can help to downgrade for backward compatibility with earlier Java versions. +

+

New in 2025.2

+ + diff --git a/java/java-impl/resources/inspectionDescriptions/MigrateToJavaLangIo.html b/java/java-impl/resources/inspectionDescriptions/MigrateToJavaLangIo.html new file mode 100644 index 000000000000..84d0d7946863 --- /dev/null +++ b/java/java-impl/resources/inspectionDescriptions/MigrateToJavaLangIo.html @@ -0,0 +1,20 @@ + + +Reports System.out.println() and System.out.print() calls and suggests replacing them with methods from java.lang.IO class. +

+ This inspection helps migrate code to use the newer java.lang.IO class

+

+ Example: +

+
System.out.println("Hello, World!");
+

After the quick-fix is applied:

+
IO.println("Hello, World!");
+ +

Configure the inspection:

+
    +
  • Use the Report in regular classes option to report and suggest fixes in regular classes
  • +
  • Use the Report in compact source files option to report and suggest fixes in compact source files
  • +
+

New in 2025.2

+ + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterCaretAtClass.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterCaretAtClass.java index 4c089060f5d4..95026c717808 100644 --- a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterCaretAtClass.java +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterCaretAtClass.java @@ -1,3 +1,3 @@ void main() { - System.out.println("Hello, world!"); + IO.println("Hello, world!"); } diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterExtendsObject.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterExtendsObject.java index 4c089060f5d4..95026c717808 100644 --- a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterExtendsObject.java +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterExtendsObject.java @@ -1,3 +1,3 @@ void main() { - System.out.println("Hello, world!"); + IO.println("Hello, world!"); } diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterSimple.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterSimple.java index 8a68ff5fa658..a6966323350a 100644 --- a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterSimple.java +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterSimple.java @@ -1,4 +1,4 @@ void main() { - System.out.println("Hello, world!"); + IO.println("Hello, world!"); } diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterSimpleWithComments.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterSimpleWithComments.java index 501a9b0b9e67..c23b8a0a732d 100644 --- a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterSimpleWithComments.java +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterSimpleWithComments.java @@ -4,6 +4,6 @@ */ /*comments2*/ /*comments3*/ void main(/*test*/) { //comments4 - System.out.println("Hello, world!"); + IO.println("Hello, world!"); } //comments5 \ No newline at end of file diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithEnhancedMain.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithEnhancedMain.java index 4c089060f5d4..95026c717808 100644 --- a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithEnhancedMain.java +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithEnhancedMain.java @@ -1,3 +1,3 @@ void main() { - System.out.println("Hello, world!"); + IO.println("Hello, world!"); } diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithEnhancedMain2.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithEnhancedMain2.java index 4c089060f5d4..95026c717808 100644 --- a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithEnhancedMain2.java +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithEnhancedMain2.java @@ -1,3 +1,3 @@ void main() { - System.out.println("Hello, world!"); + IO.println("Hello, world!"); } diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithImportConflict.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithImportConflict.java index 5bcfb40e3884..96d7074bb83e 100644 --- a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithImportConflict.java +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithImportConflict.java @@ -2,5 +2,5 @@ import p.List; void main(String[] args) { List a = null; - System.out.println("Hello, world!" + args); + IO.println("Hello, world!" + args); } diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithImportConflictDemandsOverModule.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithImportConflictDemandsOverModule.java index e88b254611d3..3c68aa13a1ed 100644 --- a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithImportConflictDemandsOverModule.java +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithImportConflictDemandsOverModule.java @@ -2,5 +2,5 @@ import p.List; void main() { List a = null; - System.out.println("Hello, world!"); + IO.println("Hello, world!"); } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithPackage.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithPackage.java index 4c089060f5d4..95026c717808 100644 --- a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithPackage.java +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithPackage.java @@ -1,3 +1,3 @@ void main() { - System.out.println("Hello, world!"); + IO.println("Hello, world!"); } diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithSeveralIO.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithSeveralIO.java new file mode 100644 index 000000000000..4e85388db97f --- /dev/null +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/afterWithSeveralIO.java @@ -0,0 +1,7 @@ + +void main() { + IO.println("Hello, world!"); + IO.println("Hello, world!"); + IO.println("Hello, world!"); + IO.print("Hello, world!"); +} diff --git a/java/java-tests/testData/inspection/explicitToImplicitClassMigration/beforeWithSeveralIO.java b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/beforeWithSeveralIO.java new file mode 100644 index 000000000000..202ea2fbb202 --- /dev/null +++ b/java/java-tests/testData/inspection/explicitToImplicitClassMigration/beforeWithSeveralIO.java @@ -0,0 +1,9 @@ + +public class beforeWithSeveralIO { + public static void main(String[] args) { + System.out.println("Hello, world!"); + System.out.println("Hello, world!"); + System.out.println("Hello, world!"); + System.out.print("Hello, world!"); + } +} diff --git a/java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintUnresolved.java b/java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintUnresolved.java new file mode 100644 index 000000000000..e61fd3871c94 --- /dev/null +++ b/java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintUnresolved.java @@ -0,0 +1,3 @@ +void main() { +System.out.print("Hello"); +} diff --git a/java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintlnResolved.java b/java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintlnResolved.java new file mode 100644 index 000000000000..5180d2d609a1 --- /dev/null +++ b/java/java-tests/testData/inspection/migrateFromJavaLangIo/afterPrintlnResolved.java @@ -0,0 +1,5 @@ +class Test { + public static void main(String[] args) { + System.out.println("Hello"); + } +} diff --git a/java/java-tests/testData/inspection/migrateFromJavaLangIo/beforePrintUnresolved.java b/java/java-tests/testData/inspection/migrateFromJavaLangIo/beforePrintUnresolved.java new file mode 100644 index 000000000000..01dddcd81bce --- /dev/null +++ b/java/java-tests/testData/inspection/migrateFromJavaLangIo/beforePrintUnresolved.java @@ -0,0 +1,3 @@ +void main() { +IO/*some*/.print("Hello"); +} diff --git a/java/java-tests/testData/inspection/migrateFromJavaLangIo/beforePrintlnResolved.java b/java/java-tests/testData/inspection/migrateFromJavaLangIo/beforePrintlnResolved.java new file mode 100644 index 000000000000..03704a093b96 --- /dev/null +++ b/java/java-tests/testData/inspection/migrateFromJavaLangIo/beforePrintlnResolved.java @@ -0,0 +1,5 @@ +class Test { + public static void main(String[] args) { + IO.println("Hello"); + } +} diff --git a/java/java-tests/testData/inspection/migrateToJavaLangIo/afterPrintRegularClass.java b/java/java-tests/testData/inspection/migrateToJavaLangIo/afterPrintRegularClass.java new file mode 100644 index 000000000000..78d4da2bfeec --- /dev/null +++ b/java/java-tests/testData/inspection/migrateToJavaLangIo/afterPrintRegularClass.java @@ -0,0 +1,5 @@ +class Test { + public static void main(String[] args) { + IO.print("Hello"); + } +} diff --git a/java/java-tests/testData/inspection/migrateToJavaLangIo/afterPrintlnImplicitClass.java b/java/java-tests/testData/inspection/migrateToJavaLangIo/afterPrintlnImplicitClass.java new file mode 100644 index 000000000000..abe3426892b0 --- /dev/null +++ b/java/java-tests/testData/inspection/migrateToJavaLangIo/afterPrintlnImplicitClass.java @@ -0,0 +1,3 @@ +public static void main(String[] args) { + IO.println("Hello"); +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintRegularClass.java b/java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintRegularClass.java new file mode 100644 index 000000000000..99bc4b4a3990 --- /dev/null +++ b/java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintRegularClass.java @@ -0,0 +1,5 @@ +class Test { + public static void main(String[] args) { + System./*some comment*/out.print("Hello"); + } +} diff --git a/java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintf.java b/java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintf.java new file mode 100644 index 000000000000..664d0b6c3ad9 --- /dev/null +++ b/java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintf.java @@ -0,0 +1,3 @@ +public static void main(String[] args) { + System.out.printf("Hello"); +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintlnImplicitClass.java b/java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintlnImplicitClass.java new file mode 100644 index 000000000000..c629d39a89bc --- /dev/null +++ b/java/java-tests/testData/inspection/migrateToJavaLangIo/beforePrintlnImplicitClass.java @@ -0,0 +1,3 @@ +public static void main(String[] args) { + System.out.println("Hello"); +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/ExplicitToImplicitClassMigrationInspectionInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/ExplicitToImplicitClassMigrationInspectionInspectionTest.java index 750df78b8588..fd06cb93b4d3 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/ExplicitToImplicitClassMigrationInspectionInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/ExplicitToImplicitClassMigrationInspectionInspectionTest.java @@ -94,6 +94,8 @@ public class ExplicitToImplicitClassMigrationInspectionInspectionTest extends Li ); } + public void testWithSeveralIO() { doTest(); } + private void doNotFind() { myFixture.enableInspections(new ExplicitToImplicitClassMigrationInspection()); myFixture.testHighlighting(true, false, true, "before" + getTestName(false) + ".java"); @@ -103,6 +105,7 @@ public class ExplicitToImplicitClassMigrationInspectionInspectionTest extends Li } private void doTest() { + MigrateToJavaLangIoInspectionTest.addIOClass(myFixture); myFixture.enableInspections(new ExplicitToImplicitClassMigrationInspection()); myFixture.testHighlighting(true, false, true, "before" + getTestName(false) + ".java"); myFixture.checkPreviewAndLaunchAction(myFixture.findSingleIntention( diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspectionTest.java new file mode 100644 index 000000000000..441abfa0d134 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateFromJavaLangIoInspectionTest.java @@ -0,0 +1,36 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.codeInspection; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInspection.MigrateFromJavaLangIoInspection; +import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; + +public class MigrateFromJavaLangIoInspectionTest extends LightJavaCodeInsightFixtureTestCase { + + @NotNull + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return JAVA_LATEST_WITH_LATEST_JDK; + } + + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath() + "/inspection/migrateFromJavaLangIo/"; + } + + public void testPrintlnResolved() { + MigrateToJavaLangIoInspectionTest.addIOClass(myFixture); + doTest("Replace with 'System.out.println()'"); + } + + public void testPrintUnresolved() { doTest("Replace with 'System.out.print()'"); } + + private void doTest(String message) { + myFixture.enableInspections(new MigrateFromJavaLangIoInspection()); + myFixture.testHighlighting(true, true, true, "before" + getTestName(false) + ".java"); + myFixture.checkPreviewAndLaunchAction(myFixture.findSingleIntention(message)); + myFixture.checkResultByFile("after" + getTestName(false) + ".java"); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateToJavaLangIoInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateToJavaLangIoInspectionTest.java new file mode 100644 index 000000000000..a4f197df01d9 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/MigrateToJavaLangIoInspectionTest.java @@ -0,0 +1,62 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.codeInspection; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.InspectionsBundle; +import com.intellij.codeInspection.MigrateToJavaLangIoInspection; +import com.intellij.java.JavaBundle; +import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture; +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; + +public class MigrateToJavaLangIoInspectionTest extends LightJavaCodeInsightFixtureTestCase { + + @NotNull + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return JAVA_LATEST_WITH_LATEST_JDK; + } + + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath() + "/inspection/migrateToJavaLangIo/"; + } + + public void testPrintlnImplicitClass() { doTest("Replace with 'IO.println()'"); } + + public void testPrintRegularClass() { doTest("Replace with 'IO.print()'"); } + + public void testPrintf() { + doNotFind(InspectionsBundle.message("fix.all.inspection.problems.in.file", JavaBundle.message("inspection.migrate.to.java.lang.io.name"))); + } + + private void doNotFind(String message) { + addIOClass(myFixture); + MigrateToJavaLangIoInspection inspection = new MigrateToJavaLangIoInspection(); + myFixture.enableInspections(inspection); + myFixture.testHighlighting(true, true, true, "before" + getTestName(false) + ".java"); + IntentionAction intention = myFixture.getAvailableIntention(message); + assertNull(intention); + } + + private void doTest(String message) { + addIOClass(myFixture); + myFixture.enableInspections(new MigrateToJavaLangIoInspection()); + myFixture.testHighlighting(true, true, true, "before" + getTestName(false) + ".java"); + myFixture.checkPreviewAndLaunchAction(myFixture.findSingleIntention(message)); + myFixture.checkResultByFile("after" + getTestName(false) + ".java"); + } + + static void addIOClass(@NotNull JavaCodeInsightTestFixture fixture) { + fixture.addClass(""" + package java.lang; + public final class IO { + public static void println(Object obj) {} + public static void println() {} + public static void print(Object obj) {} + } + """); + } +} \ No newline at end of file diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index 1240c179b3fd..d0421f856aa7 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -733,6 +733,15 @@ inspection.explicit.to.implicit.class.migration.fix.name=Convert into compact so inspection.explicit.to.implicit.move.to.root.title=Move Simple Source File 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.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}'' +inspection.migrate.to.java.lang.io.fix.family=Use method from 'java.lang.IO' class +inspection.migrate.to.java.lang.io.option.ordinary=Report in regular classes +inspection.migrate.to.java.lang.io.option.implicit=Report in compact source files inspection.inconsistent.text.block.indent.name=Inconsistent whitespace indentation in text block inspection.inconsistent.text.block.indent.message=Text block indent consists of mixed tabs and spaces inspection.inconsistent.text.block.indent.spaces=Indent text block with spaces only