Skip to content

Commit 7feb013

Browse files
authored
Lint suppression API for Gradle (#2307)
2 parents 13d24ae + 611a45d commit 7feb013

24 files changed

+567
-298
lines changed

CHANGES.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1111

1212
## [Unreleased]
1313
### Added
14-
* APIs to support linting. (implemented in [#2148](https://github.com/diffplug/spotless/pull/2148) and [#2149](https://github.com/diffplug/spotless/pull/2149))
14+
* APIs to support linting. (implemented in [#2148](https://github.com/diffplug/spotless/pull/2148), [#2149](https://github.com/diffplug/spotless/pull/2149), [#2307](https://github.com/diffplug/spotless/pull/2307))
1515
* Spotless is still primarily a formatter, not a linter. But when formatting fails, it's more flexible to model those failures as lints so that the formatting can continue and ideally we can also capture the line numbers causing the failure.
1616
* `Lint` models a single change. A `FormatterStep` can create a lint by:
1717
* throwing an exception during formatting, ideally `throw Lint.atLine(127, "code", "Well what happened was...")`

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public static DirtyState of(Formatter formatter, File file, byte[] rawBytes) {
8080
public static DirtyState of(Formatter formatter, File file, byte[] rawBytes, String raw) {
8181
var valuePerStep = new ValuePerStep<Throwable>(formatter);
8282
DirtyState state = of(formatter, file, rawBytes, raw, valuePerStep);
83-
LintPolicy.legacyBehavior(formatter, file, valuePerStep);
83+
Formatter.legacyErrorBehavior(formatter, file, valuePerStep);
8484
return state;
8585
}
8686

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

-57
This file was deleted.

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
import java.util.List;
2929
import java.util.Objects;
3030

31+
import org.slf4j.Logger;
32+
import org.slf4j.LoggerFactory;
33+
3134
/** Formatter which performs the full formatting. */
3235
public final class Formatter implements Serializable, AutoCloseable {
3336
private static final long serialVersionUID = 1L;
@@ -130,10 +133,22 @@ public String computeLineEndings(String unix, File file) {
130133
public String compute(String unix, File file) {
131134
ValuePerStep<Throwable> exceptionPerStep = new ValuePerStep<>(this);
132135
String result = computeWithLint(unix, file, exceptionPerStep);
133-
LintPolicy.legacyBehavior(this, file, exceptionPerStep);
136+
legacyErrorBehavior(this, file, exceptionPerStep);
134137
return result;
135138
}
136139

140+
static void legacyErrorBehavior(Formatter formatter, File file, ValuePerStep<Throwable> exceptionPerStep) {
141+
for (int i = 0; i < formatter.getSteps().size(); ++i) {
142+
Throwable exception = exceptionPerStep.get(i);
143+
if (exception != null && exception != LintState.formatStepCausedNoChange()) {
144+
logger.error("Step '{}' found problem in '{}':\n{}", formatter.getSteps().get(i).getName(), file.getName(), exception.getMessage(), exception);
145+
throw ThrowingEx.asRuntimeRethrowError(exception);
146+
}
147+
}
148+
}
149+
150+
private static final Logger logger = LoggerFactory.getLogger(Formatter.class);
151+
137152
/**
138153
* Returns the result of calling all of the FormatterSteps, while also
139154
* tracking any exceptions which are thrown.

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

+38-14
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,28 @@ public static Lint atLine(int line, String ruleId, String detail) {
3737
return new Lint(line, ruleId, detail);
3838
}
3939

40-
public static Lint atLineRange(int lineStart, int lineEnd, String ruleId, String detail) {
41-
return new Lint(lineStart, lineEnd, ruleId, detail);
40+
public static Lint atLineRange(int lineStart, int lineEnd, String shortCode, String detail) {
41+
return new Lint(lineStart, lineEnd, shortCode, detail);
4242
}
4343

4444
private static final long serialVersionUID = 1L;
4545

4646
private int lineStart, lineEnd; // 1-indexed, inclusive
47-
private String ruleId; // e.g. CN_IDIOM https://spotbugs.readthedocs.io/en/stable/bugDescriptions.html#cn-class-implements-cloneable-but-does-not-define-or-use-clone-method-cn-idiom
47+
private String shortCode; // e.g. CN_IDIOM https://spotbugs.readthedocs.io/en/stable/bugDescriptions.html#cn-class-implements-cloneable-but-does-not-define-or-use-clone-method-cn-idiom
4848
private String detail;
4949

50-
private Lint(int lineStart, int lineEnd, String ruleId, String detail) {
50+
private Lint(int lineStart, int lineEnd, String shortCode, String detail) {
5151
if (lineEnd < lineStart) {
5252
throw new IllegalArgumentException("lineEnd must be >= lineStart: lineStart=" + lineStart + " lineEnd=" + lineEnd);
5353
}
5454
this.lineStart = lineStart;
5555
this.lineEnd = lineEnd;
56-
this.ruleId = LineEnding.toUnix(ruleId);
56+
this.shortCode = LineEnding.toUnix(shortCode);
5757
this.detail = LineEnding.toUnix(detail);
5858
}
5959

60-
private Lint(int line, String ruleId, String detail) {
61-
this(line, line, ruleId, detail);
60+
private Lint(int line, String shortCode, String detail) {
61+
this(line, line, shortCode, detail);
6262
}
6363

6464
public int getLineStart() {
@@ -69,8 +69,8 @@ public int getLineEnd() {
6969
return lineEnd;
7070
}
7171

72-
public String getRuleId() {
73-
return ruleId;
72+
public String getShortCode() {
73+
return shortCode;
7474
}
7575

7676
public String getDetail() {
@@ -115,12 +115,12 @@ public RuntimeException shortcut() {
115115
public String toString() {
116116
if (lineStart == lineEnd) {
117117
if (lineStart == LINE_UNDEFINED) {
118-
return "LINE_UNDEFINED: (" + ruleId + ") " + detail;
118+
return "LINE_UNDEFINED: (" + shortCode + ") " + detail;
119119
} else {
120-
return "L" + lineStart + ": (" + ruleId + ") " + detail;
120+
return "L" + lineStart + ": (" + shortCode + ") " + detail;
121121
}
122122
} else {
123-
return "L" + lineStart + "-" + lineEnd + ": (" + ruleId + ") " + detail;
123+
return "L" + lineStart + "-" + lineEnd + ": (" + shortCode + ") " + detail;
124124
}
125125
}
126126

@@ -131,12 +131,12 @@ public boolean equals(Object o) {
131131
if (o == null || getClass() != o.getClass())
132132
return false;
133133
Lint lint = (Lint) o;
134-
return lineStart == lint.lineStart && lineEnd == lint.lineEnd && Objects.equals(ruleId, lint.ruleId) && Objects.equals(detail, lint.detail);
134+
return lineStart == lint.lineStart && lineEnd == lint.lineEnd && Objects.equals(shortCode, lint.shortCode) && Objects.equals(detail, lint.detail);
135135
}
136136

137137
@Override
138138
public int hashCode() {
139-
return Objects.hash(lineStart, lineEnd, ruleId, detail);
139+
return Objects.hash(lineStart, lineEnd, shortCode, detail);
140140
}
141141

142142
/** Attempts to parse a line number from the given exception. */
@@ -189,4 +189,28 @@ private static String msgFrom(String message) {
189189
}
190190

191191
public static final int LINE_UNDEFINED = -1;
192+
193+
public void addWarningMessageTo(StringBuilder buffer, String stepName, boolean oneLine) {
194+
if (lineStart == Lint.LINE_UNDEFINED) {
195+
buffer.append("LINE_UNDEFINED");
196+
} else {
197+
buffer.append("L");
198+
buffer.append(lineStart);
199+
if (lineEnd != lineStart) {
200+
buffer.append("-").append(lineEnd);
201+
}
202+
}
203+
buffer.append(" ");
204+
buffer.append(stepName).append("(").append(shortCode).append(") ");
205+
206+
int firstNewline = detail.indexOf('\n');
207+
if (firstNewline == -1) {
208+
buffer.append(detail);
209+
} else if (oneLine) {
210+
buffer.append(detail, 0, firstNewline);
211+
buffer.append(" (...)");
212+
} else {
213+
buffer.append(detail);
214+
}
215+
}
192216
}

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

-43
This file was deleted.

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

+45-26
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,18 @@
1818
import java.io.File;
1919
import java.io.IOException;
2020
import java.nio.file.Files;
21+
import java.util.ArrayList;
22+
import java.util.Iterator;
2123
import java.util.LinkedHashMap;
2224
import java.util.List;
23-
import java.util.Map;
2425

2526
import javax.annotation.Nullable;
2627

2728
public class LintState {
2829
private final DirtyState dirtyState;
2930
private final @Nullable List<List<Lint>> lintsPerStep;
3031

31-
private LintState(DirtyState dirtyState, @Nullable List<List<Lint>> lintsPerStep) {
32+
LintState(DirtyState dirtyState, @Nullable List<List<Lint>> lintsPerStep) {
3233
this.dirtyState = dirtyState;
3334
this.lintsPerStep = lintsPerStep;
3435
}
@@ -45,23 +46,61 @@ public boolean isClean() {
4546
return dirtyState.isClean() && !isHasLints();
4647
}
4748

48-
public Map<FormatterStep, List<Lint>> getLints(Formatter formatter) {
49+
public LinkedHashMap<String, List<Lint>> getLintsByStep(Formatter formatter) {
4950
if (lintsPerStep == null) {
5051
throw new IllegalStateException("Check `isHasLints` first!");
5152
}
5253
if (lintsPerStep.size() != formatter.getSteps().size()) {
5354
throw new IllegalStateException("LintState was created with a different formatter!");
5455
}
55-
Map<FormatterStep, List<Lint>> result = new LinkedHashMap<>();
56+
LinkedHashMap<String, List<Lint>> result = new LinkedHashMap<>();
5657
for (int i = 0; i < lintsPerStep.size(); i++) {
5758
List<Lint> lints = lintsPerStep.get(i);
5859
if (lints != null) {
59-
result.put(formatter.getSteps().get(i), lints);
60+
FormatterStep step = formatter.getSteps().get(i);
61+
result.put(step.getName(), lints);
6062
}
6163
}
6264
return result;
6365
}
6466

67+
public LintState withRemovedSuppressions(Formatter formatter, String relativePath, List<LintSuppression> suppressions) {
68+
if (lintsPerStep == null) {
69+
return this;
70+
}
71+
if (formatter.getSteps().size() != lintsPerStep.size()) {
72+
throw new IllegalStateException("LintState was created with a different formatter!");
73+
}
74+
boolean changed = false;
75+
ValuePerStep<List<Lint>> perStepFiltered = new ValuePerStep<>(formatter);
76+
for (int i = 0; i < lintsPerStep.size(); i++) {
77+
FormatterStep step = formatter.getSteps().get(i);
78+
List<Lint> lintsOriginal = lintsPerStep.get(i);
79+
if (lintsOriginal != null) {
80+
List<Lint> lints = new ArrayList<>(lintsOriginal);
81+
Iterator<Lint> iter = lints.iterator();
82+
while (iter.hasNext()) {
83+
Lint lint = iter.next();
84+
for (LintSuppression suppression : suppressions) {
85+
if (suppression.suppresses(relativePath, step, lint)) {
86+
changed = true;
87+
iter.remove();
88+
break;
89+
}
90+
}
91+
}
92+
if (!lints.isEmpty()) {
93+
perStepFiltered.set(i, lints);
94+
}
95+
}
96+
}
97+
if (changed) {
98+
return new LintState(dirtyState, perStepFiltered.indexOfFirstValue() == -1 ? null : perStepFiltered);
99+
} else {
100+
return this;
101+
}
102+
}
103+
65104
public String asStringDetailed(File file, Formatter formatter) {
66105
return asString(file, formatter, false);
67106
}
@@ -81,27 +120,7 @@ private String asString(File file, Formatter formatter, boolean oneLine) {
81120
FormatterStep step = formatter.getSteps().get(i);
82121
for (Lint lint : lints) {
83122
result.append(file.getName()).append(":");
84-
if (lint.getLineStart() == Lint.LINE_UNDEFINED) {
85-
result.append("LINE_UNDEFINED");
86-
} else {
87-
result.append("L");
88-
result.append(lint.getLineStart());
89-
if (lint.getLineEnd() != lint.getLineStart()) {
90-
result.append("-").append(lint.getLineEnd());
91-
}
92-
}
93-
result.append(" ");
94-
result.append(step.getName()).append("(").append(lint.getRuleId()).append(") ");
95-
96-
int firstNewline = lint.getDetail().indexOf('\n');
97-
if (firstNewline == -1) {
98-
result.append(lint.getDetail());
99-
} else if (oneLine) {
100-
result.append(lint.getDetail(), 0, firstNewline);
101-
result.append(" (...)");
102-
} else {
103-
result.append(lint.getDetail());
104-
}
123+
lint.addWarningMessageTo(result, step.getName(), oneLine);
105124
result.append("\n");
106125
}
107126
}

0 commit comments

Comments
 (0)