Skip to content

Commit f3be100

Browse files
committed
Introduce LintState which efficiently reads lint data from both format calls and lint calls.
1 parent fe92d05 commit f3be100

File tree

3 files changed

+159
-15
lines changed

3 files changed

+159
-15
lines changed

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public boolean didNotConverge() {
4646
return this == didNotConverge;
4747
}
4848

49-
private byte[] canonicalBytes() {
49+
byte[] canonicalBytes() {
5050
if (canonicalBytes == null) {
5151
throw new IllegalStateException("First make sure that {@code !isClean()} and {@code !didNotConverge()}");
5252
}
@@ -81,7 +81,7 @@ public static class Calculation {
8181
private final Formatter formatter;
8282
private final File file;
8383
private final byte[] rawBytes;
84-
private final String raw;
84+
final String raw;
8585

8686
private Calculation(Formatter formatter, File file, byte[] rawBytes) {
8787
this.formatter = formatter;
@@ -101,10 +101,19 @@ private Calculation(Formatter formatter, File file, byte[] rawBytes) {
101101
* due to diverging idempotence.
102102
*/
103103
public DirtyState calculateDirtyState() {
104+
return calculateDirtyState(new ValuePerStep<>(formatter));
105+
}
106+
107+
/**
108+
* Calculates whether the given file is dirty according to a PaddedCell invocation of the given formatter.
109+
* DirtyState includes the clean state of the file, as well as a warning if we were not able to apply the formatter
110+
* due to diverging idempotence.
111+
*/
112+
DirtyState calculateDirtyState(ValuePerStep<Throwable> exceptionPerStep) {
104113
String rawUnix = LineEnding.toUnix(raw);
105114

106115
// enforce the format
107-
String formattedUnix = formatter.compute(rawUnix, file);
116+
String formattedUnix = formatter.computeWithLint(rawUnix, file, exceptionPerStep);
108117
// convert the line endings if necessary
109118
String formatted = formatter.computeLineEndings(formattedUnix, file);
110119

@@ -115,13 +124,13 @@ public DirtyState calculateDirtyState() {
115124
}
116125

117126
// F(input) != input, so we'll do a padded check
118-
String doubleFormattedUnix = formatter.compute(formattedUnix, file);
127+
String doubleFormattedUnix = formatter.computeWithLint(formattedUnix, file, exceptionPerStep);
119128
if (doubleFormattedUnix.equals(formattedUnix)) {
120129
// most dirty files are idempotent-dirty, so this is a quick-short circuit for that common case
121130
return new DirtyState(formattedBytes);
122131
}
123132

124-
PaddedCell cell = PaddedCell.check(formatter, file, rawUnix);
133+
PaddedCell cell = PaddedCell.check(formatter, file, rawUnix, exceptionPerStep);
125134
if (!cell.isResolvable()) {
126135
return didNotConverge;
127136
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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.io.File;
19+
import java.io.IOException;
20+
import java.nio.file.Files;
21+
import java.util.LinkedHashMap;
22+
import java.util.List;
23+
import java.util.Map;
24+
25+
import javax.annotation.Nullable;
26+
27+
public class LintState {
28+
private final DirtyState dirtyState;
29+
private final @Nullable List<List<Lint>> lintsPerStep;
30+
31+
private LintState(DirtyState dirtyState, @Nullable List<List<Lint>> lintsPerStep) {
32+
this.dirtyState = dirtyState;
33+
this.lintsPerStep = lintsPerStep;
34+
}
35+
36+
public DirtyState getDirtyState() {
37+
return dirtyState;
38+
}
39+
40+
public boolean isHasLints() {
41+
return lintsPerStep != null;
42+
}
43+
44+
public Map<FormatterStep, List<Lint>> getLints(Formatter formatter) {
45+
if (lintsPerStep == null) {
46+
throw new IllegalStateException("Check `isHasLints` first!");
47+
}
48+
if (lintsPerStep.size() != formatter.getSteps().size()) {
49+
throw new IllegalStateException("LintState was created with a different formatter!");
50+
}
51+
Map<FormatterStep, List<Lint>> result = new LinkedHashMap<>();
52+
for (int i = 0; i < lintsPerStep.size(); i++) {
53+
List<Lint> lints = lintsPerStep.get(i);
54+
if (lints != null) {
55+
result.put(formatter.getSteps().get(i), lints);
56+
}
57+
}
58+
return result;
59+
}
60+
61+
public static LintState of(Formatter formatter, File file) throws IOException {
62+
return of(formatter, file, Files.readAllBytes(file.toPath()));
63+
}
64+
65+
public static LintState of(Formatter formatter, File file, byte[] rawBytes) {
66+
var exceptions = new ValuePerStep<Throwable>(formatter);
67+
var dirtyCalculation = DirtyState.of(formatter, file, rawBytes);
68+
var dirty = dirtyCalculation.calculateDirtyState(exceptions);
69+
70+
String toLint = LineEnding.toUnix(dirty.isClean() || dirty.didNotConverge() ? dirtyCalculation.raw : new String(dirty.canonicalBytes(), formatter.getEncoding()));
71+
72+
var lints = new ValuePerStep<List<Lint>>(formatter);
73+
// if a step did not throw an exception, then it gets to check for lints if it wants
74+
for (int i = 0; i < formatter.getSteps().size(); i++) {
75+
FormatterStep step = formatter.getSteps().get(i);
76+
Throwable exception = exceptions.get(i);
77+
if (exception == null || exception == formatStepCausedNoChange()) {
78+
try {
79+
var lintsForStep = step.lint(toLint, file);
80+
if (lintsForStep != null && !lintsForStep.isEmpty()) {
81+
lints.set(i, lintsForStep);
82+
}
83+
} catch (Exception e) {
84+
lints.set(i, List.of(Lint.createFromThrowable(step, toLint, e)));
85+
}
86+
}
87+
}
88+
// for steps that did throw an exception, we will turn those into lints
89+
// we try to reuse the exception if possible, but that is only possible if other steps
90+
// didn't change the formatted value. so we start at the end, and note when the string
91+
// gets changed by a step. if it does, we rerun the steps to get an exception with accurate line numbers.
92+
boolean nothingHasChangedSinceLast = true;
93+
for (int i = formatter.getSteps().size() - 1; i >= 0; i--) {
94+
FormatterStep step = formatter.getSteps().get(i);
95+
Throwable exception = exceptions.get(i);
96+
if (exception != null && exception != formatStepCausedNoChange()) {
97+
nothingHasChangedSinceLast = false;
98+
}
99+
Throwable exceptionForLint;
100+
if (nothingHasChangedSinceLast) {
101+
exceptionForLint = exceptions.get(i);
102+
} else {
103+
// steps changed the content, so we need to rerun to get an exception with accurate line numbers
104+
try {
105+
step.format(toLint, file);
106+
exceptionForLint = null; // the exception "went away" because it got fixed by a later step
107+
} catch (Throwable e) {
108+
exceptionForLint = e;
109+
}
110+
}
111+
List<Lint> lintsForStep;
112+
if (exceptionForLint instanceof Lint.Has) {
113+
lintsForStep = ((Lint.Has) exceptionForLint).getLints();
114+
} else if (exceptionForLint != null) {
115+
lintsForStep = List.of(Lint.createFromThrowable(step, toLint, exceptionForLint));
116+
} else {
117+
lintsForStep = List.of();
118+
}
119+
if (!lintsForStep.isEmpty()) {
120+
lints.set(i, lintsForStep);
121+
}
122+
}
123+
return new LintState(dirty, lints.indexOfFirstValue() == -1 ? null : lints);
124+
}
125+
126+
static Throwable formatStepCausedNoChange() {
127+
return FormatterCausedNoChange.INSTANCE;
128+
}
129+
130+
private static class FormatterCausedNoChange extends Exception {
131+
private static final long serialVersionUID = 1L;
132+
133+
static final FormatterCausedNoChange INSTANCE = new FormatterCausedNoChange();
134+
}
135+
}

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,29 +86,29 @@ public static PaddedCell check(Formatter formatter, File file) {
8686
byte[] rawBytes = ThrowingEx.get(() -> Files.readAllBytes(file.toPath()));
8787
String raw = new String(rawBytes, formatter.getEncoding());
8888
String original = LineEnding.toUnix(raw);
89-
return check(formatter, file, original, MAX_CYCLE);
89+
return check(formatter, file, original, MAX_CYCLE, new ValuePerStep<>(formatter));
9090
}
9191

9292
public static PaddedCell check(Formatter formatter, File file, String originalUnix) {
93-
return check(
94-
Objects.requireNonNull(formatter, "formatter"),
95-
Objects.requireNonNull(file, "file"),
96-
Objects.requireNonNull(originalUnix, "originalUnix"),
97-
MAX_CYCLE);
93+
return check(formatter, file, originalUnix, new ValuePerStep<>(formatter));
94+
}
95+
96+
public static PaddedCell check(Formatter formatter, File file, String originalUnix, ValuePerStep<Throwable> exceptionPerStep) {
97+
return check(formatter, file, originalUnix, MAX_CYCLE, exceptionPerStep);
9898
}
9999

100100
private static final int MAX_CYCLE = 10;
101101

102-
private static PaddedCell check(Formatter formatter, File file, String original, int maxLength) {
102+
private static PaddedCell check(Formatter formatter, File file, String original, int maxLength, ValuePerStep<Throwable> exceptionPerStep) {
103103
if (maxLength < 2) {
104104
throw new IllegalArgumentException("maxLength must be at least 2");
105105
}
106-
String appliedOnce = formatter.compute(original, file);
106+
String appliedOnce = formatter.computeWithLint(original, file, exceptionPerStep);
107107
if (appliedOnce.equals(original)) {
108108
return Type.CONVERGE.create(file, Collections.singletonList(appliedOnce));
109109
}
110110

111-
String appliedTwice = formatter.compute(appliedOnce, file);
111+
String appliedTwice = formatter.computeWithLint(appliedOnce, file, exceptionPerStep);
112112
if (appliedOnce.equals(appliedTwice)) {
113113
return Type.CONVERGE.create(file, Collections.singletonList(appliedOnce));
114114
}
@@ -118,7 +118,7 @@ private static PaddedCell check(Formatter formatter, File file, String original,
118118
appliedN.add(appliedTwice);
119119
String input = appliedTwice;
120120
while (appliedN.size() < maxLength) {
121-
String output = formatter.compute(input, file);
121+
String output = formatter.computeWithLint(input, file, exceptionPerStep);
122122
if (output.equals(input)) {
123123
return Type.CONVERGE.create(file, appliedN);
124124
} else {

0 commit comments

Comments
 (0)