Add lint detection for System.out.println add kotlin.io.println usage.

This commit is contained in:
Alex Hart
2025-09-19 09:26:05 -03:00
committed by Jeffrey Starke
parent 9e1cec7a60
commit c901639ce8
5 changed files with 353 additions and 2 deletions

View File

@@ -196,7 +196,6 @@ class ConversationListFilterPullView @JvmOverloads constructor(
}
fun openImmediate() {
println("openImmediate from $state")
if (state == FilterPullState.CLOSED) {
setState(FilterPullState.OPEN_APEX, source)
setState(FilterPullState.OPENING, source)

View File

@@ -22,7 +22,9 @@ class Registry : IssueRegistry() {
RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE,
ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE,
StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE,
CardViewDetector.CARD_VIEW_USAGE
CardViewDetector.CARD_VIEW_USAGE,
SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE,
SystemOutPrintLnDetector.KOTLIN_IO_PRINTLN_USAGE
)
override val api = CURRENT_API

View File

@@ -0,0 +1,100 @@
package org.signal.lint
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Scope.Companion.JAVA_FILE_SCOPE
import com.android.tools.lint.detector.api.Severity.ERROR
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UQualifiedReferenceExpression
/**
* Lint detector that flags usage of System.out.println and kotlin.io.println methods.
*/
class SystemOutPrintLnDetector : Detector(), Detector.UastScanner {
override fun getApplicableMethodNames(): List<String> {
return listOf("println", "print")
}
@Suppress("UnstableApiUsage")
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
val evaluator = context.evaluator
if (evaluator.isMemberInClass(method, "java.io.PrintStream")) {
if (isSystemOutCall(node.receiver)) {
context.report(
issue = SYSTEM_OUT_PRINTLN_USAGE,
scope = node,
location = context.getLocation(node),
message = "Using 'System.out.${method.name}' instead of Signal Logger",
quickfixData = createQuickFix(node)
)
}
}
// Check for kotlin.io.println (top-level function)
if (method.name == "println" && evaluator.isMemberInClass(method, "kotlin.io.ConsoleKt")) {
context.report(
issue = KOTLIN_IO_PRINTLN_USAGE,
scope = node,
location = context.getLocation(node),
message = "Using 'kotlin.io.println' instead of Signal Logger.",
quickfixData = createQuickFix(node)
)
}
}
private fun isSystemOutCall(receiver: UExpression?): Boolean {
return receiver is UQualifiedReferenceExpression &&
receiver.selector.asRenderString() == "out" &&
receiver.receiver.asRenderString().endsWith("System")
}
private fun createQuickFix(node: UCallExpression): LintFix {
val arguments = node.valueArguments
val message = if (arguments.isNotEmpty()) arguments[0].asSourceString() else "\"\""
val fixSource = "org.signal.core.util.logging.Log.d(TAG, $message)"
return fix()
.group()
.add(
fix()
.replace()
.text(node.sourcePsi?.text)
.shortenNames()
.reformat(true)
.with(fixSource)
.build()
)
.build()
}
companion object {
val SYSTEM_OUT_PRINTLN_USAGE: Issue = Issue.create(
id = "SystemOutPrintLnUsage",
briefDescription = "Usage of System.out.println/print",
explanation = "System.out.println/print should not be used in production code. Use Signal Logger instead.",
category = MESSAGES,
priority = 5,
severity = ERROR,
implementation = Implementation(SystemOutPrintLnDetector::class.java, JAVA_FILE_SCOPE)
)
val KOTLIN_IO_PRINTLN_USAGE: Issue = Issue.create(
id = "KotlinIOPrintLnUsage",
briefDescription = "Usage of kotlin.io.println",
explanation = "kotlin.io.println should not be used in production code. Use proper logging instead.",
category = MESSAGES,
priority = 5,
severity = ERROR,
implementation = Implementation(SystemOutPrintLnDetector::class.java, JAVA_FILE_SCOPE)
)
}
}

View File

@@ -0,0 +1,232 @@
package org.signal.lint
import com.android.tools.lint.checks.infrastructure.TestFiles.java
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
import com.android.tools.lint.checks.infrastructure.TestLintTask
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import java.util.Scanner
class SystemOutPrintLnDetectorTest {
@Test
fun systemOutPrintlnUsed_Java() {
TestLintTask.lint()
.allowMissingSdk()
.files(
java(
"""
package foo;
public class Example {
public void log() {
System.out.println("Hello World");
}
}
""".trimIndent()
)
)
.issues(SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE)
.run()
.expect(
"""
src/foo/Example.java:4: Error: Using 'System.out.println' instead of proper logging [SystemOutPrintLnUsage]
System.out.println("Hello World");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
""".trimIndent()
)
.expectFixDiffs(
"""
Fix for src/foo/Example.java line 4: Replace with org.signal.core.util.logging.Log.d(TAG, "Hello World"):
@@ -4 +4
- System.out.println("Hello World");
+ org.signal.core.util.logging.Log.d(TAG, "Hello World");
""".trimIndent()
)
}
@Test
fun systemOutPrintUsed_Java() {
TestLintTask.lint()
.allowMissingSdk()
.files(
java(
"""
package foo;
public class Example {
public void log() {
System.out.print("Hello");
}
}
""".trimIndent()
)
)
.issues(SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE)
.run()
.expect(
"""
src/foo/Example.java:4: Error: Using 'System.out.print' instead of proper logging [SystemOutPrintLnUsage]
System.out.print("Hello");
~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
""".trimIndent()
)
.expectFixDiffs(
"""
Fix for src/foo/Example.java line 4: Replace with org.signal.core.util.logging.Log.d(TAG, "Hello"):
@@ -4 +4
- System.out.print("Hello");
+ org.signal.core.util.logging.Log.d(TAG, "Hello");
""".trimIndent()
)
}
@Test
fun kotlinIOPrintlnUsed_Kotlin() {
TestLintTask.lint()
.allowMissingSdk()
.files(
kotlinIOStub,
kotlin(
"""
package foo
import kotlin.io.println
class Example {
fun log() {
println("Hello World")
}
}
""".trimIndent()
)
)
.issues(SystemOutPrintLnDetector.KOTLIN_IO_PRINTLN_USAGE)
.run()
.expect(
"""
src/foo/Example.kt:5: Error: Using 'kotlin.io.println' instead of proper logging [KotlinIOPrintLnUsage]
println("Hello World")
~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
""".trimIndent()
)
.expectFixDiffs(
"""
Fix for src/foo/Example.kt line 5: Replace with org.signal.core.util.logging.Log.d(TAG, "Hello World"):
@@ -5 +5
- println("Hello World")
+ org.signal.core.util.logging.Log.d(TAG, "Hello World")
""".trimIndent()
)
}
@Test
fun kotlinIOPrintlnUsed_TopLevel_Kotlin() {
TestLintTask.lint()
.allowMissingSdk()
.files(
kotlinIOStub,
kotlin(
"""
package foo
fun example() {
println("Hello World")
}
""".trimIndent()
)
)
.issues(SystemOutPrintLnDetector.KOTLIN_IO_PRINTLN_USAGE)
.run()
.expect(
"""
src/foo/test.kt:3: Error: Using 'kotlin.io.println' instead of proper logging [KotlinIOPrintLnUsage]
println("Hello World")
~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
""".trimIndent()
)
.expectFixDiffs(
"""
Fix for src/foo/test.kt line 3: Replace with org.signal.core.util.logging.Log.d(TAG, "Hello World"):
@@ -3 +3
- println("Hello World")
+ org.signal.core.util.logging.Log.d(TAG, "Hello World")
""".trimIndent()
)
}
@Test
fun systemOutPrintlnWithNoArgs_Java() {
TestLintTask.lint()
.allowMissingSdk()
.files(
java(
"""
package foo;
public class Example {
public void log() {
System.out.println();
}
}
""".trimIndent()
)
)
.issues(SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE)
.run()
.expect(
"""
src/foo/Example.java:4: Error: Using 'System.out.println' instead of proper logging [SystemOutPrintLnUsage]
System.out.println();
~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
""".trimIndent()
)
.expectFixDiffs(
"""
Fix for src/foo/Example.java line 4: Replace with org.signal.core.util.logging.Log.d(TAG, ""):
@@ -4 +4
- System.out.println();
+ org.signal.core.util.logging.Log.d(TAG, "");
""".trimIndent()
)
}
@Test
fun regularPrintStreamMethodsNotFlagged() {
TestLintTask.lint()
.allowMissingSdk()
.files(
java(
"""
package foo;
import java.io.PrintStream;
import java.io.ByteArrayOutputStream;
public class Example {
public void log() {
PrintStream ps = new PrintStream(new ByteArrayOutputStream());
ps.println("This should not be flagged");
}
}
""".trimIndent()
)
)
.issues(
SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE,
SystemOutPrintLnDetector.KOTLIN_IO_PRINTLN_USAGE
)
.run()
.expectClean()
}
companion object {
private val kotlinIOStub = kotlin(readResourceAsString("KotlinIOStub.kt"))
private fun readResourceAsString(resourceName: String): String {
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
assertNotNull(inputStream)
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
assertTrue(scanner.hasNext())
return scanner.next()
}
}
}

View File

@@ -0,0 +1,18 @@
@file:JvmName("ConsoleKt")
package kotlin.io
/**
* Stub for kotlin.io.println function for testing purposes
*/
fun println(message: Any?) {
// Stub implementation
}
fun println() {
// Stub implementation
}
fun print(message: Any?) {
// Stub implementation
}