diff --git a/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/KnownFindingType.java b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/KnownFindingType.java new file mode 100644 index 0000000000..c07fa38c62 --- /dev/null +++ b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/KnownFindingType.java @@ -0,0 +1,25 @@ +/* + * SonarLint Core - Commons + * Copyright (C) 2016-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarsource.sonarlint.core.commons; + +public enum KnownFindingType { + ISSUE, + HOTSPOT +} diff --git a/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/repository/KnownFindingsRepository.java b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/repository/KnownFindingsRepository.java new file mode 100644 index 0000000000..cbb8460d19 --- /dev/null +++ b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/repository/KnownFindingsRepository.java @@ -0,0 +1,149 @@ +/* + * SonarLint Core - Commons + * Copyright (C) 2016-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarsource.sonarlint.core.commons.storage.repository; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import org.jooq.Configuration; +import org.jooq.Record; +import org.sonarsource.sonarlint.core.commons.KnownFinding; +import org.sonarsource.sonarlint.core.commons.KnownFindingType; +import org.sonarsource.sonarlint.core.commons.LineWithHash; +import org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash; +import org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase; + +import static org.sonarsource.sonarlint.core.commons.storage.model.Tables.KNOWN_FINDINGS; + +public class KnownFindingsRepository { + + private final SonarLintDatabase database; + + public KnownFindingsRepository(SonarLintDatabase database) { + this.database = database; + } + + public void storeKnownIssues(String configurationScopeId, Path clientRelativePath, List newKnownIssues) { + storeKnownFindings(configurationScopeId, clientRelativePath, newKnownIssues, KnownFindingType.ISSUE); + } + + public void storeKnownSecurityHotspots(String configurationScopeId, Path clientRelativePath, List newKnownSecurityHotspots) { + storeKnownFindings(configurationScopeId, clientRelativePath, newKnownSecurityHotspots, KnownFindingType.HOTSPOT); + } + + public List loadSecurityHotspotsForFile(String configurationScopeId, Path filePath) { + return getKnownFindingsForFile(configurationScopeId, filePath, KnownFindingType.HOTSPOT); + } + + public List loadIssuesForFile(String configurationScopeId, Path filePath) { + return getKnownFindingsForFile(configurationScopeId, filePath, KnownFindingType.ISSUE); + } + + private void storeKnownFindings(String configurationScopeId, Path clientRelativePath, List newKnownFindings, KnownFindingType type) { + database.dsl().transaction((Configuration trx) -> newKnownFindings.forEach(finding -> { + var textRangeWithHash = finding.getTextRangeWithHash(); + var startLine = textRangeWithHash == null ? null : textRangeWithHash.getStartLine(); + var startLineOffset = textRangeWithHash == null ? null : textRangeWithHash.getStartLineOffset(); + var endLine = textRangeWithHash == null ? null : textRangeWithHash.getEndLine(); + var endLineOffset = textRangeWithHash == null ? null : textRangeWithHash.getEndLineOffset(); + var textRangeHash = textRangeWithHash == null ? null : textRangeWithHash.getHash(); + + var lineWithHash = finding.getLineWithHash(); + var line = lineWithHash == null ? null : lineWithHash.getNumber(); + var lineHash = lineWithHash == null ? null : lineWithHash.getHash(); + var introDate = LocalDateTime.ofInstant(finding.getIntroductionDate(), ZoneId.systemDefault()); + trx.dsl().mergeInto(KNOWN_FINDINGS) + .using(trx.dsl().selectOne()) + .on(KNOWN_FINDINGS.ID.eq(finding.getId())) + .whenMatchedThenUpdate() + .set(KNOWN_FINDINGS.CONFIGURATION_SCOPE_ID, configurationScopeId) + .set(KNOWN_FINDINGS.IDE_RELATIVE_FILE_PATH, clientRelativePath.toString()) + .set(KNOWN_FINDINGS.SERVER_KEY, finding.getServerKey()) + .set(KNOWN_FINDINGS.RULE_KEY, finding.getRuleKey()) + .set(KNOWN_FINDINGS.MESSAGE, finding.getMessage()) + .set(KNOWN_FINDINGS.INTRODUCTION_DATE, introDate) + .set(KNOWN_FINDINGS.FINDING_TYPE, type.name()) + .set(KNOWN_FINDINGS.START_LINE, startLine) + .set(KNOWN_FINDINGS.START_LINE_OFFSET, startLineOffset) + .set(KNOWN_FINDINGS.END_LINE, endLine) + .set(KNOWN_FINDINGS.END_LINE_OFFSET, endLineOffset) + .set(KNOWN_FINDINGS.TEXT_RANGE_HASH, textRangeHash) + .set(KNOWN_FINDINGS.LINE, line) + .set(KNOWN_FINDINGS.LINE_HASH, lineHash) + .whenNotMatchedThenInsert(KNOWN_FINDINGS.ID, KNOWN_FINDINGS.CONFIGURATION_SCOPE_ID, KNOWN_FINDINGS.IDE_RELATIVE_FILE_PATH, KNOWN_FINDINGS.SERVER_KEY, + KNOWN_FINDINGS.RULE_KEY, KNOWN_FINDINGS.MESSAGE, KNOWN_FINDINGS.INTRODUCTION_DATE, KNOWN_FINDINGS.FINDING_TYPE, + KNOWN_FINDINGS.START_LINE, KNOWN_FINDINGS.START_LINE_OFFSET, KNOWN_FINDINGS.END_LINE, KNOWN_FINDINGS.END_LINE_OFFSET, KNOWN_FINDINGS.TEXT_RANGE_HASH, + KNOWN_FINDINGS.LINE, KNOWN_FINDINGS.LINE_HASH) + .values(finding.getId(), configurationScopeId, clientRelativePath.toString(), finding.getServerKey(), finding.getRuleKey(), + finding.getMessage(), introDate, type.name(), + startLine, startLineOffset, endLine, endLineOffset, textRangeHash, + line, lineHash + ) + .execute(); + })); + } + + private List getKnownFindingsForFile(String configurationScopeId, Path filePath, KnownFindingType type) { + var issuesInFile = database.dsl() + .selectFrom(KNOWN_FINDINGS) + .where(KNOWN_FINDINGS.CONFIGURATION_SCOPE_ID.eq(configurationScopeId) + .and(KNOWN_FINDINGS.IDE_RELATIVE_FILE_PATH.eq(filePath.toString())) + .and(KNOWN_FINDINGS.FINDING_TYPE.eq(type.name())) + ) + .fetch(); + return issuesInFile.stream() + .map(KnownFindingsRepository::recordToKnownFinding) + .toList(); + } + + private static KnownFinding recordToKnownFinding(Record rec) { + var id = rec.get(KNOWN_FINDINGS.ID); + var introductionDate = rec.get(KNOWN_FINDINGS.INTRODUCTION_DATE).atZone(ZoneId.systemDefault()).toInstant(); + var textRangeWithHash = getTextRangeWithHash(rec); + var lineWithHash = getLineWithHash(rec); + return new KnownFinding( + id, + rec.get(KNOWN_FINDINGS.SERVER_KEY), + textRangeWithHash, lineWithHash, + rec.get(KNOWN_FINDINGS.RULE_KEY), + rec.get(KNOWN_FINDINGS.MESSAGE), + introductionDate + ); + } + + private static LineWithHash getLineWithHash(Record rec) { + if (rec.get(KNOWN_FINDINGS.LINE) == null) return null; + var line = rec.get(KNOWN_FINDINGS.LINE); + var hash = rec.get(KNOWN_FINDINGS.LINE_HASH); + return new LineWithHash(line, hash); + } + + private static TextRangeWithHash getTextRangeWithHash(Record rec) { + if (rec.get(KNOWN_FINDINGS.START_LINE) == null) return null; + var startLine = rec.get(KNOWN_FINDINGS.START_LINE); + var endLine = rec.get(KNOWN_FINDINGS.END_LINE); + var startLineOffset = rec.get(KNOWN_FINDINGS.START_LINE_OFFSET); + var endLineOffset = rec.get(KNOWN_FINDINGS.END_LINE_OFFSET); + var hash = rec.get(KNOWN_FINDINGS.TEXT_RANGE_HASH); + return new TextRangeWithHash(startLine, startLineOffset, endLine, endLineOffset, hash); + } + +} diff --git a/backend/commons/src/main/resources/db/migration/V1__create_ai_codefix_settings_table.sql b/backend/commons/src/main/resources/db/migration/V1__create_ai_codefix_settings_table.sql deleted file mode 100644 index a268b1f9ef..0000000000 --- a/backend/commons/src/main/resources/db/migration/V1__create_ai_codefix_settings_table.sql +++ /dev/null @@ -1,12 +0,0 @@ --- Flyway migration: create AI_CODEFIX_SETTINGS table for H2 --- Initial schema includes per-connection scoping via connection_id -CREATE TABLE IF NOT EXISTS AI_CODEFIX_SETTINGS ( - connection_id VARCHAR(255) NOT NULL PRIMARY KEY, - supported_rules VARCHAR(200) ARRAY, - organization_eligible BOOLEAN, - enablement VARCHAR(64), - enabled_project_keys VARCHAR(400) ARRAY, - CONSTRAINT pk_ai_codefix_settings PRIMARY KEY (connection_id) -); - - diff --git a/backend/commons/src/main/resources/db/migration/V1__init_database.sql b/backend/commons/src/main/resources/db/migration/V1__init_database.sql new file mode 100644 index 0000000000..aae7050140 --- /dev/null +++ b/backend/commons/src/main/resources/db/migration/V1__init_database.sql @@ -0,0 +1,32 @@ +-- Flyway migration: create AI_CODEFIX_SETTINGS table for H2 +-- Initial schema includes per-connection scoping via connection_id +CREATE TABLE IF NOT EXISTS AI_CODEFIX_SETTINGS ( + connection_id VARCHAR(255) NOT NULL PRIMARY KEY, + supported_rules VARCHAR(200) ARRAY, + organization_eligible BOOLEAN, + enablement VARCHAR(64), + enabled_project_keys VARCHAR(400) ARRAY, + CONSTRAINT pk_ai_codefix_settings PRIMARY KEY (connection_id) +); + +CREATE TABLE IF NOT EXISTS KNOWN_FINDINGS ( + -- UUID + id UUID NOT NULL PRIMARY KEY, + configuration_scope_id VARCHAR(255) NOT NULL, + ide_relative_file_path VARCHAR(255) NOT NULL, + server_key VARCHAR(255), + rule_key VARCHAR(255) NOT NULL, + message VARCHAR(255) NOT NULL, + introduction_date TIMESTAMP NOT NULL, + finding_type VARCHAR(255) NOT NULL, + -- TextRangeWithHash + start_line INT, + start_line_offset INT, + end_line INT, + end_line_offset INT, + text_range_hash VARCHAR(255), + -- LineWithHash + line INT, + line_hash VARCHAR(255) +); + diff --git a/backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/AiCodeFixRepositoryTests.java b/backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/AiCodeFixRepositoryTests.java new file mode 100644 index 0000000000..3f4f80858d --- /dev/null +++ b/backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/AiCodeFixRepositoryTests.java @@ -0,0 +1,85 @@ +/* + * SonarLint Core - Commons + * Copyright (C) 2016-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarsource.sonarlint.core.commons.storage.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; +import org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabaseMode; +import org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase; +import org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabaseInitParams; +import org.sonarsource.sonarlint.core.commons.storage.model.AiCodeFix; + +class AiCodeFixRepositoryTests { + + @RegisterExtension + static SonarLintLogTester logTester = new SonarLintLogTester(); + + @TempDir + Path temp; + + @Test + void upsert_and_get_should_persist_to_h2_file_database() { + // Given a file-based H2 database under a temporary storage root + var storageRoot = temp.resolve("storage"); + + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var aiCodeFixRepo = new AiCodeFixRepository(db); + + var entityToStore = new AiCodeFix( + "test-connection", + Set.of("java:S100", "js:S200"), + true, + AiCodeFix.Enablement.ENABLED_FOR_SOME_PROJECTS, + Set.of("project-a", "project-b") + ); + + // When we upsert the entity + aiCodeFixRepo.upsert(entityToStore); + + // And shutdown the first DB to force closing connections + db.shutdown(); + + // Create a new repository with a fresh DB instance pointing to the same storage root + var db2 = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo2 = new AiCodeFixRepository(db2); + // With a different connection id, no settings should be visible + var loadedOptDifferent = repo2.get("test-connection-2"); + assertThat(loadedOptDifferent).isEmpty(); + + // With the same connection id, we should read back exactly what we stored + var repoSame = new AiCodeFixRepository(db2); + var loadedOpt = repoSame.get("test-connection"); + assertThat(loadedOpt).isPresent(); + var loaded = loadedOpt.get(); + + assertThat(loaded.supportedRules()).containsExactlyInAnyOrder("java:S100", "js:S200"); + assertThat(loaded.organizationEligible()).isTrue(); + assertThat(loaded.enablement()).isEqualTo(AiCodeFix.Enablement.ENABLED_FOR_SOME_PROJECTS); + assertThat(loaded.enabledProjectKeys()).containsExactlyInAnyOrder("project-a", "project-b"); + + db2.shutdown(); + } +} diff --git a/backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/KnownFindingsRepositoryTests.java b/backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/KnownFindingsRepositoryTests.java new file mode 100644 index 0000000000..dc9edf46e3 --- /dev/null +++ b/backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/KnownFindingsRepositoryTests.java @@ -0,0 +1,100 @@ +/* + * SonarLint Core - Commons + * Copyright (C) 2016-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarsource.sonarlint.core.commons.storage.repository; + +import java.nio.file.Path; +import java.time.Instant; +import java.util.ArrayList; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.sonarsource.sonarlint.core.commons.KnownFinding; +import org.sonarsource.sonarlint.core.commons.LineWithHash; +import org.sonarsource.sonarlint.core.commons.api.TextRangeWithHash; +import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; +import org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabase; +import org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabaseInitParams; +import org.sonarsource.sonarlint.core.commons.storage.SonarLintDatabaseMode; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class KnownFindingsRepositoryTests { + + @RegisterExtension + static SonarLintLogTester logTester = new SonarLintLogTester(); + + @Test + void testKnownFindingsRepository(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new KnownFindingsRepository(db); + + var filePath = Path.of("/file/path"); + var configScopeId = "configScopeId"; + var issues = new ArrayList(); + var issueUuid1 = UUID.randomUUID(); + var issueIntroDate1 = Instant.now(); + var issue1 = new KnownFinding(issueUuid1, "test-message", new TextRangeWithHash(1, 2, 3, 4, "hash1"), + new LineWithHash(1, "hash"), "test-issue-rule-1", "Test issue message 1", issueIntroDate1); + issues.add(issue1); + var issueUuid2 = UUID.randomUUID(); + var issueIntroDate2 = Instant.now(); + var issue2 = new KnownFinding(issueUuid2, "test-message", new TextRangeWithHash(5, 6, 7, 8, "hash2"), + new LineWithHash(1, "hash"), "test-issue-rule-2", "Test issue message 2", issueIntroDate2); + issues.add(issue2); + var hotspots = new ArrayList(); + var hotspotUuid1 = UUID.randomUUID(); + var hotspotIntroDate1 = Instant.now(); + var hotspot1 = new KnownFinding(hotspotUuid1, "test-message", new TextRangeWithHash(1, 2, 3, 4, "hash1"), + new LineWithHash(1, "hash"), "test-hotspot-rule-1", "Test hotspot message 1", hotspotIntroDate1); + hotspots.add(hotspot1); + var hotspotUuid2 = UUID.randomUUID(); + var hotspotIntroDate2 = Instant.now(); + var hotspot2 = new KnownFinding(hotspotUuid2, "test-message", new TextRangeWithHash(5, 6, 7, 8, "hash2"), + new LineWithHash(1, "hash"), "test-hotspot-rule-2", "Test hotspot message 2", hotspotIntroDate2); + hotspots.add(hotspot2); + + repo.storeKnownIssues(configScopeId, filePath, issues); + repo.storeKnownSecurityHotspots(configScopeId, filePath, hotspots); + + var knownIssues = repo.loadIssuesForFile(configScopeId, filePath); + var knownHotspots = repo.loadSecurityHotspotsForFile(configScopeId, filePath); + + assertThat(knownIssues).hasSize(2); + assertThat(knownHotspots).hasSize(2); + var knownIssue = knownIssues.get(0); + assertThat(knownIssue.getRuleKey()).isEqualTo(issue1.getRuleKey()); + assertThat(knownIssue.getServerKey()).isEqualTo(issue1.getServerKey()); + assertThat(knownIssue.getMessage()).isEqualTo(issue1.getMessage()); + assertThat(knownIssue.getTextRangeWithHash()).isEqualTo(issue1.getTextRangeWithHash()); + assertThat(knownIssue.getLineWithHash().getNumber()).isEqualTo(issue1.getLineWithHash().getNumber()); + assertThat(knownIssue.getLineWithHash().getHash()).isEqualTo(issue1.getLineWithHash().getHash()); + var knownHotspot = knownHotspots.get(0); + assertThat(knownHotspot.getRuleKey()).isEqualTo(hotspot1.getRuleKey()); + assertThat(knownHotspot.getServerKey()).isEqualTo(hotspot1.getServerKey()); + assertThat(knownHotspot.getMessage()).isEqualTo(hotspot1.getMessage()); + assertThat(knownHotspot.getTextRangeWithHash()).isEqualTo(hotspot1.getTextRangeWithHash()); + assertThat(knownHotspot.getLineWithHash().getNumber()).isEqualTo(hotspot1.getLineWithHash().getNumber()); + assertThat(knownHotspot.getLineWithHash().getHash()).isEqualTo(hotspot1.getLineWithHash().getHash()); + } + +} diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/TrackingService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/TrackingService.java index b70754b91e..a776ac347e 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/TrackingService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/tracking/TrackingService.java @@ -43,6 +43,8 @@ import org.sonarsource.sonarlint.core.commons.NewCodeDefinition; import org.sonarsource.sonarlint.core.commons.RuleType; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; +import org.sonarsource.sonarlint.core.commons.monitoring.DogfoodEnvironmentDetectionService; +import org.sonarsource.sonarlint.core.commons.storage.repository.KnownFindingsRepository; import org.sonarsource.sonarlint.core.commons.util.git.GitService; import org.sonarsource.sonarlint.core.commons.util.git.exceptions.GitException; import org.sonarsource.sonarlint.core.event.MatchingSessionEndedEvent; @@ -57,6 +59,7 @@ import org.sonarsource.sonarlint.core.rpc.protocol.client.fs.GetBaseDirParams; import org.sonarsource.sonarlint.core.serverapi.hotspot.ServerHotspot; import org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue; +import org.sonarsource.sonarlint.core.storage.SonarLintDatabaseService; import org.sonarsource.sonarlint.core.storage.StorageService; import org.sonarsource.sonarlint.core.sync.FindingsSynchronizationService; import org.sonarsource.sonarlint.core.tracking.matching.IssueMatcher; @@ -88,11 +91,14 @@ public class TrackingService { private final NewCodeService newCodeService; private final ApplicationEventPublisher eventPublisher; private final GitService gitService; + private final DogfoodEnvironmentDetectionService dogfoodEnvironmentDetectionService; + private final SonarLintDatabaseService databaseService; public TrackingService(SonarLintRpcClient client, ConfigurationRepository configurationRepository, SonarProjectBranchTrackingService branchTrackingService, PathTranslationService pathTranslationService, FindingReportingService reportingService, KnownFindingsStorageService knownFindingsStorageService, StorageService storageService, LocalOnlyIssueRepository localOnlyIssueRepository, LocalOnlyIssueStorageService localOnlyIssueStorageService, FindingsSynchronizationService findingsSynchronizationService, - NewCodeService newCodeService, ApplicationEventPublisher eventPublisher) { + NewCodeService newCodeService, ApplicationEventPublisher eventPublisher, DogfoodEnvironmentDetectionService dogfoodEnvironmentDetectionService, + SonarLintDatabaseService databaseService) { this.client = client; this.configurationRepository = configurationRepository; this.branchTrackingService = branchTrackingService; @@ -106,6 +112,8 @@ public TrackingService(SonarLintRpcClient client, ConfigurationRepository config this.newCodeService = newCodeService; this.eventPublisher = eventPublisher; this.gitService = GitService.create(); + this.dogfoodEnvironmentDetectionService = dogfoodEnvironmentDetectionService; + this.databaseService = databaseService; } @EventListener @@ -188,17 +196,32 @@ private MatchingResult matchWithServerFindings(String configurationScopeId, Matc return new MatchingResult(issuesToReport, hotspotsToReport); } - private static void storeTrackedIssues(XodusKnownFindingsStore knownIssuesStore, String configurationScopeId, Path clientRelativePath, Collection newKnownIssues) { - knownIssuesStore.storeKnownIssues(configurationScopeId, clientRelativePath, - newKnownIssues.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(), - i.getIntroductionDate())).toList()); + private void storeTrackedIssues(XodusKnownFindingsStore knownIssuesStore, String configurationScopeId, Path clientRelativePath, Collection newKnownIssues) { + if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) { + var knownFindingsRepository = new KnownFindingsRepository(databaseService.getDatabase()); + knownFindingsRepository.storeKnownIssues(configurationScopeId, clientRelativePath, + newKnownIssues.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(), + i.getIntroductionDate())).toList()); + } else { + knownIssuesStore.storeKnownIssues(configurationScopeId, clientRelativePath, + newKnownIssues.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(), + i.getIntroductionDate())).toList()); + } } - private static void storeTrackedSecurityHotspots(XodusKnownFindingsStore knownIssuesStore, String configurationScopeId, Path clientRelativePath, + private void storeTrackedSecurityHotspots(XodusKnownFindingsStore knownIssuesStore, String configurationScopeId, Path clientRelativePath, Collection newKnownSecurityHotspots) { - knownIssuesStore.storeKnownSecurityHotspots(configurationScopeId, clientRelativePath, - newKnownSecurityHotspots.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(), - i.getIntroductionDate())).toList()); + if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) { + var knownFindingsRepository = new KnownFindingsRepository(databaseService.getDatabase()); + knownFindingsRepository.storeKnownSecurityHotspots(configurationScopeId, clientRelativePath, + newKnownSecurityHotspots.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(), + i.getIntroductionDate())).toList()); + } else { + knownIssuesStore.storeKnownSecurityHotspots(configurationScopeId, clientRelativePath, + newKnownSecurityHotspots.stream().map(i -> new KnownFinding(i.getId(), i.getServerKey(), i.getTextRangeWithHash(), i.getLineWithHash(), i.getRuleKey(), i.getMessage(), + i.getIntroductionDate())).toList()); + } + } private List matchWithServerIssues(Path serverRelativePath, List> serverIssues, @@ -278,10 +301,22 @@ private static TrackedIssue updateTrackedIssueWithLocalOnlyIssueData(TrackedIssu private MatchingSession startMatchingSession(String configurationScopeId, Set fileRelativePaths, Set fileUris, UnaryOperator fileContentProvider) { var knownFindingsStore = knownFindingsStorageService.get(); - var issuesByRelativePath = fileRelativePaths.stream() - .collect(toMap(Function.identity(), relativePath -> knownFindingsStore.loadIssuesForFile(configurationScopeId, relativePath))); - var hotspotsByRelativePath = fileRelativePaths.stream() - .collect(toMap(Function.identity(), relativePath -> knownFindingsStore.loadSecurityHotspotsForFile(configurationScopeId, relativePath))); + var dogfoodEnvironment = dogfoodEnvironmentDetectionService.isDogfoodEnvironment(); + Map> issuesByRelativePath; + Map> hotspotsByRelativePath; + if (dogfoodEnvironment) { + var knownFindingsRepository = new KnownFindingsRepository(databaseService.getDatabase()); + issuesByRelativePath = fileRelativePaths.stream() + .collect(toMap(Function.identity(), relativePath -> knownFindingsRepository.loadIssuesForFile(configurationScopeId, relativePath))); + hotspotsByRelativePath = fileRelativePaths.stream() + .collect(toMap(Function.identity(), relativePath -> knownFindingsRepository.loadSecurityHotspotsForFile(configurationScopeId, relativePath))); + } else { + issuesByRelativePath = fileRelativePaths.stream() + .collect(toMap(Function.identity(), relativePath -> knownFindingsStore.loadIssuesForFile(configurationScopeId, relativePath))); + hotspotsByRelativePath = fileRelativePaths.stream() + .collect(toMap(Function.identity(), relativePath -> knownFindingsStore.loadSecurityHotspotsForFile(configurationScopeId, relativePath))); + } + var introductionDateProvider = getIntroductionDateProvider(configurationScopeId, fileRelativePaths, fileUris, fileContentProvider); var previousFindings = new KnownFindings(issuesByRelativePath, hotspotsByRelativePath); return new MatchingSession(previousFindings, introductionDateProvider);