Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `RedundantJump` analysis rule, which flags redundant jump statements, e.g., `Continue`, `Exit`.
- `LoopExecutingAtMostOnce` analysis rule, which flags loop statements that can execute at most once.
- **API:** `RepeatStatementNode::getGuardExpression` method.
- **API:** `RepeatStatementNode::getStatementList` method.
- **API:** `CaseStatementNode::getSelectorExpression` method.
- **API:** `CaseItemStatementNode::getStatement` method.

### Fixed

- Parsing errors where adjacent `>` and `=` tokens were wrongly interpreted as the `>=` operator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -161,28 +162,74 @@ public void verifyIssues() {

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

for (Issue issue : issues) {
IssueLocation issueLocation = issue.primaryLocation();

TextRange textRange = issueLocation.textRange();
if (textRange == null) {
throw new AssertionError(
String.format(
"Expected issues to be raised at line level, not at %s level",
issueLocation.inputComponent().isFile() ? "file" : "project"));
Optional<IssueExpectation> expectedIssue = findIssue(issue, expectations);
if (expectedIssue.isPresent()) {
expectations.remove(expectedIssue.get());
} else {
unexpectedIssues.add(expectationFromIssue(issue));
}
}

assertIssueMismatchesEmpty(expectations, unexpectedIssues);
}

private static IssueExpectation expectationFromIssue(Issue issue) {
int primaryLine = getStartingLine(issue.primaryLocation());
List<List<Integer>> actualLines =
issue.flows().stream()
.map(
flow ->
flow.locations().stream()
.map(location -> getStartingLine(location) - primaryLine)
.collect(Collectors.toList()))
.sorted(Comparator.comparing(list -> list.get(0)))
.collect(Collectors.toList());
return new IssueExpectation(primaryLine, actualLines);
}

private static Optional<IssueExpectation> findIssue(
Issue issue, List<IssueExpectation> expectations) {
int line = getStartingLine(issue.primaryLocation());

Integer line = textRange.start().line();
if (!expectedLines.remove(line)) {
unexpectedLines.add(line);
for (IssueExpectation expectation : expectations) {
if (expectation.getBeginLine() != line
|| expectation.getFlowLines().size() != issue.flows().size()) {
continue;
}
List<List<Integer>> expectedLines =
expectation.getFlowLines().stream()
.map(
offsets ->
offsets.stream().map(offset -> offset + line).collect(Collectors.toList()))
.collect(Collectors.toList());
List<List<Integer>> actualLines =
issue.flows().stream()
.map(
flow ->
flow.locations().stream()
.map(CheckVerifierImpl::getStartingLine)
.collect(Collectors.toList()))
.collect(Collectors.toList());
if (expectedLines.equals(actualLines)) {
return Optional.of(expectation);
}
}
return Optional.empty();
}

assertIssueMismatchesEmpty(expectedLines, unexpectedLines);
private static int getStartingLine(IssueLocation location) {
TextRange textRange = location.textRange();
if (textRange == null) {
throw new AssertionError(
String.format(
"Expected issues to be raised at line level, not at %s level",
location.inputComponent().isFile() ? "file" : "project"));
}
return textRange.start().line();
}

private void assertQuickFixes(
Expand Down Expand Up @@ -324,17 +371,17 @@ private static boolean textEditMatches(
}

private static void assertIssueMismatchesEmpty(
List<Integer> expectedLines, List<Integer> unexpectedLines) {
if (!expectedLines.isEmpty() || !unexpectedLines.isEmpty()) {
List<IssueExpectation> expectedIssues, List<IssueExpectation> unexpectedIssues) {
if (!expectedIssues.isEmpty() || !unexpectedIssues.isEmpty()) {
StringBuilder message = new StringBuilder("Issues were ");
if (!expectedLines.isEmpty()) {
message.append("expected at ").append(expectedLines);
if (!expectedIssues.isEmpty()) {
message.append("expected at ").append(expectedIssues);
}
if (!expectedLines.isEmpty() && !unexpectedLines.isEmpty()) {
if (!expectedIssues.isEmpty() && !unexpectedIssues.isEmpty()) {
message.append(", ");
}
if (!unexpectedLines.isEmpty()) {
message.append("unexpected at ").append(unexpectedLines);
if (!unexpectedIssues.isEmpty()) {
message.append("unexpected at ").append(unexpectedIssues);
}
throw new AssertionError(message.toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,21 @@
import au.com.integradev.delphi.file.DelphiFile.DelphiInputFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.sonar.plugins.communitydelphi.api.token.DelphiToken;

class Expectations {
private static final Pattern NONCOMPLIANT_PATTERN =
Pattern.compile("(?i)^//\\s*Noncompliant(?:@([-+]\\d+))?\\b");
Pattern.compile("(?i)^//\\s*Noncompliant(?:@([-+]\\d+))?\\b(.+)?");

private static final Pattern FLOW_PATTERN = Pattern.compile("\\s*\\(([^)]*)\\)");
private static final Pattern FLOW_LINE_OFFSET_PATTERN =
Pattern.compile("\\s*,?\\s*([+-]?\\d+)\\b");

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

private static IssueExpectation parseNoncompliantComment(int beginLine, MatchResult matchResult) {
String offset = matchResult.group(1);
String flows = matchResult.group(2);

int lineOffset = parseIssueOffset(offset);
List<List<Integer>> flowLines = parseFlows(flows);

return new IssueExpectation(beginLine + lineOffset, flowLines);
}

private static int parseIssueOffset(String offset) {
if (offset == null) {
return new IssueExpectation(beginLine);
return 0;
}

try {
return new IssueExpectation(beginLine + Integer.parseInt(offset));
return Integer.parseInt(offset);
} catch (NumberFormatException e) {
throw new AssertionError(
String.format(
"Failed to parse 'Noncompliant' comment line offset '%s' as an integer.", offset));
}
}

private static List<List<Integer>> parseFlows(String flows) {
List<List<Integer>> flowLines = new ArrayList<>();
if (flows == null) {
return flowLines;
}
Matcher flowMatcher = FLOW_PATTERN.matcher(flows);
while (flowMatcher.find()) {
String flow = flowMatcher.group(1);
List<Integer> lines = parseFlowLines(flow);
if (!lines.isEmpty()) {
flowLines.add(lines);
}
}
flowLines.sort(Comparator.comparing(list -> list.get(0)));
return flowLines;
}

private static List<Integer> parseFlowLines(String flow) {
List<Integer> lines = new ArrayList<>();
if (flow == null) {
return lines;
}
Matcher lineMatcher = FLOW_LINE_OFFSET_PATTERN.matcher(flow);
while (lineMatcher.find()) {
String flowLineOffset = lineMatcher.group(1);
try {
lines.add(Integer.parseInt(flowLineOffset));
} catch (NumberFormatException e) {
throw new AssertionError(
String.format(
"Failed to parse 'Noncompliant' flow line offset '%s' as an integer.",
flowLineOffset));
}
}
return lines;
}

private static class MatchResultOnLine {
private final MatchResult matchResult;
private final int line;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,38 @@
*/
package au.com.integradev.delphi.checks.verifier;

import java.util.List;
import java.util.stream.Collectors;

class IssueExpectation {
private final int beginLine;
private final List<List<Integer>> flowLines;

public IssueExpectation(int beginLine) {
public IssueExpectation(int beginLine, List<List<Integer>> flowLines) {
this.beginLine = beginLine;
this.flowLines = flowLines;
}

public int getBeginLine() {
return beginLine;
}

public List<List<Integer>> getFlowLines() {
return flowLines;
}

@Override
public String toString() {
return "{"
+ beginLine
+ " "
+ flowLines.stream()
.map(
list ->
"("
+ list.stream().map(Object::toString).collect(Collectors.joining(", "))
+ ")")
.collect(Collectors.joining(" "))
+ "}";
}
}
Loading