Skip to content

Commit

Permalink
Diktat is a strict coding standard for Kotlin and a custom set of rul…
Browse files Browse the repository at this point in the history
…es for detecting code smells, code style issues and bugs.

https://github.com/cqfn/diKTat/blob/master/README.md
  • Loading branch information
Cheshiriks committed Feb 5, 2021
1 parent cd84982 commit 9b5b488
Show file tree
Hide file tree
Showing 22 changed files with 895 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
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 diktat ([#789](https://github.com/diffplug/spotless/pull/789))

## [2.11.0] - 2021-01-04
### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ lib('java.RemoveUnusedImportsStep') +'{{yes}} | {{yes}}
extra('java.EclipseJdtFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
lib('kotlin.KtLintStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
lib('kotlin.KtfmtStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('kotlin.DiktatStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('markdown.FreshMarkStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
lib('npm.PrettierFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
Expand Down Expand Up @@ -95,6 +96,7 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}}
| [`java.EclipseJdtFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
| [`kotlin.KtLintStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
| [`kotlin.KtfmtStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`kotlin.DiktatStep`](lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`markdown.FreshMarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
| [`npm.PrettierFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`npm.TsFmtFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
Expand Down
186 changes: 186 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
* 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.kotlin;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;

import com.diffplug.spotless.*;

/** Wraps up [diktat](https://github.com/cqfn/diKTat) as a FormatterStep. */
public class DiktatStep {

// prevent direct instantiation
private DiktatStep() {}

private static final String DEFAULT_VERSION = "0.4.0";
static final String NAME = "diktat";
static final String PACKAGE_DIKTAT = "org.cqfn.diktat";
static final String PACKAGE_KTLINT = "com.pinterest.ktlint";
static final String MAVEN_COORDINATE = PACKAGE_DIKTAT + ":diktat-rules:";

public static String defaultVersionDiktat() {
return DEFAULT_VERSION;
}

public static FormatterStep create(Provisioner provisioner) {
return create(defaultVersionDiktat(), provisioner);
}

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

public static FormatterStep create(String versionDiktat, Provisioner provisioner, FileSignature config) {
return create(versionDiktat, provisioner, Collections.emptyMap(), config);
}

public static FormatterStep create(String versionDiktat, Provisioner provisioner, Map<String, String> userData, FileSignature config) {
return create(versionDiktat, provisioner, false, userData, config);
}

public static FormatterStep createForScript(String versionDiktat, Provisioner provisioner, FileSignature config) {
return createForScript(versionDiktat, provisioner, Collections.emptyMap(), config);
}

public static FormatterStep createForScript(String versionDiktat, Provisioner provisioner, Map<String, String> userData, FileSignature config) {
return create(versionDiktat, provisioner, true, userData, config);
}

public static FormatterStep create(String versionDiktat, Provisioner provisioner, boolean isScript, Map<String, String> userData, FileSignature config) {
Objects.requireNonNull(versionDiktat, "versionDiktat");
Objects.requireNonNull(provisioner, "provisioner");
return FormatterStep.createLazy(NAME,
() -> new DiktatStep.State(versionDiktat, provisioner, isScript, userData, config),
DiktatStep.State::createFormat);
}

static final class State implements Serializable {

private static final long serialVersionUID = 1L;

/** Are the files being linted Kotlin script files. */
private final boolean isScript;
private final FileSignature config;
private final String pkg;
private final String pkgKtlint;
final JarState jar;
private final TreeMap<String, String> userData;

State(String versionDiktat, Provisioner provisioner, boolean isScript, Map<String, String> userData, FileSignature config) throws IOException {

HashSet<String> pkgSet = new HashSet<>();
pkgSet.add(MAVEN_COORDINATE + versionDiktat);

this.userData = new TreeMap<>(userData);
this.pkg = PACKAGE_DIKTAT;
this.pkgKtlint = PACKAGE_KTLINT;
this.jar = JarState.from(pkgSet, provisioner);
this.isScript = isScript;
this.config = config;
}

FormatterFunc createFormat() throws Exception {

ClassLoader classLoader = jar.getClassLoader();

// first, we get the diktat rules
if (config != null) {
System.setProperty("diktat.config.path", config.getOnlyFile().getAbsolutePath());
}

Class<?> ruleSetProviderClass = classLoader.loadClass(pkg + ".ruleset.rules.DiktatRuleSetProvider");
Object diktatRuleSet = ruleSetProviderClass.getMethod("get").invoke(ruleSetProviderClass.newInstance());
Iterable<?> ruleSets = Collections.singletonList(diktatRuleSet);

// next, we create an error callback which throws an assertion error when the format is bad
Class<?> function2Interface = classLoader.loadClass("kotlin.jvm.functions.Function2");
Class<?> lintErrorClass = classLoader.loadClass(pkgKtlint + ".core.LintError");
Method detailGetter = lintErrorClass.getMethod("getDetail");
Method lineGetter = lintErrorClass.getMethod("getLine");
Method colGetter = lintErrorClass.getMethod("getCol");

// grab the KtLint singleton
Class<?> ktlintClass = classLoader.loadClass(pkgKtlint + ".core.KtLint");
Object ktlint = ktlintClass.getDeclaredField("INSTANCE").get(null);

Class<?> paramsClass = classLoader.loadClass(pkgKtlint + ".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 formatterFunc = (input, file) -> {
ArrayList<Object> errors = new ArrayList<>();

Object formatterCallback = Proxy.newProxyInstance(classLoader, new Class[]{function2Interface},
(proxy, method, args) -> {
Object lintError = args[0]; //ktlint.core.LintError
boolean corrected = (Boolean) args[1];
if (!corrected) {
errors.add(lintError);
}
return null;
});

userData.put("file_path", file.getAbsolutePath());
try {
Object params = constructor.newInstance(
/* fileName, nullable */ file.getName(),
/* text */ input,
/* ruleSets */ ruleSets,
/* userData */ userData,
/* callback */ formatterCallback,
/* script */ isScript,
/* editorConfigPath, nullable */ null,
/* debug */ false);
String result = (String) formatterMethod.invoke(ktlint, params);
if (!errors.isEmpty()) {
StringBuilder error = new StringBuilder("");
error.append("There are ").append(errors.size()).append(" unfixed errors:");
for (Object er : errors) {
String detail = (String) detailGetter.invoke(er);
int line = (Integer) lineGetter.invoke(er);
int col = (Integer) colGetter.invoke(er);

error.append(System.lineSeparator()).append("Error on line: ").append(line).append(", column: ").append(col).append(" cannot be fixed automatically")
.append(System.lineSeparator()).append(detail);
}
throw new AssertionError(error);
}
return result;
} catch (InvocationTargetException e) {
throw ThrowingEx.unwrapCause(e);
}
};

return formatterFunc;
}

}

}
2 changes: 2 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).

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

## [5.9.0] - 2021-01-04
### Added
Expand Down
16 changes: 15 additions & 1 deletion plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui
- **Languages**
- [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [clang-format](#clang-format), [prettier](#prettier))
- [Groovy](#groovy) ([eclipse groovy](#eclipse-groovy))
- [Kotlin](#kotlin) ([ktlint](#ktlint), [ktfmt](#ktfmt), [prettier](#prettier))
- [Kotlin](#kotlin) ([ktlint](#ktlint), [ktfmt](#ktfmt), [prettier](#prettier), [diktat](#diktat))
- [Scala](#scala) ([scalafmt](#scalafmt))
- [C/C++](#cc) ([clang-format](#clang-format), [eclipse cdt](#eclipse-cdt))
- [Python](#python) ([black](#black))
Expand Down Expand Up @@ -253,6 +253,7 @@ spotless { // if you are using build.gradle.kts, instead of 'spotless {' use:
// by default the target is every '.kt' and '.kts` file in the java sourcesets
ktlint() // has its own section below
ktfmt() // has its own section below
diktat() // has its own section below
prettier() // has its own section below
licenseHeader '/* (C)$YEAR */' // or licenseHeaderFile
}
Expand Down Expand Up @@ -288,6 +289,19 @@ spotless {
ktfmt('0.15').dropboxStyle() // version and dropbox style are optional
```

<a name="applying-diktat-to-kotlin-files"></a>

### diktat

[homepage](https://github.com/cqfn/diKTat). [changelog](https://github.com/cqfn/diKTat/releases). You can provide configuration path manually as `configPath`.

```kotlin
spotless {
kotlin {
// version and configPath are both optional
diktat('0.4.0').configPath("full/path/to/diktat-analysis.yml")
```

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

## Scala
Expand Down
1 change: 1 addition & 0 deletions plugin-gradle/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ if (version.endsWith('-SNAPSHOT')) {
'eclipse',
'ktlint',
'ktfmt',
'diktat',
'tsfmt',
'prettier',
'scalafmt',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
*/
package com.diffplug.gradle.spotless;

import static com.diffplug.spotless.FileSignature.signAsList;
import static com.diffplug.spotless.kotlin.KotlinConstants.LICENSE_HEADER_DELIMITER;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
Expand All @@ -28,7 +31,9 @@
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet;

import com.diffplug.spotless.FileSignature;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.kotlin.DiktatStep;
import com.diffplug.spotless.kotlin.KtLintStep;
import com.diffplug.spotless.kotlin.KtfmtStep;
import com.diffplug.spotless.kotlin.KtfmtStep.Style;
Expand Down Expand Up @@ -122,6 +127,38 @@ private FormatterStep createStep() {
}
}

/** Adds the specified version of [diktat](https://github.com/cqfn/diKTat). */
public DiktatFormatExtension diktat(String version) {
Objects.requireNonNull(version);
return new DiktatFormatExtension(version, null);
}

public DiktatFormatExtension diktat() {
return diktat(DiktatStep.defaultVersionDiktat());
}

public class DiktatFormatExtension {

private final String version;
private FileSignature config;

DiktatFormatExtension(String version, FileSignature config) {
this.version = version;
this.config = config;
addStep(createStep());
}

public void withConfig(String path) throws IOException {
// Specify the path to the configuration file
this.config = signAsList(new File(path));
replaceStep(createStep());
}

private FormatterStep createStep() {
return DiktatStep.create(version, provisioner(), config);
}
}

/** If the user hasn't specified the files yet, we'll assume he/she means all of the kotlin files. */
@Override
protected void setupTask(SpotlessTask task) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2020 DiffPlug
* Copyright 2016-2021 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 @@ -15,14 +15,20 @@
*/
package com.diffplug.gradle.spotless;

import static com.diffplug.spotless.FileSignature.signAsList;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;

import javax.inject.Inject;

import com.diffplug.common.collect.ImmutableSortedMap;
import com.diffplug.spotless.FileSignature;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.kotlin.DiktatStep;
import com.diffplug.spotless.kotlin.KtLintStep;
import com.diffplug.spotless.kotlin.KtfmtStep;
import com.diffplug.spotless.kotlin.KtfmtStep.Style;
Expand Down Expand Up @@ -104,6 +110,38 @@ private FormatterStep createStep() {
}
}

/** Adds the specified version of [diktat](https://github.com/cqfn/diKTat). */
public DiktatFormatExtension diktat(String version) {
Objects.requireNonNull(version, "version");
return new DiktatFormatExtension(version, null);
}

public DiktatFormatExtension diktat() {
return diktat(DiktatStep.defaultVersionDiktat());
}

public class DiktatFormatExtension {

private final String version;
private FileSignature config;

DiktatFormatExtension(String version, FileSignature config) {
this.version = version;
this.config = config;
addStep(createStep());
}

public void withConfig(String path) throws IOException {
// Specify the path to the configuration file
this.config = signAsList(new File(path));
replaceStep(createStep());
}

private FormatterStep createStep() {
return DiktatStep.createForScript(version, provisioner(), config);
}
}

@Override
protected void setupTask(SpotlessTask task) {
if (target == null) {
Expand Down

0 comments on commit 9b5b488

Please sign in to comment.