[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 -->
<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>
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

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.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.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.AbstractUastVisitor
private const val MIN_TEXT_LENGTH = 3
private const val MAX_PART_COUNT = 10
private const val WITH_THROWABLE = "withThrowable"
@@ -33,8 +32,14 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
@JvmField
var mySkipErrorLogLevel: Boolean = true
@JvmField
var myMinTextLength: Int = 5
override fun getOptionsPane(): OptPane {
return OptPane.pane(
OptPane.number("myMinTextLength",
JvmAnalysisBundle.message("jvm.inspection.logging.similar.message.problem.min.similar.length"),
3, 100),
OptPane.checkbox("mySkipErrorLogLevel",
JvmAnalysisBundle.message("jvm.inspection.logging.similar.message.problem.skip.on.error"))
)
@@ -55,12 +60,13 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
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)
}
inner class PlaceholderCountMatchesArgumentCountVisitor(
private val holder: ProblemsHolder,
private val myMinTextLength: Int,
) : AbstractUastNonRecursiveVisitor() {
override fun visitFile(node: UFile): Boolean {
@@ -114,7 +120,7 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
val alreadyHasWarning = mutableSetOf<Int>()
for (firstIndex in 0..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)) {
registerProblem(holder, currentGroup[firstIndex].call, currentGroup[secondIndex].call)
}
@@ -130,12 +136,6 @@ class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() {
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> {
val result = mutableSetOf<UCallExpression>()
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 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.isEmpty() || second.isEmpty()) return false
if (first.size >= MAX_PART_COUNT || second.size >= MAX_PART_COUNT) return false
val firstIterator = PartHolderIterator(first)
val secondIterator = PartHolderIterator(second)
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()) {
//example: "something {} something", `{}` is skipped here
if (!firstIterator.isText()) {
@@ -412,5 +423,11 @@ private fun similar(first: List<LoggingStringPartEvaluator.PartHolder>?,
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);
<weak_warning descr="Similar log messages">logger.atError()
.setMessage("aaa {}")
.setMessage("aaaaa {}")
.log()</weak_warning>;
<weak_warning descr="Similar log messages">logger.atError()
.setMessage("aaa 2{}")
.setMessage("aaaaa 2{}")
.log()</weak_warning>;
}
}

View File

@@ -69,5 +69,26 @@ class KotlinLoggingSimilarMessageInspectionTest : LoggingSimilarMessageInspectio
}
""".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())
}
}