Skip to content

Add maxRuleVersion configuration for version-based rule filtering - #943

Open
hugoncosta wants to merge 2 commits into
JLLeitschuh:mainfrom
hugoncosta:main
Open

Add maxRuleVersion configuration for version-based rule filtering#943
hugoncosta wants to merge 2 commits into
JLLeitschuh:mainfrom
hugoncosta:main

Conversation

@hugoncosta

Copy link
Copy Markdown

Address the feature request in #942

Summary

Adds maxRuleVersion configuration to allow filtering rules based on their @SinceKtlint version annotation.

Changes

  • Add maxRuleVersion property to KtlintExtension
  • Implement version compatibility checking with semantic versioning support
  • Filter rules in KtLintInvocation100 based on maxRuleVersion setting
  • Add comprehensive tests and documentation

More context in the issue mentioned above.

@hugoncosta

Copy link
Copy Markdown
Author

For reference, the equivalent change in Ktlint has been merged - ktlint/ktlint#3101

* @param maxVersion The maximum allowed version (from maxRuleVersion configuration)
* @return true if the rule version is compatible (should be included), false otherwise
*/
private fun isVersionCompatible(ruleVersion: String, maxVersion: String): Boolean {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use net.swiftzer.semver.SemVer elsewhere in this project, so might as well use it here

@wakingrufus

Copy link
Copy Markdown
Collaborator

Thanks for the contribution!
I understand now that this feature request is to delegate to a new feature of ktlint itself, not something that would effect the dependencies we are setting up. This looks good to me.

@hugoncosta

Copy link
Copy Markdown
Author

Appreciate the feedback, I've gone ahead and implemented that, let me know your thoughts on it

@wakingrufus
wakingrufus self-requested a review August 22, 2025 19:56
@wakingrufus

Copy link
Copy Markdown
Collaborator

Looking good. one more question: how will this interact with versions of ktlint from before the annotation was retained at runtime? I think it should fail "open" in that case, and just include all rules. We support using ktlint versions back to 1.0.0, so this must be considered.

@hugoncosta

Copy link
Copy Markdown
Author

That's a very good point - I was thinking more of in the future, if someone doesn't include it, but in practice, my change guarantees (with a reflection based test) that all rules will have the annotation, the issue is definitively the backwards compatibility, where some of them do not have it. I'll ensure that if it doesn't find it (the current scenario on 1.7.1 and below), it will accept all of them. I'll also make that clear in the javadoc of the new parameter.

@hugoncosta
hugoncosta force-pushed the main branch 2 times, most recently from c6a9984 to 98d1e09 Compare August 23, 2025 07:36
@hugoncosta

Copy link
Copy Markdown
Author

Ok actually the backwards compatibility was already baked in. I've updated the comments and added the @SInCE in the API itself, as the code was already merged and is currently planned as part of the 1.8.0 release

@JLLeitschuh JLLeitschuh left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are the big thing. They need to cover behavior, not just that the flags can/can't be set

Comment on lines +720 to +751
@DisplayName("Should allow setting maxRuleVersion")
@CommonTest
fun allowMaxRuleVersionConfiguration(gradleVersion: GradleVersion) {
project(gradleVersion) {
withCleanSources()
//language=Groovy
buildGradle.appendText(
"""

ktlint {
maxRuleVersion = "1.0.0"
}
""".trimIndent()
)

build(CHECK_PARENT_TASK_NAME) {
assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
}
}

@DisplayName("Should work without maxRuleVersion set")
@CommonTest
fun workWithoutMaxRuleVersion(gradleVersion: GradleVersion) {
project(gradleVersion) {
withCleanSources()

build(CHECK_PARENT_TASK_NAME) {
assertThat(task(":$mainSourceSetCheckTaskName")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
}
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... These test that the maxRuleVersion can be set, but not that it behaves in a way that is expected. We should have a test for that.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree, but for that, I think we'll need to park this PR until Ktlint 1.8.0 gets published, as the annotation isn't present at runtime (what we need) before. Or do you know of any other way to build against the unpublished Ktlint version?

Comment on lines +61 to +73
private fun isRuleCompatibleWithVersion(ruleProvider: RuleProvider, maxVersion: String): Boolean {
// Use reflection to check for @SinceKtlint annotation
val ruleClass = ruleProvider.createNewRuleInstance()::class.java
val sinceAnnotation = ruleClass.getAnnotation(SinceKtlint::class.java)

return if (sinceAnnotation != null) {
isVersionCompatible(sinceAnnotation.version, maxVersion)
} else {
// If no annotation, assume it's from an older ktlint version (before annotations were included
// at runtime) and include it for backward compatibility
true
}
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This filtering isn't a feature built into ktlint itself as a library? I guess we are doing the rule loading manually, so I suppose this makes sense.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct - per Paul's comment, it's the Ktlint Gradle plugin that provides the rules, so it's up to Ktlint Gradle to filter them out (if required). See ktlint/ktlint#3099 (comment)

@hugoncosta

Copy link
Copy Markdown
Author

Hello, happy new year, back to this as Paul has launched Ktlint 1.8.0 thus allowing me to finish the work here. Let me know your thoughts on the latest modifications

Comment thread plugin/gradle/libs.versions.toml Outdated
[versions]
kotlin = "1.5.31"
ktlint = "1.0.0"
ktlint = "1.8.0"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the implementation of this would require us to compile against 1.8.0?
If so, we will need to wait until we are ready for a major version release to merge this feature.
If you would like this to be released sooner, I think isRuleCompatibleWithFilters would have to be reimplementated using reflection in order to be able to "work" (as a no-op) when older ktlint versions are used

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea. I've gone ahead and implemented it using reflection, it's working as expected and doesn't force us to make a new major version :)

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcom.pinterest.ktlint/​ktlint-ruleset-standard@​1.0.09510089100100
Addedcom.pinterest.ktlint/​ktlint-cli-reporter-core@​1.0.09810010010099
Addedcom.pinterest.ktlint/​ktlint-cli-reporter-baseline@​1.0.010010010010099

View full report

@hugoncosta

Copy link
Copy Markdown
Author

Hello, anything I can address in this to get it through? We have been using this internally in 1000s of packages successfully.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants