Skip to content

Commit

Permalink
Add option to use ktlint experimental ruleset (#1168 fixes #409)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg committed Apr 22, 2022
2 parents 53673d3 + 672bf30 commit 4b7067b
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 31 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -10,6 +10,8 @@ This document is intended for Spotless developers.
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).

## [Unreleased]
### Added
* Added support for enabling ktlint experimental ruleset. ([#1145](https://github.com/diffplug/spotless/pull/1168))

## [2.24.2] - 2022-04-06

Expand Down
4 changes: 0 additions & 4 deletions build.gradle
Expand Up @@ -19,7 +19,3 @@ spotless {
endWithNewline()
}
}

static Class<?> spotBugsTaskType() {
return com.github.spotbugs.snom.SpotBugsTask
}
1 change: 1 addition & 0 deletions lib/build.gradle
Expand Up @@ -47,6 +47,7 @@ dependencies {
String VER_KTLINT='0.43.2'
ktlintCompileOnly "com.pinterest:ktlint:$VER_KTLINT"
ktlintCompileOnly "com.pinterest.ktlint:ktlint-core:$VER_KTLINT"
ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-experimental:$VER_KTLINT"
ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-standard:$VER_KTLINT"

// used for markdown formatting
Expand Down
Expand Up @@ -16,14 +16,15 @@
package com.diffplug.spotless.glue.ktlint;

import java.io.File;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.pinterest.ktlint.core.KtLint;
import com.pinterest.ktlint.core.KtLint.Params;
import com.pinterest.ktlint.core.LintError;
import com.pinterest.ktlint.core.RuleSet;
import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider;
import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider;

import com.diffplug.spotless.FormatterFunc;
Expand All @@ -38,8 +39,13 @@ public class KtlintFormatterFunc implements FormatterFunc.NeedsFile {
private final Function2<? super LintError, ? super Boolean, Unit> formatterCallback;
private final boolean isScript;

public KtlintFormatterFunc(boolean isScript, Map<String, String> userData) {
rulesets = Collections.singletonList(new StandardRuleSetProvider().get());
public KtlintFormatterFunc(boolean isScript, boolean useExperimental, Map<String, String> userData) {
rulesets = new ArrayList<>();
rulesets.add(new StandardRuleSetProvider().get());

if (useExperimental) {
rulesets.add(new ExperimentalRuleSetProvider().get());
}
this.userData = userData;
formatterCallback = new FormatterCallback();
this.isScript = isScript;
Expand Down
38 changes: 25 additions & 13 deletions lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 DiffPlug
* Copyright 2016-2022 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,6 +21,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -49,26 +50,26 @@ public static FormatterStep create(Provisioner provisioner) {
}

public static FormatterStep create(String version, Provisioner provisioner) {
return create(version, provisioner, Collections.emptyMap());
return create(version, provisioner, false, Collections.emptyMap());
}

public static FormatterStep create(String version, Provisioner provisioner, Map<String, String> userData) {
return create(version, provisioner, false, userData);
public static FormatterStep create(String version, Provisioner provisioner, boolean useExperimental, Map<String, String> userData) {
return create(version, provisioner, false, useExperimental, userData);
}

public static FormatterStep createForScript(String version, Provisioner provisioner) {
return create(version, provisioner, true, Collections.emptyMap());
return create(version, provisioner, true, false, Collections.emptyMap());
}

public static FormatterStep createForScript(String version, Provisioner provisioner, Map<String, String> userData) {
return create(version, provisioner, true, userData);
public static FormatterStep createForScript(String version, Provisioner provisioner, boolean useExperimental, Map<String, String> userData) {
return create(version, provisioner, true, useExperimental, userData);
}

private static FormatterStep create(String version, Provisioner provisioner, boolean isScript, Map<String, String> userData) {
private static FormatterStep create(String version, Provisioner provisioner, boolean isScript, boolean useExperimental, Map<String, String> userData) {
Objects.requireNonNull(version, "version");
Objects.requireNonNull(provisioner, "provisioner");
return FormatterStep.createLazy(NAME,
() -> new State(version, provisioner, isScript, userData),
() -> new State(version, provisioner, isScript, useExperimental, userData),
State::createFormat);
}

Expand All @@ -84,10 +85,12 @@ static final class State implements Serializable {
private final String pkg;
/** The jar that contains the formatter. */
final JarState jarState;
private final boolean useExperimental;
private final TreeMap<String, String> userData;
private final boolean useParams;

State(String version, Provisioner provisioner, boolean isScript, Map<String, String> userData) throws IOException {
State(String version, Provisioner provisioner, boolean isScript, boolean useExperimental, Map<String, String> userData) throws IOException {
this.useExperimental = useExperimental;
this.userData = new TreeMap<>(userData);
String coordinate;
if (BadSemver.version(version) < BadSemver.version(0, 32)) {
Expand All @@ -105,17 +108,26 @@ static final class State implements Serializable {
FormatterFunc createFormat() throws Exception {
if (useParams) {
Class<?> formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.ktlint.KtlintFormatterFunc");
Constructor<?> constructor = formatterFunc.getConstructor(boolean.class, Map.class);
return (FormatterFunc.NeedsFile) constructor.newInstance(isScript, userData);
Constructor<?> constructor = formatterFunc.getConstructor(boolean.class, boolean.class, Map.class);
return (FormatterFunc.NeedsFile) constructor.newInstance(isScript, useExperimental, userData);
}

ClassLoader classLoader = jarState.getClassLoader();
// String KtLint::format(String input, Iterable<RuleSet> rules, Function2 errorCallback)

ArrayList<Object> ruleSets = new ArrayList<>();

// first, we get the standard rules
Class<?> standardRuleSetProviderClass = classLoader.loadClass(pkg + ".ktlint.ruleset.standard.StandardRuleSetProvider");
Object standardRuleSet = standardRuleSetProviderClass.getMethod("get").invoke(standardRuleSetProviderClass.newInstance());
Iterable<?> ruleSets = Collections.singletonList(standardRuleSet);
ruleSets.add(standardRuleSet);

// second, we get the experimental rules if desired
if (useExperimental) {
Class<?> experimentalRuleSetProviderClass = classLoader.loadClass(pkg + ".ktlint.ruleset.experimental.ExperimentalRuleSetProvider");
Object experimentalRuleSet = experimentalRuleSetProviderClass.getMethod("get").invoke(experimentalRuleSetProviderClass.newInstance());
ruleSets.add(experimentalRuleSet);
}

// next, we create an error callback which throws an assertion error when the format is bad
Class<?> function2Interface = classLoader.loadClass("kotlin.jvm.functions.Function2");
Expand Down
2 changes: 1 addition & 1 deletion plugin-gradle/CHANGES.md
Expand Up @@ -5,9 +5,9 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
## [Unreleased]
### Added
* Added a runToFixMessage property to customize the run-to-fix message in spotlessCheck task ([#1175](https://github.com/diffplug/spotless/issues/1175)).
* Added support for enabling ktlint experimental ruleset. ([#1145](https://github.com/diffplug/spotless/pull/1168))

## [6.4.2] - 2022-04-06

### Fixed
* Git user config and system config also included for defaultEndings configuration. ([#540](https://github.com/diffplug/spotless/issues/540))

Expand Down
6 changes: 4 additions & 2 deletions plugin-gradle/README.md
Expand Up @@ -340,8 +340,10 @@ spotless {
```kotlin
spotless {
kotlin {
// version and userData are both optional
ktlint('0.43.2').userData(mapOf('indent_size' to '2', 'continuation_indent_size' to '2'))
// version, setUseExperimental and userData are all optional
ktlint('0.43.2')
.setUseExperimental(true)
.userData(mapOf('indent_size' to '2', 'continuation_indent_size' to '2'))
```

### diktat
Expand Down
Expand Up @@ -59,7 +59,7 @@ public LicenseHeaderConfig licenseHeaderFile(Object licenseHeaderFile) {
/** Adds the specified version of <a href="https://github.com/pinterest/ktlint">ktlint</a>. */
public KotlinFormatExtension ktlint(String version) {
Objects.requireNonNull(version);
return new KotlinFormatExtension(version, Collections.emptyMap());
return new KotlinFormatExtension(version, false, Collections.emptyMap());
}

public KotlinFormatExtension ktlint() {
Expand All @@ -69,23 +69,32 @@ public KotlinFormatExtension ktlint() {
public class KotlinFormatExtension {

private final String version;
private boolean useExperimental;
private Map<String, String> userData;

KotlinFormatExtension(String version, Map<String, String> config) {
KotlinFormatExtension(String version, boolean useExperimental, Map<String, String> config) {
this.version = version;
this.useExperimental = useExperimental;
this.userData = config;
addStep(createStep());
}

public void userData(Map<String, String> userData) {
public KotlinFormatExtension setUseExperimental(boolean useExperimental) {
this.useExperimental = useExperimental;
replaceStep(createStep());
return this;
}

public KotlinFormatExtension userData(Map<String, String> userData) {
// Copy the map to a sorted map because up-to-date checking is based on binary-equals of the serialized
// representation.
this.userData = userData;
replaceStep(createStep());
return this;
}

private FormatterStep createStep() {
return KtLintStep.create(version, provisioner(), userData);
return KtLintStep.create(version, provisioner(), useExperimental, userData);
}
}

Expand Down
Expand Up @@ -45,7 +45,7 @@ public KotlinGradleExtension(SpotlessExtension spotless) {
/** Adds the specified version of <a href="https://github.com/pinterest/ktlint">ktlint</a>. */
public KotlinFormatExtension ktlint(String version) {
Objects.requireNonNull(version, "version");
return new KotlinFormatExtension(version, Collections.emptyMap());
return new KotlinFormatExtension(version, false, Collections.emptyMap());
}

public KotlinFormatExtension ktlint() {
Expand All @@ -55,23 +55,32 @@ public KotlinFormatExtension ktlint() {
public class KotlinFormatExtension {

private final String version;
private boolean useExperimental;
private Map<String, String> userData;

KotlinFormatExtension(String version, Map<String, String> config) {
KotlinFormatExtension(String version, boolean useExperimental, Map<String, String> config) {
this.version = version;
this.useExperimental = useExperimental;
this.userData = config;
addStep(createStep());
}

public void userData(Map<String, String> userData) {
public KotlinFormatExtension setUseExperimental(boolean useExperimental) {
this.useExperimental = useExperimental;
replaceStep(createStep());
return this;
}

public KotlinFormatExtension userData(Map<String, String> userData) {
// Copy the map to a sorted map because up-to-date checking is based on binary-equals of the serialized
// representation.
this.userData = ImmutableSortedMap.copyOf(userData);
replaceStep(createStep());
return this;
}

private FormatterStep createStep() {
return KtLintStep.createForScript(version, provisioner(), userData);
return KtLintStep.createForScript(version, provisioner(), useExperimental, userData);
}
}

Expand Down
Expand Up @@ -139,6 +139,67 @@ void testWithIndentation() throws IOException {
assertThat(result.getOutput()).contains("Unexpected indentation (4) (it should be 6)");
}

@Test
void withExperimental() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'org.jetbrains.kotlin.jvm' version '1.5.31'",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotless {",
" kotlin {",
" ktlint().setUseExperimental(true)",
" }",
"}");
setFile("src/main/kotlin/experimental.kt").toResource("kotlin/ktlint/experimental.dirty");
gradleRunner().withArguments("spotlessApply").build();
assertFile("src/main/kotlin/experimental.kt").sameAsResource("kotlin/ktlint/experimental.clean");
}

@Test
void withExperimental_0_32() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'org.jetbrains.kotlin.jvm' version '1.5.31'",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotless {",
" kotlin {",
" ktlint('0.32.0').setUseExperimental(true)",
" }",
"}");
setFile("src/main/kotlin/basic.kt").toResource("kotlin/ktlint/basic.dirty");
gradleRunner().withArguments("spotlessApply").build();
assertFile("src/main/kotlin/basic.kt").sameAsResource("kotlin/ktlint/basic.clean");
}

/**
* Check that the sample used to verify the experimental ruleset is untouched by the default ruleset, to verify
* that enabling the experimental ruleset is actually doing something.
*
* If this test fails, it's likely that the experimental rule being used as a test graduated into the standard
* ruleset, and therefore a new experimental rule should be used to verify functionality.
*/
@Test
void experimentalSampleUnchangedWithDefaultRuleset() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'org.jetbrains.kotlin.jvm' version '1.5.31'",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotless {",
" kotlin {",
" ktlint()",
" }",
"}");
setFile("src/main/kotlin/experimental.kt").toResource("kotlin/ktlint/experimental.dirty");
gradleRunner().withArguments("spotlessApply").build();
assertFile("src/main/kotlin/experimental.kt").sameAsResource("kotlin/ktlint/experimental.dirty");
}

@Test
void testWithHeader() throws IOException {
setFile("build.gradle").toLines(
Expand Down

0 comments on commit 4b7067b

Please sign in to comment.