Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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.}}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -53,11 +52,12 @@ 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 if (isIoPrintFunction(mset)) {
reportIssue(mset, "Replace this use of IO." + mset.identifier().name() + " by a logger.");
}
}

Expand All @@ -70,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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,41 @@
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
void test() {
void test_sout() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/SystemOutOrErrUsageCheckSample.java"))
.withCheck(new SystemOutOrErrUsageCheck())
.verifyIssues();
}

@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(nonCompilingTestSourcesPath("checks/IoPrintlnUsageCheckSample.java"))
.withCheck(new SystemOutOrErrUsageCheck())
.verifyIssues();
}

@Test
void test_io_compact_source_file() {
CheckVerifier.newVerifier()
.onFile(nonCompilingTestSourcesPath("checks/IoPrintlnUsageCheckCompactSample.java"))
.withCheck(new SystemOutOrErrUsageCheck())
.verifyNoIssues();
}

@Test
void test_compact_source_file_with_regular_class() {
CheckVerifier.newVerifier()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ <h2>Why is this an issue?</h2>
<li> properly recorded </li>
<li> securely logged when dealing with sensitive data </li>
</ul>
<p>Those requirements are not met if a program directly writes to the standard outputs (e.g., System.out, System.err). That is why defining and using
a dedicated logger is highly recommended.</p>
<p>Those requirements are not met if a program directly writes to the standard outputs (e.g., System.out, System.err, IO). That is why defining and
using a dedicated logger is highly recommended.</p>
<h3>Code examples</h3>
<p>The following noncompliant code:</p>
<pre data-diff-id="1" data-diff-type="noncompliant">
class MyClass {
public void doSomething() {
System.out.println("My Message"); // Noncompliant, output directly to System.out without a logger
IO.println("Second Message"); // Noncompliant, same problem, but using Java 25 syntax.
}
}
</pre>
Expand All @@ -27,9 +28,8 @@ <h3>Code examples</h3>
Logger logger = Logger.getLogger(getClass().getName());

public void doSomething() {
// ...
logger.info("My Message"); // Compliant, output via logger
// ...
logger.info("Second Message"); // Compliant, output via logger
}
}
</pre>
Expand Down
2 changes: 1 addition & 1 deletion sonarpedia.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"languages": [
"JAVA"
],
"latest-update": "2026-01-26T14:42:25.031525200Z",
"latest-update": "2026-02-10T09:09:57.194517400Z",
"options": {
"no-language-in-filenames": true,
"preserve-filenames": false
Expand Down
Loading