From 8d26a8af63e8e83676cbfeb6e37ea4bd533e5246 Mon Sep 17 00:00:00 2001 From: "romain.birling" Date: Wed, 4 Feb 2026 13:57:24 +0100 Subject: [PATCH 1/5] Add test reproducer and fix --- .../IoPrintlnUsageCheckCompactSample.java | 20 +++++++++++++++++++ .../checks/IoPrintlnUsageCheckSample.java | 14 +++++++++++++ .../java/checks/SystemOutOrErrUsageCheck.java | 10 +++++++++- .../checks/SystemOutOrErrUsageCheckTest.java | 20 +++++++++++++++++-- 4 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckCompactSample.java create mode 100644 java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckSample.java diff --git a/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckCompactSample.java b/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckCompactSample.java new file mode 100644 index 0000000000..974efeb49a --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckCompactSample.java @@ -0,0 +1,20 @@ +import java.io.IO; + +void main() { + IO.println(""); // Compliant + IO.print(""); // Compliant + f(); + new A().f(); +} + +void f() { + IO.println(""); // Compliant + IO.print(""); // Compliant +} + +class A { + void f() { + IO.println(""); // Compliant + IO.print(""); // Compliant + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckSample.java new file mode 100644 index 0000000000..275bc47a71 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckSample.java @@ -0,0 +1,14 @@ +package checks; + + +import java.io.IO; +import java.io.PrintStream; + +class IoPrintlnUsageCheckSample { + + void f() { + IO.println(""); // Noncompliant {{Replace this use of IO.println by a logger.}} + IO.print(""); // Noncompliant {{Replace this use of IO.print by a logger.}} + } + +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java index db2a53d2bf..6aa07fa4c0 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java @@ -53,11 +53,19 @@ public void visitNode(Tree tree) { private void visitMemberSelectExpression(MemberSelectExpressionTree mset) { String name = mset.identifier().name(); - if ("out".equals(name) && isSystem(mset.expression())) { reportIssue(mset, "Replace this use of System.out by a logger."); } else if ("err".equals(name) && isSystem(mset.expression())) { reportIssue(mset, "Replace this use of System.err by a logger."); + } else { + boolean isIoFunction = mset.expression().parent() instanceof MemberSelectExpressionTree tree && + tree.expression() instanceof IdentifierTree identifierTree && + "IO".equals(identifierTree.name()); + // use contains to cover both print and println methods + boolean isIoPrintFunction = isIoFunction && mset.identifier().name().contains("print"); + if (isIoPrintFunction) { + reportIssue(mset, "Replace this use of IO." + mset.identifier().name() + " by a logger."); + } } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java index 81b19e26b6..c322a66f8e 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java @@ -23,7 +23,7 @@ class SystemOutOrErrUsageCheckTest { @Test - void test() { + void test_sout() { CheckVerifier.newVerifier() .onFile(mainCodeSourcesPath("checks/SystemOutOrErrUsageCheckSample.java")) .withCheck(new SystemOutOrErrUsageCheck()) @@ -31,13 +31,29 @@ void test() { } @Test - void test_compact_source_file() { + void test_sout_compact_source_file() { CheckVerifier.newVerifier() .onFile(mainCodeSourcesPath("checks/SystemOutOrErrUsageCheckCompactOnlyMainSample.java")) .withCheck(new SystemOutOrErrUsageCheck()) .verifyNoIssues(); } + @Test + void test_io() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/IoPrintlnUsageCheckSample.java")) + .withCheck(new SystemOutOrErrUsageCheck()) + .verifyIssues(); + } + + @Test + void test_io_compact_source_file() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/IoPrintlnUsageCheckCompactSample.java")) + .withCheck(new SystemOutOrErrUsageCheck()) + .verifyNoIssues(); + } + @Test void test_compact_source_file_with_regular_class() { CheckVerifier.newVerifier() From 018f8cc78624d5a30e78176d5b4787891aaeb6c3 Mon Sep 17 00:00:00 2001 From: "romain.birling" Date: Wed, 4 Feb 2026 14:35:36 +0100 Subject: [PATCH 2/5] Fix cannot find symbol: class IO location: package java.io --- .../IoPrintlnUsageCheckCompactSample.java | 20 +++++++++++++++++++ .../checks/IoPrintlnUsageCheckSample.java | 14 +++++++++++++ .../checks/SystemOutOrErrUsageCheckTest.java | 5 +++-- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckCompactSample.java create mode 100644 java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckSample.java diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckCompactSample.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckCompactSample.java new file mode 100644 index 0000000000..974efeb49a --- /dev/null +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckCompactSample.java @@ -0,0 +1,20 @@ +import java.io.IO; + +void main() { + IO.println(""); // Compliant + IO.print(""); // Compliant + f(); + new A().f(); +} + +void f() { + IO.println(""); // Compliant + IO.print(""); // Compliant +} + +class A { + void f() { + IO.println(""); // Compliant + IO.print(""); // Compliant + } +} diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckSample.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckSample.java new file mode 100644 index 0000000000..275bc47a71 --- /dev/null +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckSample.java @@ -0,0 +1,14 @@ +package checks; + + +import java.io.IO; +import java.io.PrintStream; + +class IoPrintlnUsageCheckSample { + + void f() { + IO.println(""); // Noncompliant {{Replace this use of IO.println by a logger.}} + IO.print(""); // Noncompliant {{Replace this use of IO.print by a logger.}} + } + +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java index c322a66f8e..43e91a8cf5 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java @@ -20,6 +20,7 @@ import org.sonar.java.checks.verifier.CheckVerifier; import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; +import static org.sonar.java.checks.verifier.TestUtils.nonCompilingTestSourcesPath; class SystemOutOrErrUsageCheckTest { @Test @@ -41,7 +42,7 @@ void test_sout_compact_source_file() { @Test void test_io() { CheckVerifier.newVerifier() - .onFile(mainCodeSourcesPath("checks/IoPrintlnUsageCheckSample.java")) + .onFile(nonCompilingTestSourcesPath("checks/IoPrintlnUsageCheckSample.java")) .withCheck(new SystemOutOrErrUsageCheck()) .verifyIssues(); } @@ -49,7 +50,7 @@ void test_io() { @Test void test_io_compact_source_file() { CheckVerifier.newVerifier() - .onFile(mainCodeSourcesPath("checks/IoPrintlnUsageCheckCompactSample.java")) + .onFile(nonCompilingTestSourcesPath("checks/IoPrintlnUsageCheckCompactSample.java")) .withCheck(new SystemOutOrErrUsageCheck()) .verifyNoIssues(); } From 7915facd54a2fd0882d93981ab394eb166c419fc Mon Sep 17 00:00:00 2001 From: "romain.birling" Date: Wed, 4 Feb 2026 14:49:00 +0100 Subject: [PATCH 3/5] Deleted non compiling check source files from default check source files --- .../IoPrintlnUsageCheckCompactSample.java | 20 ------------------- .../checks/IoPrintlnUsageCheckSample.java | 14 ------------- 2 files changed, 34 deletions(-) delete mode 100644 java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckCompactSample.java delete mode 100644 java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckSample.java diff --git a/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckCompactSample.java b/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckCompactSample.java deleted file mode 100644 index 974efeb49a..0000000000 --- a/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckCompactSample.java +++ /dev/null @@ -1,20 +0,0 @@ -import java.io.IO; - -void main() { - IO.println(""); // Compliant - IO.print(""); // Compliant - f(); - new A().f(); -} - -void f() { - IO.println(""); // Compliant - IO.print(""); // Compliant -} - -class A { - void f() { - IO.println(""); // Compliant - IO.print(""); // Compliant - } -} diff --git a/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckSample.java deleted file mode 100644 index 275bc47a71..0000000000 --- a/java-checks-test-sources/default/src/main/java/checks/IoPrintlnUsageCheckSample.java +++ /dev/null @@ -1,14 +0,0 @@ -package checks; - - -import java.io.IO; -import java.io.PrintStream; - -class IoPrintlnUsageCheckSample { - - void f() { - IO.println(""); // Noncompliant {{Replace this use of IO.println by a logger.}} - IO.print(""); // Noncompliant {{Replace this use of IO.print by a logger.}} - } - -} From 03705e4020b20bec2559164b9180a26c5149d379 Mon Sep 17 00:00:00 2001 From: "romain.birling" Date: Thu, 5 Feb 2026 10:29:03 +0100 Subject: [PATCH 4/5] Rebase fix conflicts --- .../java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java index 6aa07fa4c0..24b7487ec9 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java @@ -22,7 +22,6 @@ import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.Tree; - import java.util.List; @Rule(key = "S106") From 68afecac29a9c7f47149327d0c3f6eea8fca035d Mon Sep 17 00:00:00 2001 From: "romain.birling" Date: Thu, 5 Feb 2026 12:01:19 +0100 Subject: [PATCH 5/5] Address Asya's review - Extract isIoPrintFunction into a dedicated method --- .../java/checks/SystemOutOrErrUsageCheck.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java index 24b7487ec9..18aece244c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java @@ -56,15 +56,8 @@ private void visitMemberSelectExpression(MemberSelectExpressionTree mset) { reportIssue(mset, "Replace this use of System.out by a logger."); } else if ("err".equals(name) && isSystem(mset.expression())) { reportIssue(mset, "Replace this use of System.err by a logger."); - } else { - boolean isIoFunction = mset.expression().parent() instanceof MemberSelectExpressionTree tree && - tree.expression() instanceof IdentifierTree identifierTree && - "IO".equals(identifierTree.name()); - // use contains to cover both print and println methods - boolean isIoPrintFunction = isIoFunction && mset.identifier().name().contains("print"); - if (isIoPrintFunction) { - reportIssue(mset, "Replace this use of IO." + mset.identifier().name() + " by a logger."); - } + } else if (isIoPrintFunction(mset)) { + reportIssue(mset, "Replace this use of IO." + mset.identifier().name() + " by a logger."); } } @@ -77,4 +70,12 @@ private static boolean isSystem(ExpressionTree expression) { } return identifierTree != null && "System".equals(identifierTree.name()); } + + private static boolean isIoPrintFunction(MemberSelectExpressionTree mset) { + boolean isIoFunction = mset.expression().parent() instanceof MemberSelectExpressionTree tree && + tree.expression() instanceof IdentifierTree identifierTree && + "IO".equals(identifierTree.name()); + // use contains to cover both print and println methods + return isIoFunction && mset.identifier().name().contains("print"); + } }