Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
* CleanThat Java Refactorer ([#???](https://github.com/diffplug/spotless/pull/???))

## [2.34.1] - 2023-02-05
### Changes
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ lib('java.PalantirJavaFormatStep') +'{{yes}} | {{yes}}
lib('java.RemoveUnusedImportsStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
extra('java.EclipseJdtFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
lib('java.FormatAnnotationsStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('java.CleanthatJavaStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
lib('json.gson.GsonStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('json.JacksonJsonStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('json.JsonSimpleStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
Expand Down Expand Up @@ -131,6 +132,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}}
| [`java.RemoveUnusedImportsStep`](lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
| [`java.EclipseJdtFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
| [`java.FormatAnnotationsStep`](lib/src/main/java/com/diffplug/spotless/java/FormatAnnotationsStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`java.CleanthatJavaStep`](lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
| [`json.gson.GsonStep`](lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`json.JacksonJsonStep`](lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`json.JsonSimpleStep`](lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
Expand Down
8 changes: 7 additions & 1 deletion lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def NEEDS_GLUE = [
'diktat',
'scalafmt',
'jackson',
'gson'
'gson',
'cleanthat'
]
for (glue in NEEDS_GLUE) {
sourceSets.register(glue) {
Expand Down Expand Up @@ -54,6 +55,7 @@ dependencies {
testCommonImplementation "org.junit.jupiter:junit-jupiter:$VER_JUNIT"
testCommonImplementation "org.assertj:assertj-core:$VER_ASSERTJ"
testCommonImplementation "com.diffplug.durian:durian-testlib:$VER_DURIAN"
testCommonImplementation 'io.github.solven-eu.cleanthat:java:2.1'

// used for pom sorting
sortPomCompileOnly 'com.github.ekryd.sortpom:sortpom-sorter:3.0.0'
Expand Down Expand Up @@ -100,6 +102,10 @@ dependencies {
flexmarkCompileOnly 'com.vladsch.flexmark:flexmark-all:0.62.2'

gsonCompileOnly 'com.google.code.gson:gson:2.10.1'

// TODO How can one add a test module like 'compatKtLint0Dot48Dot0'?
// cleanthatCompileAndTestOnly 'io.github.solven-eu.cleanthat:java:2.1'
cleanthatCompileOnly 'io.github.solven-eu.cleanthat:java:2.1'
}

// we'll hold the core lib to a high standard
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2023 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.java;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.diffplug.spotless.FormatterFunc;

import eu.solven.cleanthat.config.pojo.CleanthatEngineProperties;
import eu.solven.cleanthat.engine.java.IJdkVersionConstants;
import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorer;
import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorerProperties;
import eu.solven.cleanthat.formatter.LineEnding;

public class JavaCleanthatRefactorerFunc implements FormatterFunc {
private static final Logger LOGGER = LoggerFactory.getLogger(JavaCleanthatRefactorerFunc.class);

private String jdkVersion;
private List<String> included;
private List<String> excluded;

public JavaCleanthatRefactorerFunc(String jdkVersion, List<String> included, List<String> excluded) {
this.jdkVersion = jdkVersion == null ? IJdkVersionConstants.JDK_8 : jdkVersion;
this.included = included == null ? Collections.emptyList() : included;
this.excluded = excluded == null ? Collections.emptyList() : excluded;
}

public JavaCleanthatRefactorerFunc() {
this(IJdkVersionConstants.JDK_8, Arrays.asList(JavaRefactorerProperties.WILDCARD), Arrays.asList());
}

@Override
public String apply(String input) throws Exception {
CleanthatEngineProperties engineProperties = CleanthatEngineProperties.builder().engineVersion(jdkVersion).build();

JavaRefactorerProperties refactorerProperties = new JavaRefactorerProperties();

refactorerProperties.setIncluded(included);
refactorerProperties.setExcluded(excluded);

JavaRefactorer refactorer = new JavaRefactorer(engineProperties, refactorerProperties);

LOGGER.debug("Processing sourceJdk={} included={} excluded={}", jdkVersion, included, excluded);
LOGGER.debug("Available mutators: {}", JavaRefactorer.getAllIncluded());

// Spotless calls steps always with LF eol.
return refactorer.doFormat(input, LineEnding.LF);
}

}
158 changes: 158 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2023 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.java;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.JarState;
import com.diffplug.spotless.Jvm;
import com.diffplug.spotless.Provisioner;

/**
* Enables CleanThat as a SpotLess step.
*
* @author Benoit Lacelle
*/
// https://github.com/diffplug/spotless/blob/main/CONTRIBUTING.md#how-to-add-a-new-formatterstep
public final class CleanthatJavaStep {

private static final String NAME = "cleanthat";
private static final String MAVEN_COORDINATE = "io.github.solven-eu.cleanthat:java";

private static final Jvm.Support<String> JVM_SUPPORT = Jvm.<String> support(NAME).add(11, "2.1");

// prevent direct instantiation
private CleanthatJavaStep() {}

/** Creates a step which apply default CleanThat mutators. */
public static FormatterStep create(Provisioner provisioner) {
return create(defaultVersion(), provisioner);
}

/** Creates a step which apply default CleanThat mutators. */
public static FormatterStep create(String version, Provisioner provisioner) {
return create(MAVEN_COORDINATE, version, defaultJdkVersion(), defaultExcludedMutators(), defaultMutators(), provisioner);
}

public static String defaultJdkVersion() {
// see IJdkVersionConstants.JDK_7
// https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#source
// 1.7 is the default for 'maven-compiler-plugin' since 3.9.0
return "1.7";
}

public static List<String> defaultExcludedMutators() {
return List.of();
}

/**
* By default, we include all available rules
* @return
*/
public static List<String> defaultMutators() {
// see JavaRefactorerProperties.WILDCARD
return List.of("*");
}

/** Creates a step which apply selected CleanThat mutators. */
public static FormatterStep create(String groupArtifact,
String version,
String sourceJdkVersion,
List<String> excluded,
List<String> included,
Provisioner provisioner) {
Objects.requireNonNull(groupArtifact, "groupArtifact");
if (groupArtifact.chars().filter(ch -> ch == ':').count() != 1) {
throw new IllegalArgumentException("groupArtifact must be in the form 'groupId:artifactId'. it was: " + groupArtifact);
}
Objects.requireNonNull(version, "version");
Objects.requireNonNull(provisioner, "provisioner");
return FormatterStep.createLazy(NAME,
() -> new JavaRefactorerState(NAME, groupArtifact, version, sourceJdkVersion, excluded, included, provisioner),
JavaRefactorerState::createFormat);
}

/** Get default formatter version */
public static String defaultVersion() {
return JVM_SUPPORT.getRecommendedFormatterVersion();
}

public static String defaultGroupArtifact() {
return MAVEN_COORDINATE;
}

static final class JavaRefactorerState implements Serializable {
private static final long serialVersionUID = 1L;

final JarState jarState;
final String stepName;
final String version;

final String sourceJdkVersion;
final List<String> included;
final List<String> excluded;

JavaRefactorerState(String stepName, String version, Provisioner provisioner) throws IOException {
this(stepName, MAVEN_COORDINATE, version, defaultJdkVersion(), defaultExcludedMutators(), defaultMutators(), provisioner);
}

JavaRefactorerState(String stepName,
String groupArtifact,
String version,
String sourceJdkVersion,
List<String> included,
List<String> excluded,
Provisioner provisioner) throws IOException {
JVM_SUPPORT.assertFormatterSupported(version);
ModuleHelper.doOpenInternalPackagesIfRequired();
this.jarState = JarState.from(groupArtifact + ":" + version, provisioner);
this.stepName = stepName;
this.version = version;

this.sourceJdkVersion = sourceJdkVersion;
this.included = included;
this.excluded = excluded;
}

@SuppressWarnings("PMD.UseProperClassLoader")
FormatterFunc createFormat() {
ClassLoader classLoader = jarState.getClassLoader();

Object formatter;
Method formatterMethod;
try {
Class<?> formatterClazz = classLoader.loadClass("com.diffplug.spotless.glue.java.JavaCleanthatRefactorerFunc");
Constructor<?> formatterConstructor = formatterClazz.getConstructor(String.class, List.class, List.class);

formatter = formatterConstructor.newInstance(sourceJdkVersion, included, excluded);
formatterMethod = formatterClazz.getMethod("apply", String.class);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Issue executing the formatter", e);
}
return JVM_SUPPORT.suggestLaterVersionOnError(version, input -> {
return (String) formatterMethod.invoke(formatter, input);
});
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2023 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.java;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorer;

public class JavaCleanthatRefactorerFuncTest {
@Test
public void testMutatorsDetection() {
Assertions.assertThat(JavaRefactorer.getAllIncluded()).isNotEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2023 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.compat;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

public class KtLintCompat0Dot48Dot0AdapterTest {
@Test
public void testDefaults(@TempDir Path path) throws IOException {
KtLintCompat0Dot48Dot0Adapter ktLintCompat0Dot48Dot0Adapter = new KtLintCompat0Dot48Dot0Adapter();
String text = loadAndWriteText(path, "empty_class_body.kt");
final Path filePath = Paths.get(path.toString(), "empty_class_body.kt");

Map<String, String> userData = new HashMap<>();

Map<String, Object> editorConfigOverrideMap = new HashMap<>();

String formatted = ktLintCompat0Dot48Dot0Adapter.format(text, filePath, false, false, null, userData, editorConfigOverrideMap);
assertEquals("class empty_class_body\n", formatted);
}

@Test
public void testEditorConfigCanDisable(@TempDir Path path) throws IOException {
KtLintCompat0Dot48Dot0Adapter ktLintCompat0Dot48Dot0Adapter = new KtLintCompat0Dot48Dot0Adapter();
String text = loadAndWriteText(path, "fails_no_semicolons.kt");
final Path filePath = Paths.get(path.toString(), "fails_no_semicolons.kt");

Map<String, String> userData = new HashMap<>();

Map<String, Object> editorConfigOverrideMap = new HashMap<>();
editorConfigOverrideMap.put("indent_style", "tab");
editorConfigOverrideMap.put("ktlint_standard_no-semi", "disabled");
// ktlint_filename is an invalid rule in ktlint 0.48.0
editorConfigOverrideMap.put("ktlint_filename", "disabled");

String formatted = ktLintCompat0Dot48Dot0Adapter.format(text, filePath, false, false, null, userData, editorConfigOverrideMap);
assertEquals("class fails_no_semicolons {\n\tval i = 0;\n}\n", formatted);
}

private static String loadAndWriteText(Path path, String name) throws IOException {
try (InputStream is = KtLintCompat0Dot48Dot0AdapterTest.class.getResourceAsStream("/" + name)) {
Files.copy(is, path.resolve(name));
}
return new String(Files.readAllBytes(path.resolve(name)), StandardCharsets.UTF_8);
}

}
2 changes: 2 additions & 0 deletions plugin-maven/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 `1.27.0`).

## [Unreleased]
### Added
* CleanThat Java Refactorer ([#???](https://github.com/diffplug/spotless/pull/???))

## [2.32.0] - 2023-02-05
### Added
Expand Down
Loading