Skip to content

Commit 132f429

Browse files
authoredFeb 9, 2021
Support diktat - Kotlin code style analyzer (#789)
2 parents cd84982 + 5880a54 commit 132f429

File tree

22 files changed

+894
-20
lines changed

22 files changed

+894
-20
lines changed
 

‎CHANGES.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This document is intended for Spotless developers.
1010
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).
1111

1212
## [Unreleased]
13+
### Added
14+
* Support for diktat ([#789](https://github.com/diffplug/spotless/pull/789))
1315

1416
## [2.11.0] - 2021-01-04
1517
### Added

‎README.md

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ lib('java.RemoveUnusedImportsStep') +'{{yes}} | {{yes}}
6060
extra('java.EclipseJdtFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
6161
lib('kotlin.KtLintStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
6262
lib('kotlin.KtfmtStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
63+
lib('kotlin.DiktatStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
6364
lib('markdown.FreshMarkStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
6465
lib('npm.PrettierFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
6566
lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
@@ -95,6 +96,7 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}}
9596
| [`java.EclipseJdtFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
9697
| [`kotlin.KtLintStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
9798
| [`kotlin.KtfmtStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
99+
| [`kotlin.DiktatStep`](lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
98100
| [`markdown.FreshMarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
99101
| [`npm.PrettierFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
100102
| [`npm.TsFmtFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* Copyright 2021 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.Constructor;
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.lang.reflect.Method;
23+
import java.lang.reflect.Proxy;
24+
import java.util.*;
25+
26+
import javax.annotation.Nullable;
27+
28+
import com.diffplug.spotless.*;
29+
30+
/** Wraps up [diktat](https://github.com/cqfn/diKTat) as a FormatterStep. */
31+
public class DiktatStep {
32+
33+
// prevent direct instantiation
34+
private DiktatStep() {}
35+
36+
private static final String DEFAULT_VERSION = "0.4.0";
37+
static final String NAME = "diktat";
38+
static final String PACKAGE_DIKTAT = "org.cqfn.diktat";
39+
static final String PACKAGE_KTLINT = "com.pinterest.ktlint";
40+
static final String MAVEN_COORDINATE = PACKAGE_DIKTAT + ":diktat-rules:";
41+
42+
public static String defaultVersionDiktat() {
43+
return DEFAULT_VERSION;
44+
}
45+
46+
public static FormatterStep create(Provisioner provisioner) {
47+
return create(defaultVersionDiktat(), provisioner);
48+
}
49+
50+
public static FormatterStep create(String versionDiktat, Provisioner provisioner) {
51+
return create(versionDiktat, provisioner, Collections.emptyMap(), null);
52+
}
53+
54+
public static FormatterStep create(String versionDiktat, Provisioner provisioner, @Nullable FileSignature config) {
55+
return create(versionDiktat, provisioner, Collections.emptyMap(), config);
56+
}
57+
58+
public static FormatterStep create(String versionDiktat, Provisioner provisioner, Map<String, String> userData, @Nullable FileSignature config) {
59+
return create(versionDiktat, provisioner, false, userData, config);
60+
}
61+
62+
public static FormatterStep createForScript(String versionDiktat, Provisioner provisioner, @Nullable FileSignature config) {
63+
return createForScript(versionDiktat, provisioner, Collections.emptyMap(), config);
64+
}
65+
66+
public static FormatterStep createForScript(String versionDiktat, Provisioner provisioner, Map<String, String> userData, @Nullable FileSignature config) {
67+
return create(versionDiktat, provisioner, true, userData, config);
68+
}
69+
70+
public static FormatterStep create(String versionDiktat, Provisioner provisioner, boolean isScript, Map<String, String> userData, @Nullable FileSignature config) {
71+
Objects.requireNonNull(versionDiktat, "versionDiktat");
72+
Objects.requireNonNull(provisioner, "provisioner");
73+
return FormatterStep.createLazy(NAME,
74+
() -> new DiktatStep.State(versionDiktat, provisioner, isScript, userData, config),
75+
DiktatStep.State::createFormat);
76+
}
77+
78+
static final class State implements Serializable {
79+
80+
private static final long serialVersionUID = 1L;
81+
82+
/** Are the files being linted Kotlin script files. */
83+
private final boolean isScript;
84+
private final @Nullable FileSignature config;
85+
private final String pkg;
86+
private final String pkgKtlint;
87+
final JarState jar;
88+
private final TreeMap<String, String> userData;
89+
90+
State(String versionDiktat, Provisioner provisioner, boolean isScript, Map<String, String> userData, @Nullable FileSignature config) throws IOException {
91+
92+
HashSet<String> pkgSet = new HashSet<>();
93+
pkgSet.add(MAVEN_COORDINATE + versionDiktat);
94+
95+
this.userData = new TreeMap<>(userData);
96+
this.pkg = PACKAGE_DIKTAT;
97+
this.pkgKtlint = PACKAGE_KTLINT;
98+
this.jar = JarState.from(pkgSet, provisioner);
99+
this.isScript = isScript;
100+
this.config = config;
101+
}
102+
103+
FormatterFunc createFormat() throws Exception {
104+
105+
ClassLoader classLoader = jar.getClassLoader();
106+
107+
// first, we get the diktat rules
108+
if (config != null) {
109+
System.setProperty("diktat.config.path", config.getOnlyFile().getAbsolutePath());
110+
}
111+
112+
Class<?> ruleSetProviderClass = classLoader.loadClass(pkg + ".ruleset.rules.DiktatRuleSetProvider");
113+
Object diktatRuleSet = ruleSetProviderClass.getMethod("get").invoke(ruleSetProviderClass.newInstance());
114+
Iterable<?> ruleSets = Collections.singletonList(diktatRuleSet);
115+
116+
// next, we create an error callback which throws an assertion error when the format is bad
117+
Class<?> function2Interface = classLoader.loadClass("kotlin.jvm.functions.Function2");
118+
Class<?> lintErrorClass = classLoader.loadClass(pkgKtlint + ".core.LintError");
119+
Method detailGetter = lintErrorClass.getMethod("getDetail");
120+
Method lineGetter = lintErrorClass.getMethod("getLine");
121+
Method colGetter = lintErrorClass.getMethod("getCol");
122+
123+
// grab the KtLint singleton
124+
Class<?> ktlintClass = classLoader.loadClass(pkgKtlint + ".core.KtLint");
125+
Object ktlint = ktlintClass.getDeclaredField("INSTANCE").get(null);
126+
127+
Class<?> paramsClass = classLoader.loadClass(pkgKtlint + ".core.KtLint$Params");
128+
// and its constructor
129+
Constructor<?> constructor = paramsClass.getConstructor(
130+
/* fileName, nullable */ String.class,
131+
/* text */ String.class,
132+
/* ruleSets */ Iterable.class,
133+
/* userData */ Map.class,
134+
/* callback */ function2Interface,
135+
/* script */ boolean.class,
136+
/* editorConfigPath, nullable */ String.class,
137+
/* debug */ boolean.class);
138+
Method formatterMethod = ktlintClass.getMethod("format", paramsClass);
139+
FormatterFunc.NeedsFile formatterFunc = (input, file) -> {
140+
ArrayList<Object> errors = new ArrayList<>();
141+
142+
Object formatterCallback = Proxy.newProxyInstance(classLoader, new Class[]{function2Interface},
143+
(proxy, method, args) -> {
144+
Object lintError = args[0]; //ktlint.core.LintError
145+
boolean corrected = (Boolean) args[1];
146+
if (!corrected) {
147+
errors.add(lintError);
148+
}
149+
return null;
150+
});
151+
152+
userData.put("file_path", file.getAbsolutePath());
153+
try {
154+
Object params = constructor.newInstance(
155+
/* fileName, nullable */ file.getName(),
156+
/* text */ input,
157+
/* ruleSets */ ruleSets,
158+
/* userData */ userData,
159+
/* callback */ formatterCallback,
160+
/* script */ isScript,
161+
/* editorConfigPath, nullable */ null,
162+
/* debug */ false);
163+
String result = (String) formatterMethod.invoke(ktlint, params);
164+
if (!errors.isEmpty()) {
165+
StringBuilder error = new StringBuilder("");
166+
error.append("There are ").append(errors.size()).append(" unfixed errors:");
167+
for (Object er : errors) {
168+
String detail = (String) detailGetter.invoke(er);
169+
int line = (Integer) lineGetter.invoke(er);
170+
int col = (Integer) colGetter.invoke(er);
171+
172+
error.append(System.lineSeparator()).append("Error on line: ").append(line).append(", column: ").append(col).append(" cannot be fixed automatically")
173+
.append(System.lineSeparator()).append(detail);
174+
}
175+
throw new AssertionError(error);
176+
}
177+
return result;
178+
} catch (InvocationTargetException e) {
179+
throw ThrowingEx.unwrapCause(e);
180+
}
181+
};
182+
183+
return formatterFunc;
184+
}
185+
186+
}
187+
188+
}

‎plugin-gradle/CHANGES.md

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).
44

55
## [Unreleased]
6+
### Added
7+
* Support for diktat in KotlinGradleExtension ([#789](https://github.com/diffplug/spotless/pull/789))
68

79
## [5.9.0] - 2021-01-04
810
### Added

‎plugin-gradle/README.md

+17-7
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui
6060
- **Languages**
6161
- [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [clang-format](#clang-format), [prettier](#prettier))
6262
- [Groovy](#groovy) ([eclipse groovy](#eclipse-groovy))
63-
- [Kotlin](#kotlin) ([ktlint](#ktlint), [ktfmt](#ktfmt), [prettier](#prettier))
63+
- [Kotlin](#kotlin) ([ktfmt](#ktfmt), [ktlint](#ktlint), [diktat](#diktat), [prettier](#prettier))
6464
- [Scala](#scala) ([scalafmt](#scalafmt))
6565
- [C/C++](#cc) ([clang-format](#clang-format), [eclipse cdt](#eclipse-cdt))
6666
- [Python](#python) ([black](#black))
@@ -251,8 +251,9 @@ spotless { // if you are using build.gradle.kts, instead of 'spotless {' use:
251251
// configure<com.diffplug.gradle.spotless.SpotlessExtension> {
252252
kotlin {
253253
// by default the target is every '.kt' and '.kts` file in the java sourcesets
254-
ktlint() // has its own section below
255254
ktfmt() // has its own section below
255+
ktlint() // has its own section below
256+
diktat() // has its own section below
256257
prettier() // has its own section below
257258
licenseHeader '/* (C)$YEAR */' // or licenseHeaderFile
258259
}
@@ -263,6 +264,16 @@ spotless { // if you are using build.gradle.kts, instead of 'spotless {' use:
263264
}
264265
```
265266

267+
### ktfmt
268+
269+
[homepage](https://github.com/facebookincubator/ktfmt). [changelog](https://github.com/facebookincubator/ktfmt/releases).
270+
271+
```kotlin
272+
spotless {
273+
kotlin {
274+
ktfmt('0.15').dropboxStyle() // version and dropbox style are optional
275+
```
276+
266277
<a name="applying-ktlint-to-kotlin-files"></a>
267278

268279
### ktlint
@@ -276,16 +287,15 @@ spotless {
276287
ktlint('0.37.2').userData(mapOf('indent_size' to '2', 'continuation_indent_size' to '2'])
277288
```
278289

279-
<a name="applying-ktfmt-to-kotlin-files"></a>
280-
281-
### ktfmt
290+
### diktat
282291

283-
[homepage](https://github.com/facebookincubator/ktfmt). [changelog](https://github.com/facebookincubator/ktfmt/releases).
292+
[homepage](https://github.com/cqfn/diKTat). [changelog](https://github.com/cqfn/diKTat/releases). You can provide configuration path manually as `configFile`.
284293

285294
```kotlin
286295
spotless {
287296
kotlin {
288-
ktfmt('0.15').dropboxStyle() // version and dropbox style are optional
297+
// version and configFile are both optional
298+
diktat('0.4.0').configFile("full/path/to/diktat-analysis.yml")
289299
```
290300

291301
<a name="applying-scalafmt-to-scala-files"></a>

‎plugin-gradle/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ if (version.endsWith('-SNAPSHOT')) {
6969
'eclipse',
7070
'ktlint',
7171
'ktfmt',
72+
'diktat',
7273
'tsfmt',
7374
'prettier',
7475
'scalafmt',

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

+39
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static com.diffplug.spotless.kotlin.KotlinConstants.LICENSE_HEADER_DELIMITER;
1919

20+
import java.io.IOException;
2021
import java.util.Collections;
2122
import java.util.Map;
2223
import java.util.Objects;
@@ -28,7 +29,9 @@
2829
import org.gradle.api.plugins.JavaPluginConvention;
2930
import org.gradle.api.tasks.SourceSet;
3031

32+
import com.diffplug.spotless.FileSignature;
3133
import com.diffplug.spotless.FormatterStep;
34+
import com.diffplug.spotless.kotlin.DiktatStep;
3235
import com.diffplug.spotless.kotlin.KtLintStep;
3336
import com.diffplug.spotless.kotlin.KtfmtStep;
3437
import com.diffplug.spotless.kotlin.KtfmtStep.Style;
@@ -122,6 +125,42 @@ private FormatterStep createStep() {
122125
}
123126
}
124127

128+
/** Adds the specified version of [diktat](https://github.com/cqfn/diKTat). */
129+
public DiktatFormatExtension diktat(String version) {
130+
Objects.requireNonNull(version);
131+
return new DiktatFormatExtension(version);
132+
}
133+
134+
public DiktatFormatExtension diktat() {
135+
return diktat(DiktatStep.defaultVersionDiktat());
136+
}
137+
138+
public class DiktatFormatExtension {
139+
140+
private final String version;
141+
private FileSignature config;
142+
143+
DiktatFormatExtension(String version) {
144+
this.version = version;
145+
addStep(createStep());
146+
}
147+
148+
public DiktatFormatExtension configFile(Object file) throws IOException {
149+
// Specify the path to the configuration file
150+
if (file == null) {
151+
this.config = null;
152+
} else {
153+
this.config = FileSignature.signAsList(getProject().file(file));
154+
}
155+
replaceStep(createStep());
156+
return this;
157+
}
158+
159+
private FormatterStep createStep() {
160+
return DiktatStep.create(version, provisioner(), config);
161+
}
162+
}
163+
125164
/** If the user hasn't specified the files yet, we'll assume he/she means all of the kotlin files. */
126165
@Override
127166
protected void setupTask(SpotlessTask task) {

0 commit comments

Comments
 (0)
Please sign in to comment.