Skip to content

Commit

Permalink
SLCORE-1161 Expose if issue is fixable when rule is supported
Browse files Browse the repository at this point in the history
  • Loading branch information
damien-urruty-sonarsource committed Feb 20, 2025
1 parent 4dfa6d2 commit b31c998
Show file tree
Hide file tree
Showing 32 changed files with 744 additions and 245 deletions.
11 changes: 11 additions & 0 deletions API_CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# 10.16

## New features

* Introduce a new `org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.AiCodeFixRpcService` class, containing a `suggestFix(SuggestFixParams)` method.
* Introduce a new `isAiCodeFixable` method in `org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto`.

## Deprecation

* Deprecate the `org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.SonarCloudAlternativeEnvironmentDto` constructor. It is replaced by an overload in which the new API base URL should be provided.

# 10.14

## Breaking changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ public static GetRuleDetailsResponse toRuleDetailsResponse(RuleDetailsForAnalysi
.toList(), RuleDetailsAdapter.adapt(ruleDetails.getVulnerabilityProbability()));
}

public static RaisedIssueDto toRaisedIssueDto(TrackedIssue issue, NewCodeDefinition newCodeDefinition, boolean isMQRMode) {
public static RaisedIssueDto toRaisedIssueDto(TrackedIssue issue, NewCodeDefinition newCodeDefinition, boolean isMQRMode, boolean isAiCodeFixable) {
return new RaisedIssueDto(issue.getId(), issue.getServerKey(), issue.getRuleKey(), issue.getMessage(),
isMQRMode ? Either.forRight(new MQRModeDetails(RuleDetailsAdapter.adapt(issue.getCleanCodeAttribute()), RuleDetailsAdapter.toDto(issue.getImpacts())))
: Either.forLeft(new StandardModeDetails(RuleDetailsAdapter.adapt(issue.getSeverity()), RuleDetailsAdapter.adapt(issue.getType()))),
requireNonNull(issue.getIntroductionDate()), newCodeDefinition.isOnNewCode(issue.getIntroductionDate()), issue.isResolved(),
toTextRangeDto(issue.getTextRangeWithHash()),
issue.getFlows().stream().map(RuleDetailsAdapter::adapt).toList(),
issue.getQuickFixes().stream().map(RuleDetailsAdapter::adapt).toList(),
issue.getRuleDescriptionContextKey());
issue.getRuleDescriptionContextKey(), isAiCodeFixable);
}

public static RaisedHotspotDto toRaisedHotspotDto(TrackedIssue issue, NewCodeDefinition newCodeDefinition, boolean isMQRMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ public static SonarCloudActiveEnvironment prod() {
public SonarCloudActiveEnvironment() {
}

public SonarCloudActiveEnvironment(URI uri, URI webSocketsEndpointUri) {
this.alternativeUris = new SonarQubeCloudUris(uri, webSocketsEndpointUri);
public SonarCloudActiveEnvironment(URI uri, URI apiUri, URI webSocketsEndpointUri) {
this.alternativeUris = new SonarQubeCloudUris(uri, apiUri, webSocketsEndpointUri);
}

public URI getUri(SonarCloudRegion region) {
return alternativeUris != null ? alternativeUris.productionUri : region.getProductionUri();
}

public URI getApiUri(SonarCloudRegion region) {
return alternativeUris != null ? alternativeUris.productionUri : region.getApiProductionUri();
return alternativeUris != null ? alternativeUris.productionApiUri : region.getApiProductionUri();
}

public URI getWebSocketsEndpointUri(SonarCloudRegion region) {
Expand Down Expand Up @@ -75,21 +75,6 @@ private boolean isAlternativeUri(String uri) {
return alternativeUris != null && removeEnd(alternativeUris.productionUri.toString(), "/").equals(removeEnd(uri, "/"));
}

private static class SonarQubeCloudUris {
private final URI productionUri;
private final URI wsUri;

public SonarQubeCloudUris(URI productionUri, URI wsUri) {
this.productionUri = productionUri;
this.wsUri = wsUri;
}

public URI getProductionUri() {
return productionUri;
}

public URI getWsUri() {
return wsUri;
}
private record SonarQubeCloudUris(URI productionUri, URI productionApiUri, URI wsUri) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ private void republishPreviouslyRaisedHotspots(String connectionId, SecurityHots

private static RaisedHotspotDto changedHotspotUpdater(RaisedHotspotDto raisedHotspotDto, SecurityHotspotChangedEvent event) {
if (event.getHotspotKey().equals(raisedHotspotDto.getServerKey())) {
return raisedHotspotDto.builder().withHotspotStatus(HotspotStatus.valueOf(event.getStatus().name())).buildHotspot();
return raisedHotspotDto.withHotspotStatus(HotspotStatus.valueOf(event.getStatus().name()));
}
return raisedHotspotDto;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
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;
import org.sonarsource.sonarlint.core.reporting.FindingReportingService;
import org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;
Expand Down Expand Up @@ -106,11 +107,12 @@ public class IssueService {
private final NewCodeService newCodeService;
private final RulesService rulesService;
private final TaintVulnerabilityTrackingService taintVulnerabilityTrackingService;
private final AiCodeFixService aiCodeFixService;

public IssueService(ConfigurationRepository configurationRepository, ConnectionManager connectionManager, StorageService storageService,
LocalOnlyIssueStorageService localOnlyIssueStorageService, LocalOnlyIssueRepository localOnlyIssueRepository,
ApplicationEventPublisher eventPublisher, FindingReportingService findingReportingService, SeverityModeService severityModeService,
NewCodeService newCodeService, RulesService rulesService, TaintVulnerabilityTrackingService taintVulnerabilityTrackingService) {
NewCodeService newCodeService, RulesService rulesService, TaintVulnerabilityTrackingService taintVulnerabilityTrackingService, AiCodeFixService aiCodeFixService) {
this.configurationRepository = configurationRepository;
this.connectionManager = connectionManager;
this.storageService = storageService;
Expand All @@ -122,6 +124,7 @@ public IssueService(ConfigurationRepository configurationRepository, ConnectionM
this.newCodeService = newCodeService;
this.rulesService = rulesService;
this.taintVulnerabilityTrackingService = taintVulnerabilityTrackingService;
this.aiCodeFixService = aiCodeFixService;
}

public void changeStatus(String configurationScopeId, String issueKey, ResolutionStatus newStatus, boolean isTaintIssue, SonarLintCancelMonitor cancelMonitor) {
Expand Down Expand Up @@ -353,7 +356,8 @@ public EffectiveIssueDetailsDto getEffectiveIssueDetails(String configurationSco
}
var isMQRMode = severityModeService.isMQRModeForConnection(connectionId);
var newCodeDefinition = newCodeService.getFullNewCodeDefinition(configurationScopeId).orElseGet(NewCodeDefinition::withAlwaysNew);
var maybeIssue = findingReportingService.findReportedIssue(findingId, newCodeDefinition, isMQRMode);
var aiCodeFixFeature = effectiveBinding.flatMap(aiCodeFixService::getFeature);
var maybeIssue = findingReportingService.findReportedIssue(findingId, newCodeDefinition, isMQRMode, aiCodeFixFeature);
var maybeHotspot = findingReportingService.findReportedHotspot(findingId, newCodeDefinition, isMQRMode);
var maybeTaint = taintVulnerabilityTrackingService.getTaintVulnerability(configurationScopeId, findingId, cancelMonitor);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SonarLint Core - Implementation
* 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.remediation.aicodefix;

import org.sonarsource.sonarlint.core.repository.reporting.RaisedIssue;
import org.sonarsource.sonarlint.core.serverconnection.AiCodeFixSettings;
import org.sonarsource.sonarlint.core.tracking.TrackedIssue;

public record AiCodeFixFeature(AiCodeFixSettings settings) {

public boolean isFixable(TrackedIssue issue) {
return settings.supportedRules().contains(issue.getRuleKey()) && issue.getTextRangeWithHash() != null;
}

public boolean isFixable(RaisedIssue issue) {
return settings.supportedRules().contains(issue.issueDto().getRuleKey()) && issue.issueDto().getTextRange() != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package org.sonarsource.sonarlint.core.remediation.aicodefix;

import java.util.Optional;
import java.util.UUID;
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
Expand All @@ -36,6 +37,7 @@
import org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.SuggestFixResponse;
import org.sonarsource.sonarlint.core.serverapi.fixsuggestions.AiSuggestionRequestBodyDto;
import org.sonarsource.sonarlint.core.serverapi.fixsuggestions.AiSuggestionResponseBodyDto;
import org.sonarsource.sonarlint.core.storage.StorageService;

import static java.util.Objects.requireNonNull;
import static org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode.CONFIG_SCOPE_NOT_BOUND;
Expand All @@ -50,22 +52,25 @@ public class AiCodeFixService {
private final ConnectionManager connectionManager;
private final PreviouslyRaisedFindingsRepository previouslyRaisedFindingsRepository;
private final ClientFileSystemService clientFileSystemService;
private final StorageService storageService;

public AiCodeFixService(ConnectionConfigurationRepository connectionRepository, ConfigurationRepository configurationRepository, ConnectionManager connectionManager,
PreviouslyRaisedFindingsRepository previouslyRaisedFindingsRepository, ClientFileSystemService clientFileSystemService) {
PreviouslyRaisedFindingsRepository previouslyRaisedFindingsRepository, ClientFileSystemService clientFileSystemService, StorageService storageService) {
this.connectionRepository = connectionRepository;
this.configurationRepository = configurationRepository;
this.connectionManager = connectionManager;
this.previouslyRaisedFindingsRepository = previouslyRaisedFindingsRepository;
this.clientFileSystemService = clientFileSystemService;
this.storageService = storageService;
}

public SuggestFixResponse suggestFix(String configurationScopeId, UUID issueId, SonarLintCancelMonitor cancelMonitor) {
var sonarQubeCloudBinding = ensureBoundToSonarQubeCloud(configurationScopeId);
var connection = connectionManager.getConnectionOrThrow(sonarQubeCloudBinding.binding().connectionId());
var responseBodyDto = connection.withClientApiAndReturn(serverApi -> previouslyRaisedFindingsRepository.findRaisedIssueById(issueId)
.map(issue -> {
if (!isFixable(issue)) {
var aiCodeFixFeature = getFeature(sonarQubeCloudBinding.binding());
if (!aiCodeFixFeature.map(feature -> feature.isFixable(issue)).orElse(false)) {
throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams, "The provided issue cannot be fixed", issueId));
}
return serverApi.fixSuggestions().getAiSuggestion(toDto(sonarQubeCloudBinding.organizationKey, sonarQubeCloudBinding.binding().sonarProjectKey(), issue),
Expand All @@ -75,8 +80,9 @@ public SuggestFixResponse suggestFix(String configurationScopeId, UUID issueId,
return adapt(responseBodyDto);
}

private static boolean isFixable(RaisedIssue issue) {
return issue.issueDto().getTextRange() != null;
public Optional<AiCodeFixFeature> getFeature(Binding binding) {
return storageService.connection(binding.connectionId()).aiCodeFix().read()
.map(AiCodeFixFeature::new);
}

private static SuggestFixResponse adapt(AiSuggestionResponseBodyDto responseBodyDto) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* SonarLint Core - Implementation
* 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.
*/
@ParametersAreNonnullByDefault
package org.sonarsource.sonarlint.core.remediation.aicodefix;

import javax.annotation.ParametersAreNonnullByDefault;
Loading

0 comments on commit b31c998

Please sign in to comment.