From 3b0f5b153b7eb9368a980b5b5a5c10638bc07222 Mon Sep 17 00:00:00 2001 From: Damien Urruty Date: Thu, 6 Nov 2025 15:29:27 +0100 Subject: [PATCH 1/2] SLCORE-1763 Migrate XodusLocalOnlyIssueStore to H2 DB --- .../repository/LocalOnlyIssuesRepository.java | 220 +++++++++++ .../V2__create_local_only_issues_table.sql | 30 ++ .../LocalOnlyIssuesRepositoryTests.java | 370 ++++++++++++++++++ .../sonarlint/core/issue/IssueService.java | 101 +++-- .../core/tracking/TrackingService.java | 13 +- 5 files changed, 711 insertions(+), 23 deletions(-) create mode 100644 backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/repository/LocalOnlyIssuesRepository.java create mode 100644 backend/commons/src/main/resources/db/migration/V2__create_local_only_issues_table.sql create mode 100644 backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/LocalOnlyIssuesRepositoryTests.java diff --git a/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/repository/LocalOnlyIssuesRepository.java b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/repository/LocalOnlyIssuesRepository.java new file mode 100644 index 0000000000..f5ed861027 --- /dev/null +++ b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/storage/repository/LocalOnlyIssuesRepository.java @@ -0,0 +1,220 @@ +/* + * 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.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.jooq.Configuration; +import org.jooq.Record; +import org.sonarsource.sonarlint.core.commons.IssueStatus; +import org.sonarsource.sonarlint.core.commons.LineWithHash; +import org.sonarsource.sonarlint.core.commons.LocalOnlyIssue; +import org.sonarsource.sonarlint.core.commons.LocalOnlyIssueResolution; +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.LOCAL_ONLY_ISSUES; + +public class LocalOnlyIssuesRepository { + + private final SonarLintDatabase database; + + public LocalOnlyIssuesRepository(SonarLintDatabase database) { + this.database = database; + } + + public List loadForFile(String configurationScopeId, Path filePath) { + var issuesInFile = database.dsl() + .selectFrom(LOCAL_ONLY_ISSUES) + .where(LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID.eq(configurationScopeId) + .and(LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH.eq(filePath.toString()))) + .fetch(); + return issuesInFile.stream() + .map(LocalOnlyIssuesRepository::recordToLocalOnlyIssue) + .toList(); + } + + public List loadAll(String configurationScopeId) { + var allIssues = database.dsl() + .selectFrom(LOCAL_ONLY_ISSUES) + .where(LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID.eq(configurationScopeId)) + .fetch(); + return allIssues.stream() + .map(LocalOnlyIssuesRepository::recordToLocalOnlyIssue) + .toList(); + } + + public void storeLocalOnlyIssue(String configurationScopeId, LocalOnlyIssue issue) { + database.dsl().transaction((Configuration trx) -> { + var textRangeWithHash = issue.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 = issue.getLineWithHash(); + var line = lineWithHash == null ? null : lineWithHash.getNumber(); + var lineHash = lineWithHash == null ? null : lineWithHash.getHash(); + + var resolution = issue.getResolution(); + var resolutionStatus = resolution == null ? null : resolution.getStatus().name(); + var resolutionDate = resolution == null ? null : LocalDateTime.ofInstant(resolution.getResolutionDate(), ZoneId.systemDefault()); + var comment = resolution == null ? null : resolution.getComment(); + + trx.dsl().mergeInto(LOCAL_ONLY_ISSUES) + .using(trx.dsl().selectOne()) + .on(LOCAL_ONLY_ISSUES.ID.eq(issue.getId())) + .whenMatchedThenUpdate() + .set(LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID, configurationScopeId) + .set(LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH, issue.getServerRelativePath().toString()) + .set(LOCAL_ONLY_ISSUES.RULE_KEY, issue.getRuleKey()) + .set(LOCAL_ONLY_ISSUES.MESSAGE, issue.getMessage()) + .set(LOCAL_ONLY_ISSUES.RESOLUTION_STATUS, resolutionStatus) + .set(LOCAL_ONLY_ISSUES.RESOLUTION_DATE, resolutionDate) + .set(LOCAL_ONLY_ISSUES.COMMENT, comment) + .set(LOCAL_ONLY_ISSUES.START_LINE, startLine) + .set(LOCAL_ONLY_ISSUES.START_LINE_OFFSET, startLineOffset) + .set(LOCAL_ONLY_ISSUES.END_LINE, endLine) + .set(LOCAL_ONLY_ISSUES.END_LINE_OFFSET, endLineOffset) + .set(LOCAL_ONLY_ISSUES.TEXT_RANGE_HASH, textRangeHash) + .set(LOCAL_ONLY_ISSUES.LINE, line) + .set(LOCAL_ONLY_ISSUES.LINE_HASH, lineHash) + .whenNotMatchedThenInsert( + LOCAL_ONLY_ISSUES.ID, + LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID, + LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH, + LOCAL_ONLY_ISSUES.RULE_KEY, + LOCAL_ONLY_ISSUES.MESSAGE, + LOCAL_ONLY_ISSUES.RESOLUTION_STATUS, + LOCAL_ONLY_ISSUES.RESOLUTION_DATE, + LOCAL_ONLY_ISSUES.COMMENT, + LOCAL_ONLY_ISSUES.START_LINE, + LOCAL_ONLY_ISSUES.START_LINE_OFFSET, + LOCAL_ONLY_ISSUES.END_LINE, + LOCAL_ONLY_ISSUES.END_LINE_OFFSET, + LOCAL_ONLY_ISSUES.TEXT_RANGE_HASH, + LOCAL_ONLY_ISSUES.LINE, + LOCAL_ONLY_ISSUES.LINE_HASH) + .values( + issue.getId(), + configurationScopeId, + issue.getServerRelativePath().toString(), + issue.getRuleKey(), + issue.getMessage(), + resolutionStatus, + resolutionDate, + comment, + startLine, + startLineOffset, + endLine, + endLineOffset, + textRangeHash, + line, + lineHash) + .execute(); + }); + } + + public boolean removeIssue(UUID issueId) { + var deleted = database.dsl() + .deleteFrom(LOCAL_ONLY_ISSUES) + .where(LOCAL_ONLY_ISSUES.ID.eq(issueId)) + .execute(); + return deleted > 0; + } + + public boolean removeAllIssuesForFile(String configurationScopeId, Path filePath) { + var deleted = database.dsl() + .deleteFrom(LOCAL_ONLY_ISSUES) + .where(LOCAL_ONLY_ISSUES.CONFIGURATION_SCOPE_ID.eq(configurationScopeId) + .and(LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH.eq(filePath.toString()))) + .execute(); + return deleted > 0; + } + + public Optional find(UUID issueId) { + var issue = database.dsl() + .selectFrom(LOCAL_ONLY_ISSUES) + .where(LOCAL_ONLY_ISSUES.ID.eq(issueId)) + .fetchOne(); + return issue == null ? Optional.empty() : Optional.of(recordToLocalOnlyIssue(issue)); + } + + public void purgeIssuesOlderThan(Instant limit) { + var limitDateTime = LocalDateTime.ofInstant(limit, ZoneId.systemDefault()); + database.dsl() + .deleteFrom(LOCAL_ONLY_ISSUES) + .where(LOCAL_ONLY_ISSUES.RESOLUTION_DATE.isNotNull() + .and(LOCAL_ONLY_ISSUES.RESOLUTION_DATE.le(limitDateTime))) + .execute(); + } + + private static LocalOnlyIssue recordToLocalOnlyIssue(Record rec) { + var id = rec.get(LOCAL_ONLY_ISSUES.ID); + var serverRelativePath = Path.of(rec.get(LOCAL_ONLY_ISSUES.SERVER_RELATIVE_PATH)); + var ruleKey = rec.get(LOCAL_ONLY_ISSUES.RULE_KEY); + var message = rec.get(LOCAL_ONLY_ISSUES.MESSAGE); + + var textRangeWithHash = getTextRangeWithHash(rec); + var lineWithHash = getLineWithHash(rec); + + LocalOnlyIssueResolution resolution = null; + var resolutionStatus = rec.get(LOCAL_ONLY_ISSUES.RESOLUTION_STATUS); + var resolutionDate = rec.get(LOCAL_ONLY_ISSUES.RESOLUTION_DATE); + if (resolutionStatus != null && resolutionDate != null) { + var status = IssueStatus.valueOf(resolutionStatus); + var instant = resolutionDate.atZone(ZoneId.systemDefault()).toInstant(); + var comment = rec.get(LOCAL_ONLY_ISSUES.COMMENT); + resolution = new LocalOnlyIssueResolution(status, instant, comment); + } + + return new LocalOnlyIssue(id, serverRelativePath, textRangeWithHash, lineWithHash, ruleKey, message, resolution); + } + + private static LineWithHash getLineWithHash(Record rec) { + var line = rec.get(LOCAL_ONLY_ISSUES.LINE); + if (line == null) { + return null; + } + var hash = rec.get(LOCAL_ONLY_ISSUES.LINE_HASH); + return new LineWithHash(line, hash); + } + + private static TextRangeWithHash getTextRangeWithHash(Record rec) { + var startLine = rec.get(LOCAL_ONLY_ISSUES.START_LINE); + if (startLine == null) { + return null; + } + var endLine = rec.get(LOCAL_ONLY_ISSUES.END_LINE); + var startLineOffset = rec.get(LOCAL_ONLY_ISSUES.START_LINE_OFFSET); + var endLineOffset = rec.get(LOCAL_ONLY_ISSUES.END_LINE_OFFSET); + var hash = rec.get(LOCAL_ONLY_ISSUES.TEXT_RANGE_HASH); + if (hash == null) { + return null; + } + return new TextRangeWithHash(startLine, startLineOffset, endLine, endLineOffset, hash); + } +} diff --git a/backend/commons/src/main/resources/db/migration/V2__create_local_only_issues_table.sql b/backend/commons/src/main/resources/db/migration/V2__create_local_only_issues_table.sql new file mode 100644 index 0000000000..27263402a2 --- /dev/null +++ b/backend/commons/src/main/resources/db/migration/V2__create_local_only_issues_table.sql @@ -0,0 +1,30 @@ +-- Flyway migration: create LOCAL_ONLY_ISSUES table for H2 +-- This table stores local-only issues (issues detected locally but not yet on the server) + +CREATE TABLE IF NOT EXISTS LOCAL_ONLY_ISSUES ( + id UUID NOT NULL PRIMARY KEY, + configuration_scope_id VARCHAR(255) NOT NULL, + server_relative_path VARCHAR(1000) NOT NULL, + rule_key VARCHAR(255) NOT NULL, + message VARCHAR(255) NOT NULL, + -- Resolution fields (nullable when issue is not resolved) + resolution_status VARCHAR(50), + resolution_date TIMESTAMP, + comment VARCHAR(255), + -- TextRangeWithHash fields (nullable) + start_line INT, + start_line_offset INT, + end_line INT, + end_line_offset INT, + text_range_hash VARCHAR(255), + -- LineWithHash fields (nullable) + line INT, + line_hash VARCHAR(255) +); + +CREATE INDEX IF NOT EXISTS idx_local_only_issues_config_scope_file + ON LOCAL_ONLY_ISSUES(configuration_scope_id, server_relative_path); + +CREATE INDEX IF NOT EXISTS idx_local_only_issues_resolution_date + ON LOCAL_ONLY_ISSUES(resolution_date); + diff --git a/backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/LocalOnlyIssuesRepositoryTests.java b/backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/LocalOnlyIssuesRepositoryTests.java new file mode 100644 index 0000000000..78f3ee4bf1 --- /dev/null +++ b/backend/commons/src/test/java/org/sonarsource/sonarlint/core/commons/storage/repository/LocalOnlyIssuesRepositoryTests.java @@ -0,0 +1,370 @@ +/* + * 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.time.temporal.ChronoUnit; +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.IssueStatus; +import org.sonarsource.sonarlint.core.commons.LineWithHash; +import org.sonarsource.sonarlint.core.commons.LocalOnlyIssue; +import org.sonarsource.sonarlint.core.commons.LocalOnlyIssueResolution; +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.Assertions.assertThat; + +class LocalOnlyIssuesRepositoryTests { + + @RegisterExtension + static SonarLintLogTester logTester = new SonarLintLogTester(); + + @Test + void should_store_and_load_issues_for_file(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var filePath = Path.of("/file/path"); + var configScopeId = "configScopeId"; + var issueUuid1 = UUID.randomUUID(); + var issue1 = new LocalOnlyIssue(issueUuid1, filePath, new TextRangeWithHash(1, 2, 3, 4, "hash1"), + new LineWithHash(1, "linehash1"), "test-rule-1", "Test issue message 1", null); + var issueUuid2 = UUID.randomUUID(); + var issue2 = new LocalOnlyIssue(issueUuid2, filePath, new TextRangeWithHash(5, 6, 7, 8, "hash2"), + new LineWithHash(2, "linehash2"), "test-rule-2", "Test issue message 2", null); + + repo.storeLocalOnlyIssue(configScopeId, issue1); + repo.storeLocalOnlyIssue(configScopeId, issue2); + + var loadedIssues = repo.loadForFile(configScopeId, filePath); + + assertThat(loadedIssues).hasSize(2); + assertThat(loadedIssues).extracting(LocalOnlyIssue::getId).containsExactlyInAnyOrder(issueUuid1, issueUuid2); + var loadedIssue1 = loadedIssues.stream().filter(i -> i.getId().equals(issueUuid1)).findFirst().orElseThrow(); + assertThat(loadedIssue1.getRuleKey()).isEqualTo("test-rule-1"); + assertThat(loadedIssue1.getMessage()).isEqualTo("Test issue message 1"); + assertThat(loadedIssue1.getTextRangeWithHash()).isEqualTo(new TextRangeWithHash(1, 2, 3, 4, "hash1")); + assertThat(loadedIssue1.getLineWithHash().getNumber()).isEqualTo(1); + assertThat(loadedIssue1.getLineWithHash().getHash()).isEqualTo("linehash1"); + assertThat(loadedIssue1.getResolution()).isNull(); + } + + @Test + void should_store_and_load_resolved_issue(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var filePath = Path.of("/file/path"); + var configScopeId = "configScopeId"; + var issueUuid = UUID.randomUUID(); + var resolutionDate = Instant.now().truncatedTo(ChronoUnit.MILLIS); + var resolution = new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, resolutionDate, "Test comment"); + var issue = new LocalOnlyIssue(issueUuid, filePath, new TextRangeWithHash(1, 2, 3, 4, "hash1"), + new LineWithHash(1, "linehash1"), "test-rule-1", "Test issue message", resolution); + + repo.storeLocalOnlyIssue(configScopeId, issue); + + var loadedIssues = repo.loadForFile(configScopeId, filePath); + + assertThat(loadedIssues).hasSize(1); + var loadedIssue = loadedIssues.get(0); + assertThat(loadedIssue.getId()).isEqualTo(issueUuid); + assertThat(loadedIssue.getResolution()).isNotNull(); + assertThat(loadedIssue.getResolution().getStatus()).isEqualTo(IssueStatus.WONT_FIX); + assertThat(loadedIssue.getResolution().getResolutionDate()).isEqualTo(resolutionDate); + assertThat(loadedIssue.getResolution().getComment()).isEqualTo("Test comment"); + } + + @Test + void should_store_issue_without_text_range_and_line_hash(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var filePath = Path.of("/file/path"); + var configScopeId = "configScopeId"; + var issueUuid = UUID.randomUUID(); + var issue = new LocalOnlyIssue(issueUuid, filePath, null, null, "test-rule-1", "Test issue message", null); + + repo.storeLocalOnlyIssue(configScopeId, issue); + + var loadedIssues = repo.loadForFile(configScopeId, filePath); + + assertThat(loadedIssues).hasSize(1); + var loadedIssue = loadedIssues.get(0); + assertThat(loadedIssue.getId()).isEqualTo(issueUuid); + assertThat(loadedIssue.getTextRangeWithHash()).isNull(); + assertThat(loadedIssue.getLineWithHash()).isNull(); + } + + @Test + void should_load_all_issues_for_configuration_scope(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var configScopeId = "configScopeId"; + var filePath1 = Path.of("/file/path1"); + var filePath2 = Path.of("/file/path2"); + var issue1 = new LocalOnlyIssue(UUID.randomUUID(), filePath1, null, null, "test-rule-1", "Message 1", null); + var issue2 = new LocalOnlyIssue(UUID.randomUUID(), filePath2, null, null, "test-rule-2", "Message 2", null); + var issue3 = new LocalOnlyIssue(UUID.randomUUID(), filePath1, null, null, "test-rule-3", "Message 3", null); + + repo.storeLocalOnlyIssue(configScopeId, issue1); + repo.storeLocalOnlyIssue(configScopeId, issue2); + repo.storeLocalOnlyIssue(configScopeId, issue3); + + var allIssues = repo.loadAll(configScopeId); + + assertThat(allIssues).hasSize(3); + assertThat(allIssues).extracting(LocalOnlyIssue::getId) + .containsExactlyInAnyOrder(issue1.getId(), issue2.getId(), issue3.getId()); + } + + @Test + void should_find_issue_by_id(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var configScopeId = "configScopeId"; + var issueUuid = UUID.randomUUID(); + var issue = new LocalOnlyIssue(issueUuid, Path.of("/file/path"), null, null, "test-rule-1", "Test message", null); + + repo.storeLocalOnlyIssue(configScopeId, issue); + + var foundIssue = repo.find(issueUuid); + + assertThat(foundIssue).isPresent(); + assertThat(foundIssue.get().getId()).isEqualTo(issueUuid); + assertThat(foundIssue.get().getRuleKey()).isEqualTo("test-rule-1"); + assertThat(foundIssue.get().getMessage()).isEqualTo("Test message"); + } + + @Test + void should_return_empty_when_issue_not_found(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var foundIssue = repo.find(UUID.randomUUID()); + + assertThat(foundIssue).isEmpty(); + } + + @Test + void should_remove_issue(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var configScopeId = "configScopeId"; + var filePath = Path.of("/file/path"); + var issueUuid1 = UUID.randomUUID(); + var issueUuid2 = UUID.randomUUID(); + var issue1 = new LocalOnlyIssue(issueUuid1, filePath, null, null, "test-rule-1", "Message 1", null); + var issue2 = new LocalOnlyIssue(issueUuid2, filePath, null, null, "test-rule-2", "Message 2", null); + + repo.storeLocalOnlyIssue(configScopeId, issue1); + repo.storeLocalOnlyIssue(configScopeId, issue2); + + var removed = repo.removeIssue(issueUuid1); + + assertThat(removed).isTrue(); + var remainingIssues = repo.loadForFile(configScopeId, filePath); + assertThat(remainingIssues).hasSize(1); + assertThat(remainingIssues.get(0).getId()).isEqualTo(issueUuid2); + } + + @Test + void should_return_false_when_removing_nonexistent_issue(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var removed = repo.removeIssue(UUID.randomUUID()); + + assertThat(removed).isFalse(); + } + + @Test + void should_remove_all_issues_for_file(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var configScopeId = "configScopeId"; + var filePath1 = Path.of("/file/path1"); + var filePath2 = Path.of("/file/path2"); + var issue1 = new LocalOnlyIssue(UUID.randomUUID(), filePath1, null, null, "test-rule-1", "Message 1", null); + var issue2 = new LocalOnlyIssue(UUID.randomUUID(), filePath1, null, null, "test-rule-2", "Message 2", null); + var issue3 = new LocalOnlyIssue(UUID.randomUUID(), filePath2, null, null, "test-rule-3", "Message 3", null); + + repo.storeLocalOnlyIssue(configScopeId, issue1); + repo.storeLocalOnlyIssue(configScopeId, issue2); + repo.storeLocalOnlyIssue(configScopeId, issue3); + + var removed = repo.removeAllIssuesForFile(configScopeId, filePath1); + + assertThat(removed).isTrue(); + assertThat(repo.loadForFile(configScopeId, filePath1)).isEmpty(); + assertThat(repo.loadForFile(configScopeId, filePath2)).hasSize(1); + } + + @Test + void should_update_existing_issue(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var configScopeId = "configScopeId"; + var filePath = Path.of("/file/path"); + var issueUuid = UUID.randomUUID(); + var issue1 = new LocalOnlyIssue(issueUuid, filePath, null, null, "test-rule-1", "Original message", null); + + repo.storeLocalOnlyIssue(configScopeId, issue1); + + var resolution = new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, Instant.now().truncatedTo(ChronoUnit.MILLIS), "Updated comment"); + var issue2 = new LocalOnlyIssue(issueUuid, filePath, new TextRangeWithHash(1, 2, 3, 4, "hash"), + new LineWithHash(1, "linehash"), "test-rule-1", "Updated message", resolution); + + repo.storeLocalOnlyIssue(configScopeId, issue2); + + var loadedIssues = repo.loadForFile(configScopeId, filePath); + + assertThat(loadedIssues).hasSize(1); + var loadedIssue = loadedIssues.get(0); + assertThat(loadedIssue.getId()).isEqualTo(issueUuid); + assertThat(loadedIssue.getMessage()).isEqualTo("Updated message"); + assertThat(loadedIssue.getTextRangeWithHash()).isEqualTo(new TextRangeWithHash(1, 2, 3, 4, "hash")); + assertThat(loadedIssue.getLineWithHash().getNumber()).isEqualTo(1); + assertThat(loadedIssue.getLineWithHash().getHash()).isEqualTo("linehash"); + assertThat(loadedIssue.getResolution()).isNotNull(); + assertThat(loadedIssue.getResolution().getStatus()).isEqualTo(IssueStatus.WONT_FIX); + assertThat(loadedIssue.getResolution().getComment()).isEqualTo("Updated comment"); + } + + @Test + void should_purge_old_resolved_issues(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var configScopeId = "configScopeId"; + var filePath = Path.of("/file/path"); + var oldDate = Instant.now().minus(10, ChronoUnit.DAYS); + var recentDate = Instant.now().minus(1, ChronoUnit.DAYS); + var limit = Instant.now().minus(5, ChronoUnit.DAYS); + + var oldIssue = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, "test-rule-1", "Old issue", + new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, oldDate.truncatedTo(ChronoUnit.MILLIS), "comment")); + var recentIssue = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, "test-rule-2", "Recent issue", + new LocalOnlyIssueResolution(IssueStatus.WONT_FIX, recentDate.truncatedTo(ChronoUnit.MILLIS), "comment")); + var unresolvedIssue = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, "test-rule-3", "Unresolved issue", null); + + repo.storeLocalOnlyIssue(configScopeId, oldIssue); + repo.storeLocalOnlyIssue(configScopeId, recentIssue); + repo.storeLocalOnlyIssue(configScopeId, unresolvedIssue); + + repo.purgeIssuesOlderThan(limit); + + var remainingIssues = repo.loadAll(configScopeId); + assertThat(remainingIssues).hasSize(2); + assertThat(remainingIssues).extracting(LocalOnlyIssue::getId) + .containsExactlyInAnyOrder(recentIssue.getId(), unresolvedIssue.getId()); + } + + @Test + void should_isolate_issues_by_configuration_scope(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var configScopeId1 = "configScopeId1"; + var configScopeId2 = "configScopeId2"; + var filePath = Path.of("/file/path"); + var issue1 = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, "test-rule-1", "Message 1", null); + var issue2 = new LocalOnlyIssue(UUID.randomUUID(), filePath, null, null, "test-rule-2", "Message 2", null); + + repo.storeLocalOnlyIssue(configScopeId1, issue1); + repo.storeLocalOnlyIssue(configScopeId2, issue2); + + assertThat(repo.loadAll(configScopeId1)).hasSize(1); + assertThat(repo.loadAll(configScopeId2)).hasSize(1); + assertThat(repo.loadForFile(configScopeId1, filePath)).hasSize(1); + assertThat(repo.loadForFile(configScopeId2, filePath)).hasSize(1); + } + + @Test + void should_handle_issue_with_only_text_range(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var configScopeId = "configScopeId"; + var filePath = Path.of("/file/path"); + var issueUuid = UUID.randomUUID(); + var issue = new LocalOnlyIssue(issueUuid, filePath, new TextRangeWithHash(1, 2, 3, 4, "hash1"), + null, "test-rule-1", "Test message", null); + + repo.storeLocalOnlyIssue(configScopeId, issue); + + var loadedIssues = repo.loadForFile(configScopeId, filePath); + + assertThat(loadedIssues).hasSize(1); + var loadedIssue = loadedIssues.get(0); + assertThat(loadedIssue.getTextRangeWithHash()).isEqualTo(new TextRangeWithHash(1, 2, 3, 4, "hash1")); + assertThat(loadedIssue.getLineWithHash()).isNull(); + } + + @Test + void should_handle_issue_with_only_line_hash(@TempDir Path temp) { + var storageRoot = temp.resolve("storage"); + var db = new SonarLintDatabase(new SonarLintDatabaseInitParams(storageRoot, SonarLintDatabaseMode.FILE)); + var repo = new LocalOnlyIssuesRepository(db); + + var configScopeId = "configScopeId"; + var filePath = Path.of("/file/path"); + var issueUuid = UUID.randomUUID(); + var issue = new LocalOnlyIssue(issueUuid, filePath, null, + new LineWithHash(5, "linehash"), "test-rule-1", "Test message", null); + + repo.storeLocalOnlyIssue(configScopeId, issue); + + var loadedIssues = repo.loadForFile(configScopeId, filePath); + + assertThat(loadedIssues).hasSize(1); + var loadedIssue = loadedIssues.get(0); + assertThat(loadedIssue.getTextRangeWithHash()).isNull(); + assertThat(loadedIssue.getLineWithHash().getNumber()).isEqualTo(5); + assertThat(loadedIssue.getLineWithHash().getHash()).isEqualTo("linehash"); + } + +} + diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java index b2013feacc..b9833a704a 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java @@ -42,12 +42,13 @@ import org.sonarsource.sonarlint.core.commons.NewCodeDefinition; import org.sonarsource.sonarlint.core.commons.Transition; import org.sonarsource.sonarlint.core.commons.Version; +import org.sonarsource.sonarlint.core.commons.monitoring.DogfoodEnvironmentDetectionService; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; +import org.sonarsource.sonarlint.core.commons.storage.repository.LocalOnlyIssuesRepository; import org.sonarsource.sonarlint.core.event.LocalOnlyIssueStatusChangedEvent; import org.sonarsource.sonarlint.core.event.ServerIssueStatusChangedEvent; import org.sonarsource.sonarlint.core.event.SonarServerEventReceivedEvent; import org.sonarsource.sonarlint.core.local.only.LocalOnlyIssueStorageService; -import org.sonarsource.sonarlint.core.local.only.XodusLocalOnlyIssueStore; import org.sonarsource.sonarlint.core.mode.SeverityModeService; import org.sonarsource.sonarlint.core.newcode.NewCodeService; import org.sonarsource.sonarlint.core.remediation.aicodefix.AiCodeFixService; @@ -74,6 +75,7 @@ import org.sonarsource.sonarlint.core.serverapi.push.IssueChangedEvent; import org.sonarsource.sonarlint.core.serverconnection.ServerInfoSynchronizer; import org.sonarsource.sonarlint.core.serverconnection.storage.ProjectServerIssueStore; +import org.sonarsource.sonarlint.core.storage.SonarLintDatabaseService; import org.sonarsource.sonarlint.core.storage.StorageService; import org.sonarsource.sonarlint.core.tracking.LocalOnlyIssueRepository; import org.sonarsource.sonarlint.core.tracking.TaintVulnerabilityTrackingService; @@ -109,11 +111,14 @@ public class IssueService { private final ActiveRulesService activeRulesService; private final TaintVulnerabilityTrackingService taintVulnerabilityTrackingService; private final AiCodeFixService aiCodeFixService; + private final DogfoodEnvironmentDetectionService dogfoodEnvironmentDetectionService; + private final SonarLintDatabaseService databaseService; public IssueService(ConfigurationRepository configurationRepository, SonarQubeClientManager sonarQubeClientManager, StorageService storageService, LocalOnlyIssueStorageService localOnlyIssueStorageService, LocalOnlyIssueRepository localOnlyIssueRepository, ApplicationEventPublisher eventPublisher, FindingReportingService findingReportingService, SeverityModeService severityModeService, - NewCodeService newCodeService, ActiveRulesService activeRulesService, TaintVulnerabilityTrackingService taintVulnerabilityTrackingService, AiCodeFixService aiCodeFixService) { + NewCodeService newCodeService, ActiveRulesService activeRulesService, TaintVulnerabilityTrackingService taintVulnerabilityTrackingService, AiCodeFixService aiCodeFixService, + DogfoodEnvironmentDetectionService dogfoodEnvironmentDetectionService, SonarLintDatabaseService databaseService) { this.configurationRepository = configurationRepository; this.sonarQubeClientManager = sonarQubeClientManager; this.storageService = storageService; @@ -126,6 +131,8 @@ public IssueService(ConfigurationRepository configurationRepository, SonarQubeCl this.activeRulesService = activeRulesService; this.taintVulnerabilityTrackingService = taintVulnerabilityTrackingService; this.aiCodeFixService = aiCodeFixService; + this.dogfoodEnvironmentDetectionService = dogfoodEnvironmentDetectionService; + this.databaseService = databaseService; } public void changeStatus(String configurationScopeId, String issueKey, ResolutionStatus newStatus, boolean isTaintIssue, SonarLintCancelMonitor cancelMonitor) { @@ -153,10 +160,10 @@ public void changeStatus(String configurationScopeId, String issueKey, Resolutio var coreStatus = org.sonarsource.sonarlint.core.commons.IssueStatus.valueOf(newStatus.name()); var issue = localIssueOpt.get(); issue.resolve(coreStatus); - var localOnlyIssueStore = localOnlyIssueStorageService.get(); + var allIssues = loadAllLocalOnlyIssues(configurationScopeId); serverConnection.withClientApi(serverApi -> serverApi.issue() - .anticipatedTransitions(binding.sonarProjectKey(), concat(localOnlyIssueStore.loadAll(configurationScopeId), issue), cancelMonitor)); - localOnlyIssueStore.storeLocalOnlyIssue(configurationScopeId, issue); + .anticipatedTransitions(binding.sonarProjectKey(), concat(allIssues, issue), cancelMonitor)); + storeLocalOnlyIssue(configurationScopeId, issue); eventPublisher.publishEvent(new LocalOnlyIssueStatusChangedEvent(issue)); } } @@ -286,24 +293,21 @@ public boolean reopenIssue(String configurationScopeId, String issueId, boolean public boolean reopenAllIssuesForFile(ReopenAllIssuesForFileParams params, SonarLintCancelMonitor cancelMonitor) { var configurationScopeId = params.getConfigurationScopeId(); var ideRelativePath = params.getIdeRelativePath(); - var localOnlyIssueStore = localOnlyIssueStorageService.get(); - removeAllIssuesForFile(localOnlyIssueStore, configurationScopeId, ideRelativePath, cancelMonitor); - return localOnlyIssueStorageService.get().removeAllIssuesForFile(configurationScopeId, ideRelativePath); + removeAllIssuesForFile(configurationScopeId, ideRelativePath, cancelMonitor); + return removeAllIssuesForFileFromStore(configurationScopeId, ideRelativePath); } - private void removeAllIssuesForFile(XodusLocalOnlyIssueStore localOnlyIssueStore, - String configurationScopeId, Path filePath, SonarLintCancelMonitor cancelMonitor) { - var allIssues = localOnlyIssueStore.loadAll(configurationScopeId); - var issuesForFile = localOnlyIssueStore.loadForFile(configurationScopeId, filePath); + private void removeAllIssuesForFile(String configurationScopeId, Path filePath, SonarLintCancelMonitor cancelMonitor) { + var allIssues = loadAllLocalOnlyIssues(configurationScopeId); + var issuesForFile = loadLocalOnlyIssuesForFile(configurationScopeId, filePath); var issuesToSync = subtract(allIssues, issuesForFile); var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); sonarQubeClientManager.getClientOrThrow(binding.connectionId()) .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.sonarProjectKey(), issuesToSync, cancelMonitor)); } - private void removeIssueOnServer(XodusLocalOnlyIssueStore localOnlyIssueStore, - String configurationScopeId, UUID issueId, SonarLintCancelMonitor cancelMonitor) { - var allIssues = localOnlyIssueStore.loadAll(configurationScopeId); + private void removeIssueOnServer(String configurationScopeId, UUID issueId, SonarLintCancelMonitor cancelMonitor) { + var allIssues = loadAllLocalOnlyIssues(configurationScopeId); var issuesToSync = allIssues.stream().filter(it -> !it.getId().equals(issueId)).toList(); var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); sonarQubeClientManager.getClientOrThrow(binding.connectionId()) @@ -311,19 +315,18 @@ private void removeIssueOnServer(XodusLocalOnlyIssueStore localOnlyIssueStore, } private void setCommentOnLocalOnlyIssue(String configurationScopeId, UUID issueId, String comment, SonarLintCancelMonitor cancelMonitor) { - var localOnlyIssueStore = localOnlyIssueStorageService.get(); - var optionalLocalOnlyIssue = localOnlyIssueStore.find(issueId); + var optionalLocalOnlyIssue = findLocalOnlyIssue(issueId); if (optionalLocalOnlyIssue.isPresent()) { var commentedIssue = optionalLocalOnlyIssue.get(); var resolution = commentedIssue.getResolution(); if (resolution != null) { resolution.setComment(comment); - var issuesToSync = localOnlyIssueStore.loadAll(configurationScopeId); + var issuesToSync = loadAllLocalOnlyIssues(configurationScopeId); issuesToSync.replaceAll(issue -> issue.getId().equals(issueId) ? commentedIssue : issue); var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); sonarQubeClientManager.getClientOrThrow(binding.connectionId()) .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.sonarProjectKey(), issuesToSync, cancelMonitor)); - localOnlyIssueStore.storeLocalOnlyIssue(configurationScopeId, commentedIssue); + storeLocalOnlyIssue(configurationScopeId, commentedIssue); } } else { throw issueNotFoundException(issueId.toString()); @@ -355,9 +358,8 @@ private boolean reopenLocalIssue(String issueId, String configurationScopeId, So return false; } var issueUuid = issueUuidOptional.get(); - var localOnlyIssueStore = localOnlyIssueStorageService.get(); - removeIssueOnServer(localOnlyIssueStore, configurationScopeId, issueUuid, cancelMonitor); - return localOnlyIssueStorageService.get().removeIssue(issueUuid); + removeIssueOnServer(configurationScopeId, issueUuid, cancelMonitor); + return removeLocalOnlyIssue(issueUuid); } public EffectiveIssueDetailsDto getEffectiveIssueDetails(String configurationScopeId, UUID findingId, SonarLintCancelMonitor cancelMonitor) @@ -535,4 +537,59 @@ private static Optional asUUID(String key) { return Optional.empty(); } } + + // Helper methods to abstract between Xodus and H2 storage + private List loadAllLocalOnlyIssues(String configurationScopeId) { + if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) { + var repository = new LocalOnlyIssuesRepository(databaseService.getDatabase()); + return repository.loadAll(configurationScopeId); + } else { + return localOnlyIssueStorageService.get().loadAll(configurationScopeId); + } + } + + private List loadLocalOnlyIssuesForFile(String configurationScopeId, Path filePath) { + if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) { + var repository = new LocalOnlyIssuesRepository(databaseService.getDatabase()); + return repository.loadForFile(configurationScopeId, filePath); + } else { + return localOnlyIssueStorageService.get().loadForFile(configurationScopeId, filePath); + } + } + + private Optional findLocalOnlyIssue(UUID issueId) { + if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) { + var repository = new LocalOnlyIssuesRepository(databaseService.getDatabase()); + return repository.find(issueId); + } else { + return localOnlyIssueStorageService.get().find(issueId); + } + } + + private void storeLocalOnlyIssue(String configurationScopeId, LocalOnlyIssue issue) { + if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) { + var repository = new LocalOnlyIssuesRepository(databaseService.getDatabase()); + repository.storeLocalOnlyIssue(configurationScopeId, issue); + } else { + localOnlyIssueStorageService.get().storeLocalOnlyIssue(configurationScopeId, issue); + } + } + + private boolean removeLocalOnlyIssue(UUID issueId) { + if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) { + var repository = new LocalOnlyIssuesRepository(databaseService.getDatabase()); + return repository.removeIssue(issueId); + } else { + return localOnlyIssueStorageService.get().removeIssue(issueId); + } + } + + private boolean removeAllIssuesForFileFromStore(String configurationScopeId, Path filePath) { + if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) { + var repository = new LocalOnlyIssuesRepository(databaseService.getDatabase()); + return repository.removeAllIssuesForFile(configurationScopeId, filePath); + } else { + return localOnlyIssueStorageService.get().removeAllIssuesForFile(configurationScopeId, filePath); + } + } } 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 a776ac347e..6ddfe80a92 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 @@ -45,6 +45,7 @@ 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.storage.repository.LocalOnlyIssuesRepository; 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; @@ -178,7 +179,7 @@ private MatchingResult matchWithServerFindings(String configurationScopeId, Matc var ideRelativePath = e.getKey(); var serverRelativePath = translation.ideToServerPath(ideRelativePath); var serverIssues = storageService.binding(binding).findings().load(activeBranch, serverRelativePath); - var localOnlyIssues = localOnlyIssueStorageService.get().loadForFile(configurationScopeId, serverRelativePath); + var localOnlyIssues = loadLocalOnlyIssuesForFile(configurationScopeId, serverRelativePath); var matches = matchWithServerIssues(serverRelativePath, serverIssues, localOnlyIssues, e.getValue()); return Map.entry(ideRelativePath, matches); }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); @@ -359,4 +360,14 @@ private static Instant determineIntroductionDate(Path path, Collection private record MatchingResult(Map> issuesToReport, Map> hotspotsToReport) { } + + // Helper method to abstract between Xodus and H2 storage + private List loadLocalOnlyIssuesForFile(String configurationScopeId, Path filePath) { + if (dogfoodEnvironmentDetectionService.isDogfoodEnvironment()) { + var repository = new LocalOnlyIssuesRepository(databaseService.getDatabase()); + return repository.loadForFile(configurationScopeId, filePath); + } else { + return localOnlyIssueStorageService.get().loadForFile(configurationScopeId, filePath); + } + } } From 9d549c5a9b79206b99074bdd108c5095be924a96 Mon Sep 17 00:00:00 2001 From: Eray Felek Date: Fri, 7 Nov 2025 12:15:28 +0100 Subject: [PATCH 2/2] Fix unwanted analysis --- .github/workflows/build.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb917e526c..554e62857a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,22 +72,12 @@ jobs: SCANNER_VERSION: 5.1.0.4751 PULL_REQUEST: ${{ github.event.pull_request.number || '' }} run: | - if git rev-parse --is-shallow-repository --quiet >/dev/null 2>&1; then - echo "Fetch Git references for SonarQube analysis..." - git fetch --unshallow || true - fi - maven_goals=("verify" "org.sonarsource.scanner.maven:sonar-maven-plugin:${SCANNER_VERSION}:sonar") + mvn -Pcoverage -Dcommercial verify + maven_goals=("org.sonarsource.scanner.maven:sonar-maven-plugin:${SCANNER_VERSION}:sonar") maven_goals+=("-Pcoverage" "-Dcommercial") sonar_props=("-Dsonar.host.url=${SONAR_HOST_URL}" "-Dsonar.token=${SONAR_TOKEN}") sonar_props+=("-Dsonar.projectVersion=${CURRENT_VERSION}" "-Dsonar.scm.revision=$GITHUB_SHA") sonar_props+=("-Dsonar.coverage.jacoco.xmlReportPaths=${{ github.workspace }}/report-aggregate/target/site/jacoco-aggregate/jacoco.xml") - if [[ "${GITHUB_EVENT_NAME:?}" == "pull_request" ]]; then - sonar_props+=("-Dsonar.pullrequest.key=$PULL_REQUEST") - sonar_props+=("-Dsonar.pullrequest.branch=$GITHUB_HEAD_REF") - sonar_props+=("-Dsonar.pullrequest.base=$GITHUB_BASE_REF") - echo "Fetch ${GITHUB_BASE_REF:?} for SonarQube analysis..." - git fetch origin "${GITHUB_BASE_REF}" || true - fi echo "Maven command: mvn ${maven_goals[*]} ${sonar_props[*]}" mvn "${maven_goals[@]}" "${sonar_props[@]}" - name: Generate test report on failure