All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonarsource.sonarlint.ls.SonarLintLanguageServer Maven / Gradle / Ivy

There is a newer version: 3.12.0.75621
Show newest version
/*
 * SonarLint Language Server
 * Copyright (C) 2009-2024 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.ls;

import com.google.common.annotations.VisibleForTesting;
import com.google.gson.JsonPrimitive;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeNotebookDocumentParams;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams;
import org.eclipse.lsp4j.DidCloseNotebookDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenNotebookDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveNotebookDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.NotebookDocumentSyncRegistrationOptions;
import org.eclipse.lsp4j.NotebookSelector;
import org.eclipse.lsp4j.NotebookSelectorCell;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.ServerInfo;
import org.eclipse.lsp4j.SetTraceParams;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.TextDocumentSyncOptions;
import org.eclipse.lsp4j.WorkspaceFoldersOptions;
import org.eclipse.lsp4j.WorkspaceServerCapabilities;
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
import org.eclipse.lsp4j.services.NotebookDocumentService;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.sonarsource.sonarlint.core.commons.SonarLintUserHome;
import org.sonarsource.sonarlint.core.commons.api.SonarLanguage;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.GetSupportedFilePatternsParams;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.GetSupportedFilePatternsResponse;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetBindingSuggestionParams;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetSharedConnectedModeConfigFileParams;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetSharedConnectedModeConfigFileResponse;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.HelpGenerateUserTokenResponse;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.ListUserOrganizationsResponse;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.OrganizationDto;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionParams;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckStatusChangePermittedParams;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.OpenHotspotInBrowserParams;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.AddIssueCommentParams;
import org.sonarsource.sonarlint.core.rpc.protocol.client.binding.GetBindingSuggestionsResponse;
import org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionStatus;
import org.sonarsource.sonarlint.core.rpc.protocol.common.Language;
import org.sonarsource.sonarlint.ls.SonarLintExtendedLanguageClient.ConnectionCheckResult;
import org.sonarsource.sonarlint.ls.backend.BackendInitParams;
import org.sonarsource.sonarlint.ls.backend.BackendServiceFacade;
import org.sonarsource.sonarlint.ls.clientapi.SonarLintVSCodeClient;
import org.sonarsource.sonarlint.ls.connected.ProjectBindingManager;
import org.sonarsource.sonarlint.ls.connected.TaintVulnerabilitiesCache;
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;
import org.sonarsource.sonarlint.ls.file.FileTypeClassifier;
import org.sonarsource.sonarlint.ls.file.OpenFilesCache;
import org.sonarsource.sonarlint.ls.file.VersionedOpenFile;
import org.sonarsource.sonarlint.ls.folders.ModuleEventsProcessor;
import org.sonarsource.sonarlint.ls.folders.WorkspaceFolderBranchManager;
import org.sonarsource.sonarlint.ls.folders.WorkspaceFoldersManager;
import org.sonarsource.sonarlint.ls.java.JavaConfigCache;
import org.sonarsource.sonarlint.ls.log.LanguageClientLogger;
import org.sonarsource.sonarlint.ls.notebooks.NotebookDiagnosticPublisher;
import org.sonarsource.sonarlint.ls.notebooks.OpenNotebooksCache;
import org.sonarsource.sonarlint.ls.notebooks.VersionedOpenNotebook;
import org.sonarsource.sonarlint.ls.progress.LSProgressMonitor;
import org.sonarsource.sonarlint.ls.settings.SettingsManager;
import org.sonarsource.sonarlint.ls.settings.WorkspaceFolderSettingsChangeListener;
import org.sonarsource.sonarlint.ls.settings.WorkspaceSettingsChangeListener;
import org.sonarsource.sonarlint.ls.standalone.notifications.PromotionalNotifications;
import org.sonarsource.sonarlint.ls.telemetry.SonarLintTelemetry;
import org.sonarsource.sonarlint.ls.telemetry.TelemetryInitParams;
import org.sonarsource.sonarlint.ls.util.CatchingRunnable;
import org.sonarsource.sonarlint.ls.util.EnumLabelsMapper;
import org.sonarsource.sonarlint.ls.util.ExitingInputStream;
import org.sonarsource.sonarlint.ls.util.Utils;

import static java.lang.String.format;
import static java.net.URI.create;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static org.sonarsource.sonarlint.ls.CommandManager.SONARLINT_SHOW_ISSUE_DETAILS_FROM_CODE_ACTION_COMMAND;
import static org.sonarsource.sonarlint.ls.CommandManager.SONARLINT_SHOW_SECURITY_HOTSPOT_FLOWS;
import static org.sonarsource.sonarlint.ls.SonarLintExtendedLanguageClient.ConnectionCheckResult.failure;
import static org.sonarsource.sonarlint.ls.SonarLintExtendedLanguageClient.ConnectionCheckResult.success;
import static org.sonarsource.sonarlint.ls.backend.BackendService.ROOT_CONFIGURATION_SCOPE;
import static org.sonarsource.sonarlint.ls.util.URIUtils.getFullFileUriFromFragments;
import static org.sonarsource.sonarlint.ls.util.Utils.getConnectionNameFromConnectionCheckParams;
import static org.sonarsource.sonarlint.ls.util.Utils.getValidateConnectionParamsForNewConnection;
import static org.sonarsource.sonarlint.ls.util.Utils.hotspotStatusOfTitle;

public class SonarLintLanguageServer implements SonarLintExtendedLanguageServer, WorkspaceService, TextDocumentService, NotebookDocumentService {

  public static final String JUPYTER_NOTEBOOK_TYPE = "jupyter-notebook";
  public static final String PYTHON_LANGUAGE = "python";
  private final SonarLintExtendedLanguageClient client;
  private final SonarLintTelemetry telemetry;
  private final WorkspaceFoldersManager workspaceFoldersManager;
  private final SettingsManager settingsManager;
  private final ProjectBindingManager bindingManager;
  private final ForcedAnalysisCoordinator forcedAnalysisCoordinator;
  private final TaintVulnerabilitiesCache taintVulnerabilitiesCache;
  private final OpenFilesCache openFilesCache;
  private final OpenNotebooksCache openNotebooksCache;
  private final CommandManager commandManager;
  private final ExecutorService lspThreadPool;
  private final LSProgressMonitor progressMonitor;
  private final HostInfoProvider hostInfoProvider;
  private final WorkspaceFolderBranchManager branchManager;
  private final JavaConfigCache javaConfigCache;
  private final IssuesCache issuesCache;
  private final HotspotsCache securityHotspotsCache;
  private final DiagnosticPublisher diagnosticPublisher;
  private final LanguageClientLogger lsLogOutput;

  private final NotebookDiagnosticPublisher notebookDiagnosticPublisher;
  private final PromotionalNotifications promotionalNotifications;
  private final ServerSentEventsHandlerService serverSentEventsHandler;
  private final FileTypeClassifier fileTypeClassifier;
  private final AnalysisHelper analysisHelper;

  private String appName;

  /**
   * Keep track of value 'sonarlint.trace.server' on client side. Not used currently, but keeping it just in case.
   */
  private TraceValue traceLevel;

  private final ModuleEventsProcessor moduleEventsProcessor;
  private final BackendServiceFacade backendServiceFacade;
  private final Collection analyzers;
  private final CountDownLatch shutdownLatch;

  private final ExecutorService branchChangeEventExecutor;

  SonarLintLanguageServer(InputStream inputStream, OutputStream outputStream, Collection analyzers) {
    this.lspThreadPool = Executors.newCachedThreadPool(Utils.threadFactory("SonarQube for VS Code LSP message processor", false));

    var input = new ExitingInputStream(inputStream, this);
    var launcher = new Launcher.Builder()
      .setLocalService(this)
      .setRemoteInterface(SonarLintExtendedLanguageClient.class)
      .setInput(input)
      .setOutput(outputStream)
      .setExecutorService(lspThreadPool)
      .create();
    this.branchChangeEventExecutor = Executors.newSingleThreadExecutor(Utils.threadFactory("SonarQube for VS Code branch change event handler", false));

    this.analyzers = analyzers;
    this.client = launcher.getRemoteProxy();
    this.lsLogOutput = new LanguageClientLogger(this.client);
    this.openFilesCache = new OpenFilesCache(lsLogOutput);

    this.issuesCache = new IssuesCache();
    this.securityHotspotsCache = new HotspotsCache();
    this.taintVulnerabilitiesCache = new TaintVulnerabilitiesCache();
    this.notebookDiagnosticPublisher = new NotebookDiagnosticPublisher(client, issuesCache);
    this.openNotebooksCache = new OpenNotebooksCache(lsLogOutput, notebookDiagnosticPublisher);
    this.notebookDiagnosticPublisher.setOpenNotebooksCache(openNotebooksCache);
    this.hostInfoProvider = new HostInfoProvider();
    var skippedPluginsNotifier = new SkippedPluginsNotifier(client, lsLogOutput);
    this.promotionalNotifications = new PromotionalNotifications(client);
    this.progressMonitor = new LSProgressMonitor(client);
    var vsCodeClient = new SonarLintVSCodeClient(client, hostInfoProvider, lsLogOutput, taintVulnerabilitiesCache,
      skippedPluginsNotifier, promotionalNotifications, progressMonitor);
    this.backendServiceFacade = new BackendServiceFacade(vsCodeClient, lsLogOutput, client);
    vsCodeClient.setBackendServiceFacade(backendServiceFacade);
    this.workspaceFoldersManager = new WorkspaceFoldersManager(backendServiceFacade, lsLogOutput);
    this.diagnosticPublisher = new DiagnosticPublisher(client, taintVulnerabilitiesCache, issuesCache,
      securityHotspotsCache, openNotebooksCache);
    vsCodeClient.setDiagnosticPublisher(diagnosticPublisher);
    this.settingsManager = new SettingsManager(this.client, this.workspaceFoldersManager, backendServiceFacade, lsLogOutput);
    vsCodeClient.setSettingsManager(settingsManager);
    vsCodeClient.setWorkspaceFoldersManager(workspaceFoldersManager);
    backendServiceFacade.setSettingsManager(settingsManager);
    this.fileTypeClassifier = new FileTypeClassifier(lsLogOutput);
    javaConfigCache = new JavaConfigCache(client, openFilesCache, lsLogOutput);
    this.settingsManager.addListener(lsLogOutput);
    this.bindingManager = new ProjectBindingManager(workspaceFoldersManager, settingsManager,
      client, lsLogOutput, backendServiceFacade, openNotebooksCache);
    vsCodeClient.setBindingManager(bindingManager);
    this.telemetry = new SonarLintTelemetry(backendServiceFacade, lsLogOutput);
    this.backendServiceFacade.setTelemetry(telemetry);
    this.settingsManager.addListener(telemetry);
    this.settingsManager.addListener((WorkspaceSettingsChangeListener) bindingManager);
    this.settingsManager.addListener((WorkspaceFolderSettingsChangeListener) bindingManager);
    this.workspaceFoldersManager.addListener(settingsManager);
    var smartNotifications = new SmartNotifications(client, telemetry);
    vsCodeClient.setSmartNotifications(smartNotifications);
    this.moduleEventsProcessor = new ModuleEventsProcessor(workspaceFoldersManager, fileTypeClassifier, javaConfigCache, backendServiceFacade, settingsManager);
    this.analysisHelper = new AnalysisHelper(client, lsLogOutput, workspaceFoldersManager, javaConfigCache, settingsManager,
      issuesCache, securityHotspotsCache, diagnosticPublisher,
      openNotebooksCache, notebookDiagnosticPublisher, openFilesCache);
    vsCodeClient.setAnalysisTaskExecutor(analysisHelper);
    this.forcedAnalysisCoordinator = new ForcedAnalysisCoordinator(workspaceFoldersManager, bindingManager, openFilesCache, openNotebooksCache, client, backendServiceFacade);
    vsCodeClient.setAnalysisScheduler(forcedAnalysisCoordinator);
    this.serverSentEventsHandler = new ServerSentEventsHandler(forcedAnalysisCoordinator, bindingManager);
    vsCodeClient.setServerSentEventsHandlerService(serverSentEventsHandler);
    bindingManager.setAnalysisManager(forcedAnalysisCoordinator);
    this.settingsManager.addListener((WorkspaceSettingsChangeListener) forcedAnalysisCoordinator);
    this.settingsManager.addListener((WorkspaceFolderSettingsChangeListener) forcedAnalysisCoordinator);
    this.commandManager = new CommandManager(client, settingsManager, bindingManager, telemetry, taintVulnerabilitiesCache,
      issuesCache, securityHotspotsCache, backendServiceFacade, workspaceFoldersManager, openNotebooksCache, lsLogOutput);

    this.branchManager = new WorkspaceFolderBranchManager(backendServiceFacade, lsLogOutput);
    vsCodeClient.setBranchManager(branchManager);
    this.workspaceFoldersManager.addListener(this.branchManager);
    this.workspaceFoldersManager.setBindingManager(bindingManager);
    var cleanAsYouCodeManager = new CleanAsYouCodeManager(diagnosticPublisher, openFilesCache, backendServiceFacade);
    this.settingsManager.addListener(cleanAsYouCodeManager);
    this.shutdownLatch = new CountDownLatch(1);
    launcher.startListening();
  }

  static SonarLintLanguageServer bySocket(int port, Collection analyzers) throws IOException {
    var socket = new Socket("localhost", port);
    return new SonarLintLanguageServer(socket.getInputStream(), socket.getOutputStream(), analyzers);
  }

  static SonarLintLanguageServer byStdio(List analyzers) {
    return new SonarLintLanguageServer(System.in, System.out, analyzers);
  }

  @Override
  public CompletableFuture initialize(InitializeParams params) {
    return CompletableFutures.computeAsync(cancelToken -> {
      cancelToken.checkCanceled();
      this.traceLevel = parseTraceLevel(params.getTrace());

      var options = Utils.parseToMap(params.getInitializationOptions());
      if (options == null) {
        options = Collections.emptyMap();
      }
      var showVerboseLogs = (boolean) options.getOrDefault("showVerboseLogs", true);
      lsLogOutput.initialize(showVerboseLogs);

      workspaceFoldersManager.initialize(params.getWorkspaceFolders());

      var productKey = (String) options.get("productKey");
      // deprecated, will be ignored when productKey present
      var telemetryStorage = (String) options.get("telemetryStorage");

      var productName = (String) options.get("productName");
      var productVersion = (String) options.get("productVersion");
      var clientInfo = ofNullable(params.getClientInfo());
      this.appName = clientInfo.map(ci -> ci.getName()).orElse("Unknown");
      var workspaceName = (String) options.get("workspaceName");
      var clientVersion = clientInfo.map(ci -> ci.getVersion()).orElse("Unknown");
      var ideVersion = appName + " " + clientVersion;
      var firstSecretDetected = (boolean) options.getOrDefault("firstSecretDetected", false);
      var platform = (String) options.get("platform");
      var architecture = (String) options.get("architecture");
      var additionalAttributes = (Map) options.getOrDefault("additionalAttributes", Map.of());
      var userAgent = productName + " " + productVersion;
      var clientNodePath = (String) options.get("clientNodePath");

      diagnosticPublisher.initialize(firstSecretDetected);

      hostInfoProvider.initialize(clientVersion, workspaceName);
      backendServiceFacade.setTelemetryInitParams(new TelemetryInitParams(productKey, telemetryStorage,
        productName, productVersion, ideVersion, platform, architecture, additionalAttributes));
      backendServiceFacade.setOmnisharpDirectory((String) additionalAttributes.get("omnisharpDirectory"));
      backendServiceFacade.setCsharpOssPath((String) additionalAttributes.get("csharpOssPath"));
      backendServiceFacade.setCsharpEnterprisePath((String) additionalAttributes.get("csharpEnterprisePath"));

      var c = new ServerCapabilities();
      c.setTextDocumentSync(getTextDocumentSyncOptions());
      c.setCodeActionProvider(true);
      var executeCommandOptions = new ExecuteCommandOptions(CommandManager.SONARLINT_SERVERSIDE_COMMANDS);
      executeCommandOptions.setWorkDoneProgress(true);
      c.setExecuteCommandProvider(executeCommandOptions);
      c.setWorkspace(getWorkspaceServerCapabilities());
      if (isEnableNotebooks(options)) {
        setNotebookSyncOptions(c);
      }

      var info = new ServerInfo("SonarLint Language Server", getServerVersion("slls-version.txt"));
      provideBackendInitData(productKey, userAgent, clientNodePath);
      return new InitializeResult(c, info);
    });
  }

  @VisibleForTesting
  public static boolean isEnableNotebooks(Map options) {
    return (boolean) options.getOrDefault("enableNotebooks", false);
  }

  private static void setNotebookSyncOptions(ServerCapabilities c) {
    var noteBookDocumentSyncOptions = new NotebookDocumentSyncRegistrationOptions();
    var notebookSelector = new NotebookSelector();
    notebookSelector.setNotebook(JUPYTER_NOTEBOOK_TYPE);
    notebookSelector.setCells(List.of(new NotebookSelectorCell(PYTHON_LANGUAGE)));
    noteBookDocumentSyncOptions.setNotebookSelector(List.of(notebookSelector));
    c.setNotebookDocumentSync(noteBookDocumentSyncOptions);
  }

  @CheckForNull
  static String getServerVersion(String fileName) {
    var classLoader = ClassLoader.getSystemClassLoader();
    try (var is = classLoader.getResourceAsStream(fileName)) {
      try (var isr = new InputStreamReader(is, StandardCharsets.UTF_8);
           var reader = new BufferedReader(isr)) {
        return reader.lines().findFirst().orElse(null);
      }
    } catch (IOException e) {
      throw new IllegalStateException("Unable to read server version", e);
    }
  }

  private static WorkspaceServerCapabilities getWorkspaceServerCapabilities() {
    var options = new WorkspaceFoldersOptions();
    options.setSupported(true);
    options.setChangeNotifications(true);

    var capabilities = new WorkspaceServerCapabilities();
    capabilities.setWorkspaceFolders(options);
    return capabilities;
  }

  private static TextDocumentSyncOptions getTextDocumentSyncOptions() {
    var textDocumentSyncOptions = new TextDocumentSyncOptions();
    textDocumentSyncOptions.setOpenClose(true);
    textDocumentSyncOptions.setChange(TextDocumentSyncKind.Full);
    return textDocumentSyncOptions;
  }

  @Override
  public CompletableFuture shutdown() {
    List.of(
        branchManager::shutdown,
        settingsManager::shutdown,
        workspaceFoldersManager::shutdown,
        moduleEventsProcessor::shutdown,
        branchChangeEventExecutor::shutdown,
        backendServiceFacade::shutdown)
      // Do last
      .forEach(this::invokeQuietly);

    // necessary to let all shutdown jobs to finish
    waitBeforeExit();
    shutdownLatch.countDown();

    return CompletableFuture.completedFuture(null);
  }

  private void waitBeforeExit() {
    try {
      lspThreadPool.awaitTermination(5, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }

  public void waitForShutDown() throws InterruptedException {
    shutdownLatch.await();
  }

  private void invokeQuietly(Runnable call) {
    try {
      call.run();
    } catch (Exception e) {
      lsLogOutput.errorWithStackTrace("Unable to properly shutdown", e);
    }
  }

  @Override
  public void exit() {
    invokeQuietly(() -> Utils.shutdownAndAwait(lspThreadPool, true));
    // The Socket will be closed by the client, and so remaining threads will die and the JVM will terminate
  }

  @Override
  public TextDocumentService getTextDocumentService() {
    return this;
  }

  @Override
  public CompletableFuture>> codeAction(CodeActionParams params) {
    return CompletableFutures.computeAsync(cancelToken -> {
      cancelToken.checkCanceled();
      return commandManager.computeCodeActions(params, cancelToken);
    });
  }

  @Override
  public void didOpen(DidOpenTextDocumentParams params) {
    var uri = create(params.getTextDocument().getUri());
    client.isOpenInEditor(uri.toString()).thenAccept(isOpen -> {
      if (Boolean.TRUE.equals(isOpen)) {
        if (openNotebooksCache.isNotebook(uri)) {
          lsLogOutput.debug(String.format("Skipping text document analysis of notebook \"%s\"", uri));
          return;
        }
        var file = openFilesCache.didOpen(uri, params.getTextDocument().getLanguageId(), params.getTextDocument().getText(), params.getTextDocument().getVersion());
        CompletableFutures.computeAsync(cancelChecker -> {
          String configScopeId;
          moduleEventsProcessor.notifyBackendWithFileLanguageAndContent(file);
          var maybeWorkspaceFolder = workspaceFoldersManager.findFolderForFile(uri);
          if (maybeWorkspaceFolder.isPresent()) {
            configScopeId = maybeWorkspaceFolder.get().getUri().toString();
          } else {
            configScopeId = ROOT_CONFIGURATION_SCOPE;
          }
          backendServiceFacade.getBackendService().didOpenFile(configScopeId, uri);
          return null;
        });
      } else {
        lsLogOutput.debug(String.format("Skipping analysis of file not open in the editor: \"%s\"", uri));
      }
    });
  }

  @Override
  public void didChange(DidChangeTextDocumentParams params) {
    var uri = create(params.getTextDocument().getUri());
    openFilesCache.didChange(uri, params.getContentChanges().get(0).getText(), params.getTextDocument().getVersion());
    Optional file = openFilesCache.getFile(uri);
    if (file.isEmpty()) {
      lsLogOutput.warn("Illegal state: trying to update file that was not open");
    } else {
      // VSCode sends us full file content in the change event
      CompletableFutures.computeAsync(cancelChecker -> {
        moduleEventsProcessor.notifyBackendWithUpdatedContent(file.get());
        return null;
      });
    }
  }

  @Override
  public void didClose(DidCloseTextDocumentParams params) {
    var uri = create(params.getTextDocument().getUri());
    openFilesCache.didClose(uri);
    javaConfigCache.didClose(uri);
    issuesCache.clear(uri);
    securityHotspotsCache.clear(uri);
    diagnosticPublisher.publishDiagnostics(uri, false);
    var maybeWorkspaceFolder = workspaceFoldersManager.findFolderForFile(uri);
    if (maybeWorkspaceFolder.isPresent()) {
      var configScopeId = maybeWorkspaceFolder.get().getUri().toString();
      backendServiceFacade.getBackendService().didCloseFile(configScopeId, uri);
    }
  }

  @Override
  public void didSave(DidSaveTextDocumentParams params) {
    // Nothin to do
  }

  @Override
  public CompletableFuture>> listAllRules() {
    return CompletableFutures.computeAsync(cancelToken -> {
      cancelToken.checkCanceled();
      return commandManager.listAllStandaloneRules();
    });
  }

  @Override
  public WorkspaceService getWorkspaceService() {
    return this;
  }

  @Override
  public CompletableFuture executeCommand(ExecuteCommandParams params) {
    return CompletableFutures.computeAsync(cancelToken -> {
      cancelToken.checkCanceled();
      commandManager.executeCommand(params, cancelToken);
      return null;
    });
  }

  @Override
  public void didChangeConfiguration(DidChangeConfigurationParams params) {
    settingsManager.didChangeConfiguration();
  }

  @Override
  public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
    moduleEventsProcessor.didChangeWatchedFiles(params.getChanges());
  }

  @Override
  public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) {
    var event = params.getEvent();
    workspaceFoldersManager.didChangeWorkspaceFolders(event);
  }

  @Override
  public void setTrace(SetTraceParams params) {
    this.traceLevel = parseTraceLevel(params.getValue());
  }

  private static TraceValue parseTraceLevel(@Nullable String trace) {
    return ofNullable(trace)
      .map(String::toUpperCase)
      .map(TraceValue::valueOf)
      .orElse(TraceValue.OFF);
  }

  @Override
  public void didOpen(DidOpenNotebookDocumentParams params) {
    var notebookUri = create(params.getNotebookDocument().getUri());
    var notebookFile = openNotebooksCache.didOpen(notebookUri, params.getNotebookDocument().getVersion(), params.getCellTextDocuments());
    var versionedOpenFile = notebookFile.asVersionedOpenFile();

    if (openFilesCache.getFile(notebookUri).isPresent()) {
      openFilesCache.didClose(notebookUri);
    }
    CompletableFutures.computeAsync(cancelChecker -> {
      moduleEventsProcessor.notifyBackendWithFileLanguageAndContent(versionedOpenFile);
      var maybeWorkspaceFolder = workspaceFoldersManager.findFolderForFile(notebookUri);
      if (maybeWorkspaceFolder.isPresent()) {
        var configScopeId = maybeWorkspaceFolder.get().getUri().toString();
        backendServiceFacade.getBackendService().didOpenFile(configScopeId, notebookUri);
      }
      return null;
    });
  }

  @Override
  public void didChange(DidChangeNotebookDocumentParams params) {
    openNotebooksCache.didChange(create(params.getNotebookDocument().getUri()), params.getNotebookDocument().getVersion(), params.getChange());
    var openNotebook = openNotebooksCache.getFile(create(params.getNotebookDocument().getUri()));
    if (openNotebook.isEmpty()) {
      lsLogOutput.warn("Illegal state: received change event for Notebook that is not open");
    } else {
      var file = openNotebook.get().asVersionedOpenFile();
      CompletableFutures.computeAsync(cancelChecker -> {
        moduleEventsProcessor.notifyBackendWithUpdatedContent(file);
        return null;
      });
    }
  }

  @Override
  public void didSave(DidSaveNotebookDocumentParams params) {
    // Nothin to do
  }

  @Override
  public void didClose(DidCloseNotebookDocumentParams params) {
    var uri = create(params.getNotebookDocument().getUri());
    issuesCache.clear(uri);
    notebookDiagnosticPublisher.removeAllExistingDiagnosticsForNotebook(uri);
    openNotebooksCache.didClose(uri);
    var maybeWorkspaceFolder = workspaceFoldersManager.findFolderForFile(uri);
    if (maybeWorkspaceFolder.isPresent()) {
      var configScopeId = maybeWorkspaceFolder.get().getUri().toString();
      backendServiceFacade.getBackendService().didCloseFile(configScopeId, uri);
    }
  }

  private enum TraceValue {
    OFF,
    MESSAGES,
    VERBOSE
  }

  @Override
  public void didClasspathUpdate(DidClasspathUpdateParams params) {
    var projectUri = create(params.getProjectUri());
    javaConfigCache.didClasspathUpdate(projectUri);
    forcedAnalysisCoordinator.didClasspathUpdate();
  }

  @Override
  public void didJavaServerModeChange(DidJavaServerModeChangeParams params) {
    var serverModeEnum = ServerMode.of(params.getServerMode());
    javaConfigCache.didServerModeChange();
    forcedAnalysisCoordinator.didServerModeChange(serverModeEnum);
  }

  @Override
  public void didLocalBranchNameChange(DidLocalBranchNameChangeParams event) {
    var branchName = event.getBranchName();
    var folderUri = event.getFolderUri();
    if (branchName != null) {
      lsLogOutput.debug(format("Folder %s is now on branch %s.", folderUri, branchName));
    } else {
      lsLogOutput.debug(format("Folder %s is now on an unknown branch.", folderUri));
      return;
    }
    branchChangeEventExecutor.submit(new CatchingRunnable(() -> backendServiceFacade.getBackendService().notifyBackendOnVcsChange(folderUri),
      t -> lsLogOutput.errorWithStackTrace("Failed to notify backend on VCS change", t)));
  }

  @Override
  public CompletableFuture checkConnection(ConnectionCheckParams params) {
    var connectionName = getConnectionNameFromConnectionCheckParams(params);
    lsLogOutput.debug(format("Received a validate connectionName request for %s", connectionName));
    var validateConnectionParams = getValidateConnectionParams(params);
    if (validateConnectionParams != null) {
      return backendServiceFacade.getBackendService().validateConnection(validateConnectionParams)
        .thenApply(validationResult -> validationResult.isSuccess() ? success(connectionName)
          : failure(connectionName, validationResult.getMessage()));
    }
    return CompletableFuture.completedFuture(failure(connectionName, format("Connection '%s' is unknown", connectionName)));
  }

  private ValidateConnectionParams getValidateConnectionParams(ConnectionCheckParams params) {
    var connectionId = params.getConnectionId();
    return connectionId != null ? bindingManager.getValidateConnectionParamsFor(connectionId) : getValidateConnectionParamsForNewConnection(params);
  }

  @Override
  public CompletableFuture> getRemoteProjectsForConnection(GetRemoteProjectsForConnectionParams getRemoteProjectsForConnectionParams) {
    return CompletableFuture.completedFuture(
      bindingManager.getRemoteProjects(getRemoteProjectsForConnectionParams.getConnectionId()));
  }

  @Override
  public CompletableFuture getSharedConnectedModeConfigFileContents(GetSharedConnectedModeConfigFileParams params) {
    return backendServiceFacade.getBackendService().getSharedConnectedModeConfigFileContents(params);
  }

  @Override
  public void onTokenUpdate(OnTokenUpdateNotificationParams onTokenUpdateNotificationParams) {
    lsLogOutput.info("Updating credentials on token change.");
    backendServiceFacade.getBackendService().didChangeCredentials(onTokenUpdateNotificationParams.getConnectionId());
    var updatedConnection = settingsManager.getCurrentSettings().getServerConnections().get(onTokenUpdateNotificationParams.getConnectionId());
    updatedConnection.setToken(onTokenUpdateNotificationParams.getToken());
    bindingManager.validateConnection(onTokenUpdateNotificationParams.getConnectionId());
  }

  @Override
  public CompletableFuture> getRemoteProjectNamesByProjectKeys(GetRemoteProjectNamesByKeysParams params) {
    try {
      return bindingManager.getRemoteProjectsByKeys(params.connectionId(), params.projectKeys());
    } catch (IllegalStateException | IllegalArgumentException failed) {
      var responseError = new ResponseError(ResponseErrorCode.InternalError, "Could not get remote project name", failed);
      return CompletableFuture.failedFuture(new ResponseErrorException(responseError));
    }
  }

  @Override
  public CompletableFuture generateToken(GenerateTokenParams params) {
    return backendServiceFacade.getBackendService().helpGenerateUserToken(params.getBaseServerUrl());
  }

  @Override
  public void openHotspotInBrowser(OpenHotspotInBrowserLsParams params) {
    var hotspotId = params.getHotspotId();
    var fileUri = create(params.getFileUri());
    var folderForFileOptional = workspaceFoldersManager.findFolderForFile(fileUri);
    if (folderForFileOptional.isEmpty()) {
      var message = "Can't find workspace folder for file "
        + fileUri.getPath() + " during attempt to open hotspot in browser.";
      lsLogOutput.error(message);
      client.showMessage(new MessageParams(MessageType.Error, message));
      return;
    }
    var workspaceFolderUri = folderForFileOptional.get().getUri();
    var branchNameOptional = bindingManager.resolveBranchNameForFolder(workspaceFolderUri);
    if (branchNameOptional.isEmpty()) {
      var message = "Can't find branch for workspace folder "
        + workspaceFolderUri.getPath() + " during attempt to open hotspot in browser.";
      lsLogOutput.error(message);
      client.showMessage(new MessageParams(MessageType.Error, message));
      return;
    }
    var raisedHotspotDto = requireNonNull(securityHotspotsCache.get(fileUri).get(hotspotId));
    var openHotspotInBrowserParams = new OpenHotspotInBrowserParams(workspaceFolderUri.toString(), requireNonNull(raisedHotspotDto.getServerIssueKey()));
    backendServiceFacade.getBackendService().openHotspotInBrowser(openHotspotInBrowserParams);
  }

  @Override
  public CompletableFuture showHotspotRuleDescription(ShowHotspotRuleDescriptionParams params) {
    var fileUri = params.fileUri;
    var showHotspotCommandParams = new ExecuteCommandParams(SONARLINT_SHOW_ISSUE_DETAILS_FROM_CODE_ACTION_COMMAND,
      List.of(new JsonPrimitive(params.getHotspotId()), new JsonPrimitive(fileUri)));
    return CompletableFutures.computeAsync(cancelToken -> {
      cancelToken.checkCanceled();
      commandManager.executeCommand(showHotspotCommandParams, cancelToken);
      return null;
    });
  }

  @Override
  public CompletableFuture helpAndFeedbackLinkClicked(HelpAndFeedbackLinkClickedNotificationParams params) {
    telemetry.helpAndFeedbackLinkClicked(params.id);
    return CompletableFuture.completedFuture(null);
  }

  public Map getEmbeddedPluginsToPath() {
    var plugins = new HashMap();
    addPluginPathOrWarn("cfamily", Language.C, plugins);
    addPluginPathOrWarn("html", Language.HTML, plugins);
    addPluginPathOrWarn("js", Language.JS, plugins);
    addPluginPathOrWarn("xml", Language.XML, plugins);
    addPluginPathOrWarn("text", Language.SECRETS, plugins);
    addPluginPathOrWarn("go", Language.GO, plugins);
    addPluginPathOrWarn("iac", Language.CLOUDFORMATION, plugins);
    analyzers.stream().filter(it -> it.toString().endsWith("sonarlintomnisharp.jar")).findFirst()
      .ifPresent(p -> plugins.put("omnisharp", p));
    return plugins;
  }

  private void addPluginPathOrWarn(String pluginName, Language language, Map plugins) {
    analyzers.stream().filter(it -> it.toString().endsWith("sonar" + pluginName + ".jar")).findFirst()
      .ifPresentOrElse(
        pluginPath -> plugins.put(SonarLanguage.valueOf(language.name()).getPluginKey(), pluginPath),
        () -> lsLogOutput.warn(format("Embedded plugin not found: %s", SonarLanguage.valueOf(language.name()).getSonarLanguageKey())));
  }

  void provideBackendInitData(String productKey, String userAgent, String clientNodePath) {
    BackendInitParams params = backendServiceFacade.getInitParams();
    params.setTelemetryProductKey(productKey);
    var actualSonarLintUserHome = Optional.ofNullable(SettingsManager.getSonarLintUserHomeOverride()).orElse(SonarLintUserHome.get());
    params.setStorageRoot(actualSonarLintUserHome.resolve("storage"));
    params.setSonarlintUserHome(actualSonarLintUserHome.toString());

    params.setEmbeddedPluginPaths(new HashSet<>(analyzers));
    params.setConnectedModeEmbeddedPluginPathsByKey(getEmbeddedPluginsToPath());
    params.setEnableSecurityHotspots(true);

    params.setEnabledLanguagesInStandaloneMode(EnabledLanguages.getStandaloneLanguages().stream()
      .map(l -> Language.valueOf(l.name())).collect(Collectors.toSet()));
    params.setExtraEnabledLanguagesInConnectedMode(EnabledLanguages.getConnectedLanguages().stream()
      .map(l -> Language.valueOf(l.name())).collect(Collectors.toSet()));
    params.setUserAgent(userAgent);
    params.setClientNodePath(clientNodePath);
  }

  public CompletableFuture showHotspotLocations(ShowHotspotLocationsParams showHotspotLocationsParams) {
    var fileUri = showHotspotLocationsParams.fileUri;
    var hotspotKey = showHotspotLocationsParams.hotspotKey;
    var showHotspotCommandParams = new ExecuteCommandParams(SONARLINT_SHOW_SECURITY_HOTSPOT_FLOWS,
      List.of(new JsonPrimitive(fileUri), new JsonPrimitive(hotspotKey)));
    return CompletableFutures.computeAsync(cancelToken -> {
      cancelToken.checkCanceled();
      commandManager.executeCommand(showHotspotCommandParams, cancelToken);
      return null;
    });
  }

  @Override
  public CompletableFuture scanFolderForHotspots(ScanFolderForHotspotsParams params) {
    return CompletableFutures.computeAsync(cancelToken -> {
      cancelToken.checkCanceled();
      runScan(params);
      return null;
    });
  }

  private void runScan(ScanFolderForHotspotsParams params) {
    backendServiceFacade.getBackendService().analyzeFullProject(params.getFolderUri(), true);
  }

  public CompletableFuture forgetFolderHotspots() {
    var filesToForget = securityHotspotsCache.keepOnly(openFilesCache.getAll());
    filesToForget.forEach(diagnosticPublisher::publishHotspots);
    return null;
  }

  @Override
  public CompletableFuture getFilePatternsForAnalysis(UriParams params) {
    return backendServiceFacade.getBackendService().getFilePatternsForAnalysis(new GetSupportedFilePatternsParams(params.getUri()));
  }

  @Override
  public CompletableFuture getBindingSuggestion(GetBindingSuggestionParams params) {
    return backendServiceFacade.getBackendService().getBindingSuggestion(params);
  }

  @Override
  public void didCreateBinding(BindingCreationMode creationMode) {
    switch (creationMode) {
      case AUTOMATIC -> telemetry.addedAutomaticBindings();
      case IMPORTED -> telemetry.addedImportedBindings();
      case MANUAL -> telemetry.addedManualBindings();
    }
  }

  @Override
  public CompletableFuture> listUserOrganizations(String token) {
    return backendServiceFacade.getBackendService().listUserOrganizations(token)
      .thenApply(ListUserOrganizationsResponse::getUserOrganizations);
  }

  @Override
  public CompletableFuture fixSuggestionResolved(FixSuggestionResolvedParams params) {
    telemetry.fixSuggestionResolved(new org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.FixSuggestionResolvedParams(params.suggestionId(),
      params.accepted() ? FixSuggestionStatus.ACCEPTED : FixSuggestionStatus.DECLINED, null));
    return CompletableFuture.completedFuture(null);
  }

  @Override
  public CompletableFuture checkIssueStatusChangePermitted(CheckIssueStatusChangePermittedParams params) {
    var bindingWrapperOpt = bindingManager.getBinding(create(params.getFolderUri()));
    if (bindingWrapperOpt.isEmpty()) {
      return CompletableFuture.completedFuture(
        new CheckIssueStatusChangePermittedResponse(false, "There is no binding for the folder: " + params.getFolderUri(), List.of()));
    }
    var connectionId = bindingWrapperOpt.get().connectionId();
    return backendServiceFacade.getBackendService().checkStatusChangePermitted(connectionId, params.getIssueKey());
  }

  @Override
  public CompletableFuture changeIssueStatus(ChangeIssueStatusParams params) {
    var coreParams = new org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ChangeIssueStatusParams(
      params.getConfigurationScopeId(), Objects.requireNonNull(params.getIssueId()), EnumLabelsMapper.resolutionStatusFromLabel(params.getNewStatus()), params.isTaintIssue());
    return backendServiceFacade.getBackendService().changeIssueStatus(coreParams).thenAccept(nothing -> {
      var key = params.getIssueId();
      if (!params.isTaintIssue()) {
        issuesCache.removeFindingWithServerKey(params.getFileUri(), key);
      }

      diagnosticPublisher.publishDiagnostics(create(params.getFileUri()), false);
      client.showMessage(new MessageParams(MessageType.Info, "Issue status was changed"));
    }).exceptionally(t -> {
      lsLogOutput.errorWithStackTrace("Error changing issue status", t);
      client.showMessage(new MessageParams(MessageType.Error, "Could not change status for the issue. Look at the SonarQube for IDE output for details."));
      return null;
    }).thenAccept(unused -> {
      if (!StringUtils.isEmpty(params.getComment())) {
        addIssueComment(new AddIssueCommentParams(params.getConfigurationScopeId(), params.getIssueId(), params.getComment()));
      }
    });
  }

  private void addIssueComment(AddIssueCommentParams params) {
    backendServiceFacade.getBackendService().addIssueComment(params)
      .thenAccept(nothing -> client.showMessage(new MessageParams(MessageType.Info, "New comment was added")));
  }

  @Override
  public CompletableFuture checkLocalDetectionSupported(UriParams params) {
    var folderUri = params.getUri();
    return backendServiceFacade.getBackendService().checkLocalDetectionSupported(folderUri)
      .thenApply(response -> new CheckLocalDetectionSupportedResponse(response.isSupported(), response.getReason()));
  }

  @Override
  public CompletableFuture getHotspotDetails(ShowHotspotRuleDescriptionParams params) {
    var fileUri = params.fileUri;
    return commandManager.getFindingDetails(fileUri, params.getHotspotId());
  }

  @Override
  public CompletableFuture changeHotspotStatus(ChangeHotspotStatusParams params) {
    var workspace = workspaceFoldersManager.findFolderForFile(create(params.getFileUri()))
      .orElseThrow(() -> new IllegalStateException("No workspace found"));
    var workspaceUri = workspace.getUri();
    var hotspotStatus = hotspotStatusOfTitle(params.getNewStatus());
    var coreParams = new org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.ChangeHotspotStatusParams(
      workspaceUri.toString(), params.getHotspotKey(), hotspotStatus);
    return backendServiceFacade.getBackendService().changeHotspotStatus(coreParams).thenAccept(nothing -> {
      var key = params.getHotspotKey();
      if (hotspotStatus != HotspotStatus.TO_REVIEW && hotspotStatus != HotspotStatus.ACKNOWLEDGED) {
        securityHotspotsCache.removeFindingWithServerKey(params.getFileUri(), key);
      } else {
        securityHotspotsCache.updateHotspotStatus(params.getFileUri(), key, hotspotStatus);
      }
      diagnosticPublisher.publishHotspots(create(params.getFileUri()));
      client.showMessage(new MessageParams(MessageType.Info, "Hotspot status was changed"));
    }).exceptionally(t -> {
      lsLogOutput.errorWithStackTrace("Error changing hotspot status", t);
      client.showMessage(new MessageParams(MessageType.Error, "Could not change status for the hotspot. Look at the SonarQube for IDE output for details."));
      return null;
    });
  }

  @Override
  public CompletableFuture getAllowedHotspotStatuses(GetAllowedHotspotStatusesParams params) {
    var folderUri = params.getFolderUri();
    var bindingOptional = bindingManager.getBinding(create(folderUri));
    if (bindingOptional.isEmpty()) {
      return CompletableFuture.completedFuture(null);
    }
    var connectionId = bindingOptional.get().connectionId();
    var checkStatusChangePermittedParams = new CheckStatusChangePermittedParams(connectionId, params.getHotspotKey());
    return backendServiceFacade.getBackendService().getAllowedHotspotStatuses(checkStatusChangePermittedParams).thenApply(r -> {
      var delegatingHotspot = securityHotspotsCache
        .findHotspotPerId(params.getFileUri(), params.getHotspotKey()).get().getValue();
      var reviewStatus = delegatingHotspot.getStatus();
      var statuses = r.getAllowedStatuses().stream().filter(s -> s != reviewStatus)
        .map(Enum::name).toList();
      return new GetAllowedHotspotStatusesResponse(
        r.isPermitted(),
        r.getNotPermittedReason(),
        statuses);
    }).exceptionally(t -> {
      lsLogOutput.errorWithStackTrace("Error changing hotspot status", t);
      client.showMessage(new MessageParams(MessageType.Error, "Could not change status for the hotspot. Look at the SonarQube for IDE output for details."));
      return null;
    });
  }

  @Override
  public void reopenResolvedLocalIssues(ReopenAllIssuesForFileParams params) {
    var reopenAllIssuesParams = new org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.ReopenAllIssuesForFileParams(
      params.getConfigurationScopeId(), Path.of(params.getRelativePath()));
    backendServiceFacade.getBackendService().reopenAllIssuesForFile(reopenAllIssuesParams).thenApply(r -> {
      if (r.isSuccess()) {
        var fullFileUri = getFullFileUriFromFragments(params.getConfigurationScopeId(), Path.of(params.getRelativePath()));
        // re-trigger analysis for the file
        backendServiceFacade.getBackendService().analyzeFilesList(params.getConfigurationScopeId(), List.of(fullFileUri));
        client.showMessage(new MessageParams(MessageType.Info, "Reopened local issues for " + params.getRelativePath()));
      } else {
        client.showMessage(new MessageParams(MessageType.Info, "There are no resolved issues in file " + params.getRelativePath()));
      }
      return r;
    }).exceptionally(e -> {
      lsLogOutput.errorWithStackTrace("Error while reopening resolved local issues", e);
      client.showMessage(new MessageParams(MessageType.Error, "Could not reopen resolved local issues. Look at the SonarQube for IDE output for details."));
      return null;
    });
  }

  @Override
  public CompletableFuture analyseOpenFileIgnoringExcludes(AnalyseOpenFileIgnoringExcludesParams params) {
    var notebookUriStr = params.getNotebookUri();
    URI documentUri;
    VersionedOpenFile versionedOpenFile;
    if (notebookUriStr != null) {
      documentUri = create(notebookUriStr);
      var version = params.getNotebookVersion();
      var notebookUri = create(notebookUriStr);
      requireNonNull(version);
      var cells = requireNonNull(params.getNotebookCells());
      var notebookFile = VersionedOpenNotebook.create(
        notebookUri, version,
        cells, notebookDiagnosticPublisher);
      versionedOpenFile = notebookFile.asVersionedOpenFile();
      openNotebooksCache.didOpen(notebookUri, version, cells);
    } else {
      var document = requireNonNull(params.getTextDocument());
      documentUri = create(document.getUri());
      versionedOpenFile = openFilesCache.didOpen(create(document.getUri()), document.getLanguageId(), document.getText(), document.getVersion());
    }
    if (versionedOpenFile != null) {
      var workspaceFolder = workspaceFoldersManager.findFolderForFile(documentUri);
      CompletableFutures.computeAsync(cancelChecker -> {
        moduleEventsProcessor.notifyBackendWithFileLanguageAndContent(versionedOpenFile);
        workspaceFolder.ifPresent(folder -> backendServiceFacade.getBackendService().analyzeFilesList(folder.getUri().toString(), List.of(documentUri)));
        return null;
      });
    }
    return CompletableFuture.completedFuture(null);
  }

}