Skip to content

Commit b57bf24

Browse files
committed
Formatter can now capture exceptions per-formatter, rethrows if you don't call it in the "special" way.
1 parent c9f52d5 commit b57bf24

File tree

2 files changed

+123
-8
lines changed

2 files changed

+123
-8
lines changed
Lines changed: 101 additions & 0 deletions
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/Formatter.java

Lines changed: 22 additions & 8 deletions
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;

0 commit comments

Comments
 (0)