Skip to content

Commit 777fd5c

Browse files
committed
Add a Lint class, along with ShortcutException for sending them.
1 parent 319fc79 commit 777fd5c

File tree

2 files changed

+168
-1
lines changed

2 files changed

+168
-1
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright 2022-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.Serializable;
19+
import java.util.Arrays;
20+
import java.util.Collection;
21+
import java.util.List;
22+
import java.util.Objects;
23+
24+
/**
25+
* Models a linted line or line range. Note that there is no concept of severity level - responsibility
26+
* for severity and confidence are pushed down to the configuration of the lint tool. If a lint makes it
27+
* to Spotless, then it is by definition.
28+
*/
29+
public final class Lint implements Serializable {
30+
/** Any exception which implements this interface will have its lints extracted and reported cleanly to the user. */
31+
public interface Has {
32+
List<Lint> getLints();
33+
}
34+
35+
/** An exception for shortcutting execution to report a lint to the user. */
36+
public static class ShortcutException extends RuntimeException implements Has {
37+
public ShortcutException(Lint... lints) {
38+
this(Arrays.asList(lints));
39+
}
40+
41+
private final List<Lint> lints;
42+
43+
public ShortcutException(Collection<Lint> lints) {
44+
this.lints = List.copyOf(lints);
45+
}
46+
47+
@Override
48+
public List<Lint> getLints() {
49+
return lints;
50+
}
51+
}
52+
53+
private static final long serialVersionUID = 1L;
54+
55+
private int lineStart, lineEnd; // 1-indexed, inclusive
56+
private String code; // 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
57+
private String msg;
58+
59+
private Lint(int lineStart, int lineEnd, String lintCode, String lintMsg) {
60+
this.lineStart = lineStart;
61+
this.lineEnd = lineEnd;
62+
this.code = LineEnding.toUnix(lintCode);
63+
this.msg = LineEnding.toUnix(lintMsg);
64+
}
65+
66+
public static Lint create(String code, String msg, int lineStart, int lineEnd) {
67+
if (lineEnd < lineStart) {
68+
throw new IllegalArgumentException("lineEnd must be >= lineStart: lineStart=" + lineStart + " lineEnd=" + lineEnd);
69+
}
70+
return new Lint(lineStart, lineEnd, code, msg);
71+
}
72+
73+
public static Lint create(String code, String msg, int line) {
74+
return new Lint(line, line, code, msg);
75+
}
76+
77+
public int getLineStart() {
78+
return lineStart;
79+
}
80+
81+
public int getLineEnd() {
82+
return lineEnd;
83+
}
84+
85+
public String getCode() {
86+
return code;
87+
}
88+
89+
public String getMsg() {
90+
return msg;
91+
}
92+
93+
@Override
94+
public String toString() {
95+
if (lineStart == lineEnd) {
96+
return lineStart + ": (" + code + ") " + msg;
97+
} else {
98+
return lineStart + "-" + lineEnd + ": (" + code + ") " + msg;
99+
}
100+
}
101+
102+
@Override
103+
public boolean equals(Object o) {
104+
if (this == o)
105+
return true;
106+
if (o == null || getClass() != o.getClass())
107+
return false;
108+
Lint lint = (Lint) o;
109+
return lineStart == lint.lineStart && lineEnd == lint.lineEnd && Objects.equals(code, lint.code) && Objects.equals(msg, lint.msg);
110+
}
111+
112+
@Override
113+
public int hashCode() {
114+
return Objects.hash(lineStart, lineEnd, code, msg);
115+
}
116+
117+
/** Attempts to parse a line number from the given exception. */
118+
static Lint createFromThrowable(FormatterStep step, String content, Throwable e) {
119+
Throwable current = e;
120+
while (current != null) {
121+
String message = current.getMessage();
122+
int lineNumber = lineNumberFor(message);
123+
if (lineNumber != -1) {
124+
return Lint.create(step.getName(), msgFrom(message), lineNumber);
125+
}
126+
current = current.getCause();
127+
}
128+
int numNewlines = (int) content.codePoints().filter(c -> c == '\n').count();
129+
return Lint.create(step.getName(), ThrowingEx.stacktrace(e), 1, 1 + numNewlines);
130+
}
131+
132+
private static int lineNumberFor(String message) {
133+
if (message == null) {
134+
return -1;
135+
}
136+
int firstColon = message.indexOf(':');
137+
if (firstColon == -1) {
138+
return -1;
139+
}
140+
String candidateNum = message.substring(0, firstColon);
141+
try {
142+
return Integer.parseInt(candidateNum);
143+
} catch (NumberFormatException e) {
144+
return -1;
145+
}
146+
}
147+
148+
private static String msgFrom(String message) {
149+
for (int i = 0; i < message.length(); ++i) {
150+
if (Character.isLetter(message.charAt(i))) {
151+
return message.substring(i);
152+
}
153+
}
154+
return "";
155+
}
156+
}

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

Lines changed: 12 additions & 1 deletion
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.
@@ -15,6 +15,9 @@
1515
*/
1616
package com.diffplug.spotless;
1717

18+
import java.io.PrintWriter;
19+
import java.io.StringWriter;
20+
1821
/**
1922
* Basic functional interfaces which throw exception, along with
2023
* static helper methods for calling them.
@@ -142,4 +145,12 @@ public WrappedAsRuntimeException(Throwable e) {
142145
super(e);
143146
}
144147
}
148+
149+
public static String stacktrace(Throwable e) {
150+
StringWriter out = new StringWriter();
151+
PrintWriter writer = new PrintWriter(out);
152+
e.printStackTrace(writer);
153+
writer.flush();
154+
return out.toString();
155+
}
145156
}

0 commit comments

Comments
 (0)