package org.sonar.server.computation.task.projectanalysis.issue;
import java.time.format.DateTimeFormatter;
+import java.util.Comparator;
import java.util.Date;
+import java.util.HashSet;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Supplier;
+import java.util.stream.IntStream;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.db.protobuf.DbCommons.TextRange;
+import org.sonar.db.protobuf.DbIssues;
+import org.sonar.db.protobuf.DbIssues.Flow;
+import org.sonar.db.protobuf.DbIssues.Location;
import org.sonar.server.computation.task.projectanalysis.analysis.Analysis;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.analysis.ScannerPlugin;
}
private static Optional<Changeset> getChangeset(ScmInfo scmInfo, DefaultIssue issue) {
- Integer line = issue.getLine();
- if (line != null) {
- Changeset changesetForLine = scmInfo.getChangesetForLine(line);
- if (changesetForLine != null) {
- return Optional.of(changesetForLine);
+ Set<Integer> involvedLines = new HashSet<>();
+ DbIssues.Locations locations = issue.getLocations();
+ if (locations != null) {
+ if (locations.hasTextRange()) {
+ addLines(involvedLines, locations.getTextRange());
+ }
+ for (Flow f : locations.getFlowList()) {
+ for (Location l : f.getLocationList()) {
+ addLines(involvedLines, l.getTextRange());
+ }
+ }
+ if (!involvedLines.isEmpty()) {
+ return involvedLines.stream()
+ .map(scmInfo::getChangesetForLine)
+ .max(Comparator.comparingLong(Changeset::getDate));
}
}
return Optional.empty();
}
+ private static void addLines(Set<Integer> involvedLines, TextRange range) {
+ IntStream.rangeClosed(range.getStartLine(), range.getEndLine()).forEach(involvedLines::add);
+ }
+
private static Date getChangeDate(Changeset changesetForLine) {
return DateUtils.longToDate(changesetForLine.getDate());
}
import org.junit.Test;
import org.sonar.api.rule.RuleKey;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.protobuf.DbCommons.TextRange;
+import org.sonar.db.protobuf.DbIssues;
+import org.sonar.db.protobuf.DbIssues.Flow;
+import org.sonar.db.protobuf.DbIssues.Location;
+import org.sonar.db.protobuf.DbIssues.Locations.Builder;
import org.sonar.server.computation.task.projectanalysis.analysis.Analysis;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.analysis.ScannerPlugin;
private IssueCreationDateCalculator calculator;
private Analysis baseAnalysis;
private Map<String, ScannerPlugin> scannerPlugins;
+ private ScmInfo scmInfo;
@Before
public void before() {
assertChangeOfCreationDateTo(1200L);
}
+ @Test
+ public void should_use_primary_location_when_backdating() {
+ currentAnalysisIs(3000L);
+
+ newIssue();
+ when(issue.getLocations()).thenReturn(DbIssues.Locations.newBuilder().setTextRange(range(2, 3)).build());
+ withScmAt(2, 1200L);
+ withScmAt(3, 1300L);
+
+ run();
+
+ assertChangeOfCreationDateTo(1300L);
+ }
+
+ @Test
+ public void should_use_flows_location_when_backdating() {
+ currentAnalysisIs(3000L);
+
+ newIssue();
+ Builder builder = DbIssues.Locations.newBuilder()
+ .setTextRange(range(2, 3));
+ Flow.Builder secondary = Flow.newBuilder().addLocation(Location.newBuilder().setTextRange(range(4, 5)));
+ builder.addFlow(secondary).build();
+ Flow.Builder flow = Flow.newBuilder()
+ .addLocation(Location.newBuilder().setTextRange(range(6, 7)))
+ .addLocation(Location.newBuilder().setTextRange(range(8, 9)));
+ builder.addFlow(flow).build();
+ when(issue.getLocations()).thenReturn(builder.build());
+ withScmAt(2, 1200L);
+ withScmAt(3, 1300L);
+ withScmAt(4, 1400L);
+ withScmAt(5, 1500L);
+ withScmAt(6, 1600L);
+ withScmAt(7, 1700L);
+ withScmAt(8, 1800L);
+ withScmAt(9, 1900L);
+
+ run();
+
+ assertChangeOfCreationDateTo(1900L);
+ }
+
+ private org.sonar.db.protobuf.DbCommons.TextRange.Builder range(int startLine, int endLine) {
+ return TextRange.newBuilder().setStartLine(startLine).setEndLine(endLine);
+ }
+
private void previousAnalysisWas(long analysisDate) {
when(analysisMetadataHolder.getBaseAnalysis())
.thenReturn(baseAnalysis);
}
private void withScm(long blame) {
- ScmInfo scmInfo = mock(ScmInfo.class);
+ createMockScmInfo();
Changeset changeset = Changeset.newChangesetBuilder().setDate(blame).setRevision("1").build();
- when(scmInfoRepository.getScmInfo(component))
- .thenReturn(Optional.of(scmInfo));
when(scmInfo.getLatestChangeset()).thenReturn(changeset);
}
+ private void createMockScmInfo() {
+ if (scmInfo == null) {
+ scmInfo = mock(ScmInfo.class);
+ when(scmInfoRepository.getScmInfo(component))
+ .thenReturn(Optional.of(scmInfo));
+ }
+ }
+
+ private void withScmAt(int line, long blame) {
+ createMockScmInfo();
+ Changeset changeset = Changeset.newChangesetBuilder().setDate(blame).setRevision("1").build();
+ when(scmInfo.getChangesetForLine(line)).thenReturn(changeset);
+ }
+
private void ruleCreatedAt(long createdAt) {
when(activeRule.getCreatedAt()).thenReturn(createdAt);
}