Skip to content

Commit a643ae7

Browse files
authored
Convert diktat integration to use a compile-only sourceset and fix fullpath issue (#1190 fixes #1189 and helps #524)
2 parents 4d45472 + 335d495 commit a643ae7

File tree

7 files changed

+122
-92
lines changed

7 files changed

+122
-92
lines changed

CHANGES.md

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ 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+
### Changes
14+
* Bump default `diktat` version to latest `1.0.1` -> `1.1.0`. ([#1190](https://github.com/diffplug/spotless/pull/1190))
15+
* Converted `diktat` integration to use a compile-only source set. (fixes [#524](https://github.com/diffplug/spotless/issues/524))
16+
* Use the full path to a file in `diktat` integration. (fixes [#1189](https://github.com/diffplug/spotless/issues/1189))
1317

1418
## [2.25.1] - 2022-04-27
1519
### Changes

lib/build.gradle

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ def NEEDS_GLUE = [
1111
'palantirJavaFormat',
1212
'ktfmt',
1313
'ktlint',
14-
'flexmark'
14+
'flexmark',
15+
'diktat'
1516
]
1617
for (glue in NEEDS_GLUE) {
1718
sourceSets.register(glue) {
@@ -50,6 +51,9 @@ dependencies {
5051
ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-experimental:$VER_KTLINT"
5152
ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-standard:$VER_KTLINT"
5253

54+
String VER_DIKTAT = "1.1.0"
55+
diktatCompileOnly "org.cqfn.diktat:diktat-rules:$VER_DIKTAT"
56+
5357
// used for markdown formatting
5458
flexmarkCompileOnly 'com.vladsch.flexmark:flexmark-all:0.62.2'
5559
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2021-2022 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.glue.diktat;
17+
18+
import java.io.File;
19+
import java.util.*;
20+
21+
import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider;
22+
23+
import com.pinterest.ktlint.core.KtLint;
24+
import com.pinterest.ktlint.core.KtLint.Params;
25+
import com.pinterest.ktlint.core.LintError;
26+
import com.pinterest.ktlint.core.RuleSet;
27+
28+
import com.diffplug.spotless.FormatterFunc;
29+
30+
import kotlin.Unit;
31+
import kotlin.jvm.functions.Function2;
32+
33+
public class DiktatFormatterFunc implements FormatterFunc.NeedsFile {
34+
35+
private final List<RuleSet> rulesets;
36+
private final Map<String, String> userData;
37+
private final Function2<? super LintError, ? super Boolean, Unit> formatterCallback;
38+
private final boolean isScript;
39+
40+
private final ArrayList<LintError> errors = new ArrayList<>();
41+
42+
public DiktatFormatterFunc(boolean isScript, Map<String, String> userData) {
43+
rulesets = Collections.singletonList(new DiktatRuleSetProvider().get());
44+
this.userData = userData;
45+
this.formatterCallback = new FormatterCallback(errors);
46+
this.isScript = isScript;
47+
}
48+
49+
static class FormatterCallback implements Function2<LintError, Boolean, Unit> {
50+
private final ArrayList<LintError> errors;
51+
52+
FormatterCallback(ArrayList<LintError> errors) {
53+
this.errors = errors;
54+
}
55+
56+
@Override
57+
public Unit invoke(LintError lintError, Boolean corrected) {
58+
if (!corrected) {
59+
errors.add(lintError);
60+
}
61+
return null;
62+
}
63+
}
64+
65+
@Override
66+
public String applyWithFile(String unix, File file) throws Exception {
67+
errors.clear();
68+
userData.put("file_path", file.getAbsolutePath());
69+
String result = KtLint.INSTANCE.format(new Params(
70+
// Unlike Ktlint, Diktat requires full path to the file.
71+
// See https://github.com/diffplug/spotless/issues/1189, https://github.com/analysis-dev/diktat/issues/1202
72+
file.getAbsolutePath(),
73+
unix,
74+
rulesets,
75+
userData,
76+
formatterCallback,
77+
isScript,
78+
null,
79+
false));
80+
81+
if (!errors.isEmpty()) {
82+
StringBuilder error = new StringBuilder();
83+
error.append("There are ").append(errors.size()).append(" unfixed errors:");
84+
for (LintError er : errors) {
85+
error.append(System.lineSeparator())
86+
.append("Error on line: ").append(er.getLine())
87+
.append(", column: ").append(er.getCol())
88+
.append(" cannot be fixed automatically")
89+
.append(System.lineSeparator())
90+
.append(er.getDetail());
91+
}
92+
throw new AssertionError(error);
93+
}
94+
95+
return result;
96+
}
97+
}

lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java

+4-87
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
import java.io.IOException;
1919
import java.io.Serializable;
2020
import java.lang.reflect.Constructor;
21-
import java.lang.reflect.InvocationTargetException;
22-
import java.lang.reflect.Method;
23-
import java.lang.reflect.Proxy;
2421
import java.util.*;
2522

2623
import javax.annotation.Nullable;
@@ -33,10 +30,9 @@ public class DiktatStep {
3330
// prevent direct instantiation
3431
private DiktatStep() {}
3532

36-
private static final String DEFAULT_VERSION = "1.0.1";
33+
private static final String DEFAULT_VERSION = "1.1.0";
3734
static final String NAME = "diktat";
3835
static final String PACKAGE_DIKTAT = "org.cqfn.diktat";
39-
static final String PACKAGE_KTLINT = "com.pinterest.ktlint";
4036
static final String MAVEN_COORDINATE = PACKAGE_DIKTAT + ":diktat-rules:";
4137

4238
public static String defaultVersionDiktat() {
@@ -82,8 +78,6 @@ static final class State implements Serializable {
8278
/** Are the files being linted Kotlin script files. */
8379
private final boolean isScript;
8480
private final @Nullable FileSignature config;
85-
private final String pkg;
86-
private final String pkgKtlint;
8781
final JarState jar;
8882
private final TreeMap<String, String> userData;
8983

@@ -93,96 +87,19 @@ static final class State implements Serializable {
9387
pkgSet.add(MAVEN_COORDINATE + versionDiktat);
9488

9589
this.userData = new TreeMap<>(userData);
96-
this.pkg = PACKAGE_DIKTAT;
97-
this.pkgKtlint = PACKAGE_KTLINT;
9890
this.jar = JarState.from(pkgSet, provisioner);
9991
this.isScript = isScript;
10092
this.config = config;
10193
}
10294

10395
FormatterFunc createFormat() throws Exception {
104-
105-
ClassLoader classLoader = jar.getClassLoader();
106-
107-
// first, we get the diktat rules
10896
if (config != null) {
10997
System.setProperty("diktat.config.path", config.getOnlyFile().getAbsolutePath());
11098
}
11199

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;
100+
Class<?> formatterFunc = jar.getClassLoader().loadClass("com.diffplug.spotless.glue.diktat.DiktatFormatterFunc");
101+
Constructor<?> constructor = formatterFunc.getConstructor(boolean.class, Map.class);
102+
return (FormatterFunc.NeedsFile) constructor.newInstance(isScript, userData);
184103
}
185-
186104
}
187-
188105
}

plugin-gradle/CHANGES.md

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

55
## [Unreleased]
6+
### Changes
7+
* Bump default `diktat` version to latest `1.0.1` -> `1.1.0`. ([#1190](https://github.com/diffplug/spotless/pull/1190))
8+
* Converted `diktat` integration to use a compile-only source set. (fixes [#524](https://github.com/diffplug/spotless/issues/524))
9+
* Use the full path to a file in `diktat` integration. (fixes [#1189](https://github.com/diffplug/spotless/issues/1189))
610

711
## [6.5.1] - 2022-04-27
812
### Changes

plugin-maven/CHANGES.md

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

55
## [Unreleased]
6+
### Changes
7+
* Bump default `diktat` version to latest `1.0.1` -> `1.1.0`. ([#1190](https://github.com/diffplug/spotless/pull/1190))
8+
* Converted `diktat` integration to use a compile-only source set. (fixes [#524](https://github.com/diffplug/spotless/issues/524))
9+
* Use the full path to a file in `diktat` integration. (fixes [#1189](https://github.com/diffplug/spotless/issues/1189))
610

711
## [2.22.3] - 2022-04-27
812
### Changes

testlib/src/test/java/com/diffplug/spotless/kotlin/DiktatStepTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ void behavior() throws Exception {
3838
assertion.isInstanceOf(AssertionError.class);
3939
assertion.hasMessage("There are 2 unfixed errors:" +
4040
System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" +
41-
System.lineSeparator() + "[FILE_NAME_INCORRECT] file name is incorrect - it should end with .kt extension and be in PascalCase: " +
41+
System.lineSeparator() + "[FILE_NAME_INCORRECT] file name is incorrect - it should end with .kt extension and be in PascalCase: testlib" +
4242
System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" +
43-
System.lineSeparator() + "[FILE_NAME_MATCH_CLASS] file name is incorrect - it should match with the class described in it if there is the only one class declared: vs Unsolvable");
43+
System.lineSeparator() + "[FILE_NAME_MATCH_CLASS] file name is incorrect - it should match with the class described in it if there is the only one class declared: testlib vs Unsolvable");
4444
});
4545
}
4646

@@ -57,9 +57,9 @@ void behaviorConf() throws Exception {
5757
assertion.isInstanceOf(AssertionError.class);
5858
assertion.hasMessage("There are 2 unfixed errors:" +
5959
System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" +
60-
System.lineSeparator() + "[FILE_NAME_INCORRECT] file name is incorrect - it should end with .kt extension and be in PascalCase: " +
60+
System.lineSeparator() + "[FILE_NAME_INCORRECT] file name is incorrect - it should end with .kt extension and be in PascalCase: testlib" +
6161
System.lineSeparator() + "Error on line: 1, column: 1 cannot be fixed automatically" +
62-
System.lineSeparator() + "[FILE_NAME_MATCH_CLASS] file name is incorrect - it should match with the class described in it if there is the only one class declared: vs Unsolvable");
62+
System.lineSeparator() + "[FILE_NAME_MATCH_CLASS] file name is incorrect - it should match with the class described in it if there is the only one class declared: testlib vs Unsolvable");
6363
});
6464
}
6565

0 commit comments

Comments
 (0)