Skip to content

Commit 9afa6dd

Browse files
committed
Handy tool for making encoding formats that don't have any forbidden characters.
1 parent 9347888 commit 9afa6dd

12 files changed

+720
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless;
17+
18+
import java.util.AbstractList;
19+
20+
import javax.annotation.Nullable;
21+
22+
/**
23+
* Fixed-size list which maintains a list of exceptions, one per step of the formatter.
24+
* Usually this list will be empty or have only a single value, so it is optimized for stack allocation in those cases.
25+
*/
26+
class ExceptionPerStep extends AbstractList<Throwable> {
27+
private final int size;
28+
private @Nullable Throwable exception;
29+
private int exceptionIdx;
30+
private @Nullable Throwable[] multipleExceptions = null;
31+
32+
ExceptionPerStep(Formatter formatter) {
33+
this.size = formatter.getSteps().size();
34+
}
35+
36+
@Override
37+
public @Nullable Throwable set(int index, Throwable exception) {
38+
if (index < 0 || index >= size) {
39+
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
40+
}
41+
if (this.exception == null) {
42+
this.exceptionIdx = index;
43+
this.exception = exception;
44+
return null;
45+
} else if (this.multipleExceptions != null) {
46+
Throwable previousValue = multipleExceptions[index];
47+
multipleExceptions[index] = exception;
48+
return previousValue;
49+
} else {
50+
if (index == exceptionIdx) {
51+
Throwable previousValue = this.exception;
52+
this.exception = exception;
53+
return previousValue;
54+
} else {
55+
multipleExceptions = new Throwable[size];
56+
multipleExceptions[exceptionIdx] = this.exception;
57+
multipleExceptions[index] = exception;
58+
return null;
59+
}
60+
}
61+
}
62+
63+
@Override
64+
public Throwable get(int index) {
65+
if (multipleExceptions != null) {
66+
return multipleExceptions[index];
67+
} else if (exceptionIdx == index) {
68+
return exception;
69+
} else {
70+
return null;
71+
}
72+
}
73+
74+
private int indexOfFirstException() {
75+
if (multipleExceptions != null) {
76+
for (int i = 0; i < multipleExceptions.length; i++) {
77+
if (multipleExceptions[i] != null) {
78+
return i;
79+
}
80+
}
81+
return -1;
82+
} else if (exception != null) {
83+
return exceptionIdx;
84+
} else {
85+
return -1;
86+
}
87+
}
88+
89+
@Override
90+
public int size() {
91+
return size;
92+
}
93+
94+
/** Rethrows the first exception in the list. */
95+
public void rethrowFirstIfPresent() {
96+
int firstException = indexOfFirstException();
97+
if (firstException != -1) {
98+
throw ThrowingEx.asRuntimeRethrowError(get(firstException));
99+
}
100+
}
101+
}

lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2023 DiffPlug
2+
* Copyright 2016-2024 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,8 @@
1616
package com.diffplug.spotless;
1717

1818
import java.io.File;
19+
import java.util.List;
1920
import java.util.Objects;
20-
import java.util.regex.Matcher;
2121
import java.util.regex.Pattern;
2222

2323
import javax.annotation.Nullable;
@@ -36,14 +36,24 @@ final class FilterByContentPatternFormatterStep extends DelegateFormatterStep {
3636
public @Nullable String format(String raw, File file) throws Exception {
3737
Objects.requireNonNull(raw, "raw");
3838
Objects.requireNonNull(file, "file");
39-
Matcher matcher = contentPattern.matcher(raw);
40-
if (matcher.find() == (onMatch == OnMatch.INCLUDE)) {
39+
if (contentPattern.matcher(raw).find() == (onMatch == OnMatch.INCLUDE)) {
4140
return delegateStep.format(raw, file);
4241
} else {
4342
return raw;
4443
}
4544
}
4645

46+
@Override
47+
public List<Lint> lint(String raw, File file) throws Exception {
48+
Objects.requireNonNull(raw, "raw");
49+
Objects.requireNonNull(file, "file");
50+
if (contentPattern.matcher(raw).find() == (onMatch == OnMatch.INCLUDE)) {
51+
return delegateStep.lint(raw, file);
52+
} else {
53+
return List.of();
54+
}
55+
}
56+
4757
@Override
4858
public boolean equals(Object o) {
4959
if (this == o) {

lib/src/main/java/com/diffplug/spotless/FilterByFileFormatterStep.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2022 DiffPlug
2+
* Copyright 2016-2024 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616
package com.diffplug.spotless;
1717

1818
import java.io.File;
19+
import java.util.List;
1920
import java.util.Objects;
2021

2122
import javax.annotation.Nullable;
@@ -39,6 +40,17 @@ final class FilterByFileFormatterStep extends DelegateFormatterStep {
3940
}
4041
}
4142

43+
@Override
44+
public List<Lint> lint(String content, File file) throws Exception {
45+
Objects.requireNonNull(content, "content");
46+
Objects.requireNonNull(file, "file");
47+
if (filter.accept(file)) {
48+
return delegateStep.lint(content, file);
49+
} else {
50+
return List.of();
51+
}
52+
}
53+
4254
@Override
4355
public boolean equals(Object o) {
4456
if (this == o) {

lib/src/main/java/com/diffplug/spotless/Formatter.java

+22-8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.nio.charset.Charset;
2727
import java.util.ArrayList;
2828
import java.util.List;
29+
import java.util.ListIterator;
2930
import java.util.Objects;
3031

3132
/** Formatter which performs the full formatting. */
@@ -127,12 +128,28 @@ public String computeLineEndings(String unix, File file) {
127128
* is guaranteed to also have unix line endings.
128129
*/
129130
public String compute(String unix, File file) {
131+
ExceptionPerStep exceptionPerStep = new ExceptionPerStep(this);
132+
String result = compute(unix, file, exceptionPerStep);
133+
exceptionPerStep.rethrowFirstIfPresent();
134+
return result;
135+
}
136+
137+
/**
138+
* Returns the result of calling all of the FormatterSteps, while also
139+
* tracking any exceptions which are thrown.
140+
* <p>
141+
* The input must have unix line endings, and the output
142+
* is guaranteed to also have unix line endings.
143+
* <p>
144+
*/
145+
String compute(String unix, File file, ExceptionPerStep exceptionPerStep) {
130146
Objects.requireNonNull(unix, "unix");
131147
Objects.requireNonNull(file, "file");
132148

133-
for (FormatterStep step : steps) {
149+
ListIterator<FormatterStep> iter = steps.listIterator();
150+
while (iter.hasNext()) {
134151
try {
135-
String formatted = step.format(unix, file);
152+
String formatted = iter.next().format(unix, file);
136153
if (formatted == null) {
137154
// This probably means it was a step that only checks
138155
// for errors and doesn't actually have any fixes.
@@ -142,12 +159,9 @@ public String compute(String unix, File file) {
142159
unix = LineEnding.toUnix(formatted);
143160
}
144161
} catch (Throwable e) {
145-
// TODO: this is bad, but it won't matter when add support for linting
146-
if (e instanceof RuntimeException) {
147-
throw (RuntimeException) e;
148-
} else {
149-
throw new RuntimeException(e);
150-
}
162+
// store the exception which was thrown, and stop execution so we don't alter line numbers
163+
exceptionPerStep.set(iter.previousIndex(), e);
164+
return unix;
151165
}
152166
}
153167
return unix;

lib/src/main/java/com/diffplug/spotless/FormatterFunc.java

+26
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.diffplug.spotless;
1717

1818
import java.io.File;
19+
import java.util.List;
1920
import java.util.Objects;
2021

2122
/**
@@ -32,6 +33,14 @@ default String apply(String unix, File file) throws Exception {
3233
return apply(unix);
3334
}
3435

36+
/**
37+
* Calculates a list of lints against the given content.
38+
* By default, that's just an throwables thrown by the lint.
39+
*/
40+
default List<Lint> lint(String content, File file) throws Exception {
41+
return List.of();
42+
}
43+
3544
/**
3645
* {@code Function<String, String>} and {@code BiFunction<String, File, String>} whose implementation
3746
* requires a resource which should be released when the function is no longer needed.
@@ -74,6 +83,14 @@ public String apply(String unix) throws Exception {
7483
@FunctionalInterface
7584
interface ResourceFunc<T extends AutoCloseable> {
7685
String apply(T resource, String unix) throws Exception;
86+
87+
/**
88+
* Calculates a list of lints against the given content.
89+
* By default, that's just an throwables thrown by the lint.
90+
*/
91+
default List<Lint> lint(T resource, String unix) throws Exception {
92+
return List.of();
93+
}
7794
}
7895

7996
/** Creates a {@link FormatterFunc.Closeable} which uses the given resource to execute the format function. */
@@ -101,6 +118,10 @@ public String apply(String unix) throws Exception {
101118
@FunctionalInterface
102119
interface ResourceFuncNeedsFile<T extends AutoCloseable> {
103120
String apply(T resource, String unix, File file) throws Exception;
121+
122+
default List<Lint> lint(T resource, String content, File file) throws Exception {
123+
return List.of();
124+
}
104125
}
105126

106127
/** Creates a {@link FormatterFunc.Closeable} which uses the given resource to execute the file-dependent format function. */
@@ -123,6 +144,11 @@ public String apply(String unix, File file) throws Exception {
123144
public String apply(String unix) throws Exception {
124145
return apply(unix, Formatter.NO_FILE_SENTINEL);
125146
}
147+
148+
@Override
149+
public List<Lint> lint(String content, File file) throws Exception {
150+
return function.lint(resource, content, file);
151+
}
126152
};
127153
}
128154
}

lib/src/main/java/com/diffplug/spotless/FormatterStep.java

+17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.io.File;
1919
import java.io.Serializable;
20+
import java.util.List;
2021
import java.util.Objects;
2122

2223
import javax.annotation.Nullable;
@@ -46,6 +47,22 @@ public interface FormatterStep extends Serializable, AutoCloseable {
4647
@Nullable
4748
String format(String rawUnix, File file) throws Exception;
4849

50+
/**
51+
* Returns a list of lints against the given file content
52+
*
53+
* @param content
54+
* the content to check
55+
* @param file
56+
* the file which {@code content} was obtained from; never null. Pass an empty file using
57+
* {@code new File("")} if and only if no file is actually associated with {@code content}
58+
* @return a list of lints
59+
* @throws Exception if the formatter step experiences a problem
60+
*/
61+
@Nullable
62+
default List<Lint> lint(String content, File file) throws Exception {
63+
return List.of();
64+
}
65+
4966
/**
5067
* Returns a new {@code FormatterStep} which, observing the value of {@code formatIfMatches},
5168
* will only apply, or not, its changes to files which pass the given filter.

lib/src/main/java/com/diffplug/spotless/FormatterStepEqualityOnStateSerialization.java

+9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.File;
1919
import java.io.Serializable;
2020
import java.util.Arrays;
21+
import java.util.List;
2122

2223
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2324

@@ -48,6 +49,14 @@ public String format(String rawUnix, File file) throws Exception {
4849
return formatter.apply(rawUnix, file);
4950
}
5051

52+
@Override
53+
public List<Lint> lint(String content, File file) throws Exception {
54+
if (formatter == null) {
55+
formatter = stateToFormatter(state());
56+
}
57+
return formatter.lint(content, file);
58+
}
59+
5160
@Override
5261
public boolean equals(Object o) {
5362
if (o == null) {

0 commit comments

Comments
 (0)