Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: supply eclipse formatter settings as XML content #2361

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

## [Unreleased]
### Changed
* Allow setting Eclipse XML config from a string, not only from files ([#2361](https://github.com/diffplug/spotless/pull/2361))
* Allow setting Eclipse config from a string, not only from files ([#2337](https://github.com/diffplug/spotless/pull/2337))
* Bump default `ktlint` version to latest `1.3.0` -> `1.4.0`. ([#2314](https://github.com/diffplug/spotless/pull/2314))
* Add _Sort Members_ feature based on [Eclipse JDT](plugin-gradle/README.md#eclipse-jdt) implementation. ([#2312](https://github.com/diffplug/spotless/pull/2312))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public abstract class EquoBasedStepBuilder {
private String formatterVersion;
private Iterable<File> settingsFiles = new ArrayList<>();
private List<String> settingProperties = new ArrayList<>();
private List<String> settingXml = new ArrayList<>();
private Map<String, String> p2Mirrors = Map.of();
private File cacheDirectory;

Expand Down Expand Up @@ -86,6 +87,10 @@ public void setPropertyPreferences(List<String> propertyPreferences) {
this.settingProperties = propertyPreferences;
}

public void setXmlPreferences(List<String> settingXml) {
this.settingXml = settingXml;
}

public void setP2Mirrors(Map<String, String> p2Mirrors) {
this.p2Mirrors = Map.copyOf(p2Mirrors);
}
Expand Down Expand Up @@ -119,7 +124,7 @@ protected void addPlatformRepo(P2Model model, String version) {

/** Returns the FormatterStep (whose state will be calculated lazily). */
public FormatterStep build() {
var roundtrippableState = new EquoStep(formatterVersion, settingProperties, FileSignature.promise(settingsFiles), JarState.promise(() -> {
var roundtrippableState = new EquoStep(formatterVersion, settingProperties, settingXml, FileSignature.promise(settingsFiles), JarState.promise(() -> {
P2QueryResult query;
try {
if (null != cacheDirectory) {
Expand Down Expand Up @@ -174,23 +179,26 @@ static class EquoStep implements Serializable {
private final JarState.Promised jarPromise;
private final ImmutableMap<String, String> stepProperties;
private List<String> settingProperties;
private List<String> settingXml;

EquoStep(
String semanticVersion,
List<String> settingProperties,
List<String> settingXml,
FileSignature.Promised settingsPromise,
JarState.Promised jarPromise,
ImmutableMap<String, String> stepProperties) {

this.semanticVersion = semanticVersion;
this.settingProperties = Optional.ofNullable(settingProperties).orElse(new ArrayList<>());
this.settingXml = Optional.ofNullable(settingXml).orElse(new ArrayList<>());
this.settingsPromise = settingsPromise;
this.jarPromise = jarPromise;
this.stepProperties = stepProperties;
}

private State state() {
return new State(semanticVersion, jarPromise.get(), settingProperties, settingsPromise.get(), stepProperties);
return new State(semanticVersion, jarPromise.get(), settingProperties, settingXml, settingsPromise.get(), stepProperties);
}
}

Expand All @@ -205,11 +213,13 @@ public static class State implements Serializable {
final FileSignature settingsFiles;
final ImmutableMap<String, String> stepProperties;
private List<String> settingProperties;
private List<String> settingXml;

public State(String semanticVersion, JarState jarState, List<String> settingProperties, FileSignature settingsFiles, ImmutableMap<String, String> stepProperties) {
public State(String semanticVersion, JarState jarState, List<String> settingProperties, List<String> settingXml, FileSignature settingsFiles, ImmutableMap<String, String> stepProperties) {
this.semanticVersion = semanticVersion;
this.jarState = jarState;
this.settingProperties = Optional.ofNullable(settingProperties).orElse(new ArrayList<>());
this.settingXml = Optional.ofNullable(settingXml).orElse(new ArrayList<>());
this.settingsFiles = settingsFiles;
this.stepProperties = stepProperties;
}
Expand All @@ -225,7 +235,8 @@ public String getSemanticVersion() {
public Properties getPreferences() {
FormatterProperties fromFiles = FormatterProperties.from(settingsFiles.files());
FormatterProperties fromPropertiesContent = FormatterProperties.fromPropertiesContent(settingProperties);
return FormatterProperties.merge(fromFiles.getProperties(), fromPropertiesContent.getProperties()).getProperties();
FormatterProperties fromXmlContent = FormatterProperties.fromXmlContent(settingXml);
return FormatterProperties.merge(fromFiles.getProperties(), fromPropertiesContent.getProperties(), fromXmlContent.getProperties()).getProperties();
}

public ImmutableMap<String, String> getStepProperties() {
Expand Down
65 changes: 53 additions & 12 deletions lib/src/main/java/com/diffplug/spotless/FormatterProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
Expand All @@ -28,6 +29,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

Expand Down Expand Up @@ -90,6 +92,25 @@ public static FormatterProperties fromPropertiesContent(Iterable<String> content
return properties;
}

public static FormatterProperties fromXmlContent(final Iterable<String> content) throws IllegalArgumentException {
final List<String> nonNullElements = toNullHostileList(content);
final FormatterProperties properties = new FormatterProperties();
nonNullElements.forEach(contentElement -> {
try {
final Properties newSettings = FileParser.XML.executeXmlContent(contentElement);
properties.properties.putAll(newSettings);
} catch (IOException | IllegalArgumentException exception) {
String message = String.format("Failed to add preferences from XML:%n%s%n", contentElement);
final String detailedMessage = exception.getMessage();
if (null != detailedMessage) {
message += String.format(" %s", detailedMessage);
}
throw new IllegalArgumentException(message, exception);
}
});
return properties;
}

public static FormatterProperties merge(Properties... properties) {
FormatterProperties merged = new FormatterProperties();
List.of(properties).stream().forEach((source) -> merged.properties.putAll(source));
Expand Down Expand Up @@ -139,20 +160,40 @@ protected Properties execute(final File file) throws IOException, IllegalArgumen
}
return properties;
}

@Override
protected Properties executeXmlContent(String content) throws IOException, IllegalArgumentException {
throw new RuntimeException("Not implemented");
}
},

XML("xml") {
@Override
protected Properties execute(final File file) throws IOException, IllegalArgumentException {
Node rootNode = getRootNode(file);
return executeWithSupplier(() -> {
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException("File not found: " + file, e);
}
});
}

@Override
protected Properties executeXmlContent(String content) throws IOException, IllegalArgumentException {
return executeWithSupplier(() -> new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
}

private Properties executeWithSupplier(Supplier<InputStream> isSupplier) throws IOException, IllegalArgumentException {
Node rootNode = getRootNode(isSupplier.get());
String nodeName = rootNode.getNodeName();
if (null == nodeName) {
throw new IllegalArgumentException("XML document does not contain a root node.");
}
return XmlParser.parse(file, rootNode);
return XmlParser.parse(isSupplier.get(), rootNode);
}

private Node getRootNode(final File file) throws IOException, IllegalArgumentException {
private Node getRootNode(final InputStream is) throws IOException, IllegalArgumentException {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
/*
Expand All @@ -166,7 +207,7 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc
*/
dbf.setFeature(LOAD_EXTERNAL_DTD_PROP, false);
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(file).getDocumentElement();
return db.parse(is).getDocumentElement();
} catch (SAXException | ParserConfigurationException e) {
throw new IllegalArgumentException("File has no valid XML syntax.", e);
}
Expand All @@ -186,6 +227,8 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc

protected abstract Properties execute(File file) throws IOException, IllegalArgumentException;

protected abstract Properties executeXmlContent(String content) throws IOException, IllegalArgumentException;

public static Properties parse(final File file) throws IOException, IllegalArgumentException {
String fileNameExtension = getFileNameExtension(file);
for (FileParser parser : FileParser.values()) {
Expand All @@ -211,19 +254,17 @@ private static String getFileNameExtension(File file) {
private enum XmlParser {
PROPERTIES("properties") {
@Override
protected Properties execute(final File xmlFile, final Node rootNode)
protected Properties execute(final InputStream xmlFile, final Node rootNode)
throws IOException, IllegalArgumentException {
final Properties properties = new Properties();
try (InputStream xmlInput = new FileInputStream(xmlFile)) {
properties.loadFromXML(xmlInput);
}
properties.loadFromXML(xmlFile);
return properties;
}
},

PROFILES("profiles") {
@Override
protected Properties execute(File file, Node rootNode) throws IOException, IllegalArgumentException {
protected Properties execute(InputStream file, Node rootNode) throws IOException, IllegalArgumentException {
final Properties properties = new Properties();
Node firstProfile = getSingleProfile(rootNode);
for (Object settingObj : getChildren(firstProfile, "setting")) {
Expand Down Expand Up @@ -285,14 +326,14 @@ public String toString() {
return this.rootNodeName;
}

protected abstract Properties execute(File file, Node rootNode) throws IOException, IllegalArgumentException;
protected abstract Properties execute(InputStream is, Node rootNode) throws IOException, IllegalArgumentException;

public static Properties parse(final File file, final Node rootNode)
public static Properties parse(final InputStream is, final Node rootNode)
throws IOException, IllegalArgumentException {
String rootNodeName = rootNode.getNodeName();
for (XmlParser parser : XmlParser.values()) {
if (parser.rootNodeName.equals(rootNodeName)) {
return parser.execute(file, rootNode);
return parser.execute(is, rootNode);
}
}
String msg = String.format("The XML root node '%1$s' is not part of the supported root nodes [%2$s].",
Expand Down
18 changes: 15 additions & 3 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,14 @@ spotless {
eclipse()
// optional: you can specify a specific version and/or config file
eclipse('4.26').configFile('eclipse-prefs.xml')
// Or supply the configuration as a string
// Or supply the configuration properties as a string
eclipse('4.26').configProperties("""
...
""")
// Or supply the configuration XML as a string
eclipse('4.26').configXml("""
...
""")
// if the access to the p2 repositories is restricted, mirrors can be
// specified using a URI prefix map as follows:
eclipse().withP2Mirrors(['https://download.eclipse.org/eclipse/updates/4.29/':'https://some.internal.mirror/4-29-updates-p2/'])
Expand Down Expand Up @@ -422,10 +426,14 @@ spotless {
greclipse()
// optional: you can specify a specific version or config file(s), version matches the Eclipse Platform
greclipse('4.26').configFile('spotless.eclipseformat.xml', 'org.codehaus.groovy.eclipse.ui.prefs')
// Or supply the configuration as a string
// Or supply the configuration properties as a string
greclipse('4.26').configProperties("""
...
""")
// Or supply the configuration XML as a string
greclipse('4.26').configXml("""
...
""")
```

Groovy-Eclipse formatting errors/warnings lead per default to a build failure. This behavior can be changed by adding the property/key value `ignoreFormatterProblems=true` to a configuration file. In this scenario, files causing problems, will not be modified by this formatter step.
Expand Down Expand Up @@ -580,10 +588,14 @@ spotles {
cpp {
// version and configFile are both optional
eclipseCdt('4.13.0').configFile('eclipse-cdt.xml')
// Or supply the configuration as a string
// Or supply the configuration properties as a string
eclipseCdt('4.13.0').configProperties("""
...
""")
// Or supply the configuration XML as a string
eclipseCdt('4.13.0').configXml("""
...
""")
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ public GrEclipseConfig configProperties(String... configs) {
return this;
}

public GrEclipseConfig configXml(String... configs) {
requireElementsNonNull(configs);
builder.setXmlPreferences(List.of(configs));
extension.replaceStep(builder.build());
return this;
}

public GrEclipseConfig withP2Mirrors(Map<String, String> mirrors) {
builder.setP2Mirrors(mirrors);
extension.replaceStep(builder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public EclipseConfig configProperties(String... configs) {
return this;
}

public EclipseConfig configXml(String... configs) {
requireElementsNonNull(configs);
builder.setXmlPreferences(List.of(configs));
replaceStep(builder.build());
return this;
}

public EclipseConfig withP2Mirrors(Map<String, String> mirrors) {
builder.setP2Mirrors(mirrors);
replaceStep(builder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,13 @@ public EclipseConfig configProperties(String... configs) {
return this;
}

public EclipseConfig configXml(String... configs) {
requireElementsNonNull(configs);
builder.setXmlPreferences(List.of(configs));
replaceStep(builder.build());
return this;
}

public EclipseConfig sortMembersDoNotSortFields(boolean doNotSortFields) {
builder.sortMembersDoNotSortFields(doNotSortFields);
replaceStep(builder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

class JavaEclipseTest extends GradleIntegrationHarness {
@Test
void settingsWithContentWithoutFile() throws IOException {
void settingsWithProprtiesContent() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
Expand All @@ -37,4 +37,27 @@ void settingsWithContentWithoutFile() throws IOException {

gradleRunner().withArguments("spotlessApply").build();
}

@Test
void settingsWithXmlContent() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
" id 'java'",
"}",
"repositories { mavenCentral() }",
"",
"spotless {",
" java { eclipse().configProperties(\"\"\"",
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>",
"<profiles version=\"12\">",
" <profile kind=\"CodeFormatterProfile\" name=\"Spotless\" version=\"12\">",
" <setting id=\"valid_line_oriented.prefs.string\" value=\"string\" />",
" </profile>",
"</profiles>",
"\"\"\") }",
"}");

gradleRunner().withArguments("spotlessApply").build();
}
}
Loading
Loading