Skip to content

Commit 1fa002e

Browse files
authored
Merge pull request #569 from RanadeepPolavarapu/feat/ktfmt
feat(ktfmt): add `ktfmt` a new Kotlin formatter from Facebook Incubator
2 parents 367fb81 + 37438a9 commit 1fa002e

File tree

16 files changed

+432
-2
lines changed

16 files changed

+432
-2
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1313
### Added
1414
* Support for google-java-format 1.8 (including test infrastructure for Java 11). ([#562](https://github.com/diffplug/spotless/issues/562))
1515
* Improved PaddedCell such that it is as performant as non-padded cell - no reason not to have it always enabled. Deprecated all of `PaddedCellBulk`. ([#561](https://github.com/diffplug/spotless/pull/561))
16+
* Support for ktfmt 0.13 ([#569](https://github.com/diffplug/spotless/pull/569))
1617
### Changed
1718
* Updated a bunch of dependencies, most notably: ([#564](https://github.com/diffplug/spotless/pull/564))
1819
* jgit `5.5.0.201909110433-r` -> `5.7.0.202003110725-r`

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ lib('java.ImportOrderStep') +'{{yes}} | {{yes}}
4646
lib('java.RemoveUnusedImportsStep') +'{{yes}} | {{yes}} | {{no}} |',
4747
extra('java.EclipseFormatterStep') +'{{yes}} | {{yes}} | {{no}} |',
4848
lib('kotlin.KtLintStep') +'{{yes}} | {{yes}} | {{no}} |',
49+
lib('kotlin.KtfmtStep') +'{{yes}} | {{yes}} | {{no}} |',
4950
lib('markdown.FreshMarkStep') +'{{yes}} | {{no}} | {{no}} |',
5051
lib('npm.PrettierFormatterStep') +'{{yes}} | {{yes}} | {{no}} |',
5152
lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}} | {{no}} |',
@@ -73,6 +74,7 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}}
7374
| [`java.RemoveUnusedImportsStep`](lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java) | :+1: | :+1: | :white_large_square: |
7475
| [`java.EclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseFormatterStep.java) | :+1: | :+1: | :white_large_square: |
7576
| [`kotlin.KtLintStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java) | :+1: | :+1: | :white_large_square: |
77+
| [`kotlin.KtfmtStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java) | :+1: | :+1: | :white_large_square: |
7678
| [`markdown.FreshMarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java) | :+1: | :white_large_square: | :white_large_square: |
7779
| [`npm.PrettierFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java) | :+1: | :+1: | :white_large_square: |
7880
| [`npm.TsFmtFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java) | :+1: | :+1: | :white_large_square: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2016 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.kotlin;
17+
18+
import java.io.IOException;
19+
import java.io.Serializable;
20+
import java.lang.reflect.InvocationTargetException;
21+
import java.lang.reflect.Method;
22+
import java.util.Objects;
23+
24+
import com.diffplug.spotless.*;
25+
26+
/** Wraps up [ktfmt](https://github.com/facebookincubator/ktfmt) as a FormatterStep. */
27+
public class KtfmtStep {
28+
// prevent direct instantiation
29+
private KtfmtStep() {}
30+
31+
private static final String DEFAULT_VERSION = "0.13";
32+
static final String NAME = "ktfmt";
33+
static final String PACKAGE = "com.facebook";
34+
static final String MAVEN_COORDINATE = PACKAGE + ":ktfmt:";
35+
36+
/**
37+
* The <code>format</code> method is available in the link below.
38+
*
39+
* @see:
40+
* https://github.com/facebookincubator/ktfmt/blob/master/core/src/main/java/com/facebook/ktfmt/Formatter.kt#L61-L65
41+
*/
42+
static final String FORMATTER_METHOD = "format";
43+
44+
/** Creates a step which formats everything - code, import order, and unused imports. */
45+
public static FormatterStep create(Provisioner provisioner) {
46+
return create(defaultVersion(), provisioner);
47+
}
48+
49+
/** Creates a step which formats everything - code, import order, and unused imports. */
50+
public static FormatterStep create(String version, Provisioner provisioner) {
51+
Objects.requireNonNull(version, "version");
52+
Objects.requireNonNull(provisioner, "provisioner");
53+
return FormatterStep.createLazy(
54+
NAME, () -> new State(version, provisioner), State::createFormat);
55+
}
56+
57+
public static String defaultVersion() {
58+
return DEFAULT_VERSION;
59+
}
60+
61+
static final class State implements Serializable {
62+
private static final long serialVersionUID = 1L;
63+
64+
private final String pkg;
65+
/** The jar that contains the eclipse formatter. */
66+
final JarState jarState;
67+
68+
State(String version, Provisioner provisioner) throws IOException {
69+
this.pkg = PACKAGE;
70+
this.jarState = JarState.from(MAVEN_COORDINATE + version, provisioner);
71+
}
72+
73+
FormatterFunc createFormat() throws Exception {
74+
ClassLoader classLoader = jarState.getClassLoader();
75+
76+
Class<?> formatterClazz = classLoader.loadClass(pkg + ".ktfmt.FormatterKt");
77+
Method formatterMethod = formatterClazz.getMethod(FORMATTER_METHOD, String.class);
78+
79+
return input -> {
80+
try {
81+
return (String) formatterMethod.invoke(formatterClazz, input);
82+
} catch (InvocationTargetException e) {
83+
throw ThrowingEx.unwrapCause(e);
84+
}
85+
};
86+
}
87+
}
88+
}

plugin-gradle/CHANGES.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
55
## [Unreleased]
66
### Added
77
* [**VS Code Extension**](https://marketplace.visualstudio.com/items?itemName=richardwillis.vscode-spotless-gradle) thanks to [@badsyntax](https://github.com/badsyntax)
8-
* Support for google-java-format 1.8 (requires you to run build on Java 11) ([#562](https://github.com/diffplug/spotless/issues/562))
8+
* Support for google-java-format 1.8 (requires build to run on Java 11+) ([#562](https://github.com/diffplug/spotless/issues/562))
9+
* Support for ktfmt 0.13 (requires build to run on Java 11+) ([#569](https://github.com/diffplug/spotless/pull/569))
910
### Changed
1011
* PaddedCell is now always enabled. It is strictly better than non-padded cell, and there is no performance penalty. [See here](https://github.com/diffplug/spotless/pull/560#issuecomment-621752798) for detailed explanation. ([#561](https://github.com/diffplug/spotless/pull/561))
1112
* Updated a bunch of dependencies, most notably jgit `5.5.0.201909110433-r` -> `5.7.0.202003110725-r`. ([#564](https://github.com/diffplug/spotless/pull/564))

plugin-gradle/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Spotless can check and apply formatting to any plain-text file, using simple rul
8484
* [Groovy Eclipse](#groovy-eclipse)'s groovy code formatter
8585
* [FreshMark](https://github.com/diffplug/freshmark) (markdown with variables)
8686
* [ktlint](https://github.com/pinterest/ktlint)
87+
* [ktfmt](https://github.com/facebookincubator/ktfmt)
8788
* [scalafmt](https://github.com/olafurpg/scalafmt)
8889
* [DBeaver sql format](https://dbeaver.jkiss.org/)
8990
* [Prettier: An opinionated code formatter](https://prettier.io)
@@ -278,6 +279,23 @@ spotless {
278279
}
279280
```
280281

282+
<a name="ktfmt"></a>
283+
284+
## Applying [ktfmt](https://github.com/facebookincubator/ktfmt) to Kotlin files
285+
286+
```gradle
287+
spotless {
288+
kotlin {
289+
// optionally takes a version
290+
ktfmt()
291+
292+
// also supports license headers
293+
licenseHeader '/* Licensed under Apache-2.0 */' // License header
294+
licenseHeaderFile 'path-to-license-file' // License header file
295+
}
296+
}
297+
```
298+
281299
<a name="sql-dbeaver"></a>
282300

283301
## Applying [DBeaver](https://dbeaver.jkiss.org/) to SQL scripts

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java

+28
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import com.diffplug.spotless.FormatterStep;
3030
import com.diffplug.spotless.kotlin.KtLintStep;
31+
import com.diffplug.spotless.kotlin.KtfmtStep;
3132

3233
public class KotlinExtension extends FormatExtension implements HasBuiltinDelimiterForLicense {
3334
static final String NAME = "kotlin";
@@ -79,6 +80,33 @@ private FormatterStep createStep() {
7980
}
8081
}
8182

83+
/** Uses the [ktfmt](https://github.com/facebookincubator/ktfmt) jar to format source code. */
84+
public KtfmtConfig ktfmt() {
85+
return ktfmt(KtfmtStep.defaultVersion());
86+
}
87+
88+
/**
89+
* Uses the given version of [ktfmt](https://github.com/facebookincubator/ktfmt) to format source
90+
* code.
91+
*/
92+
public KtfmtConfig ktfmt(String version) {
93+
Objects.requireNonNull(version);
94+
return new KtfmtConfig(version);
95+
}
96+
97+
public class KtfmtConfig {
98+
final String version;
99+
100+
KtfmtConfig(String version) {
101+
this.version = Objects.requireNonNull(version);
102+
addStep(createStep());
103+
}
104+
105+
private FormatterStep createStep() {
106+
return KtfmtStep.create(version, GradleProvisioner.fromProject(getProject()));
107+
}
108+
}
109+
82110
/** If the user hasn't specified the files yet, we'll assume he/she means all of the kotlin files. */
83111
@Override
84112
protected void setupTask(SpotlessTask task) {

plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java

+100
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.gradle.testkit.runner.BuildResult;
2424
import org.junit.Test;
2525

26+
import com.diffplug.spotless.JreVersion;
27+
2628
public class KotlinExtensionTest extends GradleIntegrationTest {
2729
private static final String HEADER = "// License Header";
2830
private static final String HEADER_WITH_YEAR = "// License Header $YEAR";
@@ -45,6 +47,28 @@ public void integration() throws IOException {
4547
assertFile("src/main/kotlin/basic.kt").sameAsResource("kotlin/ktlint/basic.clean");
4648
}
4749

50+
@Test
51+
public void integrationKtfmt() throws IOException {
52+
if (JreVersion.thisVm() == JreVersion._8) {
53+
// ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+.
54+
return;
55+
}
56+
setFile("build.gradle").toLines(
57+
"plugins {",
58+
" id 'nebula.kotlin' version '1.0.6'",
59+
" id 'com.diffplug.gradle.spotless'",
60+
"}",
61+
"repositories { mavenCentral() }",
62+
"spotless {",
63+
" kotlin {",
64+
" ktfmt()",
65+
" }",
66+
"}");
67+
setFile("src/main/kotlin/basic.kt").toResource("kotlin/ktfmt/basic.dirty");
68+
gradleRunner().withArguments("spotlessApply").build();
69+
assertFile("src/main/kotlin/basic.kt").sameAsResource("kotlin/ktfmt/basic.clean");
70+
}
71+
4872
@Test
4973
public void testWithIndentation() throws IOException {
5074
setFile("build.gradle").toLines(
@@ -82,6 +106,29 @@ public void testWithHeader() throws IOException {
82106
assertFile("src/main/kotlin/test.kt").hasContent(HEADER + "\n" + getTestResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test"));
83107
}
84108

109+
@Test
110+
public void testWithHeaderKtfmt() throws IOException {
111+
if (JreVersion.thisVm() == JreVersion._8) {
112+
// ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+.
113+
return;
114+
}
115+
setFile("build.gradle").toLines(
116+
"plugins {",
117+
" id 'nebula.kotlin' version '1.0.6'",
118+
" id 'com.diffplug.gradle.spotless'",
119+
"}",
120+
"repositories { mavenCentral() }",
121+
"spotless {",
122+
" kotlin {",
123+
" licenseHeader('" + HEADER + "')",
124+
" ktfmt()",
125+
" }",
126+
"}");
127+
setFile("src/main/kotlin/test.kt").toResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test");
128+
gradleRunner().withArguments("spotlessApply").build();
129+
assertFile("src/main/kotlin/test.kt").hasContent(HEADER + "\n" + getTestResource("kotlin/licenseheader/KotlinCodeWithoutHeaderKtfmt.test"));
130+
}
131+
85132
@Test
86133
public void testWithCustomHeaderSeparator() throws IOException {
87134
setFile("build.gradle").toLines(
@@ -101,6 +148,29 @@ public void testWithCustomHeaderSeparator() throws IOException {
101148
assertFile("src/main/kotlin/test.kt").hasContent(HEADER + "\n" + getTestResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test"));
102149
}
103150

151+
@Test
152+
public void testWithCustomHeaderSeparatorKtfmt() throws IOException {
153+
if (JreVersion.thisVm() == JreVersion._8) {
154+
// ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+.
155+
return;
156+
}
157+
setFile("build.gradle").toLines(
158+
"plugins {",
159+
" id 'nebula.kotlin' version '1.0.6'",
160+
" id 'com.diffplug.gradle.spotless'",
161+
"}",
162+
"repositories { mavenCentral() }",
163+
"spotless {",
164+
" kotlin {",
165+
" licenseHeader ('" + HEADER + "', '@file')",
166+
" ktfmt()",
167+
" }",
168+
"}");
169+
setFile("src/main/kotlin/test.kt").toResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test");
170+
gradleRunner().withArguments("spotlessApply").build();
171+
assertFile("src/main/kotlin/test.kt").hasContent(HEADER + "\n" + getTestResource("kotlin/licenseheader/KotlinCodeWithoutHeaderKtfmt.test"));
172+
}
173+
104174
@Test
105175
public void testWithNonStandardYearSeparator() throws IOException {
106176
setFile("build.gradle").toLines(
@@ -126,4 +196,34 @@ public void testWithNonStandardYearSeparator() throws IOException {
126196
matcher.startsWith(HEADER_WITH_YEAR.replace("$YEAR", String.valueOf(YearMonth.now().getYear())));
127197
});
128198
}
199+
200+
@Test
201+
public void testWithNonStandardYearSeparatorKtfmt() throws IOException {
202+
if (JreVersion.thisVm() == JreVersion._8) {
203+
// ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+.
204+
return;
205+
}
206+
setFile("build.gradle").toLines(
207+
"plugins {",
208+
" id 'nebula.kotlin' version '1.0.6'",
209+
" id 'com.diffplug.gradle.spotless'",
210+
"}",
211+
"repositories { mavenCentral() }",
212+
"spotless {",
213+
" kotlin {",
214+
" licenseHeader('" + HEADER_WITH_YEAR + "').yearSeparator(', ')",
215+
" ktfmt()",
216+
" }",
217+
"}");
218+
219+
setFile("src/main/kotlin/test.kt").toResource("kotlin/licenseheader/KotlinCodeWithMultiYearHeader.test");
220+
setFile("src/main/kotlin/test2.kt").toResource("kotlin/licenseheader/KotlinCodeWithMultiYearHeader2.test");
221+
gradleRunner().withArguments("spotlessApply").build();
222+
assertFile("src/main/kotlin/test.kt").matches(matcher -> {
223+
matcher.startsWith("// License Header 2012, 2014");
224+
});
225+
assertFile("src/main/kotlin/test2.kt").matches(matcher -> {
226+
matcher.startsWith(HEADER_WITH_YEAR.replace("$YEAR", String.valueOf(YearMonth.now().getYear())));
227+
});
228+
}
129229
}

plugin-maven/CHANGES.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
44

55
## [Unreleased]
66
### Added
7-
* Support for google-java-format 1.8 (requires you to run build on Java 11) ([#562](https://github.com/diffplug/spotless/issues/562))
7+
* Support for google-java-format 1.8 (requires build to run on Java 11+) ([#562](https://github.com/diffplug/spotless/issues/562))
8+
* Support for ktfmt 0.13 (requires build to run on Java 11+) ([#569](https://github.com/diffplug/spotless/pull/569))
89
* `mvn spotless:apply` is now guaranteed to be idempotent, even if some of the formatters are not. See [`PADDEDCELL.md` for details](https://github.com/diffplug/spotless/blob/master/PADDEDCELL.md) if you're curious. ([#565](https://github.com/diffplug/spotless/pull/565))
910
* Updated a bunch of dependencies, most notably jgit `5.5.0.201909110433-r` -> `5.7.0.202003110725-r`. ([#564](https://github.com/diffplug/spotless/pull/564))
1011

plugin-maven/README.md

+22
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ By default, all files matching `src/main/scala/**/*.scala`, `src/test/scala/**/*
153153

154154
By default, all files matching `src/main/kotlin/**/*.kt` and `src/test/kotlin/**/*.kt` Ant style pattern will be formatted. Each element under `<kotlin>` is a step, and they will be applied in the order specified. Every step is optional.
155155

156+
### Applying [ktlint](https://github.com/pinterest/ktlint) to Kotlin files
157+
156158
```xml
157159
<configuration>
158160
<kotlin>
@@ -171,6 +173,26 @@ By default, all files matching `src/main/kotlin/**/*.kt` and `src/test/kotlin/**
171173
</configuration>
172174
```
173175

176+
### Applying [ktfmt](https://github.com/facebookincubator/ktfmt) to Kotlin files
177+
178+
```xml
179+
<configuration>
180+
<kotlin>
181+
<licenseHeader>
182+
<!-- Specify either content or file, but not both -->
183+
<content>/* Licensed under Apache-2.0 */</content>
184+
<file>${basedir}/license-header</file>
185+
</licenseHeader>
186+
<endWithNewline/>
187+
<trimTrailingWhitespace/>
188+
<ktfmt>
189+
<!-- Optional, available versions: https://github.com/facebookincubator/ktfmt/releases -->
190+
<version>0.11</version>
191+
</ktfmt>
192+
</kotlin>
193+
</configuration>
194+
```
195+
174196
<a name="cpp"></a>
175197

176198
## Applying to C/C++ source

plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Kotlin.java

+4
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,8 @@ public String licenseHeaderDelimiter() {
3939
public void addKtlint(Ktlint ktlint) {
4040
addStepFactory(ktlint);
4141
}
42+
43+
public void addKtfmt(Ktfmt ktfmt) {
44+
addStepFactory(ktfmt);
45+
}
4246
}

0 commit comments

Comments
 (0)