org.sonar.plugins.csharp.RoslynProfileExporter.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.plugins.csharp.RoslynProfileExporter.java

Source

/*
 * SonarC#
 * Copyright (C) 2014-2017 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.sonar.plugins.csharp;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.Settings;
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.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;

import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.*;

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

public class RoslynProfileExporter extends ProfileExporter {
    private static final Logger LOG = Loggers.get(RoslynProfileExporter.class);
    static final String SONARANALYZER_CSHARP_NAME = "SonarAnalyzer.CSharp";
    private static final String SONARANALYZER_PARTIAL_REPO_KEY = "sonaranalyzer-cs";

    private static final String ROSLYN_REPOSITORY_PREFIX = "roslyn.";

    private final Settings settings;
    private final RulesDefinition[] rulesDefinitions;

    public RoslynProfileExporter(Settings settings, RulesDefinition[] rulesDefinitions) {
        super("roslyn-cs", "Technical exporter for the MSBuild SonarQube Scanner");
        this.settings = settings;
        this.rulesDefinitions = rulesDefinitions;
        setSupportedLanguages(CSharpPlugin.LANGUAGE_KEY);
    }

    static List<PropertyDefinition> sonarLintRepositoryProperties() {
        String analyzerVersion = getAnalyzerVersion();
        return Arrays.asList(
                PropertyDefinition.builder(pluginKeyPropertyKey(SONARANALYZER_PARTIAL_REPO_KEY))
                        .defaultValue("csharp").hidden().build(),
                PropertyDefinition.builder(pluginVersionPropertyKey(SONARANALYZER_PARTIAL_REPO_KEY))
                        .defaultValue(analyzerVersion).hidden().build(),
                PropertyDefinition.builder(staticResourceNamePropertyKey(SONARANALYZER_PARTIAL_REPO_KEY))
                        .defaultValue("SonarAnalyzer-" + analyzerVersion + ".zip").hidden().build(),
                PropertyDefinition.builder(analyzerIdPropertyKey(SONARANALYZER_PARTIAL_REPO_KEY))
                        .defaultValue(SONARANALYZER_CSHARP_NAME).hidden().build(),
                PropertyDefinition.builder(ruleNamespacePropertyKey(SONARANALYZER_PARTIAL_REPO_KEY))
                        .defaultValue(SONARANALYZER_CSHARP_NAME).hidden().build(),
                PropertyDefinition.builder(nugetPackageIdPropertyKey(SONARANALYZER_PARTIAL_REPO_KEY))
                        .defaultValue(SONARANALYZER_CSHARP_NAME).hidden().build(),
                PropertyDefinition.builder(nugetPackageVersionPropertyKey(SONARANALYZER_PARTIAL_REPO_KEY))
                        .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 {
            Multimap<String, RuleKey> activeRoslynRulesByPartialRepoKey = activeRoslynRulesByPartialRepoKey(
                    rulesProfile.getActiveRules().stream()
                            .map(r -> RuleKey.of(r.getRepositoryKey(), r.getRuleKey())).collect(toList()));

            appendLine(writer, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
            appendLine(writer, "<RoslynExportProfile Version=\"1.0\">");

            appendLine(writer, "  <Configuration>");
            appendLine(writer,
                    "    <RuleSet Name=\"Rules for SonarQube\" Description=\"This rule set was automatically generated from SonarQube.\" ToolsVersion=\"14.0\">");
            for (String partialRepoKey : activeRoslynRulesByPartialRepoKey.keySet()) {
                writeRepoRuleSet(partialRepoKey, activeRoslynRulesByPartialRepoKey.get(partialRepoKey), writer);
            }
            appendLine(writer, "    </RuleSet>");

            appendLine(writer, "    <AdditionalFiles>");

            String sonarlintParameters = analysisSettings(false, false, true, 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, "      <AdditionalFile FileName=\"SonarLint.xml\">" + base64 + "</AdditionalFile>");
            appendLine(writer, "    </AdditionalFiles>");

            appendLine(writer, "  </Configuration>");

            appendLine(writer, "  <Deployment>");

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

                appendLine(writer,
                        "      <Plugin Key=\"" + escapeXml(pluginKey) + "\" Version=\"" + escapeXml(pluginVersion)
                                + "\" StaticResourceName=\"" + escapeXml(staticResourceName) + "\" />");
            }
            appendLine(writer, "    </Plugins>");

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

                appendLine(writer, "      <NuGetPackage Id=\"" + escapeXml(packageId) + "\" Version=\""
                        + escapeXml(packageVersion) + "\" />");
            }
            appendLine(writer, "    </NuGetPackages>");

            appendLine(writer, "  </Deployment>");

            appendLine(writer, "</RoslynExportProfile>");
        } catch (Exception e) {
            LOG.error("Error exporting profile '{}' for language '{}'", rulesProfile.getName(),
                    rulesProfile.getLanguage(), e);
            throw e;
        }
    }

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

        appendLine(writer, "      <Rules AnalyzerId=\"" + escapeXml(analyzerId) + "\" RuleNamespace=\""
                + escapeXml(ruleNamespace) + "\">");

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

            String ruleKey = activeRuleKey.rule();
            activeRules.add(ruleKey);
            appendLine(writer, "        <Rule Id=\"" + escapeXml(ruleKey) + "\" Action=\"Warning\" />");
        }

        List<String> allRuleKeys = allRuleKeysByRepositoryKey(repositoryKey);
        for (String ruleKey : allRuleKeys) {
            if (!activeRules.contains(ruleKey)) {
                appendLine(writer, "        <Rule Id=\"" + escapeXml(ruleKey) + "\" Action=\"None\" />");
            }
        }

        appendLine(writer, "      </Rules>");
    }

    private static String analysisSettings(boolean includeSettings, boolean ignoreHeaderComments,
            boolean includeRules, RulesProfile ruleProfile) {
        StringBuilder sb = new StringBuilder();

        appendLine(sb, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        appendLine(sb, "<AnalysisInput>");

        if (includeSettings) {
            appendLine(sb, "  <Settings>");
            appendLine(sb, "    <Setting>");
            appendLine(sb, "      <Key>" + CSharpPlugin.IGNORE_HEADER_COMMENTS + "</Key>");
            appendLine(sb, "      <Value>" + (ignoreHeaderComments ? "true" : "false") + "</Value>");
            appendLine(sb, "    </Setting>");
            appendLine(sb, "  </Settings>");
        }

        appendLine(sb, "  <Rules>");
        if (includeRules) {
            for (ActiveRule activeRule : ruleProfile
                    .getActiveRulesByRepository(CSharpSonarRulesDefinition.REPOSITORY_KEY)) {
                appendLine(sb, "    <Rule>");
                appendLine(sb, "      <Key>" + escapeXml(activeRule.getRuleKey()) + "</Key>");
                appendParameters(sb, effectiveParameters(activeRule));
                appendLine(sb, "    </Rule>");
            }
        }
        appendLine(sb, "  </Rules>");

        appendLine(sb, "  <Files>");
        appendLine(sb, "  </Files>");

        appendLine(sb, "</AnalysisInput>");

        return sb.toString();
    }

    private static void appendParameters(StringBuilder sb, Map<String, String> parameters) {
        if (!parameters.isEmpty()) {
            appendLine(sb, "      <Parameters>");
            for (Map.Entry<String, String> parameter : parameters.entrySet()) {
                appendLine(sb, "        <Parameter>");
                appendLine(sb, "          <Key>" + escapeXml(parameter.getKey()) + "</Key>");
                appendLine(sb, "          <Value>" + escapeXml(parameter.getValue()) + "</Value>");
                appendLine(sb, "        </Parameter>");
            }
            appendLine(sb, "      </Parameters>");
        }
    }

    private static Map<String, String> effectiveParameters(ActiveRule activeRule) {
        Map<String, String> builder = new HashMap<>();

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

        for (ActiveRuleParam param : activeRule.getActiveRuleParams()) {
            checkValueNotNull(param.getValue(), activeRule.getRuleKey(), param.getKey());
            builder.put(param.getKey(), param.getValue());
        }

        for (RuleParam param : activeRule.getRule().getParams()) {
            if (!builder.containsKey(param.getKey())) {
                checkValueNotNull(param.getDefaultValue(), activeRule.getRuleKey(), param.getKey());
                builder.put(param.getKey(), param.getDefaultValue());
            }
        }

        return ImmutableMap.copyOf(builder);
    }

    private static void checkValueNotNull(@Nullable String value, String ruleKey, String paramKey) {
        if (value == null) {
            throw new IllegalStateException(
                    String.format("Rule '%s' has parameter '%s' with a null value", ruleKey, paramKey));
        }
    }

    static Multimap<String, RuleKey> activeRoslynRulesByPartialRepoKey(Iterable<RuleKey> activeRules) {
        ImmutableMultimap.Builder<String, RuleKey> builder = ImmutableMultimap.builder();

        for (RuleKey activeRule : activeRules) {
            if (activeRule.repository().startsWith(ROSLYN_REPOSITORY_PREFIX)) {
                String pluginKey = activeRule.repository().substring(ROSLYN_REPOSITORY_PREFIX.length());
                builder.put(pluginKey, activeRule);
            } else if (CSharpSonarRulesDefinition.REPOSITORY_KEY.equals(activeRule.repository())) {
                builder.put(SONARANALYZER_PARTIAL_REPO_KEY, activeRule);
            }
        }

        return builder.build();
    }

    private List<String> allRuleKeysByRepositoryKey(String repositoryKey) {
        ImmutableList.Builder<String> builder = ImmutableList.builder();
        for (RulesDefinition rulesDefinition : rulesDefinitions) {
            Context context = new Context();
            rulesDefinition.define(context);
            Repository repo = context.repository(repositoryKey);
            if (repo != null) {
                for (Rule rule : repo.rules()) {
                    builder.add(rule.key());
                }
            }
        }
        return builder.build();
    }

    private String mandatoryPropertyValue(String propertyKey) {
        return requireNonNull(settings.getDefaultValue(propertyKey),
                "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("&", "&amp;").replace("\"", "&quot;").replace("'", "&apos;").replace("<", "&lt;")
                .replace(">", "&gt;");
    }

    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");
    }
}