Skip to content

Commit aacef93

Browse files
committed
feat: add eclipse wtp formatter
eclipse wtp does initialize static variables based on the formatter type used. so in order to leverage the graal agent, we need to run each type of eclipse wtp formatter in a separate jvm - hence introducing the `SeparateJvm` test tag. The current version is working but needs cleaning up.
1 parent d8d662b commit aacef93

File tree

14 files changed

+460
-14
lines changed

14 files changed

+460
-14
lines changed

.idea/compiler.xml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
import com.diffplug.spotless.cli.picocli.usage.GenerateUsagePropertiesTask
2+
import com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep
3+
import org.gradle.plugins.ide.eclipse.model.EclipseWtp
4+
5+
//buildscript {
6+
// dependencies {
7+
// classpath libs.spotless.lib.extra
8+
// }
9+
//}
210

311
plugins {
412
id 'buildlogic.picocli-conventions'
@@ -9,6 +17,7 @@ plugins {
917

1018
version = rootProject.version
1119

20+
1221
dependencies {
1322
testImplementation project(':testlib')
1423
implementation libs.bundles.spotless.libs
@@ -17,6 +26,15 @@ dependencies {
1726
// these are fixed versions of the otherwise dynamic dependencies for spotless
1827
// this is necessary to allow for native compilation where reflective access to dynamic jars is not possible
1928
implementation libs.bundles.native.includes
29+
30+
def resourceName = "/com/diffplug/spotless/extra/eclipse_wtp_formatter/v4.21.0.lockfile"
31+
println("resource: " + resourceName)
32+
EclipseWtpFormatterStep.getResource(resourceName)
33+
.readLines()
34+
.findAll { !it.startsWith('#')} // filter out comments
35+
.each { implementation("${it}") {
36+
transitive=false
37+
} }
2038
}
2139

2240
application {
@@ -35,7 +53,7 @@ gradle.taskGraph.whenReady { TaskExecutionGraph graph ->
3553
}
3654

3755
tasks.withType(Test).configureEach {
38-
if (it.name == 'test' || it.name == 'testNpm') {
56+
if (it.name == 'test' || it.name == 'testNpm' || it.name == 'testSeparateJvm') {
3957
it.outputs.dir(nativeCompileMetaDir)
4058
if (project.hasProperty('agent')) {
4159
it.inputs.property('agent', project.property('agent')) // make sure to re-run tests if agent changes

app/src/main/java/com/diffplug/spotless/cli/SpotlessCLI.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.concurrent.Executors;
2525
import java.util.concurrent.Future;
2626

27+
import com.diffplug.spotless.cli.steps.EclipseWtp;
2728
import org.jetbrains.annotations.NotNull;
2829
import org.slf4j.Logger;
2930
import org.slf4j.LoggerFactory;
@@ -45,6 +46,7 @@
4546
import com.diffplug.spotless.cli.logging.output.Output;
4647
import com.diffplug.spotless.cli.steps.ClangFormat;
4748
import com.diffplug.spotless.cli.steps.CleanThat;
49+
import com.diffplug.spotless.cli.steps.EclipseWtp;
4850
import com.diffplug.spotless.cli.steps.FormatAnnotations;
4951
import com.diffplug.spotless.cli.steps.GoogleJavaFormat;
5052
import com.diffplug.spotless.cli.steps.LicenseHeader;
@@ -97,6 +99,7 @@
9799
subcommands = {
98100
ClangFormat.class,
99101
CleanThat.class,
102+
EclipseWtp.class,
100103
FormatAnnotations.class,
101104
GoogleJavaFormat.class,
102105
LicenseHeader.class,
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.diffplug.spotless.cli.steps;
2+
3+
4+
import com.diffplug.spotless.FormatterStep;
5+
import com.diffplug.spotless.ThrowingEx;
6+
import com.diffplug.spotless.cli.core.SpotlessActionContext;
7+
import com.diffplug.spotless.cli.help.OptionConstants;
8+
import com.diffplug.spotless.extra.EclipseBasedStepBuilder;
9+
import com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep;
10+
import org.jetbrains.annotations.NotNull;
11+
import picocli.CommandLine;
12+
13+
import java.lang.reflect.Constructor;
14+
import java.nio.file.Path;
15+
import java.util.List;
16+
import java.util.Properties;
17+
18+
@CommandLine.Command(name = "eclipse-wtp", description = "Runs Eclipse WTP formatter (" + EclipseWtp.ECLIPSE_WTP_VERSION+")")
19+
public class EclipseWtp extends SpotlessFormatterStep {
20+
21+
public static final String ECLIPSE_WTP_VERSION = "4.21.0"; // TODO we need to slurp in the lock file also
22+
23+
@CommandLine.Option(
24+
names = {"-f", "--config-file"},
25+
arity = "0",
26+
description = "The path to the Eclipse WTP configuration file.\n" +
27+
"For supported config file options see <https://github.com/diffplug/spotless/tree/main/plugin-gradle#eclipse-web-tools-platform>"
28+
)
29+
List<Path> configFiles;
30+
31+
@CommandLine.Option(
32+
names = { "-t", "--type" },
33+
description = "The type of the Eclipse WTP formatter." + OptionConstants.VALID_VALUES_SUFFIX,
34+
required = true
35+
)
36+
Type type;
37+
38+
public enum Type {
39+
CSS,
40+
HTML,
41+
JS,
42+
JSON,
43+
XML,
44+
XHTML;
45+
46+
public void initCorrespondingEclipseWtpCorePlugin() {
47+
// due to internals of eclipse wtp, invoking various variants of the formatter in the same java process
48+
// requires some static initialization to take place
49+
50+
}
51+
52+
public @NotNull EclipseWtpFormatterStep toEclipseWtpType() {
53+
EclipseWtpFormatterStep step = switch (this) {
54+
case CSS -> EclipseWtpFormatterStep.CSS;
55+
case HTML, XHTML -> EclipseWtpFormatterStep.HTML; // XHTML is treated as HTML in Eclipse WTP
56+
case JS -> EclipseWtpFormatterStep.JS;
57+
case JSON -> EclipseWtpFormatterStep.JSON;
58+
case XML -> EclipseWtpFormatterStep.XML;
59+
};
60+
61+
@SuppressWarnings("unused") Class<?> reverseLookup = lookupClass(step);
62+
return step;
63+
}
64+
65+
static Class<?> lookupClass(EclipseWtpFormatterStep step) {
66+
/* this statement is only here to
67+
1) get compile errors in case a new type is added in EclipseWtpFormatterStep
68+
2) allow graal to collect reflective Metadata*/
69+
@SuppressWarnings("unused") Class<?> reverseLookup = switch(step) {
70+
case CSS -> EclipseWtpMetadata.cssClass();
71+
case HTML -> EclipseWtpMetadata.htmlClass();
72+
case JS -> EclipseWtpMetadata.jsClass();
73+
case JSON -> EclipseWtpMetadata.jsonClass();
74+
case XML -> EclipseWtpMetadata.xmlClass();
75+
};
76+
return reverseLookup;
77+
}
78+
}
79+
80+
81+
@Override
82+
public @NotNull List<FormatterStep> prepareFormatterSteps(SpotlessActionContext context) {
83+
EclipseWtpFormatterStep wtpType = type.toEclipseWtpType();
84+
EclipseBasedStepBuilder builder = wtpType.createBuilder(context.provisioner());
85+
builder.setVersion(ECLIPSE_WTP_VERSION);
86+
if (configFiles != null && !configFiles.isEmpty()) {
87+
builder.setPreferences(
88+
configFiles.stream()
89+
.map(context::resolvePath)
90+
.map(Path::toFile)
91+
.toList());
92+
}
93+
return List.of(builder.build());
94+
}
95+
96+
97+
static class EclipseWtpMetadata {
98+
99+
private static Class<?> cssClass() {
100+
return ThrowingEx.get(() -> Class.forName("com.diffplug.spotless.extra.eclipse.wtp.EclipseCssFormatterStepImpl"));
101+
}
102+
103+
private static Class<?> htmlClass() {
104+
return ThrowingEx.get(() -> {
105+
Class<?> result = Class.forName("com.diffplug.spotless.extra.eclipse.wtp.EclipseHtmlFormatterStepImpl");
106+
Constructor<?> declaredConstructor = result.getDeclaredConstructor(Properties.class);
107+
return result;
108+
});
109+
}
110+
111+
private static Class<?> jsClass() {
112+
return ThrowingEx.get(() -> Class.forName("com.diffplug.spotless.extra.eclipse.wtp.EclipseJsFormatterStepImpl"));
113+
}
114+
115+
private static Class<?> jsonClass() {
116+
return ThrowingEx.get(() -> Class.forName("com.diffplug.spotless.extra.eclipse.wtp.EclipseJsonFormatterStepImpl"));
117+
}
118+
119+
private static Class<?> xmlClass() {
120+
return ThrowingEx.get(() -> Class.forName("com.diffplug.spotless.extra.eclipse.wtp.EclipseXmlFormatterStepImpl"));
121+
}
122+
}
123+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[
2+
{
3+
"name" : "java.lang.Class",
4+
"queryAllDeclaredConstructors" : true,
5+
"queryAllPublicConstructors" : true,
6+
"queryAllDeclaredMethods" : true,
7+
"queryAllPublicMethods" : true,
8+
"allDeclaredClasses" : true,
9+
"allPublicClasses" : true
10+
},
11+
{
12+
"name" : "java.lang.String",
13+
"fields" : [
14+
{ "name" : "value" },
15+
{ "name" : "hash" }
16+
],
17+
"methods" : [
18+
{ "name" : "<init>", "parameterTypes" : [] },
19+
{ "name" : "<init>", "parameterTypes" : ["char[]"] },
20+
{ "name" : "charAt" },
21+
{ "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
22+
]
23+
},
24+
{
25+
"name" : "java.lang.String$CaseInsensitiveComparator",
26+
"queriedMethods" : [
27+
{ "name" : "compare" }
28+
]
29+
},
30+
{
31+
"name": "com.diffplug.spotless.extra.eclipse.wtp.EclipseHtmlFormatterStepImpl",
32+
"queryAllDeclaredConstructors" : true,
33+
"queryAllPublicConstructors" : true,
34+
"queryAllDeclaredMethods" : true,
35+
"queryAllPublicMethods" : true,
36+
"allDeclaredClasses" : true,
37+
"allPublicClasses" : true
38+
}
39+
]
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.diffplug.spotless.cli.steps;
2+
3+
import com.diffplug.spotless.cli.CLIIntegrationHarness;
4+
import com.diffplug.spotless.cli.SpotlessCLIRunner;
5+
import com.diffplug.spotless.tag.CliNativeTest;
6+
import com.diffplug.spotless.tag.CliProcessTest;
7+
import com.diffplug.spotless.tag.SeparateJvmTest;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.parallel.Isolated;
10+
import org.junit.jupiter.params.ParameterizedTest;
11+
import org.junit.jupiter.params.provider.Arguments;
12+
import org.junit.jupiter.params.provider.MethodSource;
13+
14+
import java.util.Set;
15+
import java.util.stream.Collectors;
16+
import java.util.stream.Stream;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
@Isolated
21+
@SeparateJvmTest
22+
@CliNativeTest
23+
@CliProcessTest
24+
public class EclipseWtpSecondTest extends CLIIntegrationHarness {
25+
26+
// @BeforeEach
27+
// void resetEclipseFramework() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
28+
// Class<?> frameworkClass = Class.forName("com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework");
29+
// Field instanceField = frameworkClass.getDeclaredField("INSTANCE");
30+
// instanceField.setAccessible(true);
31+
// instanceField.set(null, null);
32+
//
33+
// Class<?> bundleRegistryClass = Class.forName("com.diffplug.spotless.extra.eclipse.base.osgi.FrameworkBundleRegistry");
34+
// Field registryInstanceField = bundleRegistryClass.getDeclaredField("INSTANCE");
35+
// registryInstanceField.setAccessible(true);
36+
// registryInstanceField.set(null, null);
37+
//
38+
// Class<?> registryProviderFactoryClass = Class.forName("org.eclipse.core.internal.registry.RegistryProviderFactory");
39+
// Field registryProviderField = registryProviderFactoryClass.getDeclaredField("defaultRegistryProvider");
40+
// registryProviderField.setAccessible(true);
41+
// registryProviderField.set(null, null);
42+
// }
43+
44+
@ParameterizedTest(name = "itSupportsFormattingFileType {0}")
45+
@MethodSource
46+
void itSupportsFormattingFileType(EclipseWtp.Type type, String unformatted) {
47+
String fileName = "test." + type.name().toLowerCase();
48+
setFile(fileName).toContent(unformatted);
49+
50+
SpotlessCLIRunner.Result result = cliRunner()
51+
.withTargets(fileName)
52+
.withStep(EclipseWtp.class)
53+
.withOption("--type", type.name())
54+
.run();
55+
56+
selfie().expectResource(fileName).toMatchDisk(type.name());
57+
}
58+
59+
// @Test
60+
// void itSupportsFormattingXmlFileType() {
61+
// String unformatted = "<a><b> c</b></a>";
62+
// String fileName = "test.xml";
63+
// setFile(fileName).toContent(unformatted);
64+
//
65+
// SpotlessCLIRunner.Result result = cliRunner()
66+
// .withTargets(fileName)
67+
// .withStep(EclipseWtp.class)
68+
// .withOption("--type", "XML")
69+
// .run();
70+
//
71+
// selfie().expectResource(fileName).toBe_TODO();
72+
// }
73+
74+
static Stream<Arguments> itSupportsFormattingFileType() {
75+
return Stream.of(
76+
// Arguments.of(EclipseWtp.Type.CSS, "body {\n" + "a: v; b: \n" + "v;\n" + "} \n"),
77+
Arguments.of(
78+
EclipseWtp.Type.HTML,
79+
"<!DOCTYPE html> <html>\t<head> <meta charset=\"UTF-8\"></head>\n" + "</html> "));
80+
// Arguments.of(EclipseWtp.Type.JSON, "{\"a\": \"b\",\t\"c\": { \"d\": \"e\",\"f\": \"g\"}}"),
81+
// Arguments.of(EclipseWtp.Type.JS, "function f( ) {\n" + "a.b(1,\n" + "2);}"),
82+
// Arguments.of(EclipseWtp.Type.XML, "<a><b> c</b></a>"),
83+
// Arguments.of(
84+
// EclipseWtp.Type.XHTML,
85+
// "<!DOCTYPE html> <html>\t<head> <meta charset=\"UTF-8\"></head>\n" + "</html> "));
86+
}
87+
88+
89+
@Test
90+
void readMetadata() {
91+
Set<Class<?>> set = Stream.of(EclipseWtp.Type.values())
92+
.map(t -> EclipseWtp.Type.lookupClass(t.toEclipseWtpType()))
93+
.collect(Collectors.toSet());
94+
95+
assertThat(set).isNotEmpty();
96+
}
97+
}

0 commit comments

Comments
 (0)