Skip to content

Commit 0fa67c7

Browse files
SLCORE-1589 Duplicate key exception upon analysis
1 parent bd9bef2 commit 0fa67c7

File tree

3 files changed

+60
-27
lines changed

3 files changed

+60
-27
lines changed

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
*/
2020
package org.sonarsource.sonarlint.core.tracking.matching;
2121

22-
import java.util.ArrayList;
2322
import java.util.Collection;
2423
import java.util.HashMap;
2524
import java.util.List;
@@ -52,14 +51,16 @@ public class IssueMatcher<LEFT, RIGHT> {
5251
// 7. match issues with same rule and same line hash
5352
LineHashMatchingCriterion::new);
5453

55-
private final Map<MatchingCriterionFactory, Map<MatchingCriterion, List<RIGHT>>> rightIssuesByCriterion = new HashMap<>();
54+
private final Map<MatchingCriterionFactory, Map<MatchingCriterion, RIGHT>> rightIssuesByCriterion = new HashMap<>();
55+
private final MatchingAttributesMapper<RIGHT> rightMapper;
5656

5757
public IssueMatcher(MatchingAttributesMapper<RIGHT> rightMapper, Collection<RIGHT> rightIssues) {
58+
this.rightMapper = rightMapper;
5859
for (var matchingCriterion : MATCHING_CRITERIA) {
59-
var issuesByCriterion = new HashMap<MatchingCriterion, List<RIGHT>>();
60+
var issuesByCriterion = new HashMap<MatchingCriterion, RIGHT>();
6061
for (RIGHT right : rightIssues) {
6162
var criterionAppliedToIssue = matchingCriterion.build(right, rightMapper);
62-
issuesByCriterion.computeIfAbsent(criterionAppliedToIssue, k -> new ArrayList<>()).add(right);
63+
issuesByCriterion.put(criterionAppliedToIssue, right);
6364
}
6465
rightIssuesByCriterion.put(matchingCriterion, issuesByCriterion);
6566
}
@@ -81,17 +82,21 @@ public MatchingResult<LEFT, RIGHT> matchWith(MatchingAttributesMapper<LEFT> left
8182
private void matchWithCriterion(MatchingResult<LEFT, RIGHT> result, MatchingAttributesMapper<LEFT> leftMapper, MatchingCriterionFactory criterionFactory) {
8283
for (LEFT left : result.getUnmatchedLefts()) {
8384
var leftKey = criterionFactory.build(left, leftMapper);
84-
var rightsCandidates = rightIssuesByCriterion.get(criterionFactory).get(leftKey);
85-
if (rightsCandidates != null && !rightsCandidates.isEmpty()) {
85+
var match = rightIssuesByCriterion.get(criterionFactory).get(leftKey);
86+
if (match != null) {
8687
// TODO taking the first one. Could be improved if there are more than 2 issues on the same line.
8788
// Message could be checked to take the best one.
88-
var match = rightsCandidates.iterator().next();
8989
result.recordMatch(left, match);
90-
MATCHING_CRITERIA.forEach(criterion -> rightIssuesByCriterion.get(criterion).remove(criterion.build(left, leftMapper)));
90+
MATCHING_CRITERIA.forEach(criterion -> rightIssuesByCriterion.get(criterion).remove(criterion.build(match, rightMapper)));
91+
break;
9192
}
9293
}
9394
}
9495

96+
public int getUnmatchedIssuesCount() {
97+
return rightIssuesByCriterion.entrySet().iterator().next().getValue().size();
98+
}
99+
95100
private interface MatchingCriterion {
96101
}
97102

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)