diff --git a/CHANGELOG.md b/CHANGELOG.md index 52dd9acaba..ec59b3aa93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). * Add API so that KtLint API consumer is able to process a Kotlin script snippet without having to specify a file path ([#1738](https://github.com/pinterest/ktlint/issues/1738)) * Disable the `standard:filename` rule whenever Ktlint CLI is run with option `--stdin` ([#1742](https://github.com/pinterest/ktlint/issues/1742)) * Fix initialization of the logger when `--log-level` is specified. Throw exception when an invalid value is passed. ([#1749](https://github.com/pinterest/ktlint/issues/1749)) +* Fix loading of custom rule set JARs ### Changed diff --git a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/RuleSetProviderV2.kt b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/RuleSetProviderV2.kt index 6df0f88bd9..2513afd037 100644 --- a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/RuleSetProviderV2.kt +++ b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/RuleSetProviderV2.kt @@ -65,13 +65,13 @@ public abstract class RuleSetProviderV2( require(description == null || description.length <= 400) { "Length of description should be 400 characters or less" } - require(license == null || license.length <= 80) { - "Length of license should be 80 characters or less" + require(license == null || license.length <= 120) { + "Length of license url should be 80 characters or less" } - require(repositoryUrl == null || repositoryUrl.length <= 80) { + require(repositoryUrl == null || repositoryUrl.length <= 120) { "Length of repository url should be 80 characters or less" } - require(issueTrackerUrl == null || issueTrackerUrl.length <= 80) { + require(issueTrackerUrl == null || issueTrackerUrl.length <= 120) { "Length of repository url should be 80 characters or less" } } diff --git a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/RuleRunner.kt b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/RuleRunner.kt index a5de66c34b..ca54d7c8df 100644 --- a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/RuleRunner.kt +++ b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/RuleRunner.kt @@ -51,6 +51,6 @@ internal class RuleRunner(private val provider: RuleProvider) { require(!rule.isUsedForTraversalOfAST()) { "RunAfterRules can not be removed when RuleRunner has already been used for traversal of the AST" } - this.runAfterRules - runAfterRules + this.runAfterRules = this.runAfterRules - runAfterRules } } diff --git a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/RuleRunnerSorter.kt b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/RuleRunnerSorter.kt index 6955f5d7d6..c80286637a 100644 --- a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/RuleRunnerSorter.kt +++ b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/RuleRunnerSorter.kt @@ -93,8 +93,10 @@ internal class RuleRunnerSorter { do { if (newRuleRunnersAdded) { newRuleRunnersAdded = false + // All rule runners which were (previously) blocked can now be checked again ruleRunnersIterator = blockedRuleRunners + .canRunWith(newRuleRunners) .toSet() .iterator() blockedRuleRunners.clear() @@ -157,13 +159,6 @@ internal class RuleRunnerSorter { // already added to the new list of rule which will be loaded before the current rule. newRuleRunners.add(currentRuleRunner) newRuleRunnersAdded = true - // All rule runners which were (recursively) blocked because they need to be run after the newly added rule - // runner can now be added to the new list of rule runners as well. - val ruleReferencesToUnblock = blockedRuleRunners.canRunWith(newRuleRunners) - if (ruleReferencesToUnblock.isNotEmpty()) { - newRuleRunners.addAll(ruleReferencesToUnblock) - blockedRuleRunners.removeAll(ruleReferencesToUnblock.toSet()) - } } BLOCK_UNTIL_RUN_AFTER_RULE_IS_LOADED -> { @@ -200,9 +195,9 @@ internal class RuleRunnerSorter { .sorted() val prefix = if (customRuleSetIds.isEmpty()) { - "Found cyclic dependencies between rules that should run after another rule:" + "Found cyclic dependencies between required rules that should run after another rule:" } else { - "Found cyclic dependencies between rules that should run after another rule. Please contact " + + "Found cyclic dependencies between required rules that should run after another rule. Please contact " + "the maintainer(s) of the custom rule set(s) [${customRuleSetIds.joinToString()}] before " + "creating an issue in the KtLint project. Dependencies:" } @@ -217,13 +212,6 @@ internal class RuleRunnerSorter { return newRuleRunners } - private fun Set.findRuleRunnersBlockedBy(qualifiedRuleId: String): List { - return this - .filter { it.runAfterRules.any { it.ruleId == qualifiedRuleId } } - .map { listOf(it) + this.findRuleRunnersBlockedBy(it.qualifiedRuleId) } - .flatten() - } - private fun Set.canRunWith(loadedRuleRunners: List): List = canRunWithRuleIds(loadedRuleRunners.map { it.qualifiedRuleId }) diff --git a/ktlint-core/src/test/kotlin/com/pinterest/ktlint/core/internal/RuleRunnerSorterTest.kt b/ktlint-core/src/test/kotlin/com/pinterest/ktlint/core/internal/RuleRunnerSorterTest.kt index cbb4440dd7..df7a33bd1e 100644 --- a/ktlint-core/src/test/kotlin/com/pinterest/ktlint/core/internal/RuleRunnerSorterTest.kt +++ b/ktlint-core/src/test/kotlin/com/pinterest/ktlint/core/internal/RuleRunnerSorterTest.kt @@ -222,10 +222,11 @@ class RuleRunnerSorterTest { assertThat(actual).containsExactly( "$STANDARD:$RULE_B", "$STANDARD:$RULE_C", - "$STANDARD:$RULE_A", - // Although RULE_D like RULE_C depends on RULE_B it still comes after RULE_A because that rules according to - // the default sort order comes before rule D "$STANDARD:$RULE_D", + // RULE_D is ordered before RULE_A because rules are evaluated in order of the initial sorting (A, B, C, D). In the first + // iteration of the rules, RULE_A is blocked because rule C is not yet added. RULE_B, RULE_C and RULE_D can be added during the + // first iteration as the rules are not blocked when they are evaluated. In the second iteration, RULE_A can be added as well. + "$STANDARD:$RULE_A", ) } @@ -492,7 +493,7 @@ class RuleRunnerSorterTest { ) }.withMessage( """ - Found cyclic dependencies between rules that should run after another rule: + Found cyclic dependencies between required rules that should run after another rule: - Rule with id '$STANDARD:$RULE_A' should run after rule(s) with id '$STANDARD:$RULE_B' - Rule with id '$STANDARD:$RULE_B' should run after rule(s) with id '$EXPERIMENTAL:$RULE_C' - Rule with id '$EXPERIMENTAL:$RULE_C' should run after rule(s) with id '$STANDARD:$RULE_A' @@ -509,7 +510,7 @@ class RuleRunnerSorterTest { object : R( id = RULE_A, visitorModifiers = setOf( - VisitorModifier.RunAfterRule("$EXPERIMENTAL:$RULE_B"), + VisitorModifier.RunAfterRule("$CUSTOM_RULE_SET_A:$RULE_B"), VisitorModifier.RunAfterRule(RULE_B), ), ) {}, @@ -517,21 +518,21 @@ class RuleRunnerSorterTest { id = RULE_B, visitorModifiers = setOf( VisitorModifier.RunAfterRule(RULE_C), - VisitorModifier.RunAfterRule("$EXPERIMENTAL:$RULE_C"), + VisitorModifier.RunAfterRule("$CUSTOM_RULE_SET_A:$RULE_C"), ), ) {}, object : R( - id = "$EXPERIMENTAL:$RULE_C", + id = "$CUSTOM_RULE_SET_A:$RULE_C", visitorModifier = VisitorModifier.RunAfterRule(RULE_A), ) {}, ), ) }.withMessage( """ - Found cyclic dependencies between rules that should run after another rule: - - Rule with id '$STANDARD:$RULE_A' should run after rule(s) with id '$EXPERIMENTAL:$RULE_B, $STANDARD:$RULE_B' - - Rule with id '$STANDARD:$RULE_B' should run after rule(s) with id '$STANDARD:$RULE_C, $EXPERIMENTAL:$RULE_C' - - Rule with id '$EXPERIMENTAL:$RULE_C' should run after rule(s) with id '$STANDARD:$RULE_A' + Found cyclic dependencies between required rules that should run after another rule. Please contact the maintainer(s) of the custom rule set(s) [custom-rule-set-a] before creating an issue in the KtLint project. Dependencies: + - Rule with id '$STANDARD:$RULE_A' should run after rule(s) with id '$STANDARD:$RULE_B' + - Rule with id '$STANDARD:$RULE_B' should run after rule(s) with id '$CUSTOM_RULE_SET_A:$RULE_C' + - Rule with id '$CUSTOM_RULE_SET_A:$RULE_C' should run after rule(s) with id '$STANDARD:$RULE_A' """.trimIndent(), ) } @@ -561,7 +562,7 @@ class RuleRunnerSorterTest { ) }.withMessage( """ - Found cyclic dependencies between rules that should run after another rule. Please contact the maintainer(s) of the custom rule set(s) [$CUSTOM_RULE_SET_A, $CUSTOM_RULE_SET_B] before creating an issue in the KtLint project. Dependencies: + Found cyclic dependencies between required rules that should run after another rule. Please contact the maintainer(s) of the custom rule set(s) [$CUSTOM_RULE_SET_A, $CUSTOM_RULE_SET_B] before creating an issue in the KtLint project. Dependencies: - Rule with id '$STANDARD:$RULE_C' should run after rule(s) with id '$CUSTOM_RULE_SET_B:$RULE_B' - Rule with id '$CUSTOM_RULE_SET_A:$RULE_A' should run after rule(s) with id '$STANDARD:$RULE_C' - Rule with id '$CUSTOM_RULE_SET_B:$RULE_B' should run after rule(s) with id '$CUSTOM_RULE_SET_A:$RULE_A' @@ -597,10 +598,10 @@ class RuleRunnerSorterTest { ) }.withMessage( """ - Found cyclic dependencies between rules that should run after another rule. Please contact the maintainer(s) of the custom rule set(s) [$CUSTOM_RULE_SET_A, $CUSTOM_RULE_SET_B] before creating an issue in the KtLint project. Dependencies: - - Rule with id '$STANDARD:$RULE_C' should run after rule(s) with id '$CUSTOM_RULE_SET_B:$RULE_B, $STANDARD:$RULE_B' + Found cyclic dependencies between required rules that should run after another rule. Please contact the maintainer(s) of the custom rule set(s) [$CUSTOM_RULE_SET_A, $CUSTOM_RULE_SET_B] before creating an issue in the KtLint project. Dependencies: + - Rule with id '$STANDARD:$RULE_C' should run after rule(s) with id '$CUSTOM_RULE_SET_B:$RULE_B' - Rule with id '$CUSTOM_RULE_SET_A:$RULE_A' should run after rule(s) with id '$STANDARD:$RULE_C' - - Rule with id '$CUSTOM_RULE_SET_B:$RULE_B' should run after rule(s) with id '$STANDARD:$RULE_A, $CUSTOM_RULE_SET_A:$RULE_A' + - Rule with id '$CUSTOM_RULE_SET_B:$RULE_B' should run after rule(s) with id '$CUSTOM_RULE_SET_A:$RULE_A' """.trimIndent(), ) } diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GenerateEditorConfigSubCommand.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GenerateEditorConfigSubCommand.kt index 32001dfd95..62ac0b0476 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GenerateEditorConfigSubCommand.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/GenerateEditorConfigSubCommand.kt @@ -29,8 +29,15 @@ internal class GenerateEditorConfigSubCommand : Runnable { override fun run() { commandSpec.commandLine().printCommandLineHelpOrVersionUsage() + val ruleProviders = + ktlintCommand + .ruleProvidersByRuleSetId() + .values + .flatten() + .toSet() + val ktLintRuleEngine = KtLintRuleEngine( - ruleProviders = ktlintCommand.ruleProviders(), + ruleProviders = ruleProviders, editorConfigOverride = EditorConfigOverride.from(CODE_STYLE_PROPERTY to codeStyle()), isInvokedFromCli = true, ) diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/KtlintCommandLine.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/KtlintCommandLine.kt index 00539d0aa0..59bcbd04dd 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/KtlintCommandLine.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/KtlintCommandLine.kt @@ -6,6 +6,7 @@ import com.pinterest.ktlint.core.KtLintRuleEngine import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.core.Reporter import com.pinterest.ktlint.core.ReporterProvider +import com.pinterest.ktlint.core.RuleProvider import com.pinterest.ktlint.core.api.Baseline.Status.INVALID import com.pinterest.ktlint.core.api.Baseline.Status.NOT_FOUND import com.pinterest.ktlint.core.api.EditorConfigDefaults @@ -251,20 +252,6 @@ internal class KtlintCommandLine { ?.let { path -> Paths.get(path) }, ) - private val editorConfigOverride: EditorConfigOverride - get() = - EditorConfigOverride - .EMPTY_EDITOR_CONFIG_OVERRIDE - .applyIf(experimental) { - plus(createRuleSetExecutionEditorConfigProperty("experimental:all") to RuleExecution.enabled) - }.applyIf(disabledRules.isNotBlank()) { - plus(*disabledRulesEditorConfigOverrides()) - }.applyIf(android) { - plus(CODE_STYLE_PROPERTY to CodeStyleValue.android) - }.applyIf(stdin) { - plus(createRuleExecutionEditorConfigProperty("standard:filename") to RuleExecution.disabled) - } - private fun disabledRulesEditorConfigOverrides() = disabledRules .split(",") @@ -283,6 +270,37 @@ internal class KtlintCommandLine { exitKtLintProcess(1) } + val ruleProvidersByRuleSetId = ruleProvidersByRuleSetId() + val customRuleSetIds = + ruleProvidersByRuleSetId + .filterKeys { + // Exclude the standard and experimental rule sets from Ktlint itself + it != "standard" && it != "experimental" + }.map { it.key } + + val editorConfigOverride = EditorConfigOverride + .EMPTY_EDITOR_CONFIG_OVERRIDE + .applyIf(experimental) { + logger.debug { "Add editor config override to allow the experimental rule set" } + plus(createRuleSetExecutionEditorConfigProperty("experimental:all") to RuleExecution.enabled) + }.applyIf(disabledRules.isNotBlank()) { + logger.debug { "Add editor config override to disable rules: '$disabledRules'" } + plus(*disabledRulesEditorConfigOverrides()) + }.applyIf(android) { + logger.debug { "Add editor config override to set code style to 'android'" } + plus(CODE_STYLE_PROPERTY to CodeStyleValue.android) + }.applyIf(stdin) { + logger.debug { "Add editor config override to disable 'filename' rule which can not be used in combination with reading from " } + plus(createRuleExecutionEditorConfigProperty("standard:filename") to RuleExecution.disabled) + }.applyIf(customRuleSetIds.isNotEmpty()) { + logger.debug { "Add editor config override to enable rule set(s) '$customRuleSetIds' from custom rule set JAR('s): '$rulesetJarPaths'" } + val ruleSetExecutionEditorConfigProperties = + customRuleSetIds + .map { createRuleSetExecutionEditorConfigProperty("$it:all") to RuleExecution.enabled } + .toTypedArray() + plus(*ruleSetExecutionEditorConfigProperties) + } + assertStdinAndPatternsFromStdinOptionsMutuallyExclusive() val stdinPatterns: Set = readPatternsFromStdin() @@ -299,8 +317,14 @@ internal class KtlintCommandLine { var reporter = loadReporter() + val ruleProviders = + ruleProvidersByRuleSetId + .values + .flatten() + .toSet() + val ktLintRuleEngine = KtLintRuleEngine( - ruleProviders = ruleProviders(), + ruleProviders = ruleProviders, editorConfigDefaults = editorConfigDefaults, editorConfigOverride = editorConfigOverride, isInvokedFromCli = true, @@ -347,11 +371,13 @@ internal class KtlintCommandLine { } } - internal fun ruleProviders() = + // Do not convert to "val" as the function depends on PicoCli options which are not fully instantiated until the "run" method is started + internal fun ruleProvidersByRuleSetId(): Map> = rulesetJarPaths .toFilesURIList() - .loadRuleProviders(debug) + .loadRuleProvidersByRuleSetId(debug) + // Do not convert to "val" as the function depends on PicoCli options which are not fully instantiated until the "run" method is started private fun List.toFilesURIList() = map { val jarFile = File(it.expandTildeToFullPath()) @@ -362,6 +388,7 @@ internal class KtlintCommandLine { jarFile.toURI().toURL() } + // Do not convert to "val" as the function depends on PicoCli options which are not fully instantiated until the "run" method is started internal fun configureLogger() { logger = KotlinLogging .logger {} diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/LoadRuleProviders.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/LoadRuleProviders.kt index 58c824582b..3655c6df18 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/LoadRuleProviders.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/LoadRuleProviders.kt @@ -14,53 +14,60 @@ private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() /** * Loads given list of paths to jar files. For files containing a [RuleSetProviderV2] class, get all [RuleProvider]s. */ -internal fun List.loadRuleProviders(debug: Boolean): Set = - this +internal fun List.loadRuleProvidersByRuleSetId(debug: Boolean): Map> = + getKtlintRulesets() .plus( - // Ensure that always at least one element exists in this list so that the rule sets provided by the KtLint - // CLI module itself will be found even in case no JAR files are specified - null, + getRuleProvidersFromCustomRuleSetJars(debug), ) + +private fun getKtlintRulesets(): Map> { + return loadRulesetsFrom() +} + +private fun loadRulesetsFrom(url: URL? = null): Map> = + try { + ServiceLoader + .load( + RuleSetProviderV2::class.java, + URLClassLoader(listOfNotNull(url).toTypedArray()), + ).associate { ruleSetProviderV2 -> ruleSetProviderV2.id to ruleSetProviderV2.getRuleProviders() } + } catch (e: ServiceConfigurationError) { + LOGGER.warn { "Error while loading rule set JAR '$url':\n${e.printStackTrace()}" } + emptyMap() + } + +private fun List.getRuleProvidersFromCustomRuleSetJars(debug: Boolean): Map> = + this // Remove JAR files which were provided multiple times .distinct() - .flatMap { getRuleProvidersFromJar(it, debug).values } - .flatten() - .toSet() + .flatMap { getRuleProvidersFromCustomRuleSetJar(it, debug).entries } + .associate { it.key to it.value } -private fun getRuleProvidersFromJar( - url: URL?, +private fun getRuleProvidersFromCustomRuleSetJar( + url: URL, debug: Boolean, ): Map> { - if (url != null && debug) { + if (debug) { LOGGER.debug { "JAR ruleset provided with path \"${url.path}\"" } } - return try { - ServiceLoader - .load( - RuleSetProviderV2::class.java, - URLClassLoader(listOfNotNull(url).toTypedArray()), - ).filter { - // The KtLint-root (CLI) module includes the standard and experimental rule sets. When those rule sets - // are also included in the specified JAR (url != null) then ignore them. - url == null || it.id !in KTLINT_RULE_SETS - }.associate { ruleSetProviderV2 -> ruleSetProviderV2.id to ruleSetProviderV2.getRuleProviders() } - .also { ruleSetIdMap -> - if (url != null && ruleSetIdMap.isEmpty()) { - LOGGER.warn { - """ - JAR ${url.path}, provided as command line argument, does not contain a custom ruleset provider. - Check following: - - Does the jar contain an implementation of the RuleSetProviderV2 interface? - - Does the jar contain a resource file with name "com.pinterest.ktlint.core.RuleSetProviderV2"? - - Is the resource file located in directory "src/main/resources/META-INF/services"? - - Does the resource file contain the fully qualified class name of the class implementing the RuleSetProviderV2 interface? - """.trimIndent() - } + return loadRulesetsFrom(url) + .filterKeys { + // Ignore the Ktlint rule sets when they are included in the custom rule set. + it !in KTLINT_RULE_SETS + }.also { ruleSetIdMap -> + if (ruleSetIdMap.isEmpty()) { + LOGGER.warn { + """ + JAR ${url.path}, provided as command line argument, does not contain a custom ruleset provider. + Check following: + - Does the jar contain an implementation of the RuleSetProviderV2 interface? + - Does the jar contain a resource file with name "com.pinterest.ktlint.core.RuleSetProviderV2"? + - Is the resource file located in directory "src/main/resources/META-INF/services"? + - Does the resource file contain the fully qualified class name of the class implementing the RuleSetProviderV2 interface? + """.trimIndent() } } - } catch (e: ServiceConfigurationError) { - emptyMap() - } + } } private val KTLINT_RULE_SETS = listOf("standard", "experimental") diff --git a/ktlint/src/test/kotlin/com/pinterest/ktlint/RuleSetsLoaderCLITest.kt b/ktlint/src/test/kotlin/com/pinterest/ktlint/RuleSetsLoaderCLITest.kt index 0cbf56b715..e270c7ebea 100644 --- a/ktlint/src/test/kotlin/com/pinterest/ktlint/RuleSetsLoaderCLITest.kt +++ b/ktlint/src/test/kotlin/com/pinterest/ktlint/RuleSetsLoaderCLITest.kt @@ -23,4 +23,26 @@ class RuleSetsLoaderCLITest { }.assertAll() } } + + @Test + fun `Given a custom rule set with RulesetProviderV2 defined`( + @TempDir + tempDir: Path, + ) { + val jarWithRulesetProviderV2 = "custom-ruleset/rule-set-provider-v2/ktlint-ruleset-template.jar" + CommandLineTestRunner(tempDir) + .run( + "custom-ruleset", + listOf("-R", "$tempDir/$jarWithRulesetProviderV2"), + ) { + SoftAssertions().apply { + assertNormalExitCode() + // JAR ruleset provided with path "/var/folders/24/wtp_g21953x22nr8z86gvltc0000gp/T/junit920502858262478102/custom-ruleset/rule-set-provider-v2/ktlint-ruleset-template.jar + // Add editor config override to enable rule set(s) '[indent-string-template-ruleset]' from custom rule set JAR('s): '[/var/folders/24/wtp_g21953x22nr8z86gvltc0000gp/T/junit920502858262478102/custom-ruleset/rule-set-provider-v2/ktlint-ruleset-template.jar]' + assertThat(normalOutput) + .containsLineMatching(Regex(".* JAR ruleset provided with path .*$jarWithRulesetProviderV2.*")) + .containsLineMatching(Regex(".* Add editor config override to enable rule set\\(s\\) '\\[indent-string-template-ruleset]' from custom rule set JAR\\('s\\): .*$jarWithRulesetProviderV2.*")) + }.assertAll() + } + } } diff --git a/ktlint/src/test/resources/cli/custom-ruleset/rule-set-provider-v2/ktlint-ruleset-template.jar b/ktlint/src/test/resources/cli/custom-ruleset/rule-set-provider-v2/ktlint-ruleset-template.jar index 603ea1256e..77d30a35d4 100644 Binary files a/ktlint/src/test/resources/cli/custom-ruleset/rule-set-provider-v2/ktlint-ruleset-template.jar and b/ktlint/src/test/resources/cli/custom-ruleset/rule-set-provider-v2/ktlint-ruleset-template.jar differ