diff --git a/jvm/jvm-analysis-impl/src/com/intellij/analysis/logging/resolve/LoggingArgumentSymbolReferenceProvider.kt b/jvm/jvm-analysis-impl/src/com/intellij/analysis/logging/resolve/LoggingArgumentSymbolReferenceProvider.kt index 047e2b8a40df..1262b7ff1fca 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/analysis/logging/resolve/LoggingArgumentSymbolReferenceProvider.kt +++ b/jvm/jvm-analysis-impl/src/com/intellij/analysis/logging/resolve/LoggingArgumentSymbolReferenceProvider.kt @@ -2,6 +2,8 @@ package com.intellij.analysis.logging.resolve 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.model.Symbol 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.search.SearchRequest import com.intellij.openapi.project.Project -import org.jetbrains.uast.UCallExpression -import org.jetbrains.uast.UExpression +import org.jetbrains.uast.* import org.jetbrains.uast.expressions.UInjectionHost -import org.jetbrains.uast.getParentOfType -import org.jetbrains.uast.toUElementOfType class LoggingArgumentSymbolReferenceProvider : PsiSymbolReferenceProvider { override fun getReferences(element: PsiExternalReferenceHost, hints: PsiSymbolReferenceHints): Collection { @@ -74,14 +73,19 @@ internal fun getAlignedPlaceholderCount(placeholderList: List, context: P internal fun getPlaceholderRanges(context: PlaceholderContext): List? { 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( LoggingStringPartEvaluator.PartHolder( logStringText, 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 return placeholderCountResult.placeholderRangesList +} + +private fun isKotlinMultilineString(logString: UExpression, text : String): Boolean { + return logString is UPolyadicExpression && text.startsWith("\"\"\"") && text.endsWith("\"\"\"") && text.length >= 6 } \ No newline at end of file diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingPlaceholderCountMatchesArgumentCountInspection.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingPlaceholderCountMatchesArgumentCountInspection.kt index 6bb4cc48b885..9c91c5f512a2 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingPlaceholderCountMatchesArgumentCountInspection.kt +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingPlaceholderCountMatchesArgumentCountInspection.kt @@ -56,7 +56,7 @@ class LoggingPlaceholderCountMatchesArgumentCountInspection : AbstractBaseUastLo 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) { return true diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingPlaceholderUtil.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingPlaceholderUtil.kt index cd00129e3a40..6d3491efbf6e 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingPlaceholderUtil.kt +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingPlaceholderUtil.kt @@ -124,6 +124,26 @@ internal enum class PlaceholdersStatus { 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) @@ -300,6 +320,7 @@ internal fun findAdditionalArguments(node: UCallExpression, * @param loggerType The type of the logger used. * @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 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. */ @@ -307,12 +328,13 @@ internal fun solvePlaceholderCount( loggerType: PlaceholderLoggerType, argumentCount: Int, holders: List, + placeholderCountShiftIndexStrategy: PlaceholderCountIndexStrategy ): PlaceholderCountResult { return if (loggerType == PlaceholderLoggerType.LOG4J_FORMATTED_STYLE) { countFormattedBasedPlaceholders(holders, argumentCount) } 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) } -private fun countBracesBasedPlaceholders(holders: List, loggerType: PlaceholderLoggerType): PlaceholderCountResult { +private fun countBracesBasedPlaceholders(holders: List, + loggerType: PlaceholderLoggerType, + placeholderCountShiftIndexStrategy: PlaceholderCountIndexStrategy): PlaceholderCountResult { var count = 0 var full = true val placeholderRangeList: MutableList = mutableListOf() @@ -448,14 +472,17 @@ private fun countBracesBasedPlaceholders(holders: List}", i); + } + } + """.trimIndent()) + doTest(mapOf(TextRange(3, 5) to "i")) + } + + fun `test should resolve with escape character in multiline string log4j2`() { + val multilineString = "\"\"\"\n" + + "\\\\{}" + + "\"\"\"" + 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("\\{}", i); + } + } + """.trimIndent()) + doTest(emptyMap()) + } + + fun `test should not resolve with escape character in multiline string slf4j`() { + val multilineString = "\"\"\"\n" + + "\\\\{}" + + "\"\"\"" + 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()) + } } \ No newline at end of file diff --git a/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/logging/resolve/KotlinLoggingArgumentSymbolReferenceProviderTest.kt b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/logging/resolve/KotlinLoggingArgumentSymbolReferenceProviderTest.kt index 440800d65976..afa76901b0c6 100644 --- a/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/logging/resolve/KotlinLoggingArgumentSymbolReferenceProviderTest.kt +++ b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/logging/resolve/KotlinLoggingArgumentSymbolReferenceProviderTest.kt @@ -482,4 +482,64 @@ class KotlinLoggingArgumentSymbolReferenceProviderTest : LoggingArgumentSymbolRe """.trimIndent()) 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("\\{}", i) + } + } + """.trimIndent()) + doTest(mapOf(TextRange(3, 5) to "i")) + } + + + fun `test should resolve with escape character in multiline string log4j2`() { + val multilineString = "\"\"\"\n" + + "\\\\{}" + + "\"\"\"" + 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("\\{}", i) + } + } + """.trimIndent()) + doTest(emptyMap()) + } + + + fun `test should resolve with escape character in multiline string slf4j`() { + val multilineString = "\"\"\"\n" + + "\\\\{}" + + "\"\"\"" + 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")) + } } \ No newline at end of file