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

org.sonarsource.dotnet.shared.plugins.RoslynProfileExporter Maven / Gradle / Ivy

/*
 * SonarSource :: .NET :: Shared library
 * Copyright (C) 2014-2023 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.dotnet.shared.plugins;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.api.config.Configuration;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.profiles.ProfileExporter;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.ActiveRuleParam;
import org.sonar.api.rules.RuleParam;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.server.rule.RulesDefinition.Context;
import org.sonar.api.server.rule.RulesDefinition.Repository;
import org.sonar.api.server.rule.RulesDefinition.Rule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.stream.Collectors.toList;

/**
 * This profile exporter was build to be used by the SonarQube SonarScanner for .NET (S4MSB) during the Begin step,
 * to download the SonarLint.xml.
 *
 * However, starting 2016, it is not used anymore. See commit ce945d inside the S4MSB repo.
 *
 * To see the defaults of the settings, see AbstractPropertyDefinitions.java
 *
 * See https://github.com/SonarSource/sonar-scanner-msbuild/blob/4.6.1.2049/src/SonarScanner.MSBuild.PreProcessor/Roslyn/RoslynAnalyzerProvider.cs#L150
 *
 */
public class RoslynProfileExporter extends ProfileExporter {
  private static final Logger LOG = LoggerFactory.getLogger(RoslynProfileExporter.class);
  private static final String ROSLYN_REPOSITORY_PREFIX = "roslyn.";

  private final DotNetPluginMetadata pluginMetadata;
  private final Configuration configuration;
  private final RulesDefinition[] rulesDefinitions;

  public RoslynProfileExporter(DotNetPluginMetadata pluginMetadata, Configuration configuration, RulesDefinition[] rulesDefinitions) {
    super(profileKey(pluginMetadata), "Technical exporter for the MSBuild SonarQube Scanner");
    this.pluginMetadata = pluginMetadata;
    this.configuration = configuration;
    this.rulesDefinitions = rulesDefinitions;
    setSupportedLanguages(pluginMetadata.languageKey());
  }

  private static String sonarAnalyzerPartialRepoKey(DotNetPluginMetadata pluginMetadata) {
    return "sonaranalyzer-" + pluginMetadata.languageKey();
  }

  private static String profileKey(DotNetPluginMetadata pluginMetadata) {
    return "roslyn-" + pluginMetadata.languageKey();
  }

  public static List sonarLintRepositoryProperties(DotNetPluginMetadata pluginMetadata) {
    String analyzerVersion = getAnalyzerVersion();
    return Arrays.asList(
      PropertyDefinition.builder(pluginKeyPropertyKey(sonarAnalyzerPartialRepoKey(pluginMetadata)))
        .defaultValue(pluginMetadata.pluginKey())
        .hidden()
        .build(),
      PropertyDefinition.builder(pluginVersionPropertyKey(sonarAnalyzerPartialRepoKey(pluginMetadata)))
        .defaultValue(analyzerVersion)
        .hidden()
        .build(),
      PropertyDefinition.builder(staticResourceNamePropertyKey(sonarAnalyzerPartialRepoKey(pluginMetadata)))
        .defaultValue("SonarAnalyzer-" + analyzerVersion + ".zip")
        .hidden()
        .build(),
      PropertyDefinition.builder(analyzerIdPropertyKey(sonarAnalyzerPartialRepoKey(pluginMetadata)))
        .defaultValue(pluginMetadata.sonarAnalyzerName())
        .hidden()
        .build(),
      PropertyDefinition.builder(ruleNamespacePropertyKey(sonarAnalyzerPartialRepoKey(pluginMetadata)))
        .defaultValue(pluginMetadata.sonarAnalyzerName())
        .hidden()
        .build(),
      PropertyDefinition.builder(nugetPackageIdPropertyKey(sonarAnalyzerPartialRepoKey(pluginMetadata)))
        .defaultValue(pluginMetadata.sonarAnalyzerName())
        .hidden()
        .build(),
      PropertyDefinition.builder(nugetPackageVersionPropertyKey(sonarAnalyzerPartialRepoKey(pluginMetadata)))
        .defaultValue(analyzerVersion)
        .hidden()
        .build());
  }

  private static String getAnalyzerVersion() {
    try {
      return new BufferedReader(new InputStreamReader(RoslynProfileExporter.class.getResourceAsStream("/static/version.txt"), StandardCharsets.UTF_8)).readLine();
    } catch (IOException e) {
      throw new IllegalStateException("Couldn't read C# analyzer version number from '/static/version.txt'", e);
    }
  }

  @Override
  public void exportProfile(RulesProfile rulesProfile, Writer writer) {
    try {
      Map> activeRoslynRulesByPartialRepoKey = activeRoslynRulesByPartialRepoKey(pluginMetadata, rulesProfile.getActiveRules()
        .stream()
        .map(r -> RuleKey.of(r.getRepositoryKey(), r.getRuleKey()))
        .collect(toList()));

      appendLine(writer, "");
      appendLine(writer, "");

      appendLine(writer, "  ");
      appendLine(writer, "    ");
      for (Map.Entry> partialRepoEntry : activeRoslynRulesByPartialRepoKey.entrySet()) {
        writeRepoRuleSet(partialRepoEntry.getKey(), partialRepoEntry.getValue(), writer);
      }
      appendLine(writer, "    ");

      appendLine(writer, "    ");

      String sonarlintParameters = analysisSettings(rulesProfile);
      java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();
      String base64 = new String(encoder.encode(sonarlintParameters.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
      appendLine(writer, "      " + base64 + "");
      appendLine(writer, "    ");

      appendLine(writer, "  ");

      appendLine(writer, "  ");

      appendLine(writer, "    ");
      for (String partialRepoKey : activeRoslynRulesByPartialRepoKey.keySet()) {
        String pluginKey = mandatoryPropertyValue(pluginKeyPropertyKey(partialRepoKey));
        String pluginVersion = mandatoryPropertyValue(pluginVersionPropertyKey(partialRepoKey));
        String staticResourceName = mandatoryPropertyValue(staticResourceNamePropertyKey(partialRepoKey));

        appendLine(writer,
          "      ");
      }
      appendLine(writer, "    ");

      appendLine(writer, "    ");
      for (String partialRepoKey : activeRoslynRulesByPartialRepoKey.keySet()) {
        String packageId = mandatoryPropertyValue(nugetPackageIdPropertyKey(partialRepoKey));
        String packageVersion = mandatoryPropertyValue(nugetPackageVersionPropertyKey(partialRepoKey));

        appendLine(writer, "      ");
      }
      appendLine(writer, "    ");

      appendLine(writer, "  ");

      appendLine(writer, "");
    } catch (Exception e) {
      LOG.error(String.format("Error exporting profile '%s' for language '%s'", rulesProfile.getName(), rulesProfile.getLanguage()), e);
      throw e;
    }
  }

  private void writeRepoRuleSet(String partialRepoKey, Collection ruleKeys, Writer writer) {
    String analyzerId = mandatoryPropertyValue(analyzerIdPropertyKey(partialRepoKey));
    String ruleNamespace = mandatoryPropertyValue(ruleNamespacePropertyKey(partialRepoKey));

    appendLine(writer, "      ");

    Set activeRules = new LinkedHashSet<>();
    String repositoryKey = null;
    for (RuleKey activeRuleKey : ruleKeys) {
      if (repositoryKey == null) {
        repositoryKey = activeRuleKey.repository();
      }

      String ruleKey = activeRuleKey.rule();
      activeRules.add(ruleKey);
      appendLine(writer, "        ");
    }

    List allRuleKeys = allRuleKeysByRepositoryKey(repositoryKey);
    for (String ruleKey : allRuleKeys) {
      if (!activeRules.contains(ruleKey)) {
        appendLine(writer, "        ");
      }
    }

    appendLine(writer, "      ");
  }

  private String analysisSettings(RulesProfile ruleProfile) {
    StringBuilder sb = new StringBuilder();

    appendLine(sb, "");
    appendLine(sb, "");

    appendLine(sb, "  ");
    for (ActiveRule activeRule : ruleProfile.getActiveRulesByRepository(pluginMetadata.repositoryKey())) {
      appendLine(sb, "    ");
      appendLine(sb, "      " + escapeXml(activeRule.getRuleKey()) + "");
      appendParameters(sb, effectiveParameters(activeRule));
      appendLine(sb, "    ");
    }
    appendLine(sb, "  ");

    appendLine(sb, "  ");
    appendLine(sb, "  ");

    appendLine(sb, "");

    return sb.toString();
  }

  private static void appendParameters(StringBuilder sb, Map parameters) {
    if (!parameters.isEmpty()) {
      appendLine(sb, "      ");
      for (Map.Entry parameter : parameters.entrySet()) {
        appendLine(sb, "        ");
        appendLine(sb, "          " + escapeXml(parameter.getKey()) + "");
        appendLine(sb, "          " + escapeXml(parameter.getValue()) + "");
        appendLine(sb, "        ");
      }
      appendLine(sb, "      ");
    }
  }

  private static Map effectiveParameters(ActiveRule activeRule) {
    Map result = new HashMap<>();

    if (activeRule.getRule().getTemplate() != null) {
      result.put("RuleKey", activeRule.getRuleKey());
    }

    for (ActiveRuleParam param : activeRule.getActiveRuleParams()) {
      result.put(param.getKey(), param.getValue() == null ? "" : param.getValue());
    }

    for (RuleParam param : activeRule.getRule().getParams()) {
      if (!result.containsKey(param.getKey())) {
        result.put(param.getKey(), param.getDefaultValue() == null ? "" : param.getDefaultValue());
      }
    }

    return result;
  }

  public static Map> activeRoslynRulesByPartialRepoKey(DotNetPluginMetadata pluginMetadata, Iterable activeRules) {
    Map> result = new LinkedHashMap<>();

    for (RuleKey activeRule : activeRules) {
      if (activeRule.repository().startsWith(ROSLYN_REPOSITORY_PREFIX)) {
        String pluginKey = activeRule.repository().substring(ROSLYN_REPOSITORY_PREFIX.length());
        result.putIfAbsent(pluginKey, new ArrayList<>());
        result.get(pluginKey).add(activeRule);
      } else if (pluginMetadata.repositoryKey().equals(activeRule.repository())) {
        result.putIfAbsent(sonarAnalyzerPartialRepoKey(pluginMetadata), new ArrayList<>());
        result.get(sonarAnalyzerPartialRepoKey(pluginMetadata)).add(activeRule);
      }
    }

    return result;
  }

  private List allRuleKeysByRepositoryKey(@Nullable String repositoryKey) {
    List result = new ArrayList<>();
    if (repositoryKey == null) {
      return result;
    }

    for (RulesDefinition rulesDefinition : rulesDefinitions) {
      Context context = new Context();
      rulesDefinition.define(context);
      Repository repo = context.repository(repositoryKey);
      if (repo != null) {
        for (Rule rule : repo.rules()) {
          result.add(rule.key());
        }
      }
    }
    return result;
  }

  private String mandatoryPropertyValue(String propertyKey) {
    return configuration.get(propertyKey).orElseThrow(() -> new IllegalStateException("The mandatory property \"" + propertyKey + "\" must be set by the Roslyn plugin."));
  }

  private static String pluginKeyPropertyKey(String partialRepoKey) {
    return partialRepoKey + ".pluginKey";
  }

  private static String pluginVersionPropertyKey(String partialRepoKey) {
    return partialRepoKey + ".pluginVersion";
  }

  private static String staticResourceNamePropertyKey(String partialRepoKey) {
    return partialRepoKey + ".staticResourceName";
  }

  private static String analyzerIdPropertyKey(String partialRepoKey) {
    return partialRepoKey + ".analyzerId";
  }

  private static String ruleNamespacePropertyKey(String partialRepoKey) {
    return partialRepoKey + ".ruleNamespace";
  }

  private static String nugetPackageIdPropertyKey(String partialRepoKey) {
    return partialRepoKey + ".nuget.packageId";
  }

  private static String nugetPackageVersionPropertyKey(String partialRepoKey) {
    return partialRepoKey + ".nuget.packageVersion";
  }

  private static String escapeXml(String str) {
    return str.replace("&", "&").replace("\"", """).replace("'", "'").replace("<", "<").replace(">", ">");
  }

  private static void appendLine(Writer writer, String line) {
    try {
      writer.write(line);
      writer.write("\r\n");
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  private static void appendLine(StringBuilder sb, String line) {
    sb.append(line);
    sb.append("\r\n");
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy