diff --git a/.announce b/.announce index 9f5870eb4f..5f2238003c 100755 --- a/.announce +++ b/.announce @@ -29,6 +29,7 @@ echo "Announcing $PREVIOUS_VERSION -> $VERSION" DOCUMENTATION_DIR="documentation" RELEASE_DOCS_DIR="${DOCUMENTATION_DIR}/release-latest" SNAPSHOT_DOCS_DIR="${DOCUMENTATION_DIR}/snapshot" +KTLINT_RULESET_TEMPLATE_DIR="ktlint-ruleset-template" if [ "$(git status --porcelain=v1 $DOCUMENTATION)" != "" ]; then echo "ERROR: To proceed, the current branch must not contain uncommitted changes in directory '${DOCUMENTATION_DIR}'" @@ -80,6 +81,7 @@ echo "Updating version numbers in (snapshot) installation documentation" # "can't read s/0.49.0/0.49.1/g: No such file or directory" sed -i -e "s/$PREVIOUS_VERSION/$VERSION/g" ${SNAPSHOT_DOCS_DIR}/docs/install/cli.md sed -i -e "s/$PREVIOUS_VERSION/$VERSION/g" ${SNAPSHOT_DOCS_DIR}/docs/install/integrations.md +sed -i -e "s/$PREVIOUS_VERSION/$VERSION/g" ${KTLINT_RULESET_TEMPLATE_DIR}/build.gradle.kts git --no-pager diff ${DOCUMENTATION_DIR} # ask for user confirmation before committing diff --git a/documentation/release-latest/docs/api/custom-rule-set.md b/documentation/release-latest/docs/api/custom-rule-set.md index 83156fa1c5..7a66cb6a4d 100644 --- a/documentation/release-latest/docs/api/custom-rule-set.md +++ b/documentation/release-latest/docs/api/custom-rule-set.md @@ -1,17 +1,49 @@ -!!! Tip - See [Writing your first ktlint rule](https://medium.com/@vanniktech/writing-your-first-ktlint-rule-5a1707f4ca5b) by [Niklas Baudy](https://github.com/vanniktech). +You can provide custom rules via a separate ruleset to Ktlint. A ruleset is a JAR containing one or more [Rule](https://github.com/pinterest/ktlint/blob/master/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt)s. -In a nutshell: a "rule set" is a JAR containing one or more [Rule](https://github.com/pinterest/ktlint/blob/master/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt)s. `ktlint` is relying on the [ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) to discover all available "RuleSet"s on the classpath. As a ruleset author, all you need to do is to include a `META-INF/services/RuleSetProviderV3` file containing a fully qualified name of your [RuleSetProviderV3](https://github.com/pinterest/ktlint/blob/master/ktlint-cli-ruleset-core/src/main/kotlin/com/pinterest/ktlint/cli/ruleset/core/api/RuleSetProviderV3.kt) implementation. +A complete sample project is included in this repo under the [ktlint-ruleset-template](https://github.com/pinterest/ktlint/tree/master/ktlint-ruleset-template) directory. This directory can be cloned, and used as a starting point for a new project containing your custom ruleset. ## ktlint-ruleset-template -A complete sample project (with tests and build files) is included in this repo under the [ktlint-ruleset-template](https://github.com/pinterest/ktlint/tree/master/ktlint-ruleset-template) directory (make sure to check [NoVarRuleTest](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt) as it contains some useful information). +### Gradle build + +The [Gradle build file](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/build.gradle.kts) of the sample project includes the setup for: + +* publishing the custom ruleset artifact to Maven +* the custom Gradle task 'ktlintCheck' that is using the Ktlint CLI to run the rules provided by the ktlint project, as well as the custom rule(s) from this project on the project itself ([dogfood principle](https://en.wikipedia.org/wiki/Eating_your_own_dog_food)). + +### Rule + +The Rule contains the logic for linting and formatting the code. For example, see [NoVarRuleTest](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt). + +A rule has to implement one or more of hooks below: + +* `Rule.beforeFirstNode` +* `RuleAutocorrectApproveHandler.beforeVisitChildNodes` +* `RuleAutocorrectApproveHandler.afterVisitChildNodes` +* `Rule.afterLastNode` + +!!! Tip +See `ktlint-ruleset-standard` for examples of rules that implement the hooks above. + +Upon traversal of the Abstract Syntax Tree (AST), the hooks of the Rule are visited as indicated by their names. The [Jetbrains PsiViewer plugin for IntelliJ IDEA](https://plugins.jetbrains.com/plugin/227-psiviewer) is a convenient tool to inspect the AST for any piece of code. + +![Image](../assets/images/psi-viewer.png) + +### Rule Set Provider + +The RuleSetProvider provides new instances of the rule, see [CustomRuleSetProvider](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/src/main/kotlin/yourpkgname/CustomRuleSetProvider.kt) for an example. + +`ktlint` is relying on the [ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) to discover all available "RuleSet"s on the classpath. For this, the RuleSetProvider needs to be registered in file `resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3`, see [Registration for Java ServiceLoader](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3). + +### Building the project ```shell title="Building the ktlint-ruleset-template" $ cd ktlint-ruleset-template/ $ ../gradlew build ``` +### Running Ktlint CLI with the custom ruleset + ```shell title="Provide code sample that violates rule `custom:no-var" $ echo 'var v = 0' > test.kt ``` @@ -51,8 +83,8 @@ $ ktlint -R build/libs/ktlint-ruleset-template.jar --log-level=debug --relative - standard:no-unit-return, - standard:no-unused-imports, - standard:no-wildcard-imports, - - standard:op-spacing, - - standard:parameter-list-wrapping, + - standard:op-spacing, ¡ + - standard:pa¡rameter-list-wrapping, - standard:paren-spacing, - standard:range-spacing, - standard:string-template, @@ -64,10 +96,4 @@ $ ktlint -R build/libs/ktlint-ruleset-template.jar --log-level=debug --relative ``` !!! tip - Multiple custom rule sets can be loaded at the same time. - -## Abstract Syntax Tree (AST) - -While writing/debugging [Rule](https://github.com/pinterest/ktlint/blob/master/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt)s it's often helpful to inspect the Abstract Syntax Tree (AST) of the code snippet that is to be linted / formatted. The [Jetbrain PsiViewer plugin for IntelliJ IDEA](https://github.com/JetBrains/psiviewer) is a convenient tool to inspect code as shown below: - -![Image](../assets/images/psi-viewer.png) +Multiple custom rule sets can be loaded at the same time. diff --git a/documentation/snapshot/docs/api/custom-rule-set.md b/documentation/snapshot/docs/api/custom-rule-set.md index 83156fa1c5..df02583a1b 100644 --- a/documentation/snapshot/docs/api/custom-rule-set.md +++ b/documentation/snapshot/docs/api/custom-rule-set.md @@ -1,17 +1,49 @@ -!!! Tip - See [Writing your first ktlint rule](https://medium.com/@vanniktech/writing-your-first-ktlint-rule-5a1707f4ca5b) by [Niklas Baudy](https://github.com/vanniktech). +You can provide custom rules via a separate ruleset to Ktlint. A ruleset is a JAR containing one or more [Rule](https://github.com/pinterest/ktlint/blob/master/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt)s. -In a nutshell: a "rule set" is a JAR containing one or more [Rule](https://github.com/pinterest/ktlint/blob/master/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt)s. `ktlint` is relying on the [ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) to discover all available "RuleSet"s on the classpath. As a ruleset author, all you need to do is to include a `META-INF/services/RuleSetProviderV3` file containing a fully qualified name of your [RuleSetProviderV3](https://github.com/pinterest/ktlint/blob/master/ktlint-cli-ruleset-core/src/main/kotlin/com/pinterest/ktlint/cli/ruleset/core/api/RuleSetProviderV3.kt) implementation. + A complete sample project is included in this repo under the [ktlint-ruleset-template](https://github.com/pinterest/ktlint/tree/master/ktlint-ruleset-template) directory. This directory can be cloned, and used as a starting point for a new project containing your custom ruleset. ## ktlint-ruleset-template -A complete sample project (with tests and build files) is included in this repo under the [ktlint-ruleset-template](https://github.com/pinterest/ktlint/tree/master/ktlint-ruleset-template) directory (make sure to check [NoVarRuleTest](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt) as it contains some useful information). +### Gradle build + +The [Gradle build file](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/build.gradle.kts) of the sample project includes the setup for: + +* publishing the custom ruleset artifact to Maven +* the custom Gradle task 'ktlintCheck' that is using the Ktlint CLI to run the rules provided by the ktlint project, as well as the custom rule(s) from this project on the project itself ([dogfood principle](https://en.wikipedia.org/wiki/Eating_your_own_dog_food)). + +### Rule + +The Rule contains the logic for linting and formatting the code. For example, see [NoVarRuleTest](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt). + +A rule has to implement one or more of hooks below: + +* `Rule.beforeFirstNode` +* `RuleAutocorrectApproveHandler.beforeVisitChildNodes` +* `RuleAutocorrectApproveHandler.afterVisitChildNodes` +* `Rule.afterLastNode` + +!!! Tip + See `ktlint-ruleset-standard` for examples of rules that implement the hooks above. + +Upon traversal of the Abstract Syntax Tree (AST), the hooks of the Rule are visited as indicated by their names. The [Jetbrains PsiViewer plugin for IntelliJ IDEA](https://plugins.jetbrains.com/plugin/227-psiviewer) is a convenient tool to inspect the AST for any piece of code. + +![Image](../assets/images/psi-viewer.png) + +### Rule Set Provider + +The RuleSetProvider provides new instances of the rule, see [CustomRuleSetProvider](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/src/main/kotlin/yourpkgname/CustomRuleSetProvider.kt) for an example. + +`ktlint` is relying on the [ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) to discover all available "RuleSet"s on the classpath. For this, the RuleSetProvider needs to be registered in file `resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3`, see [Registration for Java ServiceLoader](https://github.com/pinterest/ktlint/blob/master/ktlint-ruleset-template/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3). + +### Building the project ```shell title="Building the ktlint-ruleset-template" $ cd ktlint-ruleset-template/ $ ../gradlew build ``` +### Running Ktlint CLI with the custom ruleset + ```shell title="Provide code sample that violates rule `custom:no-var" $ echo 'var v = 0' > test.kt ``` @@ -51,8 +83,8 @@ $ ktlint -R build/libs/ktlint-ruleset-template.jar --log-level=debug --relative - standard:no-unit-return, - standard:no-unused-imports, - standard:no-wildcard-imports, - - standard:op-spacing, - - standard:parameter-list-wrapping, + - standard:op-spacing, ¡ + - standard:pa¡rameter-list-wrapping, - standard:paren-spacing, - standard:range-spacing, - standard:string-template, @@ -65,9 +97,3 @@ $ ktlint -R build/libs/ktlint-ruleset-template.jar --log-level=debug --relative !!! tip Multiple custom rule sets can be loaded at the same time. - -## Abstract Syntax Tree (AST) - -While writing/debugging [Rule](https://github.com/pinterest/ktlint/blob/master/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/Rule.kt)s it's often helpful to inspect the Abstract Syntax Tree (AST) of the code snippet that is to be linted / formatted. The [Jetbrain PsiViewer plugin for IntelliJ IDEA](https://github.com/JetBrains/psiviewer) is a convenient tool to inspect code as shown below: - -![Image](../assets/images/psi-viewer.png) diff --git a/ktlint-ruleset-template/build.gradle.kts b/ktlint-ruleset-template/build.gradle.kts index 70e2033796..3a90c6ce42 100644 --- a/ktlint-ruleset-template/build.gradle.kts +++ b/ktlint-ruleset-template/build.gradle.kts @@ -1,58 +1,76 @@ +// This module serves as a sample project for development of a custom ruleset. To avoid any confusion, this build setup is not reusing the +// build logic of other internal ktlint modules (https://github.com/pinterest/ktlint/issues/3048).. + plugins { - id("ktlint-kotlin-common") - `java-library` + kotlin("jvm") version "2.2.10" + // Remove the line below when this custom ruleset is not to be published to maven. If you do want to publish your ruleset to Maven, you + // still might need to configure the Maven Central repository in file `settings.gradle.xml` which is not included in the sample project + // as it conflicts with the build of the Ktlint itself. Suggested content of that file: + // dependencyResolutionManagement { + // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + // repositories { + // mavenCentral() + // } + // } `maven-publish` } -group = "com.github.username" +// Change the group name to your liking +group = "com.github.username.ktlint.ruleset" +version = "1.0-SNAPSHOT" + +// repositories { +// mavenCentral() +// } + +// Remove when the Gradle task 'ktlintCheck' is not to be added to the project +val ktlint: Configuration by configurations.creating + +// Update the version numbers of dependencies below to the most recent stable versions +dependencies { + // Remove when the Gradle task 'ktlintCheck' is not to be added to the project + ktlint("com.pinterest.ktlint:ktlint-cli:1.7.1") + + implementation("com.pinterest.ktlint:ktlint-cli-ruleset-core:1.7.1") + implementation("com.pinterest.ktlint:ktlint-rule-engine-core:1.7.1") + + testImplementation("org.junit.jupiter:junit-jupiter:5.13.4") + // Since Gradle 8 the platform launcher needs explicitly be defined as runtime dependency to avoid classpath problems + // https://docs.gradle.org/8.12/userguide/upgrading_version_8.html#test_framework_implementation_dependencies + testImplementation("org.junit.platform:junit-platform-launcher:1.13.4") + testImplementation("org.slf4j:slf4j-simple:2.0.17") + testImplementation("com.pinterest.ktlint:ktlint-test:1.7.1") +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + jvmToolchain(21) +} +// Remove when this custom ruleset is not to be published to maven val sourcesJar by tasks.registering(Jar::class) { dependsOn(tasks.classes) archiveClassifier = "sources" from(sourceSets.main.map { it.allSource }) } +// Remove when this custom ruleset is not to be published to maven val javadocJar by tasks.registering(Jar::class) { dependsOn(tasks.javadoc) archiveClassifier = "javadoc" from(tasks.javadoc.map { it.destinationDir!! }) } +// Remove when this custom ruleset is not to be published to maven artifacts { archives(sourcesJar) archives(javadocJar) } -val ktlint: Configuration by configurations.creating - -dependencies { - ktlint(projects.ktlintCli) - - implementation(projects.ktlintCliRulesetCore) - implementation(projects.ktlintRuleEngineCore) - - testImplementation(projects.ktlintTest) - testRuntimeOnly(libs.slf4j) - - testImplementation(libs.junit5.jupiter) - // Since Gradle 8 the platform launcher needs explicitly be defined as runtime dependency to avoid classpath problems - // https://docs.gradle.org/8.12/userguide/upgrading_version_8.html#test_framework_implementation_dependencies - testRuntimeOnly(libs.junit5.platform.launcher) -} - -val ktlintCheck by tasks.registering(JavaExec::class) { - dependsOn(tasks.classes) - group = LifecycleBasePlugin.VERIFICATION_GROUP - mainClass = "com.pinterest.ktlint.Main" - // Adding compiled classes of this ruleset to the classpath so that ktlint validates the ruleset using its own ruleset - classpath(ktlint, sourceSets.main.map { it.output }) - args("--log-level=debug", "src/**/*.kt") -} - -tasks.check { - dependsOn(ktlintCheck) -} - +// Remove when this custom ruleset is not to be published to maven publishing { publications { create("mavenJava") { @@ -68,3 +86,18 @@ publishing { } } } + +// Remove when the Gradle task 'ktlintCheck' is not to be added to the project +val ktlintCheck by tasks.registering(JavaExec::class) { + dependsOn(tasks.classes) + group = LifecycleBasePlugin.VERIFICATION_GROUP + mainClass = "com.pinterest.ktlint.Main" + // Adding compiled classes of this ruleset to the classpath so that ktlint validates the ruleset using its own ruleset + classpath(ktlint, sourceSets.main.map { it.output }) + args("--log-level=debug", "src/**/*.kt") +} + +// Remove when the Gradle task 'ktlintCheck' is not to be added to the project +tasks.check { + dependsOn(ktlintCheck) +} diff --git a/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt b/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt index 593cd4dbf6..4058199b9c 100644 --- a/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt +++ b/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt @@ -8,11 +8,6 @@ class NoVarRuleTest { @Test fun `No var rule`() { - // whenever KTLINT_DEBUG env variable is set to "ast" or -DktlintDebug=ast is used - // com.pinterest.ktlint.test.(lint|format) will print AST (along with other debug info) to the stderr. - // this can be extremely helpful while writing and testing rules. - // uncomment the line below to take a quick look at it - // System.setProperty("ktlintDebug", "ast") val code = """ fun fn() {