Skip to content

Commit

Permalink
Convert ktlint integration to use a compile-only sourceset. (#1012, e…
Browse files Browse the repository at this point in the history
…xample of #524)
  • Loading branch information
nedtwigg committed Dec 3, 2021
2 parents d041ba5 + c5600b5 commit 039d784
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 56 deletions.
3 changes: 2 additions & 1 deletion CHANGES.md
Expand Up @@ -10,9 +10,10 @@ 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]
### Changed
* Converted `ktlint` integration to use a compile-only source set. ([#524](https://github.com/diffplug/spotless/issues/524))

## [2.20.1] - 2021-12-01

### Changed
* Added `named` option to `licenseHeader` to support alternate license header within same format (like java) ([872](https://github.com/diffplug/spotless/issues/872)).
* Added `onlyIfContentMatches` option to `licenseHeader` to skip license header application based on source file content pattern ([#650](https://github.com/diffplug/spotless/issues/650)).
Expand Down
14 changes: 10 additions & 4 deletions lib/build.gradle
Expand Up @@ -7,7 +7,8 @@ apply from: rootProject.file('gradle/java-setup.gradle')
apply from: rootProject.file('gradle/java-publish.gradle')

def NEEDS_GLUE = [
'sortPom'
'sortPom',
'ktlint'
]
for (glue in NEEDS_GLUE) {
sourceSets.register(glue) {
Expand All @@ -20,12 +21,17 @@ for (glue in NEEDS_GLUE) {
dependencies {
// zero runtime reqs is a hard requirements for spotless-lib
// if you need a dep, put it in lib-extra
testImplementation "org.junit.jupiter:junit-jupiter:${VER_JUNIT}"
testImplementation "org.assertj:assertj-core:${VER_ASSERTJ}"
testImplementation "com.diffplug.durian:durian-testlib:${VER_DURIAN}"
testImplementation "org.junit.jupiter:junit-jupiter:$VER_JUNIT"
testImplementation "org.assertj:assertj-core:$VER_ASSERTJ"
testImplementation "com.diffplug.durian:durian-testlib:$VER_DURIAN"

// used for pom sorting
sortPomCompileOnly 'com.github.ekryd.sortpom:sortpom-sorter:3.0.0'

String VER_KTLINT='0.43.1'
ktlintCompileOnly "com.pinterest:ktlint:$VER_KTLINT"
ktlintCompileOnly "com.pinterest.ktlint:ktlint-core:$VER_KTLINT"
ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-standard:$VER_KTLINT"
}

// we'll hold the core lib to a high standard
Expand Down
@@ -0,0 +1,72 @@
/*
* Copyright 2021 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.spotless.glue.ktlint;

import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

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.standard.StandardRuleSetProvider;

import com.diffplug.spotless.FormatterFunc;

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

public class KtlintFormatterFunc implements FormatterFunc.NeedsFile {
private static final Logger logger = Logger.getLogger(KtlintFormatterFunc.class.getName());

private final List<RuleSet> rulesets;
private final Map<String, String> userData;
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());
this.userData = userData;
formatterCallback = new FormatterCallback();
this.isScript = isScript;
}

static class FormatterCallback implements Function2<LintError, Boolean, Unit> {
@Override
public Unit invoke(LintError lint, Boolean corrected) {
if (!corrected) {
throw new AssertionError("Error on line: " + lint.getLine() + ", column: " + lint.getCol() + "\n" + lint.getDetail());
}
return null;
}
}

@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));
}
}
67 changes: 16 additions & 51 deletions lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java
Expand Up @@ -103,8 +103,13 @@ static final class State implements Serializable {
}

FormatterFunc createFormat() throws Exception {
ClassLoader classLoader = jarState.getClassLoader();
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);
}

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

// first, we get the standard rules
Expand Down Expand Up @@ -134,57 +139,17 @@ FormatterFunc createFormat() throws Exception {
// grab the KtLint singleton
Class<?> ktlintClass = classLoader.loadClass(pkg + ".ktlint.core.KtLint");
Object ktlint = ktlintClass.getDeclaredField("INSTANCE").get(null);
FormatterFunc formatterFunc;
if (useParams) {
//
// In KtLint 0.34+ there is a new "format(params: Params)" function. We create an
// instance of the Params class with our configuration and invoke it here.
//

// grab the Params class
Class<?> paramsClass = classLoader.loadClass(pkg + ".ktlint.core.KtLint$Params");
// and its constructor
Constructor<?> constructor = paramsClass.getConstructor(
/* fileName, nullable */ String.class,
/* text */ String.class,
/* ruleSets */ Iterable.class,
/* userData */ Map.class,
/* callback */ function2Interface,
/* script */ boolean.class,
/* editorConfigPath, nullable */ String.class,
/* debug */ boolean.class);
Method formatterMethod = ktlintClass.getMethod("format", paramsClass);
FormatterFunc.NeedsFile needsFile = (input, file) -> {
try {
Object params = constructor.newInstance(
/* fileName, nullable */ file.getName(),
/* text */ input,
/* ruleSets */ ruleSets,
/* userData */ userData,
/* callback */ formatterCallback,
/* script */ isScript,
/* editorConfigPath, nullable */ null,
/* debug */ false);
return (String) formatterMethod.invoke(ktlint, params);
} catch (InvocationTargetException e) {
throw ThrowingEx.unwrapCause(e);
}
};
formatterFunc = FormatterFunc.needsFile(needsFile);
} else {
// and its format method
String formatterMethodName = isScript ? "formatScript" : "format";
Method formatterMethod = ktlintClass.getMethod(formatterMethodName, String.class, Iterable.class, Map.class, function2Interface);
formatterFunc = input -> {
try {
return (String) formatterMethod.invoke(ktlint, input, ruleSets, userData, formatterCallback);
} catch (InvocationTargetException e) {
throw ThrowingEx.unwrapCause(e);
}
};
}

return formatterFunc;
// and its format method
String formatterMethodName = isScript ? "formatScript" : "format";
Method formatterMethod = ktlintClass.getMethod(formatterMethodName, String.class, Iterable.class, Map.class, function2Interface);
return input -> {
try {
return (String) formatterMethod.invoke(ktlint, input, ruleSets, userData, formatterCallback);
} catch (InvocationTargetException e) {
throw ThrowingEx.unwrapCause(e);
}
};
}
}
}

0 comments on commit 039d784

Please sign in to comment.