Skip to content

Commit 33b6d41

Browse files
SLCORE-1589 Duplicate key exception upon analysis
1 parent 14a250f commit 33b6d41

File tree

3 files changed

+54
-20
lines changed

3 files changed

+54
-20
lines changed

backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/IssueMatcher.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ public class IssueMatcher<LEFT, RIGHT> {
5353
LineHashMatchingCriterion::new);
5454

5555
private final Map<MatchingCriterionFactory, Map<MatchingCriterion, List<RIGHT>>> rightIssuesByCriterion = new HashMap<>();
56+
private final MatchingAttributesMapper<RIGHT> rightMapper;
5657

5758
public IssueMatcher(MatchingAttributesMapper<RIGHT> rightMapper, Collection<RIGHT> rightIssues) {
59+
this.rightMapper = rightMapper;
5860
for (var matchingCriterion : MATCHING_CRITERIA) {
5961
var issuesByCriterion = new HashMap<MatchingCriterion, List<RIGHT>>();
6062
for (RIGHT right : rightIssues) {
@@ -87,11 +89,15 @@ private void matchWithCriterion(MatchingResult<LEFT, RIGHT> result, MatchingAttr
8789
// Message could be checked to take the best one.
8890
var match = rightsCandidates.iterator().next();
8991
result.recordMatch(left, match);
90-
MATCHING_CRITERIA.forEach(criterion -> rightIssuesByCriterion.get(criterion).remove(criterion.build(left, leftMapper)));
92+
MATCHING_CRITERIA.forEach(criterion -> rightIssuesByCriterion.get(criterion).remove(criterion.build(match, rightMapper)));
9193
}
9294
}
9395
}
9496

97+
public int getUnmatchedIssuesCount() {
98+
return rightIssuesByCriterion.entrySet().iterator().next().getValue().size();
99+
}
100+
95101
private interface MatchingCriterion {
96102
}
97103

backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/matching/MatchingSession.java

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@
3737
import org.sonarsource.sonarlint.core.tracking.TrackedIssue;
3838

3939
public class MatchingSession {
40-
private final Map<Path, List<KnownFinding>> remainingUnmatchedIssuesPerFile;
41-
private final Map<Path, List<KnownFinding>> remainingUnmatchedSecurityHotspotsPerFile;
4240
private final Map<Path, IssueMatcher<RawIssue, KnownFinding>> issueMatchersByFile = new HashMap<>();
4341
private final Map<Path, IssueMatcher<RawIssue, KnownFinding>> hotspotMatchersByFile = new HashMap<>();
4442

@@ -49,12 +47,12 @@ public class MatchingSession {
4947
private long newIssuesFound = 0;
5048

5149
public MatchingSession(KnownFindings previousFindings, IntroductionDateProvider introductionDateProvider) {
52-
this.remainingUnmatchedIssuesPerFile = previousFindings.getIssuesPerFile().entrySet().stream().map(entry -> Map.entry(entry.getKey(), new ArrayList<>(entry.getValue())))
50+
var knownIssuesPerFile = previousFindings.getIssuesPerFile().entrySet().stream().map(entry -> Map.entry(entry.getKey(), new ArrayList<>(entry.getValue())))
5351
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
54-
this.remainingUnmatchedSecurityHotspotsPerFile = previousFindings.getSecurityHotspotsPerFile().entrySet().stream()
52+
var knownSecurityHotspotsPerFile = previousFindings.getSecurityHotspotsPerFile().entrySet().stream()
5553
.map(entry -> Map.entry(entry.getKey(), new ArrayList<>(entry.getValue()))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
56-
remainingUnmatchedIssuesPerFile.forEach((path, issues) -> issueMatchersByFile.put(path, new IssueMatcher<>(new KnownIssueMatchingAttributesMapper(), issues)));
57-
remainingUnmatchedSecurityHotspotsPerFile.forEach((path, hotspots) -> hotspotMatchersByFile.put(path, new IssueMatcher<>(new KnownIssueMatchingAttributesMapper(), hotspots)));
54+
knownIssuesPerFile.forEach((path, issues) -> issueMatchersByFile.put(path, new IssueMatcher<>(new KnownIssueMatchingAttributesMapper(), issues)));
55+
knownSecurityHotspotsPerFile.forEach((path, hotspots) -> hotspotMatchersByFile.put(path, new IssueMatcher<>(new KnownIssueMatchingAttributesMapper(), hotspots)));
5856
this.introductionDateProvider = introductionDateProvider;
5957
}
6058

@@ -71,7 +69,7 @@ public TrackedIssue matchWithKnownSecurityHotspot(Path relativePath, RawIssue ne
7169
if (hotspotMatcher == null) {
7270
throw new IllegalStateException("No hotspot matcher found for " + relativePath);
7371
}
74-
var trackedSecurityHotspot = matchWithKnownFinding(relativePath, hotspotMatcher, remainingUnmatchedSecurityHotspotsPerFile, newSecurityHotspot);
72+
var trackedSecurityHotspot = matchWithKnownFinding(relativePath, hotspotMatcher, newSecurityHotspot);
7573
securityHotspotsPerFile.computeIfAbsent(relativePath, f -> new ArrayList<>()).add(trackedSecurityHotspot);
7674
relativePathsInvolved.add(relativePath);
7775
return trackedSecurityHotspot;
@@ -83,23 +81,16 @@ private TrackedIssue matchWithKnownIssue(Path relativePath, RawIssue rawIssue) {
8381
throw new IllegalStateException("No issue matcher found for " + relativePath);
8482
}
8583

86-
var trackedIssue = matchWithKnownFinding(relativePath, issueMatcher, remainingUnmatchedIssuesPerFile, rawIssue);
84+
var trackedIssue = matchWithKnownFinding(relativePath, issueMatcher, rawIssue);
8785
issuesPerFile.computeIfAbsent(relativePath, f -> new ArrayList<>()).add(trackedIssue);
8886
relativePathsInvolved.add(relativePath);
8987
return trackedIssue;
9088
}
9189

92-
private TrackedIssue matchWithKnownFinding(Path relativePath, IssueMatcher<RawIssue, KnownFinding> issueMatcher,
93-
Map<Path, List<KnownFinding>> remainingUnmatchedKnownFindingsPerFile, RawIssue newFinding) {
94-
var remainingUnmatchedKnownFindings = remainingUnmatchedKnownFindingsPerFile.get(relativePath);
90+
private TrackedIssue matchWithKnownFinding(Path relativePath, IssueMatcher<RawIssue, KnownFinding> issueMatcher, RawIssue newFinding) {
9591
var localMatchingResult = issueMatcher.matchWith(new RawIssueFindingMatchingAttributeMapper(), List.of(newFinding));
9692
return localMatchingResult.getMatchOpt(newFinding)
97-
.map(knownFinding -> {
98-
if (remainingUnmatchedKnownFindings != null) {
99-
remainingUnmatchedKnownFindings.remove(knownFinding);
100-
}
101-
return updateKnownFindingWithRawIssueData(knownFinding, newFinding);
102-
})
93+
.map(knownFinding -> updateKnownFindingWithRawIssueData(knownFinding, newFinding))
10394
.orElseGet(() -> newlyKnownIssue(relativePath, newFinding));
10495
}
10596

@@ -113,7 +104,7 @@ public static TrackedIssue updateKnownFindingWithRawIssueData(KnownFinding known
113104
}
114105

115106
private TrackedIssue newlyKnownIssue(Path relativePath, RawIssue rawFinding) {
116-
newIssuesFound ++;
107+
newIssuesFound++;
117108
var introductionDate = introductionDateProvider.determineIntroductionDate(relativePath, rawFinding.getLineNumbers());
118109
return IssueMapper.toTrackedIssue(rawFinding, introductionDate);
119110
}
@@ -135,6 +126,6 @@ public long countNewIssues() {
135126
}
136127

137128
public long countRemainingUnmatchedIssues() {
138-
return remainingUnmatchedIssuesPerFile.values().stream().mapToLong(List::size).sum();
129+
return issueMatchersByFile.values().stream().mapToLong(IssueMatcher::getUnmatchedIssuesCount).sum();
139130
}
140131
}

medium-tests/src/test/java/mediumtest/tracking/IssueTrackingMediumTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.Set;
3232
import java.util.UUID;
3333
import java.util.concurrent.TimeUnit;
34+
import java.util.stream.Collectors;
3435
import org.eclipse.jgit.api.errors.GitAPIException;
3536
import org.junit.jupiter.api.Disabled;
3637
import org.junit.jupiter.api.extension.ExtendWith;
@@ -915,6 +916,42 @@ void it_should_notify_client_when_analysis_finishes_without_starting(SonarLintTe
915916
verify(client, never()).raiseIssues(eq(CONFIG_SCOPE_ID), any(), eq(true), any());
916917
}
917918

919+
@SonarLintTest
920+
void it_should_not_match_the_same_issue_twice(SonarLintTestHarness harness, @TempDir Path baseDir) {
921+
var filePath = createFile(baseDir, baseDir.resolve("file.py").toString(),
922+
"""
923+
# TODO try this
924+
# TODO try that
925+
""");
926+
var fileUri = filePath.toUri();
927+
var client = harness.newFakeClient()
928+
.withInitialFs(CONFIG_SCOPE_ID, baseDir, List.of(new ClientFileDto(fileUri, baseDir.relativize(filePath), CONFIG_SCOPE_ID, false, null, filePath, null, null, true)))
929+
.build();
930+
var backend = harness.newBackend()
931+
.withUnboundConfigScope(CONFIG_SCOPE_ID)
932+
.withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)
933+
.start(client);
934+
935+
var raisedIssueDtos = analyzeFileAndGetAllIssues(backend, fileUri, client);
936+
assertThat(raisedIssueDtos).hasSize(2);
937+
938+
changeFileContent(baseDir, filePath.getFileName().toString(), """
939+
# TODO try that
940+
""");
941+
raisedIssueDtos = analyzeFileAndGetAllIssues(backend, fileUri, client);
942+
assertThat(raisedIssueDtos).hasSize(1);
943+
944+
changeFileContent(baseDir, filePath.getFileName().toString(), """
945+
# TODO try this
946+
# TODO try that
947+
""");
948+
raisedIssueDtos = analyzeFileAndGetAllIssues(backend, fileUri, client);
949+
assertThat(raisedIssueDtos)
950+
.hasSize(2);
951+
assertThat(raisedIssueDtos.stream().map(RaisedFindingDto::getId).collect(Collectors.toSet()))
952+
.hasSize(2);
953+
}
954+
918955
private List<RaisedIssueDto> analyzeFileAndGetAllIssuesOfRule(SonarLintTestRpcServer backend, URI fileUri, SonarLintRpcClientDelegate client, String ruleKey) {
919956
var analysisId = UUID.randomUUID();
920957
var analysisResult = backend.getAnalysisService().analyzeFilesAndTrack(

0 commit comments

Comments
 (0)