[Java. Logging] Properly handling with logging placeholders resolve with backslash character

IDEA-342484

GitOrigin-RevId: 70cbc9a537943dd3520282206ef8ddb20f01ad5f
This commit is contained in:
Georgii Ustinov
2024-04-22 13:07:17 +03:00
committed by intellij-monorepo-bot
parent 44598e3b3b
commit c25c1ba670
5 changed files with 161 additions and 9 deletions

View File

@@ -2,6 +2,8 @@
package com.intellij.analysis.logging.resolve package com.intellij.analysis.logging.resolve
import com.intellij.codeInspection.logging.* import com.intellij.codeInspection.logging.*
import com.intellij.codeInspection.logging.PlaceholderCountIndexStrategy.KOTLIN_MULTILINE_RAW_STRING
import com.intellij.codeInspection.logging.PlaceholderCountIndexStrategy.RAW_STRING
import com.intellij.codeInspection.logging.PlaceholderLoggerType.* import com.intellij.codeInspection.logging.PlaceholderLoggerType.*
import com.intellij.model.Symbol import com.intellij.model.Symbol
import com.intellij.model.psi.PsiExternalReferenceHost import com.intellij.model.psi.PsiExternalReferenceHost
@@ -10,11 +12,8 @@ import com.intellij.model.psi.PsiSymbolReferenceHints
import com.intellij.model.psi.PsiSymbolReferenceProvider import com.intellij.model.psi.PsiSymbolReferenceProvider
import com.intellij.model.search.SearchRequest import com.intellij.model.search.SearchRequest
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.uast.UCallExpression import org.jetbrains.uast.*
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.expressions.UInjectionHost import org.jetbrains.uast.expressions.UInjectionHost
import org.jetbrains.uast.getParentOfType
import org.jetbrains.uast.toUElementOfType
class LoggingArgumentSymbolReferenceProvider : PsiSymbolReferenceProvider { class LoggingArgumentSymbolReferenceProvider : PsiSymbolReferenceProvider {
override fun getReferences(element: PsiExternalReferenceHost, hints: PsiSymbolReferenceHints): Collection<PsiSymbolReference> { override fun getReferences(element: PsiExternalReferenceHost, hints: PsiSymbolReferenceHints): Collection<PsiSymbolReference> {
@@ -74,14 +73,19 @@ internal fun <T> getAlignedPlaceholderCount(placeholderList: List<T>, context: P
internal fun getPlaceholderRanges(context: PlaceholderContext): List<PlaceholderRanges>? { internal fun getPlaceholderRanges(context: PlaceholderContext): List<PlaceholderRanges>? {
val logStringText = context.logStringArgument.sourcePsi?.text ?: return null val logStringText = context.logStringArgument.sourcePsi?.text ?: return null
val type = if (isKotlinMultilineString(context.logStringArgument, logStringText)) KOTLIN_MULTILINE_RAW_STRING else RAW_STRING
val partHolders = listOf( val partHolders = listOf(
LoggingStringPartEvaluator.PartHolder( LoggingStringPartEvaluator.PartHolder(
logStringText, logStringText,
true true
) )
) )
val placeholderCountResult = solvePlaceholderCount(context.loggerType, context.placeholderParameters.size, partHolders) val placeholderCountResult = solvePlaceholderCount(context.loggerType, context.placeholderParameters.size, partHolders, type)
if (placeholderCountResult.status != PlaceholdersStatus.EXACTLY) return null if (placeholderCountResult.status != PlaceholdersStatus.EXACTLY) return null
return placeholderCountResult.placeholderRangesList return placeholderCountResult.placeholderRangesList
}
private fun isKotlinMultilineString(logString: UExpression, text : String): Boolean {
return logString is UPolyadicExpression && text.startsWith("\"\"\"") && text.endsWith("\"\"\"") && text.length >= 6
} }

View File

@@ -56,7 +56,7 @@ class LoggingPlaceholderCountMatchesArgumentCountInspection : AbstractBaseUastLo
var finalArgumentCount = context.placeholderParameters.size var finalArgumentCount = context.placeholderParameters.size
val placeholderCountHolder = solvePlaceholderCount(context.loggerType, finalArgumentCount, context.partHolderList) val placeholderCountHolder = solvePlaceholderCount(context.loggerType, finalArgumentCount, context.partHolderList, PlaceholderCountIndexStrategy.UAST_STRING)
if (placeholderCountHolder.status == PlaceholdersStatus.EMPTY) { if (placeholderCountHolder.status == PlaceholdersStatus.EMPTY) {
return true return true

View File

@@ -124,6 +124,26 @@ internal enum class PlaceholdersStatus {
EXACTLY, PARTIAL, ERROR_TO_PARSE_STRING, EMPTY EXACTLY, PARTIAL, ERROR_TO_PARSE_STRING, EMPTY
} }
internal enum class PlaceholderCountIndexStrategy {
UAST_STRING {
override fun shiftAfterEscapeChar(index: Int): Int {
return index
}
},
KOTLIN_MULTILINE_RAW_STRING {
override fun shiftAfterEscapeChar(index: Int): Int {
return index
}
},
RAW_STRING {
override fun shiftAfterEscapeChar(index: Int): Int {
return index + 1
}
};
abstract fun shiftAfterEscapeChar(index: Int): Int
}
internal class LoggerContext(val log4jAsImplementationForSlf4j: Boolean) internal class LoggerContext(val log4jAsImplementationForSlf4j: Boolean)
@@ -300,6 +320,7 @@ internal fun findAdditionalArguments(node: UCallExpression,
* @param loggerType The type of the logger used. * @param loggerType The type of the logger used.
* @param argumentCount The number of arguments that the logger is expected to handle. * @param argumentCount The number of arguments that the logger is expected to handle.
* @param holders The list of PartHolder objects representing logging string parts. * @param holders The list of PartHolder objects representing logging string parts.
* @param placeholderCountShiftIndexStrategy The strategy of shifting index for escape backslash during parsing SLF4J placeholders. It is different for regular string and psi text
* *
* @return PlaceholderCountResult returns the result of either countFormattedPlaceholders or countBracesPlaceholders based on the type of the logger. * @return PlaceholderCountResult returns the result of either countFormattedPlaceholders or countBracesPlaceholders based on the type of the logger.
*/ */
@@ -307,12 +328,13 @@ internal fun solvePlaceholderCount(
loggerType: PlaceholderLoggerType, loggerType: PlaceholderLoggerType,
argumentCount: Int, argumentCount: Int,
holders: List<LoggingStringPartEvaluator.PartHolder>, holders: List<LoggingStringPartEvaluator.PartHolder>,
placeholderCountShiftIndexStrategy: PlaceholderCountIndexStrategy
): PlaceholderCountResult { ): PlaceholderCountResult {
return if (loggerType == PlaceholderLoggerType.LOG4J_FORMATTED_STYLE) { return if (loggerType == PlaceholderLoggerType.LOG4J_FORMATTED_STYLE) {
countFormattedBasedPlaceholders(holders, argumentCount) countFormattedBasedPlaceholders(holders, argumentCount)
} }
else { else {
countBracesBasedPlaceholders(holders, loggerType) countBracesBasedPlaceholders(holders, loggerType, placeholderCountShiftIndexStrategy)
} }
} }
@@ -434,7 +456,9 @@ internal fun hasThrowableType(lastArgument: UExpression): Boolean {
return InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_THROWABLE) return InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_THROWABLE)
} }
private fun countBracesBasedPlaceholders(holders: List<LoggingStringPartEvaluator.PartHolder>, loggerType: PlaceholderLoggerType): PlaceholderCountResult { private fun countBracesBasedPlaceholders(holders: List<LoggingStringPartEvaluator.PartHolder>,
loggerType: PlaceholderLoggerType,
placeholderCountShiftIndexStrategy: PlaceholderCountIndexStrategy): PlaceholderCountResult {
var count = 0 var count = 0
var full = true var full = true
val placeholderRangeList: MutableList<PlaceholderRanges> = mutableListOf() val placeholderRangeList: MutableList<PlaceholderRanges> = mutableListOf()
@@ -448,14 +472,17 @@ private fun countBracesBasedPlaceholders(holders: List<LoggingStringPartEvaluato
val length = string.length val length = string.length
var escaped = false var escaped = false
var lastPlaceholderIndex = -1 var lastPlaceholderIndex = -1
for (i in 0 until length) { var i = 0
while (i < length) {
val c = string[i] val c = string[i]
if (c == '\\' && if (c == '\\' &&
(loggerType == PlaceholderLoggerType.SLF4J_EQUAL_PLACEHOLDERS || loggerType == PlaceholderLoggerType.SLF4J)) { (loggerType == PlaceholderLoggerType.SLF4J_EQUAL_PLACEHOLDERS || loggerType == PlaceholderLoggerType.SLF4J)) {
escaped = !escaped escaped = !escaped
i = placeholderCountShiftIndexStrategy.shiftAfterEscapeChar(i)
} }
else if (c == '{') { else if (c == '{') {
if (holderIndex != 0 && i == 0 && !holders[holderIndex - 1].isConstant) { if (holderIndex != 0 && i == 0 && !holders[holderIndex - 1].isConstant) {
i += 1
continue continue
} }
if (!escaped) { if (!escaped) {
@@ -477,6 +504,7 @@ private fun countBracesBasedPlaceholders(holders: List<LoggingStringPartEvaluato
escaped = false escaped = false
lastPlaceholderIndex = -1 lastPlaceholderIndex = -1
} }
i += 1
} }
} }
return PlaceholderCountResult(placeholderRangeList, if (full) PlaceholdersStatus.EXACTLY else PlaceholdersStatus.PARTIAL) return PlaceholderCountResult(placeholderRangeList, if (full) PlaceholdersStatus.EXACTLY else PlaceholdersStatus.PARTIAL)

View File

@@ -464,4 +464,64 @@ class JavaLoggingArgumentSymbolReferenceProviderTest : LoggingArgumentSymbolRefe
""".trimIndent()) """.trimIndent())
doTest(emptyMap()) doTest(emptyMap())
} }
fun `test should resolve with escape character in simple string log4j2`() {
myFixture.configureByText("Logging.java", """
import org.apache.logging.log4j.*;
class Logging {
private static final Logger LOG = LogManager.getLogger();
void m(int i) {
LOG.info("\\{<caret>}", i);
}
}
""".trimIndent())
doTest(mapOf(TextRange(3, 5) to "i"))
}
fun `test should resolve with escape character in multiline string log4j2`() {
val multilineString = "\"\"\"\n" +
"\\\\{<caret>}" +
"\"\"\""
myFixture.configureByText("Logging.java", """
import org.apache.logging.log4j.*;
class Logging {
private static final Logger LOG = LogManager.getLogger();
void m(int i) {
LOG.info($multilineString, i);
}
}
""".trimIndent())
doTest(mapOf(TextRange(6, 8) to "i"))
}
fun `test should not resolve with escape character in simple string slf4j`() {
myFixture.configureByText("Logging.java", """
import org.slf4j.*;
class Logging {
private static final Logger LOG = LoggerFactory.getLogger(Logging.class);
void m(int i) {
LOG.info("\\{<caret>}", i);
}
}
""".trimIndent())
doTest(emptyMap())
}
fun `test should not resolve with escape character in multiline string slf4j`() {
val multilineString = "\"\"\"\n" +
"\\\\{<caret>}" +
"\"\"\""
myFixture.configureByText("Logging.java", """
import org.slf4j.*;
class Logging {
private static final Logger LOG = LoggerFactory.getLogger(Logging.class);
void m(int i) {
LOG.info($multilineString, i);
}
}
""".trimIndent())
doTest(emptyMap())
}
} }

View File

@@ -482,4 +482,64 @@ class KotlinLoggingArgumentSymbolReferenceProviderTest : LoggingArgumentSymbolRe
""".trimIndent()) """.trimIndent())
doTest(emptyMap()) doTest(emptyMap())
} }
fun `test should resolve with escape character in simple string log4j2`() {
myFixture.configureByText("Logging.kt", """
import org.apache.logging.log4j.*
class Logging {
val LOG: Logger = LoggerFactory.getLogger(Logging.class)
fun m(i: Int) {
LOG.info("\\{<caret>}", i)
}
}
""".trimIndent())
doTest(mapOf(TextRange(3, 5) to "i"))
}
fun `test should resolve with escape character in multiline string log4j2`() {
val multilineString = "\"\"\"\n" +
"\\\\{<caret>}" +
"\"\"\""
myFixture.configureByText("Logging.kt", """
import org.apache.logging.log4j.*
class Logging {
val LOG: Logger = LoggerFactory.getLogger(Logging.class)
fun m(i: Int) {
LOG.info("$multilineString", i)
}
}
""".trimIndent())
doTest(mapOf(TextRange(7, 9) to "i"))
}
fun `test should not resolve with escape character in simple string slf4j`() {
myFixture.configureByText("Logging.kt", """
import org.slf4j.*
class Logging {
val LOG: Logger = LogManager.getFormatterLogger()
fun m(i: Int) {
LOG.info("\\{<caret>}", i)
}
}
""".trimIndent())
doTest(emptyMap())
}
fun `test should resolve with escape character in multiline string slf4j`() {
val multilineString = "\"\"\"\n" +
"\\\\{<caret>}" +
"\"\"\""
myFixture.configureByText("Logging.kt", """
import org.slf4j.*
class Logging {
val LOG: Logger = LoggerFactory.getLogger(Logging.class)
fun m(i: Int) {
LOG.info("$multilineString", i)
}
}
""".trimIndent())
doTest(mapOf(TextRange(7, 9) to "i"))
}
} }