/
ConfigValidation.kt
118 lines (106 loc) · 4.41 KB
/
ConfigValidation.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
package io.gitlab.arturbosch.detekt.core.config.validation
import io.github.detekt.tooling.api.InvalidConfig
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.ConfigValidator
import io.gitlab.arturbosch.detekt.api.Notification
import io.gitlab.arturbosch.detekt.api.Notification.Level
import io.gitlab.arturbosch.detekt.api.internal.SimpleNotification
import io.gitlab.arturbosch.detekt.core.NL
import io.gitlab.arturbosch.detekt.core.ProcessingSettings
import io.gitlab.arturbosch.detekt.core.config.YamlConfig
import io.gitlab.arturbosch.detekt.core.extensions.loadExtensions
import io.gitlab.arturbosch.detekt.core.reporting.red
import io.gitlab.arturbosch.detekt.core.reporting.yellow
/**
* Known existing properties on rule's which my be absent in the default-detekt-config.yml.
*
* We need to predefine them as the user may not have already declared an 'config'-block
* in the configuration and we want to validate the config by default.
*/
internal val DEFAULT_PROPERTY_EXCLUDES = setOf(
".*>excludes",
".*>includes",
".*>active",
".*>.*>excludes",
".*>.*>includes",
".*>.*>active",
".*>.*>autoCorrect",
".*>severity",
".*>.*>severity",
"build>weights.*",
".*>.*>ignoreAnnotated",
".*>.*>ignoreFunction",
).joinToString(",")
internal fun checkConfiguration(settings: ProcessingSettings, baseline: Config) {
var shouldValidate = settings.spec.configSpec.shouldValidateBeforeAnalysis
if (shouldValidate == null) {
val props = settings.config.subConfig("config")
shouldValidate = props.valueOrDefault("validation", true)
}
if (shouldValidate) {
val validators =
loadExtensions<ConfigValidator>(settings) + DefaultPropertiesConfigValidator(settings, baseline)
val notifications = validators.flatMap { it.validate(settings.config) }
notifications.map(Notification::message).forEach(settings::info)
val errors = notifications.filter(Notification::isError)
if (errors.isNotEmpty()) {
val problems = notifications.joinToString(NL) { "\t- ${it.renderMessage()}" }
val propsString = if (errors.size == 1) "property" else "properties"
val title = "Run failed with ${errors.size} invalid config $propsString.".red()
throw InvalidConfig("$title$NL$problems")
}
}
}
internal fun validateConfig(
config: Config,
baseline: Config,
excludePatterns: Set<Regex>
): List<Notification> {
require(baseline != Config.empty) { "Cannot validate configuration based on an empty baseline config." }
require(baseline is YamlConfig) {
val yamlConfigClass = YamlConfig::class.simpleName
val actualClass = baseline.javaClass.simpleName
"Only supported baseline config is the $yamlConfigClass. Actual type is $actualClass"
}
if (config == Config.empty) {
return emptyList()
}
return when (config) {
is YamlConfig -> validateYamlConfig(config, baseline, excludePatterns)
is ValidatableConfiguration -> config.validate(baseline, excludePatterns)
else -> error("Unsupported config type for validation: '${config::class}'.")
}
}
private fun validateYamlConfig(
configToValidate: YamlConfig,
baseline: YamlConfig,
excludePatterns: Set<Regex>
): List<Notification> {
val deprecatedProperties = loadDeprecations()
val warningsAsErrors = configToValidate
.subConfig("config")
.valueOrDefault("warningsAsErrors", false)
val validators: List<ConfigValidator> = listOf(
InvalidPropertiesConfigValidator(baseline, deprecatedProperties.keys, excludePatterns),
DeprecatedPropertiesConfigValidator(deprecatedProperties),
MissingRulesConfigValidator(baseline, excludePatterns)
)
return validators
.flatMap { it.validate(configToValidate) }
.map { notification ->
notification.transformIf(warningsAsErrors && notification.level == Level.Warning) {
SimpleNotification(
message = notification.message,
level = Level.Error
)
}
}
}
private fun <T> T.transformIf(condition: Boolean, transform: () -> T): T =
if (condition) transform() else this
internal fun Notification.renderMessage(): String =
when (level) {
Level.Error -> message.red()
Level.Warning -> message.yellow()
Level.Info -> message
}