mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
[jvm-inspections] IDEA-310343 Patterns as arguments for logging
GitOrigin-RevId: 95c45d47447b70188c9d2b869923ff12e2b5898d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5f2ac62609
commit
009300fc21
@@ -96,7 +96,7 @@
|
||||
<inspectionElementsMerger implementation="com.intellij.codeInspection.test.junit.JUnitMalformedDeclarationInspectionMerger"/>
|
||||
|
||||
<!--logging-->
|
||||
<localInspection language="UAST" enabledByDefault="false" level="WARNING" shortName="LoggingStringTemplateAsArgument"
|
||||
<localInspection language="UAST" enabledByDefault="true" level="WARNING" shortName="LoggingStringTemplateAsArgument"
|
||||
groupBundle="messages.JvmAnalysisBundle" bundle="messages.JvmAnalysisBundle"
|
||||
groupPathKey="jvm.inspections.group.name" groupKey="jvm.inspections.logging.frameworks.group.name"
|
||||
key="jvm.inspection.logging.string.template.as.argument.display.name"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports string templates that are used as arguments to <b>SLF4J</b> and <b>Log4j 2</b> logging methods.
|
||||
Methods <code>org.apache.logging.log4j.Logger.log()</code> is supported only for <b>all log levels</b> option.
|
||||
String templates are evaluated at runtime even when the logging message is not logged; this can negatively impact performance.
|
||||
It is recommended to use a parameterized log message instead, which will not be evaluated when logging is disabled.
|
||||
<p><b>Example (for Kotlin):</b></p>
|
||||
@@ -18,6 +19,19 @@ It is recommended to use a parameterized log message instead, which will not be
|
||||
when string templates contain method calls or assignment expressions.
|
||||
</p>
|
||||
<!-- tooltip end -->
|
||||
<ul>
|
||||
<li>
|
||||
Use the <b>Warn on</b> list to ignore certain higher logging levels. Higher logging levels may be always enabled, and the arguments will always be evaluated.
|
||||
</li>
|
||||
<li>
|
||||
Use the <b>Do not warn when only expressions with primitive types, their wrappers or String are included</b> options to ignore
|
||||
string templates, which contain only expressions with primitive types, their wrappers or String.
|
||||
For example, it could be useful to prevent loading lazy collections.
|
||||
Note that, creating string even only with expressions with primitive types,
|
||||
their wrappers or String at runtime can negatively impact performance.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p><small>New in 2023.1</small></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -195,6 +195,13 @@ jvm.inspections.migrate.assert.to.matcher.description=Assert expression <code>#r
|
||||
jvm.inspection.logging.string.template.as.argument.display.name=String template as argument to logging call
|
||||
jvm.inspection.logging.string.template.as.argument.problem.descriptor=String template as argument to <code>#ref()</code> logging call #loc
|
||||
jvm.inspection.logging.string.template.as.argument.quickfix.name=Replace with placeholders
|
||||
jvm.inspection.logging.string.template.as.argument.skip.on.primitives=Do not warn when only expressions with primitive types, their wrappers or String are included
|
||||
jvm.inspection.logging.string.template.as.argument.warn.on.label=Warn on:
|
||||
jvm.inspection.logging.string.template.as.argument.all.levels.option=all log levels
|
||||
jvm.inspection.logging.string.template.as.argument.warn.level.and.lower.option=warn level and lower
|
||||
jvm.inspection.logging.string.template.as.argument.info.level.and.lower.option=info level and lower
|
||||
jvm.inspection.logging.string.template.as.argument.debug.level.and.lower.option=debug level and lower
|
||||
jvm.inspection.logging.string.template.as.argument.trace.level.option=trace level
|
||||
|
||||
jvm.inspection.test.failed.line.display.name=Failed line in test
|
||||
|
||||
|
||||
@@ -3,10 +3,13 @@ package com.intellij.codeInspection.logging
|
||||
|
||||
import com.intellij.analysis.JvmAnalysisBundle
|
||||
import com.intellij.codeInspection.*
|
||||
import com.intellij.codeInspection.options.OptPane
|
||||
import com.intellij.lang.Language
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.CommonClassNames
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiType
|
||||
import com.intellij.psi.util.TypeConversionUtil
|
||||
import com.intellij.uast.UastHintedVisitorAdapter
|
||||
import org.jetbrains.uast.*
|
||||
import org.jetbrains.uast.expressions.UInjectionHost
|
||||
@@ -15,6 +18,33 @@ import org.jetbrains.uast.generate.replace
|
||||
import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
|
||||
|
||||
class LoggingStringTemplateAsArgumentInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
|
||||
@JvmField
|
||||
var myLimitLevelType: LimitLevelType = LimitLevelType.ALL
|
||||
|
||||
@JvmField
|
||||
var mySkipPrimitives: Boolean = true
|
||||
override fun getOptionsPane(): OptPane {
|
||||
return OptPane.pane(
|
||||
OptPane.dropdown(
|
||||
"myLimitLevelType",
|
||||
JvmAnalysisBundle.message("jvm.inspection.logging.string.template.as.argument.warn.on.label"),
|
||||
OptPane.option(LimitLevelType.ALL,
|
||||
JvmAnalysisBundle.message("jvm.inspection.logging.string.template.as.argument.all.levels.option")),
|
||||
OptPane.option(LimitLevelType.WARN_AND_LOWER,
|
||||
JvmAnalysisBundle.message("jvm.inspection.logging.string.template.as.argument.warn.level.and.lower.option")),
|
||||
OptPane.option(LimitLevelType.INFO_AND_LOWER,
|
||||
JvmAnalysisBundle.message("jvm.inspection.logging.string.template.as.argument.info.level.and.lower.option")),
|
||||
OptPane.option(LimitLevelType.DEBUG_AND_LOWER,
|
||||
JvmAnalysisBundle.message("jvm.inspection.logging.string.template.as.argument.debug.level.and.lower.option")),
|
||||
OptPane.option(LimitLevelType.TRACE,
|
||||
JvmAnalysisBundle.message("jvm.inspection.logging.string.template.as.argument.trace.level.option")),
|
||||
),
|
||||
OptPane.checkbox("mySkipPrimitives",
|
||||
JvmAnalysisBundle.message("jvm.inspection.logging.string.template.as.argument.skip.on.primitives"))
|
||||
)
|
||||
}
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor =
|
||||
UastHintedVisitorAdapter.create(
|
||||
holder.file.language,
|
||||
@@ -22,52 +52,103 @@ class LoggingStringTemplateAsArgumentInspection : AbstractBaseUastLocalInspectio
|
||||
arrayOf(UCallExpression::class.java),
|
||||
directOnly = true
|
||||
)
|
||||
}
|
||||
|
||||
private class LoggingStringTemplateAsArgumentVisitor(
|
||||
private val holder: ProblemsHolder,
|
||||
) : AbstractUastNonRecursiveVisitor() {
|
||||
inner class LoggingStringTemplateAsArgumentVisitor(
|
||||
private val holder: ProblemsHolder,
|
||||
) : AbstractUastNonRecursiveVisitor() {
|
||||
|
||||
override fun visitCallExpression(node: UCallExpression): Boolean {
|
||||
if (!LOG_MATCHERS.uCallMatches(node)) return true
|
||||
val valueArguments = node.valueArguments
|
||||
if (valueArguments.isEmpty()) return true
|
||||
var stringExpression = valueArguments[0]
|
||||
var indexStringExpression = 0
|
||||
if (!canBeText(stringExpression.getExpressionType())) {
|
||||
if (valueArguments.size < 2) {
|
||||
override fun visitCallExpression(node: UCallExpression): Boolean {
|
||||
if (!LOG_MATCHERS.uCallMatches(node)) return true
|
||||
if (skipAccordingLevel(node)) return true
|
||||
val valueArguments = node.valueArguments
|
||||
val uMethod = node.resolve().toUElement() as? UMethod ?: return true
|
||||
val uastParameters = uMethod.uastParameters
|
||||
if (valueArguments.isEmpty() || uastParameters.isEmpty()) return true
|
||||
var indexStringExpression = 0
|
||||
if (!uastParameters[indexStringExpression].type.canBeText()) {
|
||||
if (valueArguments.size < 2 || uastParameters.size < 2) {
|
||||
return true
|
||||
}
|
||||
indexStringExpression = 1
|
||||
if (!uastParameters[indexStringExpression].type.canBeText()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val stringExpression = valueArguments[indexStringExpression]
|
||||
|
||||
val parts: MutableList<UExpression> = mutableListOf()
|
||||
//if parameter is String and argument is NOT String, probably it is String Template like "$object" for Kotlin
|
||||
if (stringExpression !is UPolyadicExpression && !stringExpression.getExpressionType().canBeText() &&
|
||||
stringExpression.lang == Language.findLanguageByID("kotlin")) {
|
||||
val text: String = stringExpression.sourcePsi?.parent?.text ?: return true
|
||||
if (text.startsWith("$")) {
|
||||
parts.add(stringExpression)
|
||||
}
|
||||
}
|
||||
|
||||
if (stringExpression is UPolyadicExpression && isPattern(stringExpression)) {
|
||||
parts.addAll(stringExpression.operands)
|
||||
}
|
||||
|
||||
if (parts.isEmpty()) {
|
||||
return true
|
||||
}
|
||||
stringExpression = valueArguments[1]
|
||||
indexStringExpression = 1
|
||||
if (!canBeText(stringExpression.getExpressionType())) {
|
||||
|
||||
if (mySkipPrimitives && allExpressionsInPatternArePrimitivesOrWrappers(parts)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPattern(stringExpression)) {
|
||||
if (isGuarded(node)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val message = JvmAnalysisBundle.message("jvm.inspection.logging.string.template.as.argument.problem.descriptor")
|
||||
holder.registerUProblem(node, message, ConvertToPlaceHolderQuickfix(indexStringExpression))
|
||||
return true
|
||||
}
|
||||
|
||||
private fun allExpressionsInPatternArePrimitivesOrWrappers(operands: List<UExpression>): Boolean {
|
||||
return operands.all { it.getExpressionType().isPrimitiveOrWrappers() }
|
||||
}
|
||||
|
||||
val message = JvmAnalysisBundle.message("jvm.inspection.logging.string.template.as.argument.problem.descriptor")
|
||||
holder.registerUProblem(node, message, ConvertToPlaceHolderQuickfix(indexStringExpression))
|
||||
return true
|
||||
}
|
||||
private fun skipAccordingLevel(node: UCallExpression): Boolean {
|
||||
if (myLimitLevelType != LimitLevelType.ALL) {
|
||||
val loggerLevel = getLoggerLevel(node)
|
||||
if (loggerLevel == null) return true
|
||||
val notSkip: Boolean = when (loggerLevel) {
|
||||
LevelType.FATAL -> false
|
||||
LevelType.ERROR -> false
|
||||
LevelType.WARNING -> myLimitLevelType.ordinal == LimitLevelType.WARN_AND_LOWER.ordinal
|
||||
LevelType.INFO -> myLimitLevelType.ordinal <= LimitLevelType.INFO_AND_LOWER.ordinal
|
||||
LevelType.DEBUG -> myLimitLevelType.ordinal <= LimitLevelType.DEBUG_AND_LOWER.ordinal
|
||||
LevelType.TRACE -> myLimitLevelType.ordinal <= LimitLevelType.TRACE.ordinal
|
||||
}
|
||||
return !notSkip
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isPattern(stringExpression: UExpression): Boolean {
|
||||
//Perhaps, it needs to be customized for Java Pattern in the future
|
||||
return stringExpression is UPolyadicExpression && stringExpression is UInjectionHost &&
|
||||
!stringExpression.operands.all { it is ULiteralExpression }
|
||||
private fun isPattern(stringExpression: UPolyadicExpression): Boolean {
|
||||
//it needs to be customized for Java
|
||||
return stringExpression is UInjectionHost &&
|
||||
stringExpression.lang == Language.findLanguageByID("kotlin") &&
|
||||
!stringExpression.operands.all { it is ULiteralExpression }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun canBeText(expressionType: PsiType?): Boolean {
|
||||
if (
|
||||
expressionType?.equalsToText(CommonClassNames.JAVA_LANG_STRING) == true ||
|
||||
expressionType?.equalsToText(CommonClassNames.JAVA_LANG_CHAR_SEQUENCE) == true
|
||||
) return true
|
||||
return false
|
||||
}
|
||||
private fun PsiType?.canBeText(): Boolean {
|
||||
return this?.equalsToText(CommonClassNames.JAVA_LANG_STRING) == true ||
|
||||
this?.equalsToText(CommonClassNames.JAVA_LANG_CHAR_SEQUENCE) == true
|
||||
}
|
||||
|
||||
private fun PsiType?.isPrimitiveOrWrappers(): Boolean {
|
||||
return this != null && (TypeConversionUtil.isPrimitiveAndNotNull(this) ||
|
||||
TypeConversionUtil.isPrimitiveWrapper(this) ||
|
||||
canBeText())
|
||||
}
|
||||
|
||||
class ConvertToPlaceHolderQuickfix(private val indexStringExpression: Int) : LocalQuickFix {
|
||||
@@ -82,34 +163,40 @@ class ConvertToPlaceHolderQuickfix(private val indexStringExpression: Int) : Loc
|
||||
parametersBeforeString.add(valueArguments[0])
|
||||
}
|
||||
val builderString = StringBuilder()
|
||||
val template = valueArguments[indexStringExpression] as? UPolyadicExpression ?: return
|
||||
val stringTemplate = valueArguments[indexStringExpression]
|
||||
var indexOuterPlaceholder = indexStringExpression + 1
|
||||
val loggerType = getLoggerType(uCallExpression)
|
||||
if (stringTemplate is UPolyadicExpression) {
|
||||
val loggerType = getLoggerType(uCallExpression)
|
||||
|
||||
for (operand in template.operands) {
|
||||
if (operand is ULiteralExpression && operand.isString) {
|
||||
val text = operand.value.toString()
|
||||
val countPlaceHolders = countPlaceHolders(text, loggerType)
|
||||
for (index in 0 until countPlaceHolders) {
|
||||
val nextIndex = indexOuterPlaceholder + index
|
||||
if (nextIndex < valueArguments.size) {
|
||||
parametersAfterString.add(valueArguments[nextIndex])
|
||||
for (operand in stringTemplate.operands) {
|
||||
if (operand is ULiteralExpression && operand.isString) {
|
||||
val text = operand.value.toString()
|
||||
val countPlaceHolders = countPlaceHolders(text, loggerType)
|
||||
for (index in 0 until countPlaceHolders) {
|
||||
val nextIndex = indexOuterPlaceholder + index
|
||||
if (nextIndex < valueArguments.size) {
|
||||
parametersAfterString.add(valueArguments[nextIndex])
|
||||
}
|
||||
else {
|
||||
break
|
||||
}
|
||||
}
|
||||
else {
|
||||
break
|
||||
indexOuterPlaceholder += countPlaceHolders
|
||||
builderString.append(text)
|
||||
}
|
||||
else {
|
||||
if (builderString.endsWith("\\") && (loggerType == LoggerType.SLF4J_LOGGER_TYPE || loggerType == LoggerType.SLF4J_BUILDER_TYPE)) {
|
||||
builderString.append("\\")
|
||||
}
|
||||
builderString.append("{}")
|
||||
parametersAfterString.add(operand)
|
||||
}
|
||||
indexOuterPlaceholder += countPlaceHolders
|
||||
builderString.append(text)
|
||||
}
|
||||
else {
|
||||
if (builderString.endsWith("\\") && (loggerType == LoggerType.SLF4J_LOGGER_TYPE || loggerType == LoggerType.SLF4J_BUILDER_TYPE)) {
|
||||
builderString.append("\\")
|
||||
}
|
||||
builderString.append("{}")
|
||||
parametersAfterString.add(operand)
|
||||
}
|
||||
}
|
||||
else {
|
||||
builderString.append("{}")
|
||||
parametersAfterString.add(stringTemplate)
|
||||
}
|
||||
|
||||
if (indexOuterPlaceholder < valueArguments.size) {
|
||||
for (index in indexOuterPlaceholder until valueArguments.size) {
|
||||
@@ -118,10 +205,11 @@ class ConvertToPlaceHolderQuickfix(private val indexStringExpression: Int) : Loc
|
||||
}
|
||||
val elementFactory = uCallExpression.getUastElementFactory(project) ?: return
|
||||
val newText = elementFactory.createStringLiteralExpression(builderString.toString(), uCallExpression.sourcePsi) ?: return
|
||||
val newParameters = mutableListOf<UExpression>()
|
||||
newParameters.addAll(parametersBeforeString)
|
||||
newParameters.add(newText)
|
||||
newParameters.addAll(parametersAfterString)
|
||||
val newParameters = mutableListOf<UExpression>().apply {
|
||||
addAll(parametersBeforeString)
|
||||
add(newText)
|
||||
addAll(parametersAfterString)
|
||||
}
|
||||
|
||||
val methodName = uCallExpression.methodName ?: return
|
||||
val newCall = elementFactory.createCallExpression(uCallExpression.receiver, methodName, newParameters, uCallExpression.returnType,
|
||||
@@ -130,4 +218,8 @@ class ConvertToPlaceHolderQuickfix(private val indexStringExpression: Int) : Loc
|
||||
val oldCall = uCallExpression.getQualifiedParentOrThis()
|
||||
oldCall.replace(newCall)
|
||||
}
|
||||
}
|
||||
|
||||
enum class LimitLevelType {
|
||||
ALL, WARN_AND_LOWER, INFO_AND_LOWER, DEBUG_AND_LOWER, TRACE
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
package com.intellij.codeInspection.logging
|
||||
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.*
|
||||
|
||||
const val SLF4J_LOGGER = "org.slf4j.Logger"
|
||||
|
||||
@@ -42,6 +42,111 @@ fun getLoggerType(uCall: UCallExpression?): LoggerType? {
|
||||
}
|
||||
}
|
||||
|
||||
fun isGuarded(call: UCallExpression): Boolean {
|
||||
val variable: UVariable = getLoggerQualifier(call) ?: return false
|
||||
val loggerLevel = getLoggerLevel(call) ?: return false
|
||||
val ifExpression: UIfExpression? = call.getParentOfType<UIfExpression>()
|
||||
val condition = ifExpression?.condition ?: return false
|
||||
return isGuardedIn(condition, variable, loggerLevel)
|
||||
}
|
||||
|
||||
fun isGuardedIn(condition: UExpression, variable: UVariable, loggerLevel: LevelType): Boolean {
|
||||
val loggerLevelFromCondition: LevelType = getLevelFromCondition(condition, variable) ?: return false
|
||||
return loggerLevelFromCondition == loggerLevel
|
||||
}
|
||||
|
||||
fun getLevelFromCondition(condition: UExpression, variable: UVariable): LevelType? {
|
||||
if (condition is UCallExpression) {
|
||||
if ((condition.receiver as? UResolvable)?.resolveToUElement()?.sourcePsi != variable.sourcePsi) {
|
||||
return null
|
||||
}
|
||||
val methodName = condition.methodName ?: return null
|
||||
return levelTypeFromGuard(methodName)
|
||||
}
|
||||
if (condition is UQualifiedReferenceExpression) {
|
||||
if ((condition.receiver as? UResolvable)?.resolveToUElement()?.sourcePsi != variable.sourcePsi) {
|
||||
return null
|
||||
}
|
||||
val methodName = condition.resolvedName ?: return null
|
||||
return levelTypeFromGuard(methodName)
|
||||
}
|
||||
if (condition is UPolyadicExpression) {
|
||||
for (operand in condition.operands) {
|
||||
val levelFromCondition = getLevelFromCondition(operand, variable)
|
||||
if (levelFromCondition != null) return levelFromCondition
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun levelTypeFromGuard(methodName: String): LevelType? {
|
||||
if (!methodName.startsWith("is") || !methodName.endsWith("Enabled")) {
|
||||
return null
|
||||
}
|
||||
for (level in LevelType.values()) {
|
||||
if (methodName.substring(2, methodName.length - 7).equals(level.name, ignoreCase = true)) {
|
||||
return level
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getLoggerQualifier(call: UCallExpression?): UVariable? {
|
||||
if (call == null) return null
|
||||
var receiver: UExpression? = call.receiver
|
||||
if (receiver is UCallExpression) {
|
||||
receiver = receiver.receiver
|
||||
}
|
||||
if (receiver is UQualifiedReferenceExpression) {
|
||||
receiver = receiver.receiver
|
||||
}
|
||||
if (receiver is USimpleNameReferenceExpression) {
|
||||
val resolved = receiver.resolveToUElement() as? UVariable ?: return null
|
||||
if (resolved.type.equalsToText(SLF4J_LOGGER) ||
|
||||
resolved.type.equalsToText(LOG4J_LOGGER)) {
|
||||
return resolved
|
||||
}
|
||||
if (resolved.type.equalsToText(SLF4J_EVENT_BUILDER) ||
|
||||
resolved.type.equalsToText(LOG4J_LOG_BUILDER)) {
|
||||
val uastInitializer = (resolved.uastInitializer as? UQualifiedReferenceExpression) ?: return null
|
||||
return getLoggerQualifier(uastInitializer.selector as? UCallExpression)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getLoggerLevel(uCall: UCallExpression?): LevelType? {
|
||||
if (uCall == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
var levelName = uCall.methodName
|
||||
if ("log" == levelName) {
|
||||
//also, it could be LOG4J_LOGGER, for example, log(level, pattern),
|
||||
//but let's skip it, because it is usually used for dynamic choice
|
||||
var receiver: UElement = uCall.receiver ?: return null
|
||||
if (receiver is UQualifiedReferenceExpression) {
|
||||
receiver = receiver.selector
|
||||
}
|
||||
else if (receiver is USimpleNameReferenceExpression) {
|
||||
val variable = receiver.resolveToUElement() as? UVariable ?: return null
|
||||
receiver = (variable.uastInitializer as? UQualifiedReferenceExpression)?.selector ?: return null
|
||||
}
|
||||
levelName = (receiver as? UCallExpression)?.methodName
|
||||
}
|
||||
if (levelName == null) {
|
||||
return null
|
||||
}
|
||||
for (value in LevelType.values()) {
|
||||
if (value.name.equals(levelName, ignoreCase = true) ||
|
||||
"at${value.name}".equals(levelName, ignoreCase = true)
|
||||
) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun countPlaceHolders(text: String, loggerType: LoggerType?): Int {
|
||||
var count = 0
|
||||
var placeHolder = false
|
||||
@@ -73,4 +178,8 @@ fun countPlaceHolders(text: String, loggerType: LoggerType?): Int {
|
||||
|
||||
enum class LoggerType {
|
||||
SLF4J_LOGGER_TYPE, SLF4J_BUILDER_TYPE, LOG4J_LOGGER_TYPE, LOG4J_BUILDER_TYPE
|
||||
}
|
||||
|
||||
enum class LevelType {
|
||||
FATAL, ERROR, WARNING, INFO, DEBUG, TRACE
|
||||
}
|
||||
@@ -6,23 +6,26 @@ class StringTemplateAsArgument {
|
||||
private val loggerLog4J = LogManager.getLogger()
|
||||
|
||||
fun testLoggerSlf4JBuilder() {
|
||||
val variable1 = "test"
|
||||
val variable1 = 1
|
||||
loggerSlf4J.atInfo().<warning descr="String template as argument to 'log()' logging call">log</warning>("variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun testLoggerLog4J() {
|
||||
val variable1 = "test"
|
||||
val variable1 = 1
|
||||
loggerLog4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun testLoggerLog4JBuilder() {
|
||||
val variable1 = "test"
|
||||
val variable1 = 1
|
||||
loggerLog4J.atInfo().<warning descr="String template as argument to 'log()' logging call">log</warning>( "variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun testLoggerSlf4J() {
|
||||
val variable1 = "test"
|
||||
val variable1 = 1
|
||||
loggerSlf4J.info("variable1: {}", variable1)
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("${variable1}")
|
||||
loggerSlf4J.info("${getString()}")
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("${getInt()}")
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("variable1: ${variable1}")
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("variable1: $variable1")
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("variable1: $variable1", RuntimeException())
|
||||
@@ -32,4 +35,7 @@ class StringTemplateAsArgument {
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("{} variable1: $variable1 {} {} {}", 1, 2, RuntimeException())
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("{} variable1: $variable1 {}", 1, 2)
|
||||
}
|
||||
|
||||
fun getString() = "test"
|
||||
fun getInt() = 1
|
||||
}
|
||||
@@ -1,18 +1,29 @@
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.RuntimeException
|
||||
|
||||
class StringTemplateAsArgumentFix {
|
||||
private val loggerSlf4J = LoggerFactory.getLogger()
|
||||
fun testLoggerSlf4J() {
|
||||
val variable1 = "test"
|
||||
val variable2 = 1
|
||||
loggerSlf4J.info("${variable1}")
|
||||
loggerSlf4J.info("{}", variable2)
|
||||
loggerSlf4J.info("{}", getMethod())
|
||||
loggerSlf4J.info("variable1: {}", variable1)
|
||||
loggerSlf4J.info("variable1: {}", variable1)
|
||||
loggerSlf4J.info("variable1: {}", variable1)
|
||||
loggerSlf4J.info("variable1: {}", variable1, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: {}", 1, variable1)
|
||||
loggerSlf4J.info("{} variable1: {} {} variable1: {}", 1, variable1, 2, variable1)
|
||||
loggerSlf4J.info("{} variable1: {} {} variable1: {} {}", 1, variable1, 2, variable1, 3)
|
||||
loggerSlf4J.info("{} variable1: {} {} variable1: {} {}", 1, variable1, 2, variable1, 3, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: {}", 1, variable1, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: {} {}", 1, variable1, 2, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: {} {} {}", 1, variable1, 2, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: {} {} {} {}", 1, variable1, 2, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: {} {}", 1, variable1, 2)
|
||||
}
|
||||
|
||||
fun getMethod() = 1
|
||||
}
|
||||
@@ -1,18 +1,29 @@
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.RuntimeException
|
||||
|
||||
class StringTemplateAsArgumentFix {
|
||||
private val loggerSlf4J = LoggerFactory.getLogger()
|
||||
fun testLoggerSlf4J() {
|
||||
val variable1 = "test"
|
||||
val variable2 = 1
|
||||
loggerSlf4J.info("${variable1}")
|
||||
loggerSlf4J.info("${variable2}")
|
||||
loggerSlf4J.info("${getMethod()}")
|
||||
loggerSlf4J.info("variable1: {}", variable1)
|
||||
loggerSlf4J.in<caret>fo("variable1: ${variable1}")
|
||||
loggerSlf4J.info("variable1: $variable1")
|
||||
loggerSlf4J.info("variable1: $variable1", RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: $variable1", 1)
|
||||
loggerSlf4J.info("{} variable1: $variable1 {} variable1: $variable1", 1, 2)
|
||||
loggerSlf4J.info("{} variable1: $variable1 {} variable1: $variable1 {}", 1, 2, 3)
|
||||
loggerSlf4J.info("{} variable1: $variable1 {} variable1: $variable1 {}", 1, 2, 3, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: $variable1", 1, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: $variable1 {}", 1, 2, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: $variable1 {} {}", 1, 2, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: $variable1 {} {} {}", 1, 2, RuntimeException())
|
||||
loggerSlf4J.info("{} variable1: $variable1 {}", 1, 2)
|
||||
}
|
||||
|
||||
fun getMethod() = 1
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
data class Data(val i: Int)
|
||||
class StringTemplateAsArgumentGuarded {
|
||||
private val loggerSlf4J = LoggerFactory.getLogger()
|
||||
private val loggerLog4J = LogManager.getLogger()
|
||||
|
||||
|
||||
fun guardedLog4J() {
|
||||
val data = Data(1)
|
||||
if (loggerLog4J.isInfoEnabled) {
|
||||
loggerLog4J.info("$data" )
|
||||
}
|
||||
if (loggerLog4J.isInfoEnabled()) {
|
||||
loggerLog4J.info("$data" )
|
||||
}
|
||||
loggerLog4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("$data")
|
||||
}
|
||||
|
||||
|
||||
fun guardedLog4JBuilder() {
|
||||
val data = Data(1)
|
||||
val atInfo = loggerLog4J.atInfo()
|
||||
if (loggerLog4J.isInfoEnabled) {
|
||||
loggerLog4J.atInfo().log("$data" )
|
||||
}
|
||||
if (loggerLog4J.isInfoEnabled) {
|
||||
atInfo.log("$data" )
|
||||
}
|
||||
if (loggerLog4J.isDebugEnabled) {
|
||||
atInfo.<warning descr="String template as argument to 'log()' logging call">log</warning>("$data" )
|
||||
}
|
||||
if (loggerLog4J.isDebugEnabled()) {
|
||||
atInfo.<warning descr="String template as argument to 'log()' logging call">log</warning>("$data" )
|
||||
}
|
||||
loggerLog4J.atInfo().<warning descr="String template as argument to 'log()' logging call">log</warning>("$data" )
|
||||
atInfo.<warning descr="String template as argument to 'log()' logging call">log</warning>("$data" )
|
||||
}
|
||||
|
||||
fun guardedSlf4j() {
|
||||
val data = Data(1)
|
||||
if (loggerSlf4J.isInfoEnabled) {
|
||||
loggerSlf4J.info("$data" )
|
||||
}
|
||||
if (loggerSlf4J.isInfoEnabled()) {
|
||||
loggerSlf4J.info("$data" )
|
||||
}
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("$data")
|
||||
}
|
||||
|
||||
fun guardedSlf4jBuilder() {
|
||||
val data = Data(1)
|
||||
val atInfo = loggerSlf4J.atInfo()
|
||||
if (loggerSlf4J.isInfoEnabled) {
|
||||
loggerSlf4J.atInfo().log("$data" )
|
||||
}
|
||||
if (loggerSlf4J.isInfoEnabled) {
|
||||
atInfo.log("$data" )
|
||||
}
|
||||
if (loggerSlf4J.isDebugEnabled) {
|
||||
atInfo.<warning descr="String template as argument to 'log()' logging call">log</warning>("$data" )
|
||||
}
|
||||
if (loggerSlf4J.isDebugEnabled()) {
|
||||
atInfo.<warning descr="String template as argument to 'log()' logging call">log</warning>("$data" )
|
||||
}
|
||||
loggerSlf4J.atInfo().<warning descr="String template as argument to 'log()' logging call">log</warning>("$data" )
|
||||
atInfo.<warning descr="String template as argument to 'log()' logging call">log</warning>("$data" )
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
data class Data(val i: Int)
|
||||
|
||||
class StringTemplateAsArgumentSkipPrimitives {
|
||||
private val loggerSlf4J = LoggerFactory.getLogger()
|
||||
private val loggerLog4J = LogManager.getLogger()
|
||||
|
||||
fun testLoggerSlf4JBuilder() {
|
||||
val variable1 = 1
|
||||
val data = Data(1)
|
||||
loggerSlf4J.atInfo().log("variable1: ${variable1}")
|
||||
loggerSlf4J.atInfo().<warning descr="String template as argument to 'log()' logging call">log</warning>("data: ${data}")
|
||||
}
|
||||
|
||||
fun testLoggerLog4J() {
|
||||
val variable1 = 1
|
||||
val data = Data(1)
|
||||
loggerLog4J.info("variable1: ${variable1}")
|
||||
loggerLog4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("data: ${data}")
|
||||
}
|
||||
|
||||
fun testLoggerLog4JBuilder() {
|
||||
val variable1 = 1
|
||||
val data = Data(1)
|
||||
loggerLog4J.atInfo().log( "variable1: ${variable1}")
|
||||
loggerLog4J.atInfo().<warning descr="String template as argument to 'log()' logging call">log</warning>( "data: ${data}")
|
||||
}
|
||||
|
||||
fun testLoggerSlf4J() {
|
||||
val variable1 = 1
|
||||
val data = Data(1)
|
||||
loggerSlf4J.info("variable1: {}", variable1)
|
||||
loggerSlf4J.info("${variable1}")
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("${data}")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class StringTemplateAsArgumentWarnDebug {
|
||||
private val loggerSlf4J = LoggerFactory.getLogger()
|
||||
private val loggerLog4J = LogManager.getLogger()
|
||||
|
||||
fun testLoggerSlf4JBuilder() {
|
||||
val variable1 = 1
|
||||
loggerSlf4J.atInfo().log("variable1: ${variable1}")
|
||||
loggerSlf4J.atDebug().<warning descr="String template as argument to 'log()' logging call">log</warning>("variable1: ${variable1}")
|
||||
loggerSlf4J.atWarn().log("variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun testLoggerLog4J() {
|
||||
val variable1 = 1
|
||||
loggerLog4J.info("variable1: ${variable1}")
|
||||
loggerLog4J.<warning descr="String template as argument to 'debug()' logging call">debug</warning>("variable1: ${variable1}")
|
||||
loggerLog4J.warn("variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun testLoggerLog4JBuilder() {
|
||||
val variable1 = 1
|
||||
loggerLog4J.atInfo().log( "variable1: ${variable1}")
|
||||
loggerLog4J.atDebug().<warning descr="String template as argument to 'log()' logging call">log</warning>( "variable1: ${variable1}")
|
||||
loggerLog4J.atWarn().log( "variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun testLoggerSlf4J() {
|
||||
val variable1 = 1
|
||||
loggerSlf4J.info("variable1: ${variable1}")
|
||||
loggerSlf4J.<warning descr="String template as argument to 'debug()' logging call">debug</warning>("variable1: ${variable1}")
|
||||
loggerSlf4J.warn("variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun getString() = "test"
|
||||
fun getInt() = 1
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class StringTemplateAsArgumentWarnInfo {
|
||||
private val loggerSlf4J = LoggerFactory.getLogger()
|
||||
private val loggerLog4J = LogManager.getLogger()
|
||||
|
||||
fun testLoggerSlf4JBuilder() {
|
||||
val variable1 = 1
|
||||
loggerSlf4J.atInfo().<warning descr="String template as argument to 'log()' logging call">log</warning>("variable1: ${variable1}")
|
||||
loggerSlf4J.atDebug().<warning descr="String template as argument to 'log()' logging call">log</warning>("variable1: ${variable1}")
|
||||
loggerSlf4J.atWarn().log("variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun testLoggerLog4J() {
|
||||
val variable1 = 1
|
||||
loggerLog4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("variable1: ${variable1}")
|
||||
loggerLog4J.<warning descr="String template as argument to 'debug()' logging call">debug</warning>("variable1: ${variable1}")
|
||||
loggerLog4J.warn("variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun testLoggerLog4JBuilder() {
|
||||
val variable1 = 1
|
||||
loggerLog4J.atInfo().<warning descr="String template as argument to 'log()' logging call">log</warning>( "variable1: ${variable1}")
|
||||
loggerLog4J.atDebug().<warning descr="String template as argument to 'log()' logging call">log</warning>( "variable1: ${variable1}")
|
||||
loggerLog4J.atWarn().log( "variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun testLoggerSlf4J() {
|
||||
val variable1 = 1
|
||||
loggerSlf4J.<warning descr="String template as argument to 'info()' logging call">info</warning>("variable1: ${variable1}")
|
||||
loggerSlf4J.<warning descr="String template as argument to 'debug()' logging call">debug</warning>("variable1: ${variable1}")
|
||||
loggerSlf4J.warn("variable1: ${variable1}")
|
||||
}
|
||||
|
||||
fun getString() = "test"
|
||||
fun getInt() = 1
|
||||
}
|
||||
@@ -10,11 +10,27 @@ private const val INSPECTION_PATH = "/codeInspection/logging/stringTemplateAsArg
|
||||
class KotlinLoggingStringTemplateAsArgumentInspectionTest : LoggingStringTemplateAsArgumentInspectionTestBase() {
|
||||
|
||||
override fun getBasePath() = KotlinJvmAnalysisTestUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + INSPECTION_PATH
|
||||
fun `test highlighting`() {
|
||||
fun `test highlighting, myLimitLevelType=2 mySkipPrimitives=false`() {
|
||||
myFixture.testHighlighting("StringTemplateAsArgumentWarnInfo.kt")
|
||||
}
|
||||
|
||||
fun `test highlighting, myLimitLevelType=3 mySkipPrimitives=false`() {
|
||||
myFixture.testHighlighting("StringTemplateAsArgumentWarnDebug.kt")
|
||||
}
|
||||
|
||||
fun `test highlighting, myLimitLevelType=0 mySkipPrimitives=true`() {
|
||||
myFixture.testHighlighting("StringTemplateAsArgumentSkipPrimitives.kt")
|
||||
}
|
||||
|
||||
fun `test highlighting, myLimitLevelType=0 mySkipPrimitives=false`() {
|
||||
myFixture.testHighlighting("StringTemplateAsArgument.kt")
|
||||
}
|
||||
|
||||
fun `test fix`() {
|
||||
fun `test fix, myLimitLevelType=0 mySkipPrimitives=false`() {
|
||||
myFixture.testQuickFix(file = "StringTemplateAsArgumentFix.kt", checkPreview = true)
|
||||
}
|
||||
|
||||
fun `test highlighting, with guards`() {
|
||||
myFixture.testHighlighting("StringTemplateAsArgumentGuarded.kt")
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,13 @@ abstract class LoggingInspectionTestBase : UastInspectionTestBase() {
|
||||
}
|
||||
public interface Logger {
|
||||
void info(String format, Object... arguments);
|
||||
void debug(String format, Object... arguments);
|
||||
void warn(String format, Object... arguments);
|
||||
boolean isDebugEnabled();
|
||||
boolean isInfoEnabled();
|
||||
LoggingEventBuilder atInfo();
|
||||
LoggingEventBuilder atDebug();
|
||||
LoggingEventBuilder atWarn();
|
||||
LoggingEventBuilder atError();
|
||||
}
|
||||
""".trimIndent())
|
||||
@@ -29,12 +35,20 @@ abstract class LoggingInspectionTestBase : UastInspectionTestBase() {
|
||||
package org.apache.logging.log4j;
|
||||
import org.apache.logging.log4j.util.Supplier;
|
||||
public interface Logger {
|
||||
boolean isDebugEnabled();
|
||||
boolean isInfoEnabled();
|
||||
void info(String message, Object... params);
|
||||
void debug(String message, Object... params);
|
||||
void warn(String message, Object... params);
|
||||
void info(String message);
|
||||
void debug(String message);
|
||||
void warn(String message);
|
||||
void fatal(String message, Object... params);
|
||||
void error(Supplier<?> var1, Throwable var2);
|
||||
void info(String message, Supplier<?>... params);
|
||||
LogBuilder atInfo();
|
||||
LogBuilder atDebug();
|
||||
LogBuilder atWarn();
|
||||
LogBuilder atFatal();
|
||||
LogBuilder atError();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
package com.intellij.codeInspection.tests.logging
|
||||
|
||||
import com.intellij.codeInspection.InspectionProfileEntry
|
||||
import com.intellij.codeInspection.logging.LimitLevelType
|
||||
import com.intellij.codeInspection.logging.LoggingStringTemplateAsArgumentInspection
|
||||
|
||||
abstract class LoggingStringTemplateAsArgumentInspectionTestBase : LoggingInspectionTestBase() {
|
||||
override val inspection: InspectionProfileEntry
|
||||
get() = LoggingStringTemplateAsArgumentInspection()
|
||||
get() {
|
||||
val testName = getTestName(false)
|
||||
val properties = testName.split(" ").filter {
|
||||
it.contains("=")
|
||||
}.map { it.split("=") }
|
||||
val loggingStringTemplateAsArgumentInspection = LoggingStringTemplateAsArgumentInspection()
|
||||
for (property in properties) {
|
||||
if (property[0] == "myLimitLevelType") {
|
||||
loggingStringTemplateAsArgumentInspection.myLimitLevelType = LimitLevelType.values()[property[1].toInt()]
|
||||
}
|
||||
if (property[0] == "mySkipPrimitives") {
|
||||
loggingStringTemplateAsArgumentInspection.mySkipPrimitives = property[1] == "true"
|
||||
}
|
||||
}
|
||||
return loggingStringTemplateAsArgumentInspection
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user