Skip to content
3 changes: 2 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ 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 `rdf` ([#2261](https://github.com/diffplug/spotless/pull/2261))
### Changed
* Support configuring the Equo P2 cache. ([#2238](https://github.com/diffplug/spotless/pull/2238))

* Add explicit support for JSONC / CSS via biome, via the file extensions `.css` and `.jsonc`.
([#2259](https://github.com/diffplug/spotless/pull/2259))

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}}
lib('pom.SortPomStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('protobuf.BufStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
lib('python.BlackStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
lib('rdf.RdfFormatterStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
lib('scala.ScalaFmtStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
lib('shell.ShfmtStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('sql.DBeaverSQLFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
Expand Down Expand Up @@ -157,6 +158,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}}
| [`pom.SortPomStep`](lib/src/main/java/com/diffplug/spotless/pom/SortPomStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`protobuf.BufStep`](lib/src/main/java/com/diffplug/spotless/protobuf/BufStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
| [`python.BlackStep`](lib/src/main/java/com/diffplug/spotless/python/BlackStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
| [`rdf.RdfFormatterStep`](lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
| [`scala.ScalaFmtStep`](lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
| [`shell.ShfmtStep`](lib/src/main/java/com/diffplug/spotless/shell/ShfmtStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`sql.DBeaverSQLFormatterStep`](lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
Expand Down
2 changes: 0 additions & 2 deletions lib/src/main/java/com/diffplug/spotless/Formatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ public boolean isClean(File file) throws IOException {

String raw = new String(Files.readAllBytes(file.toPath()), encoding);
String unix = LineEnding.toUnix(raw);

// check the newlines (we can find these problems without even running the steps)
int totalNewLines = (int) unix.codePoints().filter(val -> val == '\n').count();
int windowsNewLines = raw.length() - unix.length();
Expand All @@ -181,7 +180,6 @@ public boolean isClean(File file) throws IOException {

// check the other formats
String formatted = compute(unix, file);

// return true iff the formatted string equals the unix one
return formatted.equals(unix);
}
Expand Down
106 changes: 106 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2024 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.rdf;

import java.io.Serializable;
import java.util.Objects;

public class RdfFormatterConfig implements Serializable {
private static final long serialVersionUID = 1L;
private boolean failOnWarning = true;
private String turtleFormatterVersion = RdfFormatterStep.LATEST_TURTLE_FORMATTER_VERSION;
private boolean verify = true;

public RdfFormatterConfig() {}

public void setFailOnWarning(boolean failOnWarning) {
this.failOnWarning = failOnWarning;
}

public boolean isFailOnWarning() {
return failOnWarning;
}

public boolean isVerify() {
return verify;
}

public void setVerify(boolean verify) {
this.verify = verify;
}

public static Builder builder() {
return new Builder();
}

public String getTurtleFormatterVersion() {
return turtleFormatterVersion;
}

public void setTurtleFormatterVersion(String turtleFormatterVersion) {
this.turtleFormatterVersion = turtleFormatterVersion;
}

public static class Builder {
RdfFormatterConfig config = new RdfFormatterConfig();

public Builder() {}

public Builder failOnWarning() {
return this.failOnWarning(true);
}

public Builder failOnWarning(boolean fail) {
this.config.setFailOnWarning(fail);
return this;
}

public Builder turtleFormatterVersion(String version) {
this.config.turtleFormatterVersion = version;
return this;
}

public Builder verify(boolean verify) {
this.config.verify = verify;
return this;
}

public Builder verify() {
this.config.verify = true;
return this;
}

public RdfFormatterConfig build() {
return config;
}
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof RdfFormatterConfig))
return false;
RdfFormatterConfig that = (RdfFormatterConfig) o;
return isFailOnWarning() == that.isFailOnWarning()
&& Objects.equals(turtleFormatterVersion, that.turtleFormatterVersion);
}

@Override
public int hashCode() {
return Objects.hash(isFailOnWarning(), turtleFormatterVersion);
}
}
165 changes: 165 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterFunc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright 2024 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.rdf;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.LineEnding;

public class RdfFormatterFunc implements FormatterFunc {
private static final Set<String> TURTLE_EXTENSIONS = Set.of("ttl", "turtle");
private static final Set<String> TRIG_EXTENSIONS = Set.of("trig");
private static final Set<String> NTRIPLES_EXTENSIONS = Set.of("n-triples", "ntriples", "nt");
private static final Set<String> NQUADS_EXTENSIONS = Set.of("n-quads", "nquads", "nq");

private final RdfFormatterStep.State state;
private final ReflectionHelper reflectionHelper;

public RdfFormatterFunc(RdfFormatterStep.State state)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
this.state = state;
this.reflectionHelper = new ReflectionHelper(state);
}

@Override
public String apply(String input) throws Exception {
throw new UnsupportedOperationException("We need to know the filename so we can guess the RDF format. Use apply(String, File) instead!");
}

@Override
public String apply(String rawUnix, File file) throws Exception {
String filename = file.getName().toLowerCase(Locale.US);
int lastDot = filename.lastIndexOf('.');
if (lastDot < 0) {
throw new IllegalArgumentException(
String.format("File %s has no file extension, cannot determine RDF format", file.getAbsolutePath()));
}
if (lastDot + 1 >= filename.length()) {
throw new IllegalArgumentException(
String.format("File %s has no file extension, cannot determine RDF format", file.getAbsolutePath()));
}
String extension = filename.substring(lastDot + 1);

try {
if (TURTLE_EXTENSIONS.contains(extension)) {
return formatTurtle(rawUnix, file, reflectionHelper);
}
if (TRIG_EXTENSIONS.contains(extension)) {
return formatTrig(rawUnix, file);
}
if (NTRIPLES_EXTENSIONS.contains(extension)) {
return formatNTriples(rawUnix, file);
}
if (NQUADS_EXTENSIONS.contains(extension)) {
return formatNQuads(rawUnix, file);
}
throw new IllegalArgumentException(String.format("Cannot handle file with extension %s", extension));
} catch (InvocationTargetException e) {
throw new RuntimeException("Error formatting file " + file.getPath(), e.getCause());
} catch (Exception e) {
throw new RuntimeException("Error formatting file " + file.getPath(), e);
}
}

private String formatNQuads(String rawUnix, File file) {
throw new UnsupportedOperationException("NQUADS formatting not supported yet");
}

private String formatNTriples(String rawUnix, File file) {
throw new UnsupportedOperationException("NTRIPLES formatting not supported yet");
}

private String formatTrig(String rawUnix, File file) {
throw new UnsupportedOperationException("TRIG formatting not supported yet");
}

private String formatTurtle(String rawUnix, File file, ReflectionHelper reflectionHelper)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException,
NoSuchFieldException, InstantiationException {
String formatted;
Object lang = reflectionHelper.getLang("TTL");
formatted = reflectionHelper.formatWithTurtleFormatter(rawUnix);
if (state.getConfig().isVerify()) {
veryfyResult(rawUnix, file, reflectionHelper, lang, formatted);
}
return LineEnding.toUnix(formatted);
}

private static void veryfyResult(String rawUnix, File file, ReflectionHelper reflectionHelper, Object lang,
String formatted) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Object modelBefore = reflectionHelper.parseToModel(rawUnix, file, lang);
Object modelAfter = reflectionHelper.parseToModel(formatted, file, lang);
if (!reflectionHelper.areModelsIsomorphic(modelBefore, modelAfter)) {
long beforeSize = reflectionHelper.modelSize(modelBefore);
long afterSize = reflectionHelper.modelSize(modelAfter);
String diffResult;
if (beforeSize != afterSize) {
diffResult = String.format("< %,d triples", beforeSize);
diffResult += String.format("> %,d triples", afterSize);
} else {
diffResult = calculateDiff(reflectionHelper, modelBefore, modelAfter);
}
throw new IllegalStateException(
"Formatted RDF is not isomorphic with original, which means that formatting changed the data.\n"
+ "This could be a bug in the formatting system leading to data corruption and should be reported. \n"
+ "If you are not scared to lose data, you can disable this check by setting the config option 'verify' to 'false'"
+ "\n\nDiff:\n"
+ diffResult);
}
}

private static String calculateDiff(ReflectionHelper reflectionHelper, Object modelBefore, Object modelAfter)
throws InvocationTargetException, IllegalAccessException {
String diffResult;
Object graphBefore = reflectionHelper.getGraph(modelBefore);
Object graphAfter = reflectionHelper.getGraph(modelAfter);

List<Object> onlyInBeforeContent = reflectionHelper.streamGraph(graphBefore)
.filter(triple -> {
try {
return !reflectionHelper.graphContainsSameTerm(graphAfter, triple);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());

List<Object> onlyInAfterContent = reflectionHelper.streamGraph(graphAfter)
.filter(triple -> {
try {
return !reflectionHelper.graphContainsSameTerm(graphBefore, triple);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
if (!(onlyInBeforeContent.isEmpty() && onlyInAfterContent.isEmpty())) {
diffResult = onlyInBeforeContent.stream().map(s -> String.format("< %s", s))
.collect(Collectors.joining("\n"));
diffResult += "\n" + onlyInAfterContent.stream().map(s -> String.format("> %s", s)).collect(Collectors.joining("\n"));
} else {
diffResult = "'before' and 'after' content differs, but we don't know why. This is probably a bug.";
}
return diffResult;
}

}
Loading
Loading