Skip to content

Commit

Permalink
SLLS-204 Open issue in IDE - allow users to continue the flow without…
Browse files Browse the repository at this point in the history
… jumping back to the browser
  • Loading branch information
kirill-knize-sonarsource authored Jan 30, 2024
1 parent 42e862a commit c8a88e7
Show file tree
Hide file tree
Showing 14 changed files with 392 additions and 283 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<properties>
<jdk.min.version>17</jdk.min.version>
<sonarlint.core.version>9.8.0.76911</sonarlint.core.version>
<sonarlint.core.version>9.8.0.76914</sonarlint.core.version>
<!-- Version used by Xodus -->
<kotlin.version>1.6.10</kotlin.version>
<!-- analyzers used for tests -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
import org.eclipse.lsp4j.services.LanguageClient;
import org.sonarsource.sonarlint.core.clientapi.backend.rules.EffectiveRuleParamDto;
import org.sonarsource.sonarlint.core.clientapi.backend.rules.RuleParamDefinitionDto;
Expand Down Expand Up @@ -68,11 +69,38 @@ public interface SonarLintExtendedLanguageClient extends LanguageClient {
@JsonNotification("sonarlint/openConnectionSettings")
void openConnectionSettings(boolean isSonarCloud);

@JsonNotification("sonarlint/assistCreatingConnection")
void assistCreatingConnection(CreateConnectionParams params);
@JsonRequest("sonarlint/assistCreatingConnection")
CompletableFuture<AssistCreatingConnectionResponse> assistCreatingConnection(CreateConnectionParams params);

class AssistCreatingConnectionResponse {
private final String newConnectionId;

public AssistCreatingConnectionResponse(@NonNull String newConnectionId) {
this.newConnectionId = newConnectionId;
}

@NonNull
public String getNewConnectionId() {
return newConnectionId;
}
}

@JsonRequest("sonarlint/assistBinding")
CompletableFuture<AssistBindingResponse> assistBinding(AssistBindingParams params);

class AssistBindingResponse {
private final String configurationScopeId;

public AssistBindingResponse(@NonNull String configurationScopeId) {
this.configurationScopeId = configurationScopeId;
}

@NonNull
public String getConfigurationScopeId() {
return configurationScopeId;
}
}

@JsonNotification("sonarlint/assistBinding")
void assistBinding(AssistBindingParams params);

@JsonNotification("sonarlint/showRuleDescription")
void showRuleDescription(ShowRuleDescriptionParams params);
Expand Down Expand Up @@ -504,10 +532,12 @@ class CreateConnectionParams {
private final boolean isSonarCloud;

private final String serverUrl;
private final String token;

public CreateConnectionParams(boolean isSonarCloud, String serverUrl) {
public CreateConnectionParams(boolean isSonarCloud, String serverUrl, @Nullable String token) {
this.isSonarCloud = isSonarCloud;
this.serverUrl = serverUrl;
this.token = token;
}

public boolean isSonarCloud() {
Expand All @@ -517,6 +547,10 @@ public boolean isSonarCloud() {
public String getServerUrl() {
return serverUrl;
}

public String getToken() {
return token;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
import org.sonarsource.sonarlint.ls.connected.ProjectBindingManager;
import org.sonarsource.sonarlint.ls.connected.TaintIssuesUpdater;
import org.sonarsource.sonarlint.ls.connected.TaintVulnerabilitiesCache;
import org.sonarsource.sonarlint.ls.connected.api.RequestsHandlerServer;
import org.sonarsource.sonarlint.ls.connected.api.HostInfoProvider;
import org.sonarsource.sonarlint.ls.connected.events.ServerSentEventsHandler;
import org.sonarsource.sonarlint.ls.connected.events.ServerSentEventsHandlerService;
import org.sonarsource.sonarlint.ls.connected.notifications.SmartNotifications;
Expand Down Expand Up @@ -170,7 +170,7 @@ public class SonarLintLanguageServer implements SonarLintExtendedLanguageServer,
private final CommandManager commandManager;
private final ProgressManager progressManager;
private final ExecutorService threadPool;
private final RequestsHandlerServer requestsHandlerServer;
private final HostInfoProvider hostInfoProvider;
private final WorkspaceFolderBranchManager branchManager;
private final JavaConfigCache javaConfigCache;
private final IssuesCache issuesCache;
Expand Down Expand Up @@ -220,8 +220,8 @@ public class SonarLintLanguageServer implements SonarLintExtendedLanguageServer,
this.notebookDiagnosticPublisher.setOpenNotebooksCache(openNotebooksCache);
this.diagnosticPublisher = new DiagnosticPublisher(client, taintVulnerabilitiesCache, issuesCache, securityHotspotsCache, openNotebooksCache);
this.progressManager = new ProgressManager(client, globalLogOutput);
this.requestsHandlerServer = new RequestsHandlerServer(client);
var vsCodeClient = new SonarLintVSCodeClient(client, requestsHandlerServer, globalLogOutput);
this.hostInfoProvider = new HostInfoProvider();
var vsCodeClient = new SonarLintVSCodeClient(client, hostInfoProvider, globalLogOutput);
this.backendServiceFacade = new BackendServiceFacade(new SonarLintBackendImpl(vsCodeClient), lsLogOutput, client);
vsCodeClient.setBackendServiceFacade(backendServiceFacade);
this.workspaceFoldersManager = new WorkspaceFoldersManager(backendServiceFacade, globalLogOutput);
Expand Down Expand Up @@ -326,7 +326,7 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
analysisScheduler.initialize();
diagnosticPublisher.initialize(firstSecretDetected);

requestsHandlerServer.initialize(clientVersion, workspaceName);
hostInfoProvider.initialize(clientVersion, workspaceName);
backendServiceFacade.setTelemetryInitParams(new TelemetryInitParams(productKey, telemetryStorage,
productName, productVersion, ideVersion, platform, architecture, additionalAttributes));
enginesFactory.setOmnisharpDirectory((String) additionalAttributes.get("omnisharpDirectory"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,32 @@
*/
package org.sonarsource.sonarlint.ls.clientapi;

import java.net.URI;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import nl.altindag.ssl.util.CertificateUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.eclipse.lsp4j.MessageActionItem;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.WorkspaceFolder;
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
import org.jetbrains.annotations.NotNull;
import org.sonarsource.sonarlint.core.clientapi.SonarLintClient;
import org.sonarsource.sonarlint.core.clientapi.client.OpenUrlInBrowserParams;
import org.sonarsource.sonarlint.core.clientapi.client.binding.AssistBindingResponse;
import org.sonarsource.sonarlint.core.clientapi.client.binding.NoBindingSuggestionFoundParams;
import org.sonarsource.sonarlint.core.clientapi.client.binding.SuggestBindingParams;
import org.sonarsource.sonarlint.core.clientapi.client.connection.AssistCreatingConnectionParams;
import org.sonarsource.sonarlint.core.clientapi.client.connection.AssistCreatingConnectionResponse;
import org.sonarsource.sonarlint.core.clientapi.client.connection.GetCredentialsParams;
import org.sonarsource.sonarlint.core.clientapi.client.connection.GetCredentialsResponse;
import org.sonarsource.sonarlint.core.clientapi.client.event.DidReceiveServerEventParams;
Expand All @@ -47,14 +63,16 @@
import org.sonarsource.sonarlint.core.commons.SonarLintUserHome;
import org.sonarsource.sonarlint.ls.EnginesFactory;
import org.sonarsource.sonarlint.ls.SonarLintExtendedLanguageClient;
import org.sonarsource.sonarlint.ls.SonarLintExtendedLanguageClient.CreateConnectionParams;
import org.sonarsource.sonarlint.ls.backend.BackendServiceFacade;
import org.sonarsource.sonarlint.ls.commands.ShowAllLocationsCommand;
import org.sonarsource.sonarlint.ls.connected.ProjectBindingManager;
import org.sonarsource.sonarlint.ls.connected.ProjectBindingWrapper;
import org.sonarsource.sonarlint.ls.connected.api.RequestsHandlerServer;
import org.sonarsource.sonarlint.ls.connected.api.HostInfoProvider;
import org.sonarsource.sonarlint.ls.connected.events.ServerSentEventsHandlerService;
import org.sonarsource.sonarlint.ls.connected.notifications.SmartNotifications;
import org.sonarsource.sonarlint.ls.log.LanguageClientLogOutput;
import org.sonarsource.sonarlint.ls.settings.ServerConnectionSettings;
import org.sonarsource.sonarlint.ls.settings.SettingsManager;
import org.sonarsource.sonarlint.ls.util.Utils;

Expand All @@ -63,16 +81,16 @@ public class SonarLintVSCodeClient implements SonarLintClient {
private final SonarLintExtendedLanguageClient client;
private SettingsManager settingsManager;
private SmartNotifications smartNotifications;
private final RequestsHandlerServer server;
private final HostInfoProvider hostInfoProvider;
private final LanguageClientLogOutput logOutput;
private ProjectBindingManager bindingManager;
private ServerSentEventsHandlerService serverSentEventsHandlerService;
private BackendServiceFacade backendServiceFacade;

public SonarLintVSCodeClient(SonarLintExtendedLanguageClient client, RequestsHandlerServer server,
public SonarLintVSCodeClient(SonarLintExtendedLanguageClient client, HostInfoProvider hostInfoProvider,
LanguageClientLogOutput logOutput) {
this.client = client;
this.server = server;
this.hostInfoProvider = hostInfoProvider;
this.logOutput = logOutput;
}

Expand All @@ -85,7 +103,9 @@ public void suggestBinding(SuggestBindingParams params) {

@Override
public CompletableFuture<FindFileByNamesInScopeResponse> findFileByNamesInScope(FindFileByNamesInScopeParams params) {
return client.findFileByNamesInFolder(new SonarLintExtendedLanguageClient.FindFileByNamesInFolder(params.getConfigScopeId(), params.getFilenames()));
return CompletableFutures.computeAsync( cancelToken -> client.findFileByNamesInFolder(
new SonarLintExtendedLanguageClient.FindFileByNamesInFolder(params.getConfigScopeId(), params.getFilenames()))
.join());
}

@Override
Expand Down Expand Up @@ -117,7 +137,7 @@ public void showSmartNotification(ShowSmartNotificationParams showSmartNotificat

@Override
public CompletableFuture<GetClientInfoResponse> getClientInfo() {
return CompletableFuture.completedFuture(server.getHostInfo());
return CompletableFuture.completedFuture(hostInfoProvider.getHostInfo());
}

@Override
Expand All @@ -136,17 +156,64 @@ public void showIssue(ShowIssueParams showIssueParams) {
}

@Override
public CompletableFuture<org.sonarsource.sonarlint.core.clientapi.client.connection.AssistCreatingConnectionResponse>
assistCreatingConnection(org.sonarsource.sonarlint.core.clientapi.client.connection.AssistCreatingConnectionParams params) {
server.showIssueOrHotspotHandleUnknownServer(params.getServerUrl());
return CompletableFuture.failedFuture(new UnsupportedOperationException());
public CompletableFuture<AssistCreatingConnectionResponse> assistCreatingConnection(AssistCreatingConnectionParams params) {
return CompletableFutures.computeAsync(cancelChecker -> {
var tokenValue = params.getTokenValue();
var workspaceFoldersFuture = client.workspaceFolders();
var assistCreatingConnectionFuture = client.assistCreatingConnection(
new CreateConnectionParams(false, params.getServerUrl(), tokenValue));
return workspaceFoldersFuture.thenCombine(assistCreatingConnectionFuture, (workspaceFolders, assistCreatingConnectionResponse) -> {
var currentConnections = getCurrentConnections(params, assistCreatingConnectionResponse);
var newConnectionId = assistCreatingConnectionResponse.getNewConnectionId();
if (newConnectionId != null) {
client.showMessage(new MessageParams(MessageType.Info, "Connection to SonarQube was successfully created."));
backendServiceFacade.getBackendService().didChangeConnections(currentConnections);
}
return new AssistCreatingConnectionResponse(newConnectionId,
workspaceFolders.stream().map(WorkspaceFolder::getUri).collect(Collectors.toSet()));
}).join();
});
}

@NotNull
private HashMap<String, ServerConnectionSettings> getCurrentConnections(AssistCreatingConnectionParams params,
@Nullable SonarLintExtendedLanguageClient.AssistCreatingConnectionResponse assistCreatingConnectionResponse) {
if (assistCreatingConnectionResponse == null) {
throw new CancellationException("Automatic connection setup was cancelled");
}
var newConnection = new ServerConnectionSettings(assistCreatingConnectionResponse.getNewConnectionId(), params.getServerUrl(), params.getTokenValue(), null, false);
var currentConnections = new HashMap<>(settingsManager.getCurrentSettings().getServerConnections());
currentConnections.put(assistCreatingConnectionResponse.getNewConnectionId(), newConnection);
return currentConnections;
}

@Override
public CompletableFuture<AssistBindingResponse> assistBinding(org.sonarsource.sonarlint.core.clientapi.client.binding.AssistBindingParams params) {
return CompletableFutures.computeAsync(cancelChecker -> client.assistBinding(params)
.thenCompose(response -> bindingManager.getUpdatedBindingForWorkspaceFolder(URI.create(response.getConfigurationScopeId())))
.thenApply(configurationScopeId -> {
var pathParts = configurationScopeId.split("/");
var projectName = pathParts[pathParts.length - 1];
client.showMessage(new MessageParams(MessageType.Info, "Project '" + projectName + "' was successfully bound to '" + params.getProjectKey() + "'."));
return new AssistBindingResponse(configurationScopeId);
}).join());
}


@Override
public CompletableFuture<org.sonarsource.sonarlint.core.clientapi.client.binding.AssistBindingResponse>
assistBinding(org.sonarsource.sonarlint.core.clientapi.client.binding.AssistBindingParams params) {
server.showHotspotOrIssueHandleNoBinding(params);
return CompletableFuture.failedFuture(new UnsupportedOperationException());
public void noBindingSuggestionFound(NoBindingSuggestionFoundParams params) {
var messageRequestParams = new ShowMessageRequestParams();
messageRequestParams.setMessage("SonarLint couldn't match SonarQube project '" + params.getProjectKey() + "' to any of the currently " +
"open workspace folders. Please open your project in VSCode and try again.");
messageRequestParams.setType(MessageType.Error);
var learnMoreAction = new MessageActionItem("Learn more");
messageRequestParams.setActions(List.of(learnMoreAction));
client.showMessageRequest(messageRequestParams)
.thenAccept(action -> {
if (learnMoreAction.equals(action)) {
client.browseTo("https://docs.sonarsource.com/sonarlint/vs-code/troubleshooting/#troubleshooting-connected-mode-setup");
}
});
}

@Override
Expand Down Expand Up @@ -207,11 +274,6 @@ public void didReceiveServerEvent(DidReceiveServerEventParams params) {
serverSentEventsHandlerService.handleEvents(params.getServerEvent());
}

@Override
public void noBindingSuggestionFound(NoBindingSuggestionFoundParams noBindingSuggestionFoundParams) {
// TODO Merge with PR on automatic binding
}

public void setSettingsManager(SettingsManager settingsManager) {
this.settingsManager = settingsManager;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
Expand Down Expand Up @@ -75,6 +79,7 @@
import static java.util.Objects.requireNonNull;
import static java.util.function.Predicate.not;
import static org.sonarsource.sonarlint.ls.util.FileUtils.getFileRelativePath;
import static org.sonarsource.sonarlint.ls.util.Utils.fixWindowsURIEncoding;
import static org.sonarsource.sonarlint.ls.util.Utils.uriHasFileScheme;

/**
Expand All @@ -85,6 +90,7 @@ public class ProjectBindingManager implements WorkspaceSettingsChangeListener, W
private final WorkspaceFoldersManager foldersManager;
private final SettingsManager settingsManager;
private final ConcurrentMap<URI, Optional<ProjectBindingWrapper>> folderBindingCache;
private final ConcurrentMap<URI, CountDownLatch> bindingUpdateQueue = new ConcurrentHashMap<>();
private final LanguageClientLogOutput globalLogOutput;
private final ConcurrentMap<URI, Optional<ProjectBindingWrapper>> fileBindingCache = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Optional<ConnectedSonarLintEngine>> connectedEngineCacheByConnectionId;
Expand Down Expand Up @@ -183,6 +189,29 @@ private Optional<ProjectBindingWrapper> getBinding(Optional<WorkspaceFolderWrapp
});
}

public CompletableFuture<String> getUpdatedBindingForWorkspaceFolder(URI folderUri) {
var bindingUpdatedLatch = new CountDownLatch(1);
var updatedBinding = new CompletableFuture<String>();
bindingUpdateQueue.put(folderUri, bindingUpdatedLatch);
Executors.newSingleThreadExecutor().submit(() -> {
var actuallyUpdated = false;
try {
actuallyUpdated = bindingUpdatedLatch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted", e);
}
if (actuallyUpdated) {
getBinding(folderUri);
updatedBinding.complete(folderUri.toString());
} else {
updatedBinding.completeExceptionally(new IllegalStateException(String.format("Expected binding update for %s did not happen", folderUri.toString())));
}
});
return updatedBinding;
}


private Optional<ProjectBindingWrapper> getBindingAndRepublishTaints(Optional<WorkspaceFolderWrapper> folder, URI fileUri) {
var maybeBinding = getBinding(folder, fileUri);
maybeBinding.ifPresent(binding ->
Expand Down Expand Up @@ -314,6 +343,9 @@ public void onChange(@Nullable WorkspaceFolderWrapper folder, @Nullable Workspac
&& (!Objects.equals(oldValue.getConnectionId(), newValue.getConnectionId()) || !Objects.equals(oldValue.getProjectKey(), newValue.getProjectKey()))) {
forceRebindDuringNextAnalysis(folder);
if (folder == null) return;
var uri = fixWindowsURIEncoding(folder.getUri());
bindingUpdateQueue.getOrDefault(uri, new CountDownLatch(1)).countDown();
bindingUpdateQueue.remove(uri);
var bindingConfigurationDto = new BindingConfigurationDto(newValue.getConnectionId(), newValue.getProjectKey(), false);
var params = new DidUpdateBindingParams(folder.getUri().toString(), bindingConfigurationDto);
backendServiceFacade.getBackendService().updateBinding(params);
Expand Down
Loading

0 comments on commit c8a88e7

Please sign in to comment.