io.gitlab.arturbosch.detekt.api.ConfigProperty.kt Maven / Gradle / Ivy
package io.gitlab.arturbosch.detekt.api
import io.gitlab.arturbosch.detekt.api.internal.valueOrDefaultCommaSeparated
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
/**
* Creates a delegated read-only property that can be used in [ConfigAware] objects. The name of the property is the
* key that is used during configuration lookup. The value of the property is evaluated only once.
*
* @param defaultValue the value that the property evaluates to when there is no key with the name of the property in
* the config. Although [T] is defined as [Any], only [String], [Int], [Boolean] and [List] are supported.
*/
fun config(
defaultValue: T
): ReadOnlyProperty = config(defaultValue) { it }
/**
* Creates a delegated read-only property that can be used in [ConfigAware] objects. The name of the property is the
* key that is used during configuration lookup. The value of the property is evaluated and transformed only once.
*
* @param defaultValue the value that the property evaluates to when there is no key with the name of the property in
* the config. Although [T] is defined as [Any], only [String], [Int], [Boolean] and [List] are supported.
* @param transformer a function that transforms the value from the configuration (or the default) into its final
* value. A typical use case for this is a conversion from a [String] into a [Regex].
*/
fun config(
defaultValue: T,
transformer: (T) -> U
): ReadOnlyProperty = TransformedConfigProperty(defaultValue, transformer)
/**
* Creates a delegated read-only property that can be used in [ConfigAware] objects. The name of the property is the
* key that is used during configuration lookup. If there is no such property, the value of the
* supplied [fallbackProperty] is also considered before using the [defaultValue].
* The value of the property is evaluated only once.
*
* This method is only intended to be used in migration scenarios where there is no way to update all configuration
* files immediately.
*
* @param fallbackProperty The reference to the configuration key to fall back to. This property must be defined as a
* configuration delegate.
* @param defaultValue the value that the property evaluates to when there is no key with the name of the property in
* the config. Although [T] is defined as [Any], only [String], [Int], [Boolean] and [List] are supported.
*/
@UnstableApi("fallback property handling is still under discussion")
fun configWithFallback(
fallbackProperty: KProperty0,
defaultValue: T
): ReadOnlyProperty = configWithFallback(fallbackProperty, defaultValue) { it }
/**
* Creates a delegated read-only property that can be used in [ConfigAware] objects. The name of the property is the
* key that is used during configuration lookup. If there is no such property, the value of the
* supplied [fallbackProperty] is also considered before using the [defaultValue].
* The value of the property is evaluated and transformed only once.
*
* This method is only intended to be used in migration scenarios where there is no way to update all configuration
* files immediately.
*
* @param fallbackProperty The reference to the configuration key to fall back to. This property must be defined as a
* configuration delegate.
* @param defaultValue the value that the property evaluates to when there is no key with the name of the property in
* the config. Although [T] is defined as [Any], only [String], [Int], [Boolean] and [List] are supported.
* @param transformer a function that transforms the value from the configuration (or the default) into its final
* value.
*/
@UnstableApi("fallback property handling is still under discussion")
fun configWithFallback(
fallbackProperty: KProperty0,
defaultValue: T,
transformer: (T) -> U
): ReadOnlyProperty =
FallbackConfigProperty(fallbackProperty, defaultValue, transformer)
/**
* Creates a delegated read-only property that can be used in [ConfigAware] objects. The name of the property is the
* key that is used during configuration lookup. The value of the property is evaluated only once.
*
* @param defaultValue the value that the property evaluates to when there is no key with the name of the property in
* the config. Although [T] is defined as [Any], only [String], [Int], [Boolean] and [List] are supported.
* @param defaultAndroidValue the value that the property evaluates to when there is no key with the name of the
* property in the config and there is a configuration property in the rule set named "android" that is set to
* true
.
*/
fun configWithAndroidVariants(
defaultValue: T,
defaultAndroidValue: T,
): ReadOnlyProperty = configWithAndroidVariants(defaultValue, defaultAndroidValue) { it }
/**
* Creates a delegated read-only property that can be used in [ConfigAware] objects. The name of the property is the
* key that is used during configuration lookup. The value of the property is evaluated and transformed only once.
*
* @param defaultValue the value that the property evaluates to when there is no key with the name of the property in
* the config. Although [T] is defined as [Any], only [String], [Int], [Boolean] and [List] are supported.
* @param defaultAndroidValue the value that the property evaluates to when there is no key with the name of the
* property in the config and there is a configuration property in the rule set named "android" that is set to
* true
.
* @param transformer a function that transforms the value from the configuration (or the default) into its final
* value.
*/
fun configWithAndroidVariants(
defaultValue: T,
defaultAndroidValue: T,
transformer: (T) -> U
): ReadOnlyProperty =
TransformedConfigPropertyWithAndroidVariants(defaultValue, defaultAndroidValue, transformer)
private fun getValueOrDefault(configAware: ConfigAware, propertyName: String, defaultValue: T): T {
@Suppress("UNCHECKED_CAST")
return when (defaultValue) {
is ValuesWithReason -> configAware.getValuesWithReasonOrDefault(propertyName, defaultValue) as T
is List<*> -> configAware.getListOrDefault(propertyName, defaultValue) as T
is String,
is Boolean,
is Int -> configAware.valueOrDefault(propertyName, defaultValue)
else -> error(
"${defaultValue.javaClass} is not supported for delegated config property '$propertyName'. " +
"Use one of String, Boolean, Int or List instead."
)
}
}
private fun ConfigAware.getListOrDefault(propertyName: String, defaultValue: List<*>): List {
return if (defaultValue.all { it is String }) {
@Suppress("UNCHECKED_CAST")
val defaultValueAsListOfStrings = defaultValue as List
valueOrDefaultCommaSeparated(propertyName, defaultValueAsListOfStrings)
} else {
error("Only lists of strings are supported. '$propertyName' is invalid. ")
}
}
private fun ConfigAware.getValuesWithReasonOrDefault(
propertyName: String,
defaultValue: ValuesWithReason
): ValuesWithReason {
val valuesAsList: List<*> = valueOrNull(propertyName) ?: return defaultValue
if (valuesAsList.all { it is String }) {
return ValuesWithReason(values = valuesAsList.map { ValueWithReason(it as String) })
}
if (valuesAsList.all { it is Map<*, *> }) {
return ValuesWithReason(
valuesAsList
.map { it as Map<*, *> }
.map { dict ->
try {
ValueWithReason(
value = dict["value"] as String,
reason = dict["reason"] as String?
)
} catch (e: ClassCastException) {
throw Config.InvalidConfigurationError(e)
} catch (@Suppress("TooGenericExceptionCaught") e: NullPointerException) {
throw Config.InvalidConfigurationError(e)
}
}
)
}
error("Only lists of strings or maps with keys 'value' and 'reason' are supported. '$propertyName' is invalid.")
}
private abstract class MemoizedConfigProperty : ReadOnlyProperty {
private var value: U? = null
override fun getValue(thisRef: ConfigAware, property: KProperty<*>): U {
return value ?: doGetValue(thisRef, property).also { value = it }
}
abstract fun doGetValue(thisRef: ConfigAware, property: KProperty<*>): U
}
private class TransformedConfigPropertyWithAndroidVariants(
private val defaultValue: T,
private val defaultAndroidValue: T,
private val transform: (T) -> U
) : MemoizedConfigProperty() {
override fun doGetValue(thisRef: ConfigAware, property: KProperty<*>): U {
val isAndroid = getValueOrDefault(thisRef, "android", false)
val value = if (isAndroid) defaultAndroidValue else defaultValue
return transform(getValueOrDefault(thisRef, property.name, value))
}
}
private class TransformedConfigProperty(
private val defaultValue: T,
private val transform: (T) -> U
) : MemoizedConfigProperty() {
override fun doGetValue(thisRef: ConfigAware, property: KProperty<*>): U {
return transform(getValueOrDefault(thisRef, property.name, defaultValue))
}
}
private class FallbackConfigProperty(
private val fallbackProperty: KProperty0,
private val defaultValue: T,
private val transform: (T) -> U
) : MemoizedConfigProperty() {
override fun doGetValue(thisRef: ConfigAware, property: KProperty<*>): U {
if (thisRef.isConfigured(property.name)) {
return transform(getValueOrDefault(thisRef, property.name, defaultValue))
}
if (thisRef.isConfigured(fallbackProperty.name)) {
return fallbackProperty.get()
}
return transform(defaultValue)
}
private fun ConfigAware.isConfigured(propertyName: String) = valueOrNull(propertyName) != null
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy