Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for KtLint editorConfigOverride #1218

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .editorconfig
Expand Up @@ -16,6 +16,8 @@ indent_size = 2
# Doc: https://youtrack.jetbrains.com/issue/IDEA-170643#focus=streamItem-27-3708697.0-0
ij_java_imports_layout = java.**,|,javax.**,|,org.**,|,com.**,|,com.diffplug.**,|,*
ij_java_use_single_class_imports = true
ij_java_class_count_to_use_import_on_demand = 999
ij_java_names_count_to_use_import_on_demand = 999
nedtwigg marked this conversation as resolved.
Show resolved Hide resolved

[*.xml.mustache]
indent_style = space
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
* Support for `editorConfigOverride` in `ktlint`. ([#1218](https://github.com/diffplug/spotless/pull/1218) fixes [#1193](https://github.com/diffplug/spotless/issues/1193))

## [2.25.3] - 2022-05-10
### Fixed
Expand Down
Expand Up @@ -17,18 +17,26 @@

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.pinterest.ktlint.core.KtLint;
import com.pinterest.ktlint.core.KtLint.Params;
import com.pinterest.ktlint.core.KtLint.ExperimentalParams;
import com.pinterest.ktlint.core.LintError;
import com.pinterest.ktlint.core.RuleSet;
import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties;
import com.pinterest.ktlint.core.api.EditorConfigOverride;
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties;
import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider;
import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider;

import com.diffplug.spotless.FormatterFunc;

import kotlin.Pair;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;

Expand All @@ -38,8 +46,13 @@ public class KtlintFormatterFunc implements FormatterFunc.NeedsFile {
private final Map<String, String> userData;
private final Function2<? super LintError, ? super Boolean, Unit> formatterCallback;
private final boolean isScript;
private final EditorConfigOverride editorConfigOverride;

public KtlintFormatterFunc(boolean isScript, boolean useExperimental, Map<String, String> userData) {
/**
* Non-empty editorConfigOverrideMap requires KtLint 0.45.2.
*/
public KtlintFormatterFunc(boolean isScript, boolean useExperimental, Map<String, String> userData,
Map<String, Object> editorConfigOverrideMap) {
rulesets = new ArrayList<>();
rulesets.add(new StandardRuleSetProvider().get());

Expand All @@ -49,6 +62,46 @@ public KtlintFormatterFunc(boolean isScript, boolean useExperimental, Map<String
this.userData = userData;
formatterCallback = new FormatterCallback();
this.isScript = isScript;

if (editorConfigOverrideMap.isEmpty()) {
this.editorConfigOverride = null;
} else {
this.editorConfigOverride = createEditorConfigOverride(editorConfigOverrideMap);
}
}

/**
* Create EditorConfigOverride from user provided parameters.
* Calling this method requires KtLint 0.45.2.
*/
private EditorConfigOverride createEditorConfigOverride(Map<String, Object> editorConfigOverrideMap) {
// Get properties from rules in the rule sets
Stream<UsesEditorConfigProperties.EditorConfigProperty<?>> ruleProperties = rulesets.stream()
.flatMap(ruleSet -> Arrays.stream(ruleSet.getRules()))
.filter(rule -> rule instanceof UsesEditorConfigProperties)
.flatMap(rule -> ((UsesEditorConfigProperties) rule).getEditorConfigProperties().stream());

// Create a mapping of properties to their names based on rule properties and default properties
Map<String, UsesEditorConfigProperties.EditorConfigProperty<?>> supportedProperties = Stream
.concat(ruleProperties, DefaultEditorConfigProperties.INSTANCE.getDefaultEditorConfigProperties().stream())
.distinct()
.collect(Collectors.toMap(property -> property.getType().getName(), property -> property));

// Create config properties based on provided property names and values
@SuppressWarnings("unchecked")
Pair<UsesEditorConfigProperties.EditorConfigProperty<?>, ?>[] properties = editorConfigOverrideMap.entrySet().stream()
.map(entry -> {
UsesEditorConfigProperties.EditorConfigProperty<?> property = supportedProperties.get(entry.getKey());
if (property != null) {
return new Pair<>(property, entry.getValue());
} else {
return null;
}
})
.filter(Objects::nonNull)
.toArray(Pair[]::new);

return EditorConfigOverride.Companion.from(properties);
}

static class FormatterCallback implements Function2<LintError, Boolean, Unit> {
Expand All @@ -63,14 +116,31 @@ public Unit invoke(LintError lint, Boolean corrected) {

@Override
public String applyWithFile(String unix, File file) throws Exception {
return KtLint.INSTANCE.format(new Params(
file.getName(),
unix,
rulesets,
userData,
formatterCallback,
isScript,
null,
false));

if (editorConfigOverride != null) {
// Use ExperimentalParams with EditorConfigOverride which requires KtLint 0.45.2
return KtLint.INSTANCE.format(new ExperimentalParams(
file.getName(),
unix,
rulesets,
userData,
formatterCallback,
isScript,
null,
false,
editorConfigOverride,
false));
} else {
// Use Params for backward compatibility
return KtLint.INSTANCE.format(new KtLint.Params(
file.getName(),
unix,
rulesets,
userData,
formatterCallback,
isScript,
null,
false));
}
}
}
13 changes: 9 additions & 4 deletions lib/src/main/java/com/diffplug/spotless/kotlin/BadSemver.java
@@ -1,5 +1,5 @@
/*
* Copyright 2021 DiffPlug
* Copyright 2021-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 @@ -26,13 +26,18 @@ protected static int version(String input) {
}
String major = matcher.group(1);
String minor = matcher.group(2);
return version(Integer.parseInt(major), Integer.parseInt(minor));
String patch = matcher.group(3);
return version(Integer.parseInt(major), Integer.parseInt(minor), patch != null ? Integer.parseInt(patch) : 0);
}

/** Ambiguous after 2147.483647.blah-blah */
protected static int version(int major, int minor, int patch) {
return major * 1_000_000 + minor * 1_000 + patch;
}

protected static int version(int major, int minor) {
return major * 1_000_000 + minor;
return version(major, minor, 0);
}

private static final Pattern BAD_SEMVER = Pattern.compile("(\\d+)\\.(\\d+)");
private static final Pattern BAD_SEMVER = Pattern.compile("(\\d+)\\.(\\d+)\\.*(\\d+)*");
}
34 changes: 23 additions & 11 deletions lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java
Expand Up @@ -50,26 +50,29 @@ public static FormatterStep create(Provisioner provisioner) {
}

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

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

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

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

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

Expand All @@ -87,11 +90,20 @@ static final class State implements Serializable {
final JarState jarState;
private final boolean useExperimental;
private final TreeMap<String, String> userData;
private final TreeMap<String, Object> editorConfigOverride;
private final boolean useParams;

State(String version, Provisioner provisioner, boolean isScript, boolean useExperimental, Map<String, String> userData) throws IOException {
State(String version, Provisioner provisioner, boolean isScript, boolean useExperimental,
Map<String, String> userData, Map<String, Object> editorConfigOverride) throws IOException {

if (!editorConfigOverride.isEmpty() &&
BadSemver.version(version) < BadSemver.version(0, 45, 2)) {
throw new IllegalStateException("KtLint editorConfigOverride supported for version 0.45.2 and later");
}

this.useExperimental = useExperimental;
this.userData = new TreeMap<>(userData);
this.editorConfigOverride = new TreeMap<>(editorConfigOverride);
String coordinate;
if (BadSemver.version(version) < BadSemver.version(0, 32)) {
coordinate = MAVEN_COORDINATE_PRE_0_32;
Expand All @@ -108,8 +120,8 @@ 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, boolean.class, Map.class);
return (FormatterFunc.NeedsFile) constructor.newInstance(isScript, useExperimental, userData);
Constructor<?> constructor = formatterFunc.getConstructor(boolean.class, boolean.class, Map.class, Map.class);
return (FormatterFunc.NeedsFile) constructor.newInstance(isScript, useExperimental, userData, editorConfigOverride);
}

ClassLoader classLoader = jarState.getClassLoader();
Expand Down
3 changes: 3 additions & 0 deletions plugin-gradle/CHANGES.md
Expand Up @@ -3,6 +3,9 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).

## [Unreleased]
### Added
* Support for `editorConfigOverride` in `ktlint`. ([#1218](https://github.com/diffplug/spotless/pull/1218) fixes [#1193](https://github.com/diffplug/spotless/issues/1193))
* If you are using properties like `indent_size`, you should pass now pass them as `editorConfigOverride` and not as `userData`.

## [6.6.1] - 2022-05-13
### Fixed
Expand Down
11 changes: 7 additions & 4 deletions plugin-gradle/README.md
Expand Up @@ -335,15 +335,18 @@ spotless {

### ktlint

[homepage](https://github.com/pinterest/ktlint). [changelog](https://github.com/pinterest/ktlint/releases). Spotless does not ([yet](https://github.com/diffplug/spotless/issues/142)) respect the `.editorconfig` settings ([ktlint docs](https://github.com/pinterest/ktlint#editorconfig)), but you can provide them manually as `userData`.
[homepage](https://github.com/pinterest/ktlint). [changelog](https://github.com/pinterest/ktlint/releases). Spotless does not ([yet](https://github.com/diffplug/spotless/issues/142)) respect the `.editorconfig` settings ([ktlint docs](https://github.com/pinterest/ktlint#editorconfig)), but you can provide them manually as `editorConfigOverride`.

```kotlin
spotless {
kotlin {
// version, setUseExperimental and userData are all optional
ktlint('0.43.2')
// version, setUseExperimental, userData and editorConfigOverride are all optional
ktlint("0.45.2")
.setUseExperimental(true)
.userData(mapOf('indent_size' to '2', 'continuation_indent_size' to '2'))
.userData(mapOf("android" to "true"))
.editorConfigOverride(mapOf("indent_size" to 2))
}
}
```

### diktat
Expand Down
Expand Up @@ -30,6 +30,7 @@
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet;

import com.diffplug.common.collect.ImmutableSortedMap;
import com.diffplug.spotless.FileSignature;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.kotlin.DiktatStep;
Expand Down Expand Up @@ -59,7 +60,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, false, Collections.emptyMap());
return new KotlinFormatExtension(version, false, Collections.emptyMap(), Collections.emptyMap());
}

public KotlinFormatExtension ktlint() {
Expand All @@ -71,11 +72,14 @@ public class KotlinFormatExtension {
private final String version;
private boolean useExperimental;
private Map<String, String> userData;
private Map<String, Object> editorConfigOverride;

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

Expand All @@ -88,13 +92,21 @@ public KotlinFormatExtension setUseExperimental(boolean useExperimental) {
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;
this.userData = ImmutableSortedMap.copyOf(userData);
replaceStep(createStep());
return this;
}

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

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

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, false, Collections.emptyMap());
return new KotlinFormatExtension(version, false, Collections.emptyMap(), Collections.emptyMap());
}

public KotlinFormatExtension ktlint() {
Expand All @@ -57,11 +57,14 @@ public class KotlinFormatExtension {
private final String version;
private boolean useExperimental;
private Map<String, String> userData;
private Map<String, Object> editorConfigOverride;

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

Expand All @@ -79,8 +82,16 @@ public KotlinFormatExtension userData(Map<String, String> userData) {
return this;
}

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

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

Expand Down