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

Java tutorial

Introduction

Here is the source code for org.sonar.plugins.csharp.CSharpSensor.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.Multimap;
import org.apache.commons.lang.SystemUtils;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.rule.ActiveRule;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.config.Settings;
import org.sonar.api.issue.NoSonarFilter;
import org.sonar.api.measures.FileLinesContextFactory;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.command.Command;
import org.sonar.api.utils.command.CommandExecutor;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonarsource.dotnet.shared.plugins.AbstractSensor;
import org.sonarsource.dotnet.shared.plugins.AnalysisInputXml;
import org.sonarsource.dotnet.shared.plugins.EncodingPerFile;
import org.sonarsource.dotnet.shared.plugins.SonarAnalyzerScannerExtractor;
import org.sonarsource.dotnet.shared.sarif.SarifParserCallback;
import org.sonarsource.dotnet.shared.sarif.SarifParserFactory;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

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

public class CSharpSensor extends AbstractSensor implements Sensor {

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

    private final Settings settings;
    private final SonarAnalyzerScannerExtractor extractor;
    private final CSharpConfiguration config;

    public CSharpSensor(Settings settings, SonarAnalyzerScannerExtractor extractor,
            FileLinesContextFactory fileLinesContextFactory, NoSonarFilter noSonarFilter,
            CSharpConfiguration config, EncodingPerFile encodingPerFile) {
        super(fileLinesContextFactory, noSonarFilter, config, encodingPerFile,
                CSharpSonarRulesDefinition.REPOSITORY_KEY);
        this.settings = settings;
        this.extractor = extractor;
        this.config = config;
    }

    @Override
    public void describe(SensorDescriptor descriptor) {
        descriptor.name("C#").onlyOnLanguage(CSharpPlugin.LANGUAGE_KEY);
    }

    @Override
    public void execute(SensorContext context) {
        if (shouldExecuteOnProject(context.fileSystem())) {
            executeInternal(context);
        }
    }

    private static boolean shouldExecuteOnProject(FileSystem fs) {
        if (!SystemUtils.IS_OS_WINDOWS) {
            LOG.debug("OS is not Windows. Skip Sensor.");
            return false;
        }

        if (!filesToAnalyze(fs).iterator().hasNext()) {
            LOG.debug("No files to analyze. Skip Sensor.");
            return false;
        }

        return true;
    }

    private static Iterable<File> filesToAnalyze(FileSystem fs) {
        return fs.files(fs.predicates().and(fs.predicates().hasType(Type.MAIN),
                fs.predicates().hasLanguage(CSharpPlugin.LANGUAGE_KEY)));
    }

    void executeInternal(SensorContext context) {
        boolean requiresAnalyzerScannerExecution = !config.isReportsComingFromMSBuild();

        LOG.info("SonarAnalyzer.Scanner needs to be executed: " + requiresAnalyzerScannerExecution);

        String roslynReportPath = settings.getString(config.getRoslynJsonReportPathProperty());
        boolean hasRoslynReportPath = roslynReportPath != null;

        Path protobufReportsDirectory;
        if (requiresAnalyzerScannerExecution) {
            // MSBuild 12 or MSBuild 14 with old scanner
            analyze(!hasRoslynReportPath, context);
            protobufReportsDirectory = protobufReportPathForMSBuild12(context);
        } else {
            protobufReportsDirectory = config.protobufReportPathFromScanner();
        }

        LOG.info("Importing analysis results from " + protobufReportsDirectory.toAbsolutePath().toString());
        importResults(context, protobufReportsDirectory, !hasRoslynReportPath);

        if (hasRoslynReportPath) {
            LOG.info("Importing Roslyn report");
            importRoslynReport(roslynReportPath, context);
        }
    }

    void analyze(boolean includeRules, SensorContext context) {
        if (includeRules) {
            LOG.warn("***********************************************************************************");
            LOG.warn("*                 Use MSBuild 14 to get the best analysis results                 *");
            LOG.warn("* The use of MSBuild 12 or the sonar-scanner to analyze C# projects is DEPRECATED *");
            LOG.warn("***********************************************************************************");

            Multimap<String, RuleKey> activeRoslynRulesByPartialRepoKey = RoslynProfileExporter
                    .activeRoslynRulesByPartialRepoKey(
                            context.activeRules().findAll().stream().map(ActiveRule::ruleKey).collect(toList()));

            if (activeRoslynRulesByPartialRepoKey.keySet().size() > 1) {
                throw new IllegalArgumentException(
                        "Custom and 3rd party Roslyn analyzers are only by MSBuild 14. Either use MSBuild 14, or disable the custom/3rd party Roslyn analyzers in your quality profile.");
            }
        }

        String analysisSettings = AnalysisInputXml.generate(true,
                settings.getBoolean("sonar.cs.ignoreHeaderComments"), includeRules, context,
                CSharpSonarRulesDefinition.REPOSITORY_KEY, CSharpPlugin.LANGUAGE_KEY,
                context.fileSystem().encoding().name());

        Path analysisInput = toolInput(context.fileSystem());
        Path analysisOutput = protobufReportPathForMSBuild12(context);

        try {
            Files.write(analysisInput, analysisSettings.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }

        File executableFile = extractor.executableFile(CSharpPlugin.LANGUAGE_KEY);

        Command command = Command.create(executableFile.getAbsolutePath())
                .addArgument(analysisInput.toAbsolutePath().toString())
                .addArgument(analysisOutput.toAbsolutePath().toString()).addArgument(CSharpPlugin.LANGUAGE_KEY);

        int exitCode = CommandExecutor.create().execute(command, new LogInfoStreamConsumer(),
                new LogErrorStreamConsumer(), Integer.MAX_VALUE);
        if (exitCode != 0) {
            throw new IllegalStateException("The .NET analyzer failed with exit code: " + exitCode
                    + " - Verify that the .NET Framework version 4.5.2 at least is installed.");
        }
    }

    private static void importRoslynReport(String reportPath, final SensorContext context) {
        Multimap<String, RuleKey> activeRoslynRulesByPartialRepoKey = RoslynProfileExporter
                .activeRoslynRulesByPartialRepoKey(
                        context.activeRules().findAll().stream().map(ActiveRule::ruleKey).collect(toList()));
        final Map<String, String> repositoryKeyByRoslynRuleKey = new HashMap<>();
        for (RuleKey activeRoslynRuleKey : activeRoslynRulesByPartialRepoKey.values()) {
            String previousRepositoryKey = repositoryKeyByRoslynRuleKey.put(activeRoslynRuleKey.rule(),
                    activeRoslynRuleKey.repository());
            if (previousRepositoryKey != null) {
                throw new IllegalArgumentException("Rule keys must be unique, but \"" + activeRoslynRuleKey.rule()
                        + "\" is defined in both the \"" + previousRepositoryKey + "\" and \""
                        + activeRoslynRuleKey.repository() + "\" rule repositories.");
            }
        }

        SarifParserCallback callback = new SarifParserCallbackImplementation(context, repositoryKeyByRoslynRuleKey);
        SarifParserFactory.create(new File(reportPath)).accept(callback);
    }

    private Path protobufReportPathForMSBuild12(SensorContext context) {
        // With MSBuild 12 or old version of the scanner, protobuf reports are not generated
        // so return a new directory in the SQ working dir and the sensor will be responsible to execute analyzer.
        return context.fileSystem().workDir().toPath().resolve(config.getAnalyzerReportDir());
    }

}