Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add exhaustiveness check for config validation (#5089)
* Add exhaustiveness check for config validation * fix ArgumentListWrapping * inject deprecated properties to validators * move converting warning level up the chain * remove exclusion pattern from abstract validator * fix EmptyDefaultConstructor Co-authored-by: Markus Schwarz <post@markus-schwarz.net>
- Loading branch information
Showing
33 changed files
with
746 additions
and
343 deletions.
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
detekt-core/src/main/kotlin/io/gitlab/arturbosch/detekt/core/config/AllRulesConfig.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 0 additions & 39 deletions
39
detekt-core/src/main/kotlin/io/gitlab/arturbosch/detekt/core/config/ConfigValidators.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
153 changes: 0 additions & 153 deletions
153
detekt-core/src/main/kotlin/io/gitlab/arturbosch/detekt/core/config/ValidateConfig.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
.../kotlin/io/gitlab/arturbosch/detekt/core/config/validation/AbstractYamlConfigValidator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package io.gitlab.arturbosch.detekt.core.config.validation | ||
|
||
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.core.config.YamlConfig | ||
|
||
internal abstract class AbstractYamlConfigValidator : ConfigValidator { | ||
|
||
override fun validate(config: Config): Collection<Notification> { | ||
require(config is YamlConfig) { | ||
val yamlConfigClass = YamlConfig::class.simpleName | ||
val actualClass = config.javaClass.simpleName | ||
|
||
"Only supported config is the $yamlConfigClass. Actual type is $actualClass" | ||
} | ||
val settings = ValidationSettings( | ||
config.subConfig("config").valueOrDefault("checkExhaustiveness", false), | ||
) | ||
|
||
return validate(config, settings) | ||
} | ||
|
||
abstract fun validate( | ||
configToValidate: YamlConfig, | ||
settings: ValidationSettings | ||
): Collection<Notification> | ||
} |
118 changes: 118 additions & 0 deletions
118
...re/src/main/kotlin/io/gitlab/arturbosch/detekt/core/config/validation/ConfigValidation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.