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

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

There is a newer version: 3.12.0.75621
Show newest version
/*
 * SonarLint Language Server
 * Copyright (C) 2009-2020 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 java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticRelatedInformation;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonarsource.sonarlint.core.client.api.common.Language;
import org.sonarsource.sonarlint.core.client.api.common.PluginDetails;
import org.sonarsource.sonarlint.core.client.api.common.analysis.AnalysisResults;
import org.sonarsource.sonarlint.core.client.api.common.analysis.ClientInputFile;
import org.sonarsource.sonarlint.core.client.api.common.analysis.Issue;
import org.sonarsource.sonarlint.core.client.api.common.analysis.Issue.Flow;
import org.sonarsource.sonarlint.core.client.api.common.analysis.IssueListener;
import org.sonarsource.sonarlint.core.client.api.common.analysis.IssueLocation;
import org.sonarsource.sonarlint.core.client.api.connected.ConnectedAnalysisConfiguration;
import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine;
import org.sonarsource.sonarlint.core.client.api.standalone.StandaloneAnalysisConfiguration;
import org.sonarsource.sonarlint.core.client.api.standalone.StandaloneSonarLintEngine;
import org.sonarsource.sonarlint.core.client.api.util.FileUtils;
import org.sonarsource.sonarlint.ls.SonarLintExtendedLanguageClient.GetJavaConfigResponse;
import org.sonarsource.sonarlint.ls.connected.ProjectBindingManager;
import org.sonarsource.sonarlint.ls.connected.ProjectBindingWrapper;
import org.sonarsource.sonarlint.ls.connected.ServerIssueTrackerWrapper;
import org.sonarsource.sonarlint.ls.folders.WorkspaceFolderWrapper;
import org.sonarsource.sonarlint.ls.folders.WorkspaceFoldersManager;
import org.sonarsource.sonarlint.ls.java.JavaSdkUtil;
import org.sonarsource.sonarlint.ls.log.LanguageClientLogOutput;
import org.sonarsource.sonarlint.ls.settings.SettingsManager;
import org.sonarsource.sonarlint.ls.settings.WorkspaceFolderSettings;
import org.sonarsource.sonarlint.ls.settings.WorkspaceSettings;
import org.sonarsource.sonarlint.ls.settings.WorkspaceSettingsChangeListener;

import static java.util.Collections.emptyMap;
import static java.util.Collections.singleton;
import static java.util.Objects.nonNull;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.joining;

public class AnalysisManager implements WorkspaceSettingsChangeListener {

  private static final int DELAY_MS = 500;
  private static final int QUEUE_POLLING_PERIOD_MS = 200;

  private static final Logger LOG = Loggers.get(AnalysisManager.class);

  public static final String TYPESCRIPT_PATH_PROP = "sonar.typescript.internal.typescriptLocation";
  static final String SONARLINT_SOURCE = "sonarlint";

  private final SonarLintExtendedLanguageClient client;

  private final Map languageIdPerFileURI = new ConcurrentHashMap<>();
  private final Map fileContentPerFileURI = new ConcurrentHashMap<>();
  private final Map> javaConfigPerFileURI = new ConcurrentHashMap<>();
  private final Map> jvmClasspathPerJavaHome = new ConcurrentHashMap<>();
  // entries in this map mean that the file is "dirty"
  private final Map eventMap = new ConcurrentHashMap<>();

  private final SonarLintTelemetry telemetry;
  private final EnginesFactory enginesFactory;
  private final WorkspaceFoldersManager workspaceFoldersManager;
  private final SettingsManager settingsManager;
  private final ProjectBindingManager bindingManager;
  private final EventWatcher watcher;
  private final LanguageClientLogOutput lsLogOutput;
  private StandaloneSonarLintEngine standaloneEngine;

  private final ExecutorService analysisExecutor;

  public AnalysisManager(LanguageClientLogOutput lsLogOutput, EnginesFactory enginesFactory, SonarLintExtendedLanguageClient client, SonarLintTelemetry telemetry,
    WorkspaceFoldersManager workspaceFoldersManager, SettingsManager settingsManager, ProjectBindingManager bindingManager) {
    this.lsLogOutput = lsLogOutput;
    this.enginesFactory = enginesFactory;
    this.client = client;
    this.telemetry = telemetry;
    this.workspaceFoldersManager = workspaceFoldersManager;
    this.settingsManager = settingsManager;
    this.bindingManager = bindingManager;
    this.analysisExecutor = Executors.newSingleThreadExecutor(Utils.threadFactory("SonarLint analysis", false));
    this.watcher = new EventWatcher();
  }

  synchronized StandaloneSonarLintEngine getOrCreateStandaloneEngine() {
    if (standaloneEngine == null) {
      standaloneEngine = enginesFactory.createStandaloneEngine();
    }
    return standaloneEngine;
  }

  public void didOpen(URI fileUri, String languageId, String fileContent) {
    languageIdPerFileURI.put(fileUri, languageId);
    fileContentPerFileURI.put(fileUri, fileContent);
    analyzeAsync(fileUri, true);
  }

  public void didChange(URI fileUri, String fileContent) {
    fileContentPerFileURI.put(fileUri, fileContent);
    eventMap.put(fileUri, System.currentTimeMillis());
  }

  private class EventWatcher extends Thread {
    private boolean stop = false;

    EventWatcher() {
      this.setDaemon(true);
      this.setName("sonarlint-auto-trigger");
    }

    public void stopWatcher() {
      stop = true;
      this.interrupt();
    }

    @Override
    public void run() {
      while (!stop) {
        checkTimers();
        try {
          Thread.sleep(QUEUE_POLLING_PERIOD_MS);
        } catch (InterruptedException e) {
          // continue until stop flag is set
        }
      }
    }

    private void checkTimers() {
      long t = System.currentTimeMillis();

      Iterator> it = eventMap.entrySet().iterator();
      while (it.hasNext()) {
        Map.Entry e = it.next();
        if (e.getValue() + DELAY_MS < t) {
          analyzeAsync(e.getKey(), false);
          it.remove();
        }
      }
    }
  }

  public void didClose(URI fileUri) {
    LOG.debug("File '{}' closed. Cleaning diagnostics.", fileUri);
    languageIdPerFileURI.remove(fileUri);
    fileContentPerFileURI.remove(fileUri);
    javaConfigPerFileURI.remove(fileUri);
    eventMap.remove(fileUri);
    client.publishDiagnostics(newPublishDiagnostics(fileUri));
  }

  public void didSave(URI fileUri, String fileContent) {
    fileContentPerFileURI.put(fileUri, fileContent);
    analyzeAsync(fileUri, false);
  }

  void analyzeAsync(URI fileUri, boolean shouldFetchServerIssues) {
    if (!fileUri.getScheme().equalsIgnoreCase("file")) {
      LOG.warn("URI '{}' is not a file, analysis not supported", fileUri);
      return;
    }
    LOG.debug("Queuing analysis of file '{}'", fileUri);
    analysisExecutor.execute(() -> analyze(fileUri, shouldFetchServerIssues));
  }

  private void analyze(URI fileUri, boolean shouldFetchServerIssues) {
    final Optional javaConfigOpt = getJavaConfigFromCacheOrFetch(fileUri);
    if (isJava(fileUri) && !javaConfigOpt.isPresent()) {
      LOG.debug("Skipping analysis of Java file '{}' because SonarLint was unable to query project configuration (classpath, source level, ...)", fileUri);
      return;
    }
    String content = fileContentPerFileURI.get(fileUri);
    Map files = new HashMap<>();
    files.put(fileUri, newPublishDiagnostics(fileUri));

    Optional workspaceFolder = workspaceFoldersManager.findFolderForFile(fileUri);

    WorkspaceFolderSettings settings = workspaceFolder.map(WorkspaceFolderWrapper::getSettings)
      .orElse(settingsManager.getCurrentDefaultFolderSettings());

    Path baseDir = workspaceFolder.map(WorkspaceFolderWrapper::getRootPath)
      // Default to take file parent dir if file is not part of any workspace
      .orElse(Paths.get(fileUri).getParent());

    IssueListener issueListener = createIssueListener(files);

    Optional binding = bindingManager.getBinding(fileUri);
    AnalysisResultsWrapper analysisResults;
    try {
      if (binding.isPresent()) {
        ConnectedSonarLintEngine connectedEngine = binding.get().getEngine();
        if (!connectedEngine.getExcludedFiles(binding.get().getBinding(),
          singleton(fileUri),
          uri -> getFileRelativePath(baseDir, uri),
          uri -> isTest(settings, uri, javaConfigOpt))
          .isEmpty()) {
          LOG.debug("Skip analysis of excluded file: {}", fileUri);
          return;
        }
        LOG.info("Analyzing file '{}'...", fileUri);
        analysisResults = analyzeConnected(binding.get(), settings, baseDir, fileUri, content, issueListener, shouldFetchServerIssues, javaConfigOpt);
      } else {
        LOG.info("Analyzing file '{}'...", fileUri);
        analysisResults = analyzeStandalone(settings, baseDir, fileUri, content, issueListener, javaConfigOpt);
      }
      SkippedPluginsNotifier.notifyOnceForSkippedPlugins(analysisResults.results, analysisResults.allPlugins, client);

      Collection analyzedLanguages = analysisResults.results.languagePerFile().values();
      if (!analyzedLanguages.isEmpty()) {
        telemetry.analysisDoneOnSingleLanguage(analyzedLanguages.iterator().next(), analysisResults.analysisTime);
      }

      // Ignore files with parsing error
      analysisResults.results.failedAnalysisFiles().stream()
        .map(ClientInputFile::getClientObject)
        .forEach(files::remove);
    } catch (Exception e) {
      LOG.error("Analysis failed.", e);
    }

    // Check if file has not being closed during the analysis
    if (fileContentPerFileURI.containsKey(fileUri)) {
      LOG.info("Found {} issue(s)", files.values().stream().mapToInt(p -> p.getDiagnostics().size()).sum());
      files.values().forEach(client::publishDiagnostics);
    }
  }

  private Optional getJavaConfigFromCacheOrFetch(URI fileUri) {
    Optional javaConfigOpt;
    try {
      javaConfigOpt = getJavaConfigFromCacheOrFetchAsync(fileUri).get(1, TimeUnit.MINUTES);
    } catch (InterruptedException e) {
      Utils.interrupted(e);
      javaConfigOpt = empty();
    } catch (Exception e) {
      LOG.warn("Unable to get Java config", e);
      javaConfigOpt = empty();
    }
    return javaConfigOpt;
  }

  private static IssueListener createIssueListener(Map files) {
    return issue -> {
      ClientInputFile inputFile = issue.getInputFile();
      if (inputFile != null) {
        URI uri = inputFile.getClientObject();
        PublishDiagnosticsParams publish = files.computeIfAbsent(uri, AnalysisManager::newPublishDiagnostics);
        convert(issue).ifPresent(publish.getDiagnostics()::add);
      }
    };
  }

  static class AnalysisResultsWrapper {
    private final AnalysisResults results;
    private final int analysisTime;
    private final Collection allPlugins;

    AnalysisResultsWrapper(AnalysisResults results, int analysisTime, Collection allPlugins) {
      this.results = results;
      this.analysisTime = analysisTime;
      this.allPlugins = allPlugins;
    }
  }

  private AnalysisResultsWrapper analyzeStandalone(WorkspaceFolderSettings settings, Path baseDir, URI uri, String content, IssueListener issueListener,
    Optional javaConfig) {
    StandaloneAnalysisConfiguration configuration = StandaloneAnalysisConfiguration.builder()
      .setBaseDir(baseDir)
      .addInputFiles(new DefaultClientInputFile(uri, getFileRelativePath(baseDir, uri), content, isTest(settings, uri, javaConfig), languageIdPerFileURI.get(uri)))
      .putAllExtraProperties(settings.getAnalyzerProperties())
      .putAllExtraProperties(configureJavaProperties(uri))
      .addExcludedRules(settingsManager.getCurrentSettings().getExcludedRules())
      .addIncludedRules(settingsManager.getCurrentSettings().getIncludedRules())
      .addRuleParameters(settingsManager.getCurrentSettings().getRuleParameters())
      .build();
    LOG.debug("Analysis triggered on '{}' with configuration: \n{}", uri, configuration.toString());

    StandaloneSonarLintEngine engine = getOrCreateStandaloneEngine();
    return analyzeWithTiming(() -> engine.analyze(configuration, issueListener, null, null),
      engine.getPluginDetails(),
      () -> {
      });
  }

  public AnalysisResultsWrapper analyzeConnected(ProjectBindingWrapper binding, WorkspaceFolderSettings settings, Path baseDir, URI uri, String content,
    IssueListener issueListener, boolean shouldFetchServerIssues, Optional javaConfig) {
    ConnectedAnalysisConfiguration configuration = ConnectedAnalysisConfiguration.builder()
      .setProjectKey(settings.getProjectKey())
      .setBaseDir(baseDir)
      .addInputFile(new DefaultClientInputFile(uri, getFileRelativePath(baseDir, uri), content, isTest(settings, uri, javaConfig), languageIdPerFileURI.get(uri)))
      .putAllExtraProperties(settings.getAnalyzerProperties())
      .putAllExtraProperties(configureJavaProperties(uri))
      .build();
    if (settingsManager.getCurrentSettings().hasLocalRuleConfiguration()) {
      LOG.debug("Local rules settings are ignored, using quality profile from server");
    }
    LOG.debug("Analysis triggered on '{}' with configuration: \n{}", uri, configuration.toString());

    List issues = new LinkedList<>();
    IssueListener collector = issues::add;

    ConnectedSonarLintEngine engine = binding.getEngine();
    return analyzeWithTiming(() -> engine.analyze(configuration, collector, null, null),
      engine.getPluginDetails(),
      () -> {
        String filePath = FileUtils.toSonarQubePath(getFileRelativePath(baseDir, uri));
        ServerIssueTrackerWrapper serverIssueTracker = binding.getServerIssueTracker();
        serverIssueTracker.matchAndTrack(filePath, issues, issueListener, shouldFetchServerIssues);
      });
  }

  /**
   * @param analyze Analysis callback
   * @param postAnalysisTask Code that will be logged outside the analysis flag, but still counted in the total analysis duration.
   */
  private AnalysisResultsWrapper analyzeWithTiming(Supplier analyze, Collection allPlugins, Runnable postAnalysisTask) {
    long start = System.currentTimeMillis();
    AnalysisResults analysisResults;
    try {
      lsLogOutput.setAnalysis(true);
      analysisResults = analyze.get();
    } finally {
      lsLogOutput.setAnalysis(false);
    }

    postAnalysisTask.run();

    int analysisTime = (int) (System.currentTimeMillis() - start);
    return new AnalysisResultsWrapper(analysisResults, analysisTime, allPlugins);
  }

  private static String getFileRelativePath(Path baseDir, URI uri) {
    return baseDir.relativize(Paths.get(uri)).toString();
  }

  static Optional convert(Issue issue) {
    if (issue.getStartLine() != null) {
      Range range = position(issue);
      Diagnostic diagnostic = new Diagnostic();
      DiagnosticSeverity severity = severity(issue.getSeverity());

      diagnostic.setSeverity(severity);
      diagnostic.setRange(range);
      diagnostic.setCode(issue.getRuleKey());
      diagnostic.setMessage(issue.getMessage());
      diagnostic.setSource(SONARLINT_SOURCE);

      List flows = issue.flows();
      // If multiple flows with more than 1 location, keep only the first flow
      if (flows.size() > 1 && flows.stream().anyMatch(f -> f.locations().size() > 1)) {
        flows = Collections.singletonList(flows.get(0));
      }
      diagnostic.setRelatedInformation(flows
        .stream()
        .flatMap(f -> f.locations().stream())
        // Message is mandatory in lsp
        .filter(l -> nonNull(l.getMessage()))
        // Ignore global issue locations
        .filter(l -> nonNull(l.getInputFile()))
        .map(l -> {
          DiagnosticRelatedInformation rel = new DiagnosticRelatedInformation();
          rel.setMessage(l.getMessage());
          rel.setLocation(new Location(l.getInputFile().uri().toString(), position(l)));
          return rel;
        }).collect(Collectors.toList()));

      return Optional.of(diagnostic);
    }
    return Optional.empty();
  }

  private static DiagnosticSeverity severity(String severity) {
    switch (severity.toUpperCase(Locale.ENGLISH)) {
      case "BLOCKER":
      case "CRITICAL":
        return DiagnosticSeverity.Error;
      case "MAJOR":
        return DiagnosticSeverity.Warning;
      case "MINOR":
        return DiagnosticSeverity.Information;
      case "INFO":
      default:
        return DiagnosticSeverity.Hint;
    }
  }

  private static Range position(Issue issue) {
    return new Range(
      new Position(
        issue.getStartLine() - 1,
        issue.getStartLineOffset()),
      new Position(
        issue.getEndLine() - 1,
        issue.getEndLineOffset()));
  }

  private static Range position(IssueLocation location) {
    return new Range(
      new Position(
        location.getStartLine() - 1,
        location.getStartLineOffset()),
      new Position(
        location.getEndLine() - 1,
        location.getEndLineOffset()));
  }

  private static PublishDiagnosticsParams newPublishDiagnostics(URI newUri) {
    PublishDiagnosticsParams p = new PublishDiagnosticsParams();

    p.setDiagnostics(new ArrayList<>());
    p.setUri(newUri.toString());

    return p;
  }

  public void initialize() {
    watcher.start();
  }

  public void shutdown() {
    watcher.stopWatcher();
    eventMap.clear();
    analysisExecutor.shutdown();
    if (standaloneEngine != null) {
      standaloneEngine.stop();
    }
  }

  public void analyzeAllOpenFilesInFolder(@Nullable WorkspaceFolderWrapper folder) {
    for (URI fileUri : fileContentPerFileURI.keySet()) {
      Optional actualFolder = workspaceFoldersManager.findFolderForFile(fileUri);
      if (actualFolder.map(f -> f.equals(folder)).orElse(folder == null)) {
        analyzeAsync(fileUri, false);
      }
    }
  }

  @Override
  public void onChange(@CheckForNull WorkspaceSettings oldValue, WorkspaceSettings newValue) {
    if (oldValue == null) {
      return;
    }
    if (!Objects.equals(oldValue.getExcludedRules(), newValue.getExcludedRules()) ||
      !Objects.equals(oldValue.getIncludedRules(), newValue.getIncludedRules()) ||
      !Objects.equals(oldValue.getRuleParameters(), newValue.getRuleParameters())) {
      analyzeAllUnboundOpenFiles();
    }
  }

  private void analyzeAllUnboundOpenFiles() {
    for (URI fileUri : fileContentPerFileURI.keySet()) {
      Optional binding = bindingManager.getBinding(fileUri);
      if (!binding.isPresent()) {
        analyzeAsync(fileUri, false);
      }
    }
  }

  private void analyzeAllOpenJavaFiles() {
    for (URI fileUri : fileContentPerFileURI.keySet()) {
      if (isJava(fileUri)) {
        analyzeAsync(fileUri, false);
      }
    }
  }

  private Map configureJavaProperties(URI fileUri) {
    Optional cachedJavaConfigOpt = ofNullable(javaConfigPerFileURI.get(fileUri)).orElse(empty());
    return cachedJavaConfigOpt.map(cachedJavaConfig -> {
      Map props = new HashMap<>();
      String vmLocationStr = cachedJavaConfig.getVmLocation();
      List jdkClassesRoots = new ArrayList<>();
      if (vmLocationStr != null) {
        Path vmLocation = Paths.get(vmLocationStr);
        jdkClassesRoots = getVmClasspathFromCacheOrCompute(vmLocation);
      }
      String classpath = Stream.concat(
        jdkClassesRoots.stream().map(Path::toAbsolutePath).map(Path::toString),
        Stream.of(cachedJavaConfig.getClasspath()))
        .collect(joining(","));
      props.put("sonar.java.source", cachedJavaConfig.getSourceLevel());
      if (!cachedJavaConfig.isTest()) {
        props.put("sonar.java.libraries", classpath);
      } else {
        props.put("sonar.java.test.libraries", classpath);
      }
      return props;
    }).orElse(emptyMap());
  }

  private List getVmClasspathFromCacheOrCompute(Path vmLocation) {
    List jdkClassesRoots;
    if (!jvmClasspathPerJavaHome.containsKey(vmLocation)) {
      jvmClasspathPerJavaHome.put(vmLocation, JavaSdkUtil.getJdkClassesRoots(vmLocation));
    }
    jdkClassesRoots = jvmClasspathPerJavaHome.get(vmLocation);
    return jdkClassesRoots;
  }

  /**
   * Try to fetch Java config. In case of any error, cache an empty result to avoid repeted calls.
   */
  private CompletableFuture> getJavaConfigFromCacheOrFetchAsync(URI fileUri) {
    if (!isJava(fileUri)) {
      return CompletableFuture.completedFuture(Optional.empty());
    }
    Optional javaConfigFromCache = javaConfigPerFileURI.get(fileUri);
    if (javaConfigFromCache != null) {
      return CompletableFuture.completedFuture(javaConfigFromCache);
    }
    return client.getJavaConfig(fileUri.toString())
      .handle((r, t) -> {
        if (t != null) {
          LOG.error("Unable to fetch Java configuration of file " + fileUri, t);
        }
        return r;
      })
      .thenApply(javaConfig -> {
        Optional configOpt = ofNullable(javaConfig);
        javaConfigPerFileURI.put(fileUri, configOpt);
        LOG.debug("Cached Java config for file '{}'", fileUri);
        return configOpt;
      });
  }

  public void didClasspathUpdate(URI projectUri) {
    for (Iterator>> it = javaConfigPerFileURI.entrySet().iterator(); it.hasNext();) {
      Entry> entry = it.next();
      Optional cachedResponseOpt = entry.getValue();
      // If we have cached an empty result, still clear the value on classpath update to force next analysis to re-attempt fetch
      if (!cachedResponseOpt.isPresent() || sameProject(projectUri, cachedResponseOpt.get())) {
        it.remove();
        LOG.debug("Evicted Java config cache for file '{}'", entry.getKey());
      }
    }
    analyzeAllOpenJavaFiles();
  }

  private static boolean sameProject(URI projectUri, GetJavaConfigResponse cachedResponse) {
    // Compare file and not directly URI because
    // file:/foo/bar and file:///foo/bar/ are not considered equals by java.net.URI
    return Paths.get(URI.create(cachedResponse.getProjectRoot())).equals(Paths.get(projectUri));
  }

  public void didServerModeChange(SonarLintExtendedLanguageServer.ServerMode serverMode) {
    LOG.debug("Clearing Java config cache on server mode change");
    javaConfigPerFileURI.clear();
    if (serverMode == SonarLintExtendedLanguageServer.ServerMode.STANDARD) {
      analyzeAllOpenJavaFiles();
    }
  }

  private boolean isTest(WorkspaceFolderSettings settings, URI fileUri, Optional javaConfig) {
    if (isJava(fileUri)
      && javaConfig
        .map(GetJavaConfigResponse::isTest)
        .orElse(false)) {
      LOG.debug("Classified as test by vscode-java");
      return true;
    }
    if (settings.getTestMatcher().matches(Paths.get(fileUri))) {
      LOG.debug("Classified as test by configured 'testFilePattern' setting");
      return true;
    }
    return false;
  }

  private boolean isJava(URI fileUri) {
    return "java".equals(languageIdPerFileURI.get(fileUri));
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy