Introduce new ConfigValidator extensions - #2285 (#2356)

* Include default config validation excludes for plugin rule sets - #2285

* Allow to validate configuration by a new ConfigValidator extension - #2285
This commit is contained in:
Artur Bosch
2020-02-21 23:42:12 +01:00
committed by GitHub
parent 9fa2eb05df
commit d0d41cf1c8
17 changed files with 147 additions and 41 deletions

View File

@@ -0,0 +1,15 @@
package io.gitlab.arturbosch.detekt.api
/**
* An extension which allows users to validate parts of the configuration.
*
* Rule authors can validate if specific properties do appear in their config
* or if their value lies in a specified range.
*/
interface ConfigValidator : Extension {
/**
* Executes queries on given config and reports any warnings or errors via [Notification]s.
*/
fun validate(config: Config): Collection<Notification>
}

View File

@@ -2,6 +2,7 @@
package io.gitlab.arturbosch.detekt.cli
import io.gitlab.arturbosch.detekt.cli.config.InvalidConfig
import io.gitlab.arturbosch.detekt.cli.runners.AstPrinter
import io.gitlab.arturbosch.detekt.cli.runners.ConfigExporter
import io.gitlab.arturbosch.detekt.cli.runners.Executable

View File

@@ -0,0 +1,26 @@
package io.gitlab.arturbosch.detekt.cli.config
import io.gitlab.arturbosch.detekt.api.ConfigValidator
import io.gitlab.arturbosch.detekt.api.Notification
import io.gitlab.arturbosch.detekt.cli.console.red
import io.gitlab.arturbosch.detekt.core.ProcessingSettings
import java.util.ServiceLoader
fun loadValidators(settings: ProcessingSettings): List<ConfigValidator> =
ServiceLoader.load(ConfigValidator::class.java, settings.pluginLoader).toList()
fun checkConfiguration(settings: ProcessingSettings) {
val props = settings.config.subConfig("config")
val shouldValidate = props.valueOrDefault("validation", true)
if (shouldValidate) {
val validators = loadValidators(settings) + DefaultPropertiesConfigValidator(settings)
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 propsString = if (errors.size == 1) "property" else "properties"
throw InvalidConfig("Run failed with ${errors.size} invalid config $propsString.".red())
}
}
}

View File

@@ -0,0 +1,29 @@
package io.gitlab.arturbosch.detekt.cli.config
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.internal.CommaSeparatedPattern
import io.gitlab.arturbosch.detekt.api.internal.DEFAULT_PROPERTY_EXCLUDES
import io.gitlab.arturbosch.detekt.api.internal.DefaultRuleSetProvider
import io.gitlab.arturbosch.detekt.api.internal.validateConfig
import io.gitlab.arturbosch.detekt.cli.loadDefaultConfig
import io.gitlab.arturbosch.detekt.core.ProcessingSettings
import io.gitlab.arturbosch.detekt.core.RuleSetLocator
class DefaultPropertiesConfigValidator(
private val settings: ProcessingSettings
) : ConfigValidator {
override fun validate(config: Config): Collection<Notification> {
fun patterns(): Set<Regex> {
val pluginExcludes = RuleSetLocator(settings).load()
.filter { it !is DefaultRuleSetProvider }
.joinToString(",") { "${it.ruleSetId}.*" }
val configExcludes = config.subConfig("config").valueOrDefault("excludes", "")
val allExcludes = "$configExcludes,$DEFAULT_PROPERTY_EXCLUDES,$pluginExcludes"
return CommaSeparatedPattern(allExcludes).mapToRegex()
}
return validateConfig(config, loadDefaultConfig(), patterns())
}
}

View File

@@ -1,3 +1,3 @@
package io.gitlab.arturbosch.detekt.cli
package io.gitlab.arturbosch.detekt.cli.config
class InvalidConfig(override val message: String?) : RuntimeException(message, null, true, false)

View File

@@ -1,16 +1,12 @@
package io.gitlab.arturbosch.detekt.cli.runners
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.Notification
import io.gitlab.arturbosch.detekt.api.internal.CommaSeparatedPattern
import io.gitlab.arturbosch.detekt.api.internal.DEFAULT_PROPERTY_EXCLUDES
import io.gitlab.arturbosch.detekt.api.internal.validateConfig
import io.gitlab.arturbosch.detekt.cli.BuildFailure
import io.gitlab.arturbosch.detekt.cli.CliArgs
import io.gitlab.arturbosch.detekt.cli.FilteredDetectionResult
import io.gitlab.arturbosch.detekt.cli.InvalidConfig
import io.gitlab.arturbosch.detekt.cli.OutputFacade
import io.gitlab.arturbosch.detekt.cli.baseline.BaselineFacade
import io.gitlab.arturbosch.detekt.cli.config.checkConfiguration
import io.gitlab.arturbosch.detekt.cli.console.red
import io.gitlab.arturbosch.detekt.cli.createClasspath
import io.gitlab.arturbosch.detekt.cli.createFilters
@@ -18,7 +14,6 @@ import io.gitlab.arturbosch.detekt.cli.createPlugins
import io.gitlab.arturbosch.detekt.cli.getOrComputeWeightedAmountOfIssues
import io.gitlab.arturbosch.detekt.cli.isValidAndSmallerOrEqual
import io.gitlab.arturbosch.detekt.cli.loadConfiguration
import io.gitlab.arturbosch.detekt.cli.loadDefaultConfig
import io.gitlab.arturbosch.detekt.cli.maxIssues
import io.gitlab.arturbosch.detekt.core.DetektFacade
import io.gitlab.arturbosch.detekt.core.ProcessingSettings
@@ -65,26 +60,6 @@ class Runner(
}
}
private fun checkConfiguration(settings: ProcessingSettings) {
val props = settings.config.subConfig("config")
val shouldValidate = props.valueOrDefault("validation", true)
fun patterns(): Set<Regex> {
val excludes = props.valueOrDefault("excludes", "") + ",$DEFAULT_PROPERTY_EXCLUDES"
return CommaSeparatedPattern(excludes).mapToRegex()
}
if (shouldValidate) {
val notifications = validateConfig(settings.config, loadDefaultConfig(), patterns())
notifications.map(Notification::message).forEach(settings::info)
val errors = notifications.filter(Notification::isError)
if (errors.isNotEmpty()) {
val propsString = if (errors.size == 1) "property" else "properties"
throw InvalidConfig("Run failed with ${errors.size} invalid config $propsString.".red())
}
}
}
private fun checkBuildFailureThreshold(result: Detektion, settings: ProcessingSettings) {
val amount = result.getOrComputeWeightedAmountOfIssues(settings.config)
val maxIssues = settings.config.maxIssues()

View File

@@ -2,7 +2,7 @@ package io.gitlab.arturbosch.detekt.cli.runners
import io.gitlab.arturbosch.detekt.cli.BuildFailure
import io.gitlab.arturbosch.detekt.cli.CliArgs
import io.gitlab.arturbosch.detekt.cli.InvalidConfig
import io.gitlab.arturbosch.detekt.cli.config.InvalidConfig
import io.gitlab.arturbosch.detekt.test.resource
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy

View File

@@ -0,0 +1,25 @@
package io.gitlab.arturbosch.detekt.sample.extensions
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.ConfigValidator
import io.gitlab.arturbosch.detekt.api.Notification
class SampleConfigValidator : ConfigValidator {
override fun validate(config: Config): Collection<Notification> {
val result = mutableListOf<Notification>()
runCatching {
config.subConfig("sample")
.subConfig("TooManyFunctions")
.valueOrNull<Boolean>("active")
}.onFailure {
result.add(SampleMessage("'active' property must be of type boolean."))
}
return result
}
}
class SampleMessage(
override val message: String,
override val level: Notification.Level = Notification.Level.Error
) : Notification

View File

@@ -5,7 +5,6 @@ import io.gitlab.arturbosch.detekt.api.OutputReport
class QualifiedNamesOutputReport : OutputReport() {
var fileName: String = "fqNames"
override val ending: String = "txt"
override fun render(detektion: Detektion): String? {

View File

@@ -0,0 +1 @@
io.gitlab.arturbosch.detekt.sample.extensions.SampleConfigValidator

View File

@@ -1,7 +1,7 @@
package io.gitlab.arturbosch.detekt.sample.extensions
import io.gitlab.arturbosch.detekt.cli.CliArgs
import io.gitlab.arturbosch.detekt.cli.InvalidConfig
import io.gitlab.arturbosch.detekt.cli.config.InvalidConfig
import io.gitlab.arturbosch.detekt.cli.console.red
import io.gitlab.arturbosch.detekt.cli.runners.Runner
import org.assertj.core.api.Assertions.assertThatCode
@@ -15,15 +15,22 @@ class SupportConfigValidationSpec : Spek({
val testDir = Files.createTempDirectory("detekt-sample")
it("fails when new rule set is not excluded") {
val args = CliArgs {
input = testDir.toString()
configResource = "included-config.yml"
}
context("failing cases") {
arrayOf(
"fails when new rule set is not excluded" to "included-config.yml",
"fails due to no configuration property present for 'sample' rule set" to "wrong-property-config.yml"
).forEach { (testCase, config) ->
it(testCase) {
val args = CliArgs {
input = testDir.toString()
configResource = config
}
assertThatCode { Runner(args).execute() }
.isInstanceOf(InvalidConfig::class.java)
.hasMessage("Run failed with 1 invalid config property.".red())
assertThatCode { Runner(args).execute() }
.isInstanceOf(InvalidConfig::class.java)
.hasMessage("Run failed with 1 invalid config property.".red())
}
}
}
it("passes with excluded new rule set") {

View File

@@ -1,9 +1,17 @@
config:
validation: true
# 1. exclude rule set 'sample' and all its nested members
# 2. exclude every property in every rule under the rule set 'sample'
excludes: "sample.*,sample>.*>.*"
# Additional properties can be useful when writing custom extensions.
# However only properties defined in the default config are known to detekt.
# All unknown properties are treated as errors and will get reported if not excluded in this config part.
excludes: "my_additional_properties"
# Properties of custom rule sets get excluded by default.
# If you want to validate them further, consider implementing a ConfigValidator.
sample:
TooManyFunctions:
active: true
# This properties are unknown to detekt and must be excluded.
my_additional_properties:
magic_number: 7
magic_string: "Hello World"

View File

@@ -1,3 +1,9 @@
# Properties of custom rule sets get excluded by default.
sample:
TooManyFunctions:
active: true
# This properties are unknown to detekt and must be excluded.
my_additional_properties:
magic_number: 7
magic_string: "Hello World"

View File

@@ -0,0 +1,5 @@
# Properties of custom rule sets get excluded by default.
sample:
TooManyFunctions:
# This property is tested via the SampleConfigValidator
active: 1 # should be true

View File

@@ -70,6 +70,13 @@ utility functions to retrieve specific or generic properties
from the underlying detekt configuration file.
|
##### [io.gitlab.arturbosch.detekt.api.ConfigValidator](../io.gitlab.arturbosch.detekt.api/-config-validator/index.html)
An extension which allows users to validate parts of the configuration.
|
##### [io.gitlab.arturbosch.detekt.api.ConsoleReport](../io.gitlab.arturbosch.detekt.api/-console-report/index.html)

View File

@@ -26,6 +26,7 @@ Currently supported extensions are:
### Inheritors
| [ConfigValidator](../-config-validator/index.html) | An extension which allows users to validate parts of the configuration.`interface ConfigValidator : `[`Extension`](./index.html) |
| [ConsoleReport](../-console-report/index.html) | Extension point which describes how findings should be printed on the console.`abstract class ConsoleReport : `[`Extension`](./index.html) |
| [FileProcessListener](../-file-process-listener/index.html) | Gather additional metrics about the analyzed kotlin file. Pay attention to the thread policy of each function!`interface FileProcessListener : `[`Extension`](./index.html) |
| [OutputReport](../-output-report/index.html) | Translates detekt's result container - [Detektion](../-detektion/index.html) - into an output report which is written inside a file.`abstract class OutputReport : `[`Extension`](./index.html) |

View File

@@ -16,6 +16,7 @@ title: io.gitlab.arturbosch.detekt.api - detekt-api
| [CompositeConfig](-composite-config/index.html) | Wraps two different configuration which should be considered when retrieving properties.`class CompositeConfig : `[`Config`](-config/index.html)`, `[`ValidatableConfiguration`](../io.gitlab.arturbosch.detekt.api.internal/-validatable-configuration/index.html) |
| [Config](-config/index.html) | A configuration holds information about how to configure specific rules.`interface Config` |
| [ConfigAware](-config-aware/index.html) | Interface which is implemented by each Rule class to provide utility functions to retrieve specific or generic properties from the underlying detekt configuration file.`interface ConfigAware : `[`Config`](-config/index.html) |
| [ConfigValidator](-config-validator/index.html) | An extension which allows users to validate parts of the configuration.`interface ConfigValidator : `[`Extension`](-extension/index.html) |
| [ConsoleReport](-console-report/index.html) | Extension point which describes how findings should be printed on the console.`abstract class ConsoleReport : `[`Extension`](-extension/index.html) |
| [Context](-context/index.html) | A context describes the storing and reporting mechanism of [Finding](-finding/index.html)'s inside a [Rule](-rule/index.html). Additionally it handles suppression and aliases management.`interface Context` |
| [CorrectableCodeSmell](-correctable-code-smell/index.html) | Represents a code smell for that can be auto corrected.`open class CorrectableCodeSmell : `[`CodeSmell`](-code-smell/index.html) |