Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.Strings;
import org.jetbrains.annotations.NotNull;
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
Expand Down Expand Up @@ -87,8 +88,14 @@ public void filesystemUpdated(FileSystemUpdatedEvent event) {
@EventListener
public void configurationScopesAdded(ConfigurationScopesAddedWithBindingEvent event) {
var listConfigScopeIds = event.getConfigScopeIds().stream()
.map(clientFs::getFiles)
.flatMap(List::stream)
.flatMap(configScopeId -> {
try {
return clientFs.getFiles(configScopeId).stream();
} catch (Exception e) {
// Config scope might not exist or have been removed
return Stream.empty();
}
})
.filter(f -> ALL_BINDING_CLUE_FILENAMES.contains(f.getFileName()) || f.isSonarlintConfigurationFile())
.map(ClientFile::getConfigScopeId)
.collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,34 @@ private Set<Path> getPluginPathsForConnection(String connectionId) {

Map<String, Path> pluginsToLoadByKey = new HashMap<>();
// order is important as e.g. embedded takes precedence over stored
pluginsToLoadByKey.putAll(pluginsStorage.getStoredPluginPathsByKey());
pluginsToLoadByKey.putAll(getEmbeddedPluginPathsByKey(connectionId));
var storedPluginPathsByKey = pluginsStorage.getStoredPluginPathsByKey();
pluginsToLoadByKey.putAll(storedPluginPathsByKey);
logger.debug("Found {} plugins in storage for connection '{}'", storedPluginPathsByKey.size(), connectionId);
if (!storedPluginPathsByKey.isEmpty()) {
logger.debug("Stored plugins: {}", storedPluginPathsByKey.keySet());
}

var embeddedPluginPathsByKey = getEmbeddedPluginPathsByKey(connectionId);
pluginsToLoadByKey.putAll(embeddedPluginPathsByKey);
logger.debug("Using {} embedded plugins for connection '{}'", embeddedPluginPathsByKey.size(), connectionId);
if (!embeddedPluginPathsByKey.isEmpty()) {
logger.debug("Embedded plugins: {}", embeddedPluginPathsByKey.keySet());
}

if (languageSupportRepository.getEnabledLanguagesInConnectedMode().contains(SonarLanguage.CS)) {
if (shouldUseEnterpriseCSharpAnalyzer(connectionId) && csharpSupport.csharpEnterprisePluginPath != null) {
pluginsToLoadByKey.put(PluginsSynchronizer.CSHARP_ENTERPRISE_PLUGIN_ID, csharpSupport.csharpEnterprisePluginPath);
logger.debug("Using enterprise C# analyzer for connection '{}'", connectionId);
} else if (csharpSupport.csharpOssPluginPath != null) {
pluginsToLoadByKey.put(SonarLanguage.CS.getPluginKey(), csharpSupport.csharpOssPluginPath);
logger.debug("Using OSS C# analyzer for connection '{}'", connectionId);
} else {
logger.debug("No C# analyzer available for connection '{}'", connectionId);
}
}
logger.debug("Total {} plugin paths to load for connection '{}'", pluginsToLoadByKey.size(), connectionId);
// Log detailed plugin paths for debugging
pluginsToLoadByKey.forEach((key, path) -> logger.debug(" Plugin '{}' -> {}", key, path));
return Set.copyOf(pluginsToLoadByKey.values());
}

Expand Down Expand Up @@ -206,32 +225,44 @@ private void evictAll(String connectionId) {
}

public boolean shouldUseEnterpriseCSharpAnalyzer(String connectionId) {
return shouldUseEnterpriseDotNetAnalyzer(connectionId, PluginsSynchronizer.CSHARP_ENTERPRISE_PLUGIN_ID);
var result = shouldUseEnterpriseDotNetAnalyzer(connectionId, PluginsSynchronizer.CSHARP_ENTERPRISE_PLUGIN_ID);
logger.debug("shouldUseEnterpriseCSharpAnalyzer('{}') = {}", connectionId, result);
return result;
}

private boolean shouldUseEnterpriseDotNetAnalyzer(String connectionId, String analyzerName) {
var connection = connectionConfigurationRepository.getConnectionById(connectionId);
var isSonarCloud = connection != null && connection.getKind() == ConnectionKind.SONARCLOUD;
if (connection == null) {
logger.debug("shouldUseEnterpriseDotNetAnalyzer: connection '{}' not found, returning false", connectionId);
return false;
}
var isSonarCloud = connection.getKind() == ConnectionKind.SONARCLOUD;
if (isSonarCloud) {
logger.debug("shouldUseEnterpriseDotNetAnalyzer: connection '{}' is SonarCloud, returning true", connectionId);
return true;
} else {
var connectionStorage = storageService.connection(connectionId);
var serverInfo = connectionStorage.serverInfo().read();
if (serverInfo.isEmpty()) {
return false;
} else {
// For SQ versions older than 10.8, enterprise C# and VB.NET analyzers were packaged in all editions.
// For newer versions, we need to check if enterprise plugin is present on the server
var serverVersion = serverInfo.get().version();
var supportsRepackagedDotnetAnalyzer = serverVersion.compareToIgnoreQualifier(REPACKAGED_DOTNET_ANALYZER_MIN_SQ_VERSION) >= 0;
var hasEnterprisePlugin = connectionStorage.plugins().getStoredPlugins().stream().map(StoredPlugin::getKey).anyMatch(analyzerName::equals);
return !supportsRepackagedDotnetAnalyzer || hasEnterprisePlugin;
}
}
var connectionStorage = storageService.connection(connectionId);
var serverInfo = connectionStorage.serverInfo().read();
if (serverInfo.isEmpty()) {
logger.debug("shouldUseEnterpriseDotNetAnalyzer: no server info for '{}', returning false", connectionId);
return false;
}
// For SQ versions older than 10.8, enterprise C# and VB.NET analyzers were packaged in all editions.
// For newer versions, we need to check if enterprise plugin is present on the server
var serverVersion = serverInfo.get().version();
var supportsRepackagedDotnetAnalyzer = serverVersion.compareToIgnoreQualifier(REPACKAGED_DOTNET_ANALYZER_MIN_SQ_VERSION) >= 0;
var storedPluginKeys = connectionStorage.plugins().getStoredPlugins().stream().map(StoredPlugin::getKey).toList();
var hasEnterprisePlugin = storedPluginKeys.contains(analyzerName);
var result = !supportsRepackagedDotnetAnalyzer || hasEnterprisePlugin;
logger.debug("shouldUseEnterpriseDotNetAnalyzer for '{}': serverVersion={}, supportsRepackaged={}, storedPlugins={}, hasEnterprise={} => {}",
analyzerName, serverVersion, supportsRepackagedDotnetAnalyzer, storedPluginKeys, hasEnterprisePlugin, result);
return result;
}

public boolean shouldUseEnterpriseVbAnalyzer(String connectionId) {
return shouldUseEnterpriseDotNetAnalyzer(connectionId, PluginsSynchronizer.VBNET_ENTERPRISE_PLUGIN_ID);
var result = shouldUseEnterpriseDotNetAnalyzer(connectionId, PluginsSynchronizer.VBNET_ENTERPRISE_PLUGIN_ID);
logger.debug("shouldUseEnterpriseVbAnalyzer('{}') = {}", connectionId, result);
return result;
}

public DotnetSupport getDotnetSupport(@Nullable String connectionId) {
Expand All @@ -255,23 +286,55 @@ public void shutdown() throws IOException {
}

static class CSharpSupport {
private static final SonarLintLogger LOG = SonarLintLogger.get();

final Path csharpOssPluginPath;
final Path csharpEnterprisePluginPath;

CSharpSupport(@Nullable LanguageSpecificRequirements languageSpecificRequirements) {
if (languageSpecificRequirements == null) {
LOG.debug("C#/VB.NET support: languageSpecificRequirements is null");
csharpOssPluginPath = null;
csharpEnterprisePluginPath = null;
} else {
var omnisharpRequirements = languageSpecificRequirements.getOmnisharpRequirements();
if (omnisharpRequirements == null) {
LOG.debug("C#/VB.NET support: omnisharpRequirements is null");
csharpOssPluginPath = null;
csharpEnterprisePluginPath = null;
} else {
csharpOssPluginPath = omnisharpRequirements.getOssAnalyzerPath();
csharpEnterprisePluginPath = omnisharpRequirements.getEnterpriseAnalyzerPath();
logAnalyzerPathInfo("OSS", csharpOssPluginPath);
logAnalyzerPathInfo("Enterprise", csharpEnterprisePluginPath);
if (csharpOssPluginPath == null && csharpEnterprisePluginPath == null) {
LOG.warn("C#/VB.NET support: Both OSS and Enterprise analyzer paths are null. C#/VB.NET analysis will not work.");
}
}
}
}

private static void logAnalyzerPathInfo(String analyzerType, @Nullable Path path) {
if (path == null) {
LOG.debug("C#/VB.NET support: {} analyzer path = null", analyzerType);
return;
}
var pathString = path.toString();
if (pathString.isBlank()) {
LOG.warn("C#/VB.NET support: {} analyzer path is blank: '{}'", analyzerType, pathString);
return;
}
LOG.debug("C#/VB.NET support: {} analyzer path = {}", analyzerType, path);
var file = path.toFile();
if (!file.exists()) {
LOG.warn("C#/VB.NET support: {} analyzer file does NOT exist: {}", analyzerType, path);
} else if (!file.isFile()) {
LOG.warn("C#/VB.NET support: {} analyzer path is not a file: {}", analyzerType, path);
} else {
var sizeBytes = file.length();
var sizeMB = sizeBytes / (1024.0 * 1024.0);
LOG.debug("C#/VB.NET support: {} analyzer file exists, size = {} bytes ({} MB)", analyzerType, sizeBytes, String.format("%.2f", sizeMB));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
import org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;
import org.sonarsource.sonarlint.core.plugin.PluginsService;
Expand Down Expand Up @@ -48,15 +49,34 @@ public RulesExtractionHelper(PluginsService pluginsService, LanguageSupportRepos

public List<SonarLintRuleDefinition> extractEmbeddedRules() {
logger.debug("Extracting standalone rules metadata");
return ruleExtractor.extractRules(pluginsService.getEmbeddedPlugins().getAllPluginInstancesByKeys(),
var pluginInstancesByKeys = pluginsService.getEmbeddedPlugins().getAllPluginInstancesByKeys();
logger.debug("Extracting rules from {} embedded plugins: {}", pluginInstancesByKeys.size(), pluginInstancesByKeys.keySet());
var rules = ruleExtractor.extractRules(pluginInstancesByKeys,
languageSupportRepository.getEnabledLanguagesInStandaloneMode(), false, false, new RuleSettings(Map.of()));
logExtractedRulesSummary(rules, "standalone");
return rules;
}

public List<SonarLintRuleDefinition> extractRulesForConnection(String connectionId, Map<String, String> globalSettings) {
logger.debug("Extracting rules metadata for connection '{}'", connectionId);
var pluginInstancesByKeys = pluginsService.getPlugins(connectionId).getAllPluginInstancesByKeys();
logger.debug("Extracting rules from {} plugins for connection '{}': {}", pluginInstancesByKeys.size(), connectionId, pluginInstancesByKeys.keySet());
var settings = new RuleSettings(globalSettings);
return ruleExtractor.extractRules(pluginsService.getPlugins(connectionId).getAllPluginInstancesByKeys(),
var rules = ruleExtractor.extractRules(pluginInstancesByKeys,
languageSupportRepository.getEnabledLanguagesInConnectedMode(), true, enableSecurityHotspots, settings);
logExtractedRulesSummary(rules, "connection '" + connectionId + "'");
return rules;
}

private void logExtractedRulesSummary(List<SonarLintRuleDefinition> rules, String context) {
if (rules.isEmpty()) {
logger.warn("No rules extracted for {}", context);
return;
}
logger.debug("Extracted {} rules for {}", rules.size(), context);
var rulesByLanguage = rules.stream()
.collect(Collectors.groupingBy(r -> r.getLanguage().getSonarLanguageKey(), Collectors.counting()));
logger.debug("Rules by language for {}: {}", context, rulesByLanguage);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.sonarsource.sonarlint.core.analysis.NodeJsService;
import org.sonarsource.sonarlint.core.commons.ConnectionKind;
import org.sonarsource.sonarlint.core.commons.Version;
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;
import org.sonarsource.sonarlint.core.languages.LanguageSupportRepository;
import org.sonarsource.sonarlint.core.plugin.skipped.SkippedPluginsRepository;
import org.sonarsource.sonarlint.core.repository.connection.AbstractConnectionConfiguration;
Expand All @@ -54,6 +56,9 @@

class PluginsServiceTest {

@RegisterExtension
private static final SonarLintLogTester logTester = new SonarLintLogTester();

private static final Path ossPath = Paths.get("folder", "oss");
private static final Path enterprisePath = Paths.get("folder", "enterprise");
private PluginsService underTest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ public PluginsLoadResult load(Configuration configuration, Set<String> disabledP
configuration.nodeCurrentVersion, configuration.enableDataflowBugDetection);

var nonSkippedPlugins = getNonSkippedPlugins(pluginCheckResultByKeys);
var skippedPlugins = getSkippedPlugins(pluginCheckResultByKeys);

logPlugins(nonSkippedPlugins);
logSkippedPlugins(skippedPlugins);

var instancesLoader = new PluginInstancesLoader();
var pluginInstancesByKeys = instancesLoader.instantiatePluginClasses(nonSkippedPlugins);
Expand Down Expand Up @@ -97,10 +100,28 @@ private static void logPlugins(Collection<PluginInfo> nonSkippedPlugins) {
}
}

private static void logSkippedPlugins(Collection<PluginRequirementsCheckResult> skippedPlugins) {
if (skippedPlugins.isEmpty()) {
return;
}
LOG.debug("Skipped {} plugins:", skippedPlugins.size());
for (PluginRequirementsCheckResult result : skippedPlugins) {
var plugin = result.getPlugin();
var skipReason = result.getSkipReason().map(Object::toString).orElse("unknown reason");
LOG.debug(" * {} {} ({}) - {}", plugin.getName(), plugin.getVersion(), plugin.getKey(), skipReason);
}
}

private static Collection<PluginInfo> getNonSkippedPlugins(Map<String, PluginRequirementsCheckResult> pluginCheckResultByKeys) {
return pluginCheckResultByKeys.values().stream()
.filter(not(PluginRequirementsCheckResult::isSkipped))
.map(PluginRequirementsCheckResult::getPlugin)
.toList();
}

private static Collection<PluginRequirementsCheckResult> getSkippedPlugins(Map<String, PluginRequirementsCheckResult> pluginCheckResultByKeys) {
return pluginCheckResultByKeys.values().stream()
.filter(PluginRequirementsCheckResult::isSkipped)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
*/
package org.sonarsource.sonarlint.core.rule.extractor;

import java.util.HashSet;

Check warning on line 22 in backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/RuleDefinitionsLoader.java

View check run for this annotation

SonarQube-Next / SonarQube Code Analysis

Remove this unused import 'java.util.HashSet'.

[S1128] Unnecessary imports should be removed See more on https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1616&issues=9100aa55-2a96-4fff-8d7d-b9d518137b63&open=9100aa55-2a96-4fff-8d7d-b9d518137b63
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;

Expand All @@ -29,15 +31,44 @@
*/
public class RuleDefinitionsLoader {

private static final SonarLintLogger LOG = SonarLintLogger.get();

private final RulesDefinition.Context context;

public RuleDefinitionsLoader(Optional<List<RulesDefinition>> pluginDefs) {
context = new RulesDefinition.Context();
for (var pluginDefinition : pluginDefs.orElse(List.of())) {
// Capture state before
var reposBefore = context.repositories().stream()
.collect(Collectors.toMap(RulesDefinition.Repository::key, r -> r.rules().size()));
try {
pluginDefinition.define(context);
// Capture state after
var reposAfter = context.repositories().stream()
.collect(Collectors.toMap(RulesDefinition.Repository::key, r -> r.rules().size()));

// Find new repos
var newRepos = reposAfter.keySet().stream()
.filter(r -> !reposBefore.containsKey(r))
.collect(Collectors.toList());

Check warning on line 53 in backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/RuleDefinitionsLoader.java

View check run for this annotation

SonarQube-Next / SonarQube Code Analysis

Replace this usage of 'Stream.collect(Collectors.toList())' with 'Stream.toList()' and ensure that the list is unmodified.

[S6204] "Stream.toList()" method should be used instead of "collectors" when unmodifiable list needed See more on https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1616&issues=7e61b9c5-6784-405d-88b1-1237767d7190&open=7e61b9c5-6784-405d-88b1-1237767d7190

// Find updated repos (rule count changed)
var updatedRepos = reposAfter.entrySet().stream()
.filter(e -> reposBefore.containsKey(e.getKey()) && !reposBefore.get(e.getKey()).equals(e.getValue()))
.map(e -> e.getKey() + " (" + reposBefore.get(e.getKey()) + " -> " + e.getValue() + " rules)")
.collect(Collectors.toList());

Check warning on line 59 in backend/rule-extractor/src/main/java/org/sonarsource/sonarlint/core/rule/extractor/RuleDefinitionsLoader.java

View check run for this annotation

SonarQube-Next / SonarQube Code Analysis

Replace this usage of 'Stream.collect(Collectors.toList())' with 'Stream.toList()' and ensure that the list is unmodified.

[S6204] "Stream.toList()" method should be used instead of "collectors" when unmodifiable list needed See more on https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1616&issues=a6235d96-981c-4bf6-bd12-3be580c8e3f4&open=a6235d96-981c-4bf6-bd12-3be580c8e3f4

if (!newRepos.isEmpty() || !updatedRepos.isEmpty()) {
var newRulesCount = newRepos.stream()
.mapToInt(repoKey -> context.repository(repoKey).rules().size())
.sum();
LOG.debug("Plugin '{}': new repos {} with {} rules, updated repos {}",
pluginDefinition.getClass().getSimpleName(), newRepos, newRulesCount, updatedRepos);
} else {
LOG.debug("Plugin '{}' did not register or update any rule repositories", pluginDefinition.getClass().getSimpleName());
}
} catch (Exception e) {
SonarLintLogger.get().warn(String.format("Failed to load rule definitions for %s, associated rules will be skipped", pluginDefinition), e);
LOG.warn(String.format("Failed to load rule definitions for %s, associated rules will be skipped", pluginDefinition), e);
}
}
}
Expand Down
Loading
Loading