-
-
Notifications
You must be signed in to change notification settings - Fork 777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for transformer function in config property delegate #3676
Conversation
Codecov Report
@@ Coverage Diff @@
## main #3676 +/- ##
============================================
+ Coverage 78.75% 78.80% +0.04%
- Complexity 2900 2901 +1
============================================
Files 473 473
Lines 9336 9335 -1
Branches 1722 1723 +1
============================================
+ Hits 7353 7356 +3
+ Misses 1075 1073 -2
+ Partials 908 906 -2
Continue to review full report at Codecov.
|
I think that we need a it more flexibility. Something like:
And we need to think about caching these values. |
I think that should be possible. As long as we do not expand the types that are used for |
+1 |
What do you think about this: fun config(
defaultValue: List<String>
): ReadOnlyProperty<ConfigAware, List<String>> = ListConfigProperty(defaultValue) { it }
fun <T : Any> config(
defaultValue: List<String>,
transform: (List<String>) -> T
): ReadOnlyProperty<ConfigAware, T> = ListConfigProperty(defaultValue, transform)
fun <T : Any> config(
defaultValue: T
): ReadOnlyProperty<ConfigAware, T> = TransformedConfigProperty(defaultValue) { it }
fun <T : Any, U : Any> config(
defaultValue: T,
transform: (T) -> U,
): ReadOnlyProperty<ConfigAware, U> = TransformedConfigProperty(defaultValue, transform)
private class TransformedConfigProperty<T : Any, U : Any>(
private val defaultValue: T,
private val transform: (T) -> U
) : ReadOnlyProperty<ConfigAware, U> {
override fun getValue(thisRef: ConfigAware, property: KProperty<*>): U {
return transform(thisRef.valueOrDefault(property.name, defaultValue))
}
}
private class ListConfigProperty<T : Any>(
private val defaultValue: List<String>,
private val transform: (List<String>) -> T
) : ReadOnlyProperty<ConfigAware, T> {
override fun getValue(thisRef: ConfigAware, property: KProperty<*>): T {
return transform(thisRef.valueOrDefaultCommaSeparated(property.name, defaultValue))
}
} It would be nice to use default values intead of overloads but in this case it's not possible. But, for the consumer, they are the same. |
d199981
to
e20c497
Compare
I really like the idea, especially after renaming them to |
Thank you for pointing out that we need to make sure that the transformer is only evaluated once. I adapted the code from |
f5d49f2
to
8559e32
Compare
8559e32
to
098f9c0
Compare
val fallbackValue = getValueOrDefault(thisRef, fallbackPropertyName, defaultValue) | ||
return transform(getValueOrDefault(thisRef, property.name, fallbackValue)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not something in this PR, but I will file an issue myself on changing the fallback value of having type () -> T
instead of T
- The benefit is that if the value is present, we do not need to initialize the fallback property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth it? We will be initializing booleans, strings, List. They will have more less the same cost to instantiate than the lambda itself and we will need to run the lambda too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On top of that please consider that We have to extract the default value for the documentation without the benefit of a BindingContext. This might end up really complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth it? We will be initializing booleans, strings, List.
I am looking for semantic correctness: If a property exists, we shouldn't read from the fallback property at all.
On top of that please consider that We have to extract the default value for the documentation without the benefit of a BindingContext. This might end up really complicated.
I don't fully get the benefit of a BindingContext, would you mind elaborate it more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind. Reading your comment on my mobile I thought you were talking about something else. You were talking about something like this? I could add it to this PR or feel free to create an issue afterwards.
override fun doGetValue(thisRef: ConfigAware, property: KProperty<*>): U {
val fallbackValueSupplier = { getValueOrDefault(thisRef, fallbackPropertyName) { defaultValue } }
return transform(getValueOrDefault(thisRef, property.name, fallbackValueSupplier))
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Yes that's what I meant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am afraid I can't find a solution without actually invoking the supplier in getValueOrDefault
which makes this exercise useless as we evaluate the fallback no matter what. I rather keep it the way it is if that is ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem. We can table this for now
...erator/src/test/kotlin/io/gitlab/arturbosch/detekt/generator/collection/RuleCollectorSpec.kt
Show resolved
Hide resolved
} | ||
it("extracts default value with method reference") { | ||
val items = subject.run(code) | ||
assertThat(items[0].configuration[1].defaultValue).isEqualTo("'false'") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to val needsQuotes = declaredTypeOrNull in TYPES_THAT_NEED_QUOTATION_FOR_DEFAULT
, should we add quotes only when the source type is String?
I imagine the default value should be false
without quotes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. The problem here is that without a binding context or reflection I don't now how to reliably determine if the default value is a string or not. This is "best effort" by looking at the property type which (with transformi) might be something else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One possible alternative is using reified types for ConfigProperty.
detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/internal/ConfigProperty.kt
Show resolved
Hide resolved
Is there anything that needs to be done here before merging? I would like to continue with #3670 if possible. |
All good. Thank you for the groundwork. |
…ekt#3676) Co-authored-by: Markus Schwarz <post@markus-schwarz.net>
This relates to a comment by @BraisGabin in #3670 addressing situations in which the configured values must be transformed.
This PR:
Examples:
This would also resolve #3672