Skip to content

Commit 5a11803

Browse files
committed
make FuzzyViolationLineMatcher ignore hint suffixes
The hint messages added for synthetic/anonymous class violations could break existing frozen violation stores, since FuzzyViolationLineMatcher would treat violations with hints as different from violations without hints. This change makes FuzzyViolationLineMatcher ignore hint suffixes (text following line separator + line separator + "Hint:") when comparing violations, similar to how it already ignores line numbers and anonymous class numbers. Resolves: #1509 Signed-off-by: chadongmin <cdm2883@naver.com>
1 parent d49621a commit 5a11803

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

archunit/src/main/java/com/tngtech/archunit/library/freeze/ViolationLineMatcherFactory.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,23 @@ private static ViolationLineMatcher createInstance(String lineMatcherClassName)
4343
}
4444

4545
/**
46-
* ignores numbers that are potentially line numbers (digits following a ':' and preceding a ')')
46+
* Ignores numbers that are potentially line numbers (digits following a ':' and preceding a ')')
4747
* or compiler-generated numbers of anonymous classes or lambda expressions (digits following a '$').
48+
* Also ignores hint suffixes (text following line separator + line separator + "Hint:") that provide
49+
* additional guidance but are not part of the core violation message.
4850
*/
4951
private static class FuzzyViolationLineMatcher implements ViolationLineMatcher {
52+
private static final String HINT_MARKER = System.lineSeparator() + System.lineSeparator() + "Hint:";
53+
5054
@Override
5155
public boolean matches(String str1, String str2) {
5256
// Compare relevant substrings, in a more performant way than a regex solution like this:
5357
//
5458
// normalize = str -> str.replaceAll(":\\d+\\)", ":0)").replaceAll("\\$\\d+", "\\$0");
5559
// return normalize.apply(str1).equals(normalize.apply(str2));
5660

57-
RelevantPartIterator relevantPart1 = new RelevantPartIterator(str1);
58-
RelevantPartIterator relevantPart2 = new RelevantPartIterator(str2);
61+
RelevantPartIterator relevantPart1 = new RelevantPartIterator(removeHintSuffix(str1));
62+
RelevantPartIterator relevantPart2 = new RelevantPartIterator(removeHintSuffix(str2));
5963
while (relevantPart1.hasNext() && relevantPart2.hasNext()) {
6064
if (!relevantPart1.next().equals(relevantPart2.next())) {
6165
return false;
@@ -64,6 +68,11 @@ public boolean matches(String str1, String str2) {
6468
return !relevantPart1.hasNext() && !relevantPart2.hasNext();
6569
}
6670

71+
private static String removeHintSuffix(String str) {
72+
int hintIndex = str.indexOf(HINT_MARKER);
73+
return hintIndex >= 0 ? str.substring(0, hintIndex) : str;
74+
}
75+
6776
static class RelevantPartIterator {
6877
private final String str;
6978
private final int length;

archunit/src/test/java/com/tngtech/archunit/library/freeze/ViolationLineMatcherFactoryTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,39 @@ public void default_matcher(String str1, String str2, boolean expected) {
5454
.as(String.format("'%s' matches '%s'", str1, str2))
5555
.isEqualTo(expected);
5656
}
57+
58+
@Test
59+
public void default_matcher_ignores_hint_suffix() {
60+
ViolationLineMatcher defaultMatcher = ViolationLineMatcherFactory.create();
61+
62+
String baseViolation = "Class <com.example.MyClass$1> does not have simple name ending with 'Service' in (MyClass.java:42)";
63+
String hintSuffix = System.lineSeparator() + System.lineSeparator() +
64+
"Hint: The failing class appears to be a synthetic or anonymous class " +
65+
"generated by the compiler (e.g., from lambdas, switch expressions, or inner classes).";
66+
String violationWithHint = baseViolation + hintSuffix;
67+
68+
assertThat(defaultMatcher.matches(baseViolation, violationWithHint))
69+
.as("violation without hint should match same violation with hint")
70+
.isTrue();
71+
assertThat(defaultMatcher.matches(violationWithHint, baseViolation))
72+
.as("violation with hint should match same violation without hint")
73+
.isTrue();
74+
assertThat(defaultMatcher.matches(violationWithHint, violationWithHint))
75+
.as("violation with hint should match itself")
76+
.isTrue();
77+
}
78+
79+
@Test
80+
public void default_matcher_ignores_hint_suffix_with_different_line_numbers() {
81+
ViolationLineMatcher defaultMatcher = ViolationLineMatcherFactory.create();
82+
83+
String violation1 = "Class <com.example.MyClass$1> does not have simple name ending with 'Service' in (MyClass.java:42)";
84+
String violation2WithHint = "Class <com.example.MyClass$2> does not have simple name ending with 'Service' in (MyClass.java:100)" +
85+
System.lineSeparator() + System.lineSeparator() +
86+
"Hint: The failing class appears to be a synthetic or anonymous class.";
87+
88+
assertThat(defaultMatcher.matches(violation1, violation2WithHint))
89+
.as("violations with different line numbers and anonymous class numbers should match even with hint")
90+
.isTrue();
91+
}
5792
}

0 commit comments

Comments
 (0)