[uast-inspection] IDEA-343298 LoggingSimilarMessageInspection should skip empty messages

- skip messages without certain beginnings and ends

GitOrigin-RevId: eb9ef8be4c8629712a0915a539fc7e9dac4c1238
This commit is contained in:
Mikhail Pyltsin
2024-01-17 19:58:37 +01:00
committed by intellij-monorepo-bot
parent 0d410b4a27
commit 23f1773740
5 changed files with 63 additions and 21 deletions

View File

@@ -17,6 +17,9 @@ These calls can be non-distinguishable from each other, and this introduces diff
<!-- tooltip end --> <!-- tooltip end -->
<ul> <ul>
<li>
Use the <b>Minimal length of a similar sequence</b> option to set minimal length of similar sequences after which calls will be reported
</li>
<li> <li>
Use the <b>Do not report calls with `error` log level</b> option to ignore messages with `error` log level and when there is an exception. Use the <b>Do not report calls with `error` log level</b> option to ignore messages with `error` log level and when there is an exception.
It can be useful, because places of calls can be found with stacktraces It can be useful, because places of calls can be found with stacktraces

View File

@@ -175,6 +175,7 @@ jvm.inspection.logging.placeholder.count.matches.argument.count.slf4j.throwable.
jvm.inspection.logging.similar.message.display.name=Non-distinguishable logging calls jvm.inspection.logging.similar.message.display.name=Non-distinguishable logging calls
jvm.inspection.logging.similar.message.problem.descriptor=Similar log messages jvm.inspection.logging.similar.message.problem.descriptor=Similar log messages
jvm.inspection.logging.similar.message.problem.min.similar.length=Minimal length of a similar sequence
jvm.inspection.logging.similar.message.problem.skip.on.error=Do not report calls with `error` log level jvm.inspection.logging.similar.message.problem.skip.on.error=Do not report calls with `error` log level
jvm.inspection.logging.condition.disagrees.with.log.statement.display.name=Log condition does not match logging call jvm.inspection.logging.condition.disagrees.with.log.statement.display.name=Log condition does not match logging call

View File

@@ -22,7 +22,6 @@ import org.jetbrains.uast.*
import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
import org.jetbrains.uast.visitor.AbstractUastVisitor import org.jetbrains.uast.visitor.AbstractUastVisitor
private const val MIN_TEXT_LENGTH = 3
private const val MAX_PART_COUNT = 10 private const val MAX_PART_COUNT = 10
private const val WITH_THROWABLE = "withThrowable" private const val WITH_THROWABLE = "withThrowable"
@@ -33,8 +32,14 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
@JvmField @JvmField
var mySkipErrorLogLevel: Boolean = true var mySkipErrorLogLevel: Boolean = true
@JvmField
var myMinTextLength: Int = 5
override fun getOptionsPane(): OptPane { override fun getOptionsPane(): OptPane {
return OptPane.pane( return OptPane.pane(
OptPane.number("myMinTextLength",
JvmAnalysisBundle.message("jvm.inspection.logging.similar.message.problem.min.similar.length"),
3, 100),
OptPane.checkbox("mySkipErrorLogLevel", OptPane.checkbox("mySkipErrorLogLevel",
JvmAnalysisBundle.message("jvm.inspection.logging.similar.message.problem.skip.on.error")) JvmAnalysisBundle.message("jvm.inspection.logging.similar.message.problem.skip.on.error"))
) )
@@ -55,12 +60,13 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
return PsiElementVisitor.EMPTY_VISITOR return PsiElementVisitor.EMPTY_VISITOR
} }
return UastHintedVisitorAdapter.create(holder.file.language, PlaceholderCountMatchesArgumentCountVisitor(holder), return UastHintedVisitorAdapter.create(holder.file.language, PlaceholderCountMatchesArgumentCountVisitor(holder, myMinTextLength),
arrayOf(UFile::class.java), directOnly = true) arrayOf(UFile::class.java), directOnly = true)
} }
inner class PlaceholderCountMatchesArgumentCountVisitor( inner class PlaceholderCountMatchesArgumentCountVisitor(
private val holder: ProblemsHolder, private val holder: ProblemsHolder,
private val myMinTextLength: Int,
) : AbstractUastNonRecursiveVisitor() { ) : AbstractUastNonRecursiveVisitor() {
override fun visitFile(node: UFile): Boolean { override fun visitFile(node: UFile): Boolean {
@@ -81,17 +87,17 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
if (group.size > 5) { if (group.size > 5) {
var firstIsTaken = true var firstIsTaken = true
var minLength: Int? = group var minLength: Int? = group
.mapNotNull { messageLog -> messageLog.parts } .mapNotNull { messageLog -> messageLog.parts }
.filter { parts -> firstIsText(parts) } .filter { parts -> firstIsText(parts) }
.minOfOrNull { parts -> parts[0].text?.length ?: 0 } .minOfOrNull { parts -> parts[0].text?.length ?: 0 }
if (minLength == null || minLength == 0) { if (minLength == null || minLength == 0) {
firstIsTaken = false firstIsTaken = false
minLength = group minLength = group
.mapNotNull { messageLog -> messageLog.parts } .mapNotNull { messageLog -> messageLog.parts }
.filter { parts -> lastIsText(parts) } .filter { parts -> lastIsText(parts) }
.minOfOrNull { parts -> parts.last().text?.length ?: 0 } .minOfOrNull { parts -> parts.last().text?.length ?: 0 }
} }
if (minLength == null || minLength == 0) { if (minLength == null || minLength == 0) {
@@ -102,7 +108,7 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
.groupBy { .groupBy {
val parts = it.parts ?: return@groupBy "" val parts = it.parts ?: return@groupBy ""
if (parts.isEmpty()) return@groupBy "" if (parts.isEmpty()) return@groupBy ""
val part0 = if(firstIsTaken) parts[0] else parts.last() val part0 = if (firstIsTaken) parts[0] else parts.last()
if (!part0.isConstant || part0.text == null || part0.text.length < minLength) return@groupBy "" if (!part0.isConstant || part0.text == null || part0.text.length < minLength) return@groupBy ""
part0.text.substring(0, minLength) part0.text.substring(0, minLength)
} }
@@ -114,7 +120,7 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
val alreadyHasWarning = mutableSetOf<Int>() val alreadyHasWarning = mutableSetOf<Int>()
for (firstIndex in 0..currentGroup.lastIndex) { for (firstIndex in 0..currentGroup.lastIndex) {
for (secondIndex in firstIndex + 1..currentGroup.lastIndex) { for (secondIndex in firstIndex + 1..currentGroup.lastIndex) {
if (similar(currentGroup[firstIndex].parts, currentGroup[secondIndex].parts)) { if (similar(currentGroup[firstIndex].parts, currentGroup[secondIndex].parts, myMinTextLength)) {
if (alreadyHasWarning.add(firstIndex)) { if (alreadyHasWarning.add(firstIndex)) {
registerProblem(holder, currentGroup[firstIndex].call, currentGroup[secondIndex].call) registerProblem(holder, currentGroup[firstIndex].call, currentGroup[secondIndex].call)
} }
@@ -130,12 +136,6 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
return super.visitFile(node) return super.visitFile(node)
} }
private fun firstIsText(parts: List<LoggingStringPartEvaluator.PartHolder>) =
parts.isNotEmpty() && parts[0].isConstant && parts[0].text?.isNotBlank() == true
private fun lastIsText(parts: List<LoggingStringPartEvaluator.PartHolder>) =
parts.isNotEmpty() && parts.last().isConstant && parts.last().text?.isNotBlank() == true
private fun collectCalls(file: UFile): Set<UCallExpression> { private fun collectCalls(file: UFile): Set<UCallExpression> {
val result = mutableSetOf<UCallExpression>() val result = mutableSetOf<UCallExpression>()
file.accept(object : AbstractUastVisitor() { file.accept(object : AbstractUastVisitor() {
@@ -301,13 +301,24 @@ private class PartHolderIterator(private val parts: List<LoggingStringPartEvalua
private data class MessageLog(val call: UCallExpression, val parts: List<LoggingStringPartEvaluator.PartHolder>?) private data class MessageLog(val call: UCallExpression, val parts: List<LoggingStringPartEvaluator.PartHolder>?)
private fun similar(first: List<LoggingStringPartEvaluator.PartHolder>?, private fun similar(first: List<LoggingStringPartEvaluator.PartHolder>?,
second: List<LoggingStringPartEvaluator.PartHolder>?): Boolean { second: List<LoggingStringPartEvaluator.PartHolder>?,
minTextLength: Int): Boolean {
if (first == null || second == null) return false if (first == null || second == null) return false
if (first.isEmpty() || second.isEmpty()) return false if (first.isEmpty() || second.isEmpty()) return false
if (first.size >= MAX_PART_COUNT || second.size >= MAX_PART_COUNT) return false if (first.size >= MAX_PART_COUNT || second.size >= MAX_PART_COUNT) return false
val firstIterator = PartHolderIterator(first) val firstIterator = PartHolderIterator(first)
val secondIterator = PartHolderIterator(second) val secondIterator = PartHolderIterator(second)
var intersection = 0 var intersection = 0
val firstFirstIsText = firstIsText(first)
val firstLastIsText = lastIsText(first)
val secondFirstIsText = firstIsText(second)
val secondLastIsText = lastIsText(second)
if (!firstFirstIsText && !firstLastIsText && (secondFirstIsText || secondLastIsText)) return false
if (!secondFirstIsText && !secondLastIsText && (firstFirstIsText || firstLastIsText)) return false
while (firstIterator.hasNext() && secondIterator.hasNext()) { while (firstIterator.hasNext() && secondIterator.hasNext()) {
//example: "something {} something", `{}` is skipped here //example: "something {} something", `{}` is skipped here
if (!firstIterator.isText()) { if (!firstIterator.isText()) {
@@ -412,5 +423,11 @@ private fun similar(first: List<LoggingStringPartEvaluator.PartHolder>?,
return false return false
} }
return intersection >= MIN_TEXT_LENGTH return intersection >= minTextLength
} }
private fun firstIsText(parts: List<LoggingStringPartEvaluator.PartHolder>) =
parts.isNotEmpty() && parts[0].isConstant && parts[0].text?.isNotBlank() == true
private fun lastIsText(parts: List<LoggingStringPartEvaluator.PartHolder>) =
parts.isNotEmpty() && parts.last().isConstant && parts.last().text?.isNotBlank() == true

View File

@@ -205,11 +205,11 @@ class JavaLoggingSimilarMessageInspectionTest : LoggingSimilarMessageInspectionT
Logger logger = LoggerFactory.getLogger(X.class); Logger logger = LoggerFactory.getLogger(X.class);
<weak_warning descr="Similar log messages">logger.atError() <weak_warning descr="Similar log messages">logger.atError()
.setMessage("aaa {}") .setMessage("aaaaa {}")
.log()</weak_warning>; .log()</weak_warning>;
<weak_warning descr="Similar log messages">logger.atError() <weak_warning descr="Similar log messages">logger.atError()
.setMessage("aaa 2{}") .setMessage("aaaaa 2{}")
.log()</weak_warning>; .log()</weak_warning>;
} }
} }

View File

@@ -69,5 +69,26 @@ class KotlinLoggingSimilarMessageInspectionTest : LoggingSimilarMessageInspectio
} }
""".trimIndent()) """.trimIndent())
} }
fun `test skip in the middle parts`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
import org.slf4j.Logger
import org.slf4j.LoggerFactory
internal class Logging {
private val LOG: Logger = LoggerFactory.getLogger(Logging::class.<error descr="[UNRESOLVED_REFERENCE] Unresolved reference: java">java</error>)
private fun request1(i: String) {
val msg = "${"\${i}"}1234356${"\${i}"}"
LOG.info(msg)
}
private fun request2(i: Int) {
val msg = "something 1234356${"\${i}"}"
LOG.info(msg)
}
}
""".trimIndent())
}
} }