/
RuleSetProviderCollector.kt
148 lines (128 loc) · 6.08 KB
/
RuleSetProviderCollector.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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package io.gitlab.arturbosch.detekt.generator.collection
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault
import io.gitlab.arturbosch.detekt.api.internal.DefaultRuleSetProvider
import io.gitlab.arturbosch.detekt.generator.collection.exception.InvalidDocumentationException
import io.gitlab.arturbosch.detekt.rules.isOverride
import org.jetbrains.kotlin.psi.KtAnnotatedExpression
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.KtSuperTypeList
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
import io.gitlab.arturbosch.detekt.api.internal.Configuration as ConfigAnnotation
data class RuleSetProvider(
val name: String,
val description: String,
val defaultActivationStatus: DefaultActivationStatus,
val rules: List<String> = emptyList(),
val configuration: List<Configuration> = emptyList()
)
class RuleSetProviderCollector : Collector<RuleSetProvider> {
override val items = mutableListOf<RuleSetProvider>()
override fun visit(file: KtFile) {
val visitor = RuleSetProviderVisitor()
file.accept(visitor)
if (visitor.containsRuleSetProvider) {
items.add(visitor.getRuleSetProvider())
}
}
}
private const val PROPERTY_RULE_SET_ID = "ruleSetId"
private val SUPPORTED_PROVIDERS =
setOf(RuleSetProvider::class.simpleName, DefaultRuleSetProvider::class.simpleName)
class RuleSetProviderVisitor : DetektVisitor() {
var containsRuleSetProvider = false
private var name: String = ""
private var description: String = ""
private var defaultActivationStatus: DefaultActivationStatus = Inactive
private val ruleNames: MutableList<String> = mutableListOf()
private val configurations = mutableListOf<Configuration>()
fun getRuleSetProvider(): RuleSetProvider {
if (name.isEmpty()) {
throw InvalidDocumentationException("RuleSetProvider without name found.")
}
if (description.isEmpty()) {
throw InvalidDocumentationException("Missing description for RuleSet $name.")
}
return RuleSetProvider(name, description, defaultActivationStatus, ruleNames, configurations)
}
override fun visitSuperTypeList(list: KtSuperTypeList) {
val superTypes = list.entries
?.map { it.typeAsUserType?.referencedName }
?.toSet()
.orEmpty()
containsRuleSetProvider = SUPPORTED_PROVIDERS.any { it in superTypes }
super.visitSuperTypeList(list)
}
override fun visitClass(ktClass: KtClass) {
description = ktClass.docComment?.getDefaultSection()?.getContent()?.trim().orEmpty()
if (ktClass.isAnnotatedWith(ActiveByDefault::class)) {
defaultActivationStatus = Active(since = ktClass.firstAnnotationParameter(ActiveByDefault::class))
}
if (ktClass.hasConfigurationKDocTag()) {
throw InvalidDocumentationException(
"Configuration of rule set ${ktClass.name} is invalid. " +
"Rule set configuration via KDoc tag is no longer supported. " +
"Use rule set config delegate instead."
)
}
super.visitClass(ktClass)
}
override fun visitProperty(property: KtProperty) {
super.visitProperty(property)
if (!containsRuleSetProvider) return
if (property.isOverride() && property.name != null && property.name == PROPERTY_RULE_SET_ID) {
name = (property.initializer as? KtStringTemplateExpression)?.entries?.get(0)?.text
?: throw InvalidDocumentationException(
"RuleSetProvider class " +
"${property.containingClass()?.name.orEmpty()} doesn't provide list of rules."
)
}
if (property.isAnnotatedWith(ConfigAnnotation::class)) {
val defaultValue = toDefaultValue(
name,
checkNotNull(property.delegate?.expression as? KtCallExpression)
.valueArguments
.first()
.text
)
configurations.add(
Configuration(
name = checkNotNull(property.name),
description = property.firstAnnotationParameter(ConfigAnnotation::class),
defaultValue = defaultValue,
defaultAndroidValue = defaultValue,
deprecated = property.firstAnnotationParameterOrNull(Deprecated::class),
)
)
}
}
override fun visitCallExpression(expression: KtCallExpression) {
super.visitCallExpression(expression)
if (expression.calleeExpression?.text == "RuleSet") {
val ruleListExpression = expression.valueArguments
.map { it.getArgumentExpression() }
.firstOrNull { it?.referenceExpression()?.text == "listOf" }
?: throw InvalidDocumentationException("RuleSetProvider $name doesn't provide list of rules.")
val ruleArgumentNames = (ruleListExpression as? KtCallExpression)
?.valueArguments
?.mapNotNull { it.getArgumentExpression() }
?.map { if (it is KtAnnotatedExpression) it.lastChild as KtCallExpression else it }
?.mapNotNull { it.referenceExpression()?.text }
.orEmpty()
ruleNames.addAll(ruleArgumentNames)
}
}
companion object {
private fun toDefaultValue(providerName: String, defaultValueText: String): DefaultValue =
createDefaultValueIfLiteral(defaultValueText)
?: throw InvalidDocumentationException(
"Unsupported default value format '$defaultValueText' " +
"in $providerName. Please use a Boolean, Int or String literal instead."
)
}
}