forked from detekt/detekt
/
TrimMultilineRawString.kt
107 lines (94 loc) · 3.67 KB
/
TrimMultilineRawString.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package io.gitlab.arturbosch.detekt.rules.style
import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.isConstant
import io.gitlab.arturbosch.detekt.rules.safeAs
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
/**
* All the Raw strings that have more than one line should be followed by `trimMargin()` or `trimIndent()`.
*
* <noncompliant>
* """
* Hello World!
* How are you?
* """
* </noncompliant>
*
* <compliant>
* """
* | Hello World!
* | How are you?
* """.trimMargin()
*
* """
* Hello World!
* How are you?
* """.trimIndent()
*
* """Hello World! How are you?"""
* </compliant>
*/
class TrimMultilineRawString(val config: Config) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Style,
"Multiline raw strings should be followed by `trimMargin()` or `trimIndent()`.",
Debt.FIVE_MINS
)
override fun visitStringTemplateExpression(expression: KtStringTemplateExpression) {
super.visitStringTemplateExpression(expression)
if (expression.isRawStringWithLineBreak() && !expression.isTrimmed() && !expression.isExpectedAsConstant()) {
report(
CodeSmell(
issue,
Entity.from(expression),
"Multiline raw strings should be followed by `trimMargin()` or `trimIndent()`",
)
)
}
}
}
fun KtStringTemplateExpression.isRawStringWithLineBreak(): Boolean =
text.startsWith("\"\"\"") && text.endsWith("\"\"\"") && entries.any {
val literalText = it.safeAs<KtLiteralStringTemplateEntry>()?.text
literalText != null && "\n" in literalText
}
fun KtStringTemplateExpression.isTrimmed(): Boolean {
val nextCall = getQualifiedExpressionForReceiver()
?.selectorExpression
?.safeAs<KtCallExpression>()
?.calleeExpression
?.text
return nextCall in trimFunctions
}
@Suppress("ReturnCount")
private fun KtStringTemplateExpression.isExpectedAsConstant(): Boolean {
val expression = KtPsiUtil.safeDeparenthesize(this)
val property = getStrictParentOfType<KtProperty>()?.takeIf { it.initializer == expression }
if (property != null && property.isConstant()) return true
val argument = expression.getStrictParentOfType<KtValueArgument>()
?.takeIf { it.getArgumentExpression() == expression }
if (argument?.parent?.parent is KtAnnotationEntry) return true
val parameter = expression.getStrictParentOfType<KtParameter>()
?.takeIf { it.defaultValue == expression }
val primaryConstructor = parameter?.parent?.parent?.safeAs<KtPrimaryConstructor>()
if (primaryConstructor?.containingClass()?.isAnnotation() == true) return true
return false
}
private val trimFunctions = listOf("trimIndent", "trimMargin")