Skip to content

Commit 5275a31

Browse files
committed
Add flow assertion to CheckVerifier comments
1 parent 29a7f45 commit 5275a31

File tree

4 files changed

+472
-26
lines changed

4 files changed

+472
-26
lines changed

delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/CheckVerifierImpl.java

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import java.nio.file.StandardCopyOption;
5050
import java.util.ArrayList;
5151
import java.util.Collection;
52+
import java.util.Comparator;
5253
import java.util.List;
5354
import java.util.Map;
5455
import java.util.Objects;
@@ -161,28 +162,74 @@ public void verifyIssues() {
161162

162163
private static void verifyIssuesOnLinesInternal(
163164
List<Issue> issues, List<IssueExpectation> expectedIssues) {
164-
List<Integer> unexpectedLines = new ArrayList<>();
165-
List<Integer> expectedLines =
166-
expectedIssues.stream().map(IssueExpectation::getBeginLine).collect(Collectors.toList());
165+
List<IssueExpectation> unexpectedIssues = new ArrayList<>();
166+
List<IssueExpectation> expectations = new ArrayList<>(expectedIssues);
167167

168168
for (Issue issue : issues) {
169-
IssueLocation issueLocation = issue.primaryLocation();
170-
171-
TextRange textRange = issueLocation.textRange();
172-
if (textRange == null) {
173-
throw new AssertionError(
174-
String.format(
175-
"Expected issues to be raised at line level, not at %s level",
176-
issueLocation.inputComponent().isFile() ? "file" : "project"));
169+
Optional<IssueExpectation> expectedIssue = findIssue(issue, expectations);
170+
if (expectedIssue.isPresent()) {
171+
expectations.remove(expectedIssue.get());
172+
} else {
173+
unexpectedIssues.add(expectationFromIssue(issue));
177174
}
175+
}
176+
177+
assertIssueMismatchesEmpty(expectations, unexpectedIssues);
178+
}
179+
180+
private static IssueExpectation expectationFromIssue(Issue issue) {
181+
int primaryLine = getStartingLine(issue.primaryLocation());
182+
List<List<Integer>> actualLines =
183+
issue.flows().stream()
184+
.map(
185+
flow ->
186+
flow.locations().stream()
187+
.map(location -> getStartingLine(location) - primaryLine)
188+
.collect(Collectors.toList()))
189+
.sorted(Comparator.comparing(list -> list.get(0)))
190+
.collect(Collectors.toList());
191+
return new IssueExpectation(primaryLine, actualLines);
192+
}
193+
194+
private static Optional<IssueExpectation> findIssue(
195+
Issue issue, List<IssueExpectation> expectations) {
196+
int line = getStartingLine(issue.primaryLocation());
178197

179-
Integer line = textRange.start().line();
180-
if (!expectedLines.remove(line)) {
181-
unexpectedLines.add(line);
198+
for (IssueExpectation expectation : expectations) {
199+
if (expectation.getBeginLine() != line
200+
|| expectation.getFlowLines().size() != issue.flows().size()) {
201+
continue;
202+
}
203+
List<List<Integer>> expectedLines =
204+
expectation.getFlowLines().stream()
205+
.map(
206+
offsets ->
207+
offsets.stream().map(offset -> offset + line).collect(Collectors.toList()))
208+
.collect(Collectors.toList());
209+
List<List<Integer>> actualLines =
210+
issue.flows().stream()
211+
.map(
212+
flow ->
213+
flow.locations().stream()
214+
.map(CheckVerifierImpl::getStartingLine)
215+
.collect(Collectors.toList()))
216+
.collect(Collectors.toList());
217+
if (expectedLines.equals(actualLines)) {
218+
return Optional.of(expectation);
182219
}
183220
}
221+
return Optional.empty();
222+
}
184223

185-
assertIssueMismatchesEmpty(expectedLines, unexpectedLines);
224+
private static int getStartingLine(IssueLocation location) {
225+
TextRange textRange = location.textRange();
226+
if (textRange == null) {
227+
throw new AssertionError(
228+
String.format(
229+
"Expected issues to be raised at line level, not at %s level",
230+
location.inputComponent().isFile() ? "file" : "project"));
231+
}
232+
return textRange.start().line();
186233
}
187234

188235
private void assertQuickFixes(
@@ -324,17 +371,17 @@ private static boolean textEditMatches(
324371
}
325372

326373
private static void assertIssueMismatchesEmpty(
327-
List<Integer> expectedLines, List<Integer> unexpectedLines) {
328-
if (!expectedLines.isEmpty() || !unexpectedLines.isEmpty()) {
374+
List<IssueExpectation> expectedIssues, List<IssueExpectation> unexpectedIssues) {
375+
if (!expectedIssues.isEmpty() || !unexpectedIssues.isEmpty()) {
329376
StringBuilder message = new StringBuilder("Issues were ");
330-
if (!expectedLines.isEmpty()) {
331-
message.append("expected at ").append(expectedLines);
377+
if (!expectedIssues.isEmpty()) {
378+
message.append("expected at ").append(expectedIssues);
332379
}
333-
if (!expectedLines.isEmpty() && !unexpectedLines.isEmpty()) {
380+
if (!expectedIssues.isEmpty() && !unexpectedIssues.isEmpty()) {
334381
message.append(", ");
335382
}
336-
if (!unexpectedLines.isEmpty()) {
337-
message.append("unexpected at ").append(unexpectedLines);
383+
if (!unexpectedIssues.isEmpty()) {
384+
message.append("unexpected at ").append(unexpectedIssues);
338385
}
339386
throw new AssertionError(message.toString());
340387
}

delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/Expectations.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,21 @@
2121
import au.com.integradev.delphi.file.DelphiFile.DelphiInputFile;
2222
import java.util.ArrayList;
2323
import java.util.Collections;
24+
import java.util.Comparator;
2425
import java.util.List;
2526
import java.util.regex.MatchResult;
27+
import java.util.regex.Matcher;
2628
import java.util.regex.Pattern;
2729
import java.util.stream.Collectors;
2830
import org.sonar.plugins.communitydelphi.api.token.DelphiToken;
2931

3032
class Expectations {
3133
private static final Pattern NONCOMPLIANT_PATTERN =
32-
Pattern.compile("(?i)^//\\s*Noncompliant(?:@([-+]\\d+))?\\b");
34+
Pattern.compile("(?i)^//\\s*Noncompliant(?:@([-+]\\d+))?\\b(.+)?");
35+
36+
private static final Pattern FLOW_PATTERN = Pattern.compile("\\s*\\(([^)]*)\\)");
37+
private static final Pattern FLOW_LINE_OFFSET_PATTERN =
38+
Pattern.compile("\\s*,?\\s*([+-]?\\d+)\\b");
3339

3440
private static final Pattern QUICK_FIX_RANGE_PATTERN =
3541
Pattern.compile("^([+-]\\d+):(\\d*) to ([+-]\\d+):(\\d*)$");
@@ -128,19 +134,65 @@ private static TextEditExpectation parseFixComment(int offset, MatchResult match
128134

129135
private static IssueExpectation parseNoncompliantComment(int beginLine, MatchResult matchResult) {
130136
String offset = matchResult.group(1);
137+
String flows = matchResult.group(2);
138+
139+
int lineOffset = parseIssueOffset(offset);
140+
List<List<Integer>> flowLines = parseFlows(flows);
141+
142+
return new IssueExpectation(beginLine + lineOffset, flowLines);
143+
}
144+
145+
private static int parseIssueOffset(String offset) {
131146
if (offset == null) {
132-
return new IssueExpectation(beginLine);
147+
return 0;
133148
}
134149

135150
try {
136-
return new IssueExpectation(beginLine + Integer.parseInt(offset));
151+
return Integer.parseInt(offset);
137152
} catch (NumberFormatException e) {
138153
throw new AssertionError(
139154
String.format(
140155
"Failed to parse 'Noncompliant' comment line offset '%s' as an integer.", offset));
141156
}
142157
}
143158

159+
private static List<List<Integer>> parseFlows(String flows) {
160+
List<List<Integer>> flowLines = new ArrayList<>();
161+
if (flows == null) {
162+
return flowLines;
163+
}
164+
Matcher flowMatcher = FLOW_PATTERN.matcher(flows);
165+
while (flowMatcher.find()) {
166+
String flow = flowMatcher.group(1);
167+
List<Integer> lines = parseFlowLines(flow);
168+
if (!lines.isEmpty()) {
169+
flowLines.add(lines);
170+
}
171+
}
172+
flowLines.sort(Comparator.comparing(list -> list.get(0)));
173+
return flowLines;
174+
}
175+
176+
private static List<Integer> parseFlowLines(String flow) {
177+
List<Integer> lines = new ArrayList<>();
178+
if (flow == null) {
179+
return lines;
180+
}
181+
Matcher lineMatcher = FLOW_LINE_OFFSET_PATTERN.matcher(flow);
182+
while (lineMatcher.find()) {
183+
String flowLineOffset = lineMatcher.group(1);
184+
try {
185+
lines.add(Integer.parseInt(flowLineOffset));
186+
} catch (NumberFormatException e) {
187+
throw new AssertionError(
188+
String.format(
189+
"Failed to parse 'Noncompliant' flow line offset '%s' as an integer.",
190+
flowLineOffset));
191+
}
192+
}
193+
return lines;
194+
}
195+
144196
private static class MatchResultOnLine {
145197
private final MatchResult matchResult;
146198
private final int line;

delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/IssueExpectation.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,38 @@
1818
*/
1919
package au.com.integradev.delphi.checks.verifier;
2020

21+
import java.util.List;
22+
import java.util.stream.Collectors;
23+
2124
class IssueExpectation {
2225
private final int beginLine;
26+
private final List<List<Integer>> flowLines;
2327

24-
public IssueExpectation(int beginLine) {
28+
public IssueExpectation(int beginLine, List<List<Integer>> flowLines) {
2529
this.beginLine = beginLine;
30+
this.flowLines = flowLines;
2631
}
2732

2833
public int getBeginLine() {
2934
return beginLine;
3035
}
36+
37+
public List<List<Integer>> getFlowLines() {
38+
return flowLines;
39+
}
40+
41+
@Override
42+
public String toString() {
43+
return "{"
44+
+ beginLine
45+
+ " "
46+
+ flowLines.stream()
47+
.map(
48+
list ->
49+
"("
50+
+ list.stream().map(Object::toString).collect(Collectors.joining(", "))
51+
+ ")")
52+
.collect(Collectors.joining(" "))
53+
+ "}";
54+
}
3155
}

0 commit comments

Comments
 (0)