Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .announce
Original file line number Diff line number Diff line change
Expand Up @@ -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}'"
Expand Down Expand Up @@ -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
Expand Down
52 changes: 39 additions & 13 deletions documentation/release-latest/docs/api/custom-rule-set.md
Original file line number Diff line number Diff line change
@@ -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
```
Expand Down Expand Up @@ -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,
Expand All @@ -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.
50 changes: 38 additions & 12 deletions documentation/snapshot/docs/api/custom-rule-set.md
Original file line number Diff line number Diff line change
@@ -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
```
Expand Down Expand Up @@ -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,
Expand All @@ -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)
99 changes: 66 additions & 33 deletions ktlint-ruleset-template/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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<MavenPublication>("mavenJava") {
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading