org.sonar.plugins.csharp.CSharpSensor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sonar-csharp-plugin Show documentation
Show all versions of sonar-csharp-plugin Show documentation
Enable analysis and reporting on C# projects.
The newest version!
/*
* SonarQube C# Plugin
* Copyright (C) 2014 SonarSource
* [email protected]
*
* 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 02
*/
package org.sonar.plugins.csharp;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
import com.google.common.io.Files;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.component.ResourcePerspectives;
import org.sonar.api.config.Settings;
import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issuable.IssueBuilder;
import org.sonar.api.issue.NoSonarFilter;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.FileLinesContext;
import org.sonar.api.measures.FileLinesContextFactory;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.PersistenceMode;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.ActiveRuleParam;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleParam;
import org.sonar.api.utils.command.Command;
import org.sonar.api.utils.command.CommandExecutor;
import org.sonar.api.utils.command.StreamConsumer;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.Map.Entry;
public class CSharpSensor implements Sensor {
private static final Logger LOG = LoggerFactory.getLogger(CSharpSensor.class);
private final Settings settings;
private final RuleRunnerExtractor extractor;
private final FileSystem fs;
private final FileLinesContextFactory fileLinesContextFactory;
private final NoSonarFilter noSonarFilter;
private final RulesProfile ruleProfile;
private final ResourcePerspectives perspectives;
public CSharpSensor(Settings settings, RuleRunnerExtractor extractor, FileSystem fs, FileLinesContextFactory fileLinesContextFactory,
NoSonarFilter noSonarFilter, RulesProfile ruleProfile,
ResourcePerspectives perspectives) {
this.settings = settings;
this.extractor = extractor;
this.fs = fs;
this.fileLinesContextFactory = fileLinesContextFactory;
this.noSonarFilter = noSonarFilter;
this.ruleProfile = ruleProfile;
this.perspectives = perspectives;
}
@Override
public boolean shouldExecuteOnProject(Project project) {
return filesToAnalyze().iterator().hasNext();
}
@Override
public void analyse(Project project, SensorContext context) {
analyze();
importResults(project, context);
}
private void analyze() {
StringBuilder sb = new StringBuilder();
appendLine(sb, "");
appendLine(sb, "");
appendLine(sb, " ");
appendLine(sb, " ");
appendLine(sb, " sonar.cs.ignoreHeaderComments ");
appendLine(sb, " " + (settings.getBoolean("sonar.cs.ignoreHeaderComments") ? "true" : "false") + " ");
appendLine(sb, " ");
appendLine(sb, " ");
appendLine(sb, " ");
for (ActiveRule activeRule : ruleProfile.getActiveRulesByRepository(CSharpPlugin.REPOSITORY_KEY)) {
appendLine(sb, " ");
Rule template = activeRule.getRule().getTemplate();
String ruleKey = template == null ? activeRule.getRuleKey() : template.getKey();
appendLine(sb, " " + ruleKey + " ");
Map parameters = effectiveParameters(activeRule);
if (!parameters.isEmpty()) {
appendLine(sb, " ");
for (Entry parameter : parameters.entrySet()) {
appendLine(sb, " ");
appendLine(sb, " " + parameter.getKey() + " ");
appendLine(sb, " " + parameter.getValue() + " ");
appendLine(sb, " ");
}
appendLine(sb, " ");
}
appendLine(sb, " ");
}
appendLine(sb, " ");
appendLine(sb, " ");
for (File file : filesToAnalyze()) {
appendLine(sb, " " + file.getAbsolutePath() + " ");
}
appendLine(sb, " ");
appendLine(sb, " ");
File analysisInput = toolInput();
File analysisOutput = toolOutput();
try {
Files.write(sb, analysisInput, Charsets.UTF_8);
} catch (IOException e) {
throw Throwables.propagate(e);
}
File executableFile = extractor.executableFile();
Command command = Command.create(executableFile.getAbsolutePath())
.addArgument(analysisInput.getAbsolutePath())
.addArgument(analysisOutput.getAbsolutePath());
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);
}
}
private static Map effectiveParameters(ActiveRule activeRule) {
Map builder = Maps.newHashMap();
if (activeRule.getRule().getTemplate() != null) {
builder.put("RuleKey", activeRule.getRuleKey());
}
for (ActiveRuleParam param : activeRule.getActiveRuleParams()) {
builder.put(param.getKey(), param.getValue());
}
for (RuleParam param : activeRule.getRule().getParams()) {
if (!builder.containsKey(param.getKey())) {
builder.put(param.getKey(), param.getDefaultValue());
}
}
return ImmutableMap.copyOf(builder);
}
private void importResults(Project project, SensorContext context) {
File analysisOutput = toolOutput();
new AnalysisResultImporter(project, context, fs, fileLinesContextFactory, noSonarFilter, perspectives).parse(analysisOutput);
}
private static class AnalysisResultImporter {
private final SensorContext context;
private final FileSystem fs;
private XMLStreamReader stream;
private final FileLinesContextFactory fileLinesContextFactory;
private final NoSonarFilter noSonarFilter;
private final ResourcePerspectives perspectives;
public AnalysisResultImporter(Project project, SensorContext context, FileSystem fs, FileLinesContextFactory fileLinesContextFactory, NoSonarFilter noSonarFilter,
ResourcePerspectives perspectives) {
this.context = context;
this.fs = fs;
this.fileLinesContextFactory = fileLinesContextFactory;
this.noSonarFilter = noSonarFilter;
this.perspectives = perspectives;
}
public void parse(File file) {
InputStreamReader reader = null;
XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
try {
reader = new InputStreamReader(new FileInputStream(file), Charsets.UTF_8);
stream = xmlFactory.createXMLStreamReader(reader);
while (stream.hasNext()) {
if (stream.next() == XMLStreamConstants.START_ELEMENT) {
String tagName = stream.getLocalName();
if ("File".equals(tagName)) {
handleFileTag();
}
}
}
} catch (IOException e) {
throw Throwables.propagate(e);
} catch (XMLStreamException e) {
throw Throwables.propagate(e);
} finally {
closeXmlStream();
Closeables.closeQuietly(reader);
}
return;
}
private void closeXmlStream() {
if (stream != null) {
try {
stream.close();
} catch (XMLStreamException e) {
throw Throwables.propagate(e);
}
}
}
private void handleFileTag() throws XMLStreamException {
InputFile inputFile = null;
while (stream.hasNext()) {
int next = stream.next();
if (next == XMLStreamConstants.END_ELEMENT && "File".equals(stream.getLocalName())) {
break;
} else if (next == XMLStreamConstants.START_ELEMENT) {
String tagName = stream.getLocalName();
if ("Path".equals(tagName)) {
String path = stream.getElementText();
inputFile = fs.inputFile(fs.predicates().hasAbsolutePath(path));
} else if ("Metrics".equals(tagName)) {
// TODO Better message
Preconditions.checkState(inputFile != null);
handleMetricsTag(inputFile);
} else if ("Issues".equals(tagName)) {
// TODO Better message
Preconditions.checkState(inputFile != null);
handleIssuesTag(inputFile);
}
}
}
}
private void handleMetricsTag(InputFile inputFile) throws XMLStreamException {
while (stream.hasNext()) {
int next = stream.next();
if (next == XMLStreamConstants.END_ELEMENT && "Metrics".equals(stream.getLocalName())) {
break;
} else if (next == XMLStreamConstants.START_ELEMENT) {
String tagName = stream.getLocalName();
if ("Lines".equals(tagName)) {
handleLinesMetricTag(inputFile);
} else if ("Classes".equals(tagName)) {
handleClassesMetricTag(inputFile);
} else if ("Accessors".equals(tagName)) {
handleAccessorsMetricTag(inputFile);
} else if ("Statements".equals(tagName)) {
handleStatementsMetricTag(inputFile);
} else if ("Functions".equals(tagName)) {
handleFunctionsMetricTag(inputFile);
} else if ("PublicApi".equals(tagName)) {
handlePublicApiMetricTag(inputFile);
} else if ("PublicUndocumentedApi".equals(tagName)) {
handlePublicUndocumentedApiMetricTag(inputFile);
} else if ("Complexity".equals(tagName)) {
handleComplexityMetricTag(inputFile);
} else if ("FileComplexityDistribution".equals(tagName)) {
handleFileComplexityDistributionMetricTag(inputFile);
} else if ("FunctionComplexityDistribution".equals(tagName)) {
handleFunctionComplexityDistributionMetricTag(inputFile);
} else if ("Comments".equals(tagName)) {
handleCommentsMetricTag(inputFile);
} else if ("LinesOfCode".equals(tagName)) {
handleLinesOfCodeMetricTag(inputFile);
}
}
}
}
private void handleLinesMetricTag(InputFile inputFile) throws XMLStreamException {
double value = Double.parseDouble(stream.getElementText());
context.saveMeasure(inputFile, CoreMetrics.LINES, value);
}
private void handleClassesMetricTag(InputFile inputFile) throws XMLStreamException {
double value = Double.parseDouble(stream.getElementText());
context.saveMeasure(inputFile, CoreMetrics.CLASSES, value);
}
private void handleAccessorsMetricTag(InputFile inputFile) throws XMLStreamException {
double value = Double.parseDouble(stream.getElementText());
context.saveMeasure(inputFile, CoreMetrics.ACCESSORS, value);
}
private void handleStatementsMetricTag(InputFile inputFile) throws XMLStreamException {
double value = Double.parseDouble(stream.getElementText());
context.saveMeasure(inputFile, CoreMetrics.STATEMENTS, value);
}
private void handleFunctionsMetricTag(InputFile inputFile) throws XMLStreamException {
double value = Double.parseDouble(stream.getElementText());
context.saveMeasure(inputFile, CoreMetrics.FUNCTIONS, value);
}
private void handlePublicApiMetricTag(InputFile inputFile) throws XMLStreamException {
double value = Double.parseDouble(stream.getElementText());
context.saveMeasure(inputFile, CoreMetrics.PUBLIC_API, value);
}
private void handlePublicUndocumentedApiMetricTag(InputFile inputFile) throws XMLStreamException {
double value = Double.parseDouble(stream.getElementText());
context.saveMeasure(inputFile, CoreMetrics.PUBLIC_UNDOCUMENTED_API, value);
}
private void handleComplexityMetricTag(InputFile inputFile) throws XMLStreamException {
double value = Double.parseDouble(stream.getElementText());
context.saveMeasure(inputFile, CoreMetrics.COMPLEXITY, value);
}
private void handleFileComplexityDistributionMetricTag(InputFile inputFile) throws XMLStreamException {
String value = stream.getElementText();
context.saveMeasure(inputFile, new Measure(CoreMetrics.FILE_COMPLEXITY_DISTRIBUTION, value).setPersistenceMode(PersistenceMode.MEMORY));
}
private void handleFunctionComplexityDistributionMetricTag(InputFile inputFile) throws XMLStreamException {
String value = stream.getElementText();
context.saveMeasure(inputFile, new Measure(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION, value).setPersistenceMode(PersistenceMode.MEMORY));
}
private void handleCommentsMetricTag(InputFile inputFile) throws XMLStreamException {
while (stream.hasNext()) {
int next = stream.next();
if (next == XMLStreamConstants.END_ELEMENT && "Comments".equals(stream.getLocalName())) {
break;
} else if (next == XMLStreamConstants.START_ELEMENT) {
String tagName = stream.getLocalName();
if ("NoSonar".equals(tagName)) {
handleNoSonarCommentsMetricTag(inputFile);
} else if ("NonBlank".equals(tagName)) {
handleNonBlankCommentsMetricTag(inputFile);
}
}
}
}
private void handleNoSonarCommentsMetricTag(InputFile inputFile) throws XMLStreamException {
ImmutableSet.Builder builder = ImmutableSet.builder();
while (stream.hasNext()) {
int next = stream.next();
if (next == XMLStreamConstants.END_ELEMENT && "NoSonar".equals(stream.getLocalName())) {
break;
} else if (next == XMLStreamConstants.START_ELEMENT) {
String tagName = stream.getLocalName();
if ("Line".equals(tagName)) {
int line = Integer.parseInt(stream.getElementText());
builder.add(line);
} else {
throw new IllegalArgumentException();
}
}
}
noSonarFilter.addComponent(((DefaultInputFile) inputFile).key(), builder.build());
}
private void handleNonBlankCommentsMetricTag(InputFile inputFile) throws XMLStreamException {
double value = 0;
FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile);
while (stream.hasNext()) {
int next = stream.next();
if (next == XMLStreamConstants.END_ELEMENT && "NonBlank".equals(stream.getLocalName())) {
break;
} else if (next == XMLStreamConstants.START_ELEMENT) {
String tagName = stream.getLocalName();
if ("Line".equals(tagName)) {
value++;
int line = Integer.parseInt(stream.getElementText());
fileLinesContext.setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, line, 1);
} else {
throw new IllegalArgumentException();
}
}
}
fileLinesContext.save();
context.saveMeasure(inputFile, CoreMetrics.COMMENT_LINES, value);
}
private void handleLinesOfCodeMetricTag(InputFile inputFile) throws XMLStreamException {
double value = 0;
FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile);
while (stream.hasNext()) {
int next = stream.next();
if (next == XMLStreamConstants.END_ELEMENT && "LinesOfCode".equals(stream.getLocalName())) {
break;
} else if (next == XMLStreamConstants.START_ELEMENT) {
String tagName = stream.getLocalName();
if ("Line".equals(tagName)) {
value++;
int line = Integer.parseInt(stream.getElementText());
fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1);
} else {
throw new IllegalArgumentException();
}
}
}
fileLinesContext.save();
context.saveMeasure(inputFile, CoreMetrics.NCLOC, value);
}
private void handleIssuesTag(InputFile inputFile) throws XMLStreamException {
Issuable issuable = perspectives.as(Issuable.class, inputFile);
while (stream.hasNext()) {
int next = stream.next();
if (next == XMLStreamConstants.END_ELEMENT && "Issues".equals(stream.getLocalName())) {
break;
} else if (next == XMLStreamConstants.START_ELEMENT) {
String tagName = stream.getLocalName();
if ("Issue".equals(tagName) && issuable != null) {
handleIssueTag(issuable);
}
}
}
}
private void handleIssueTag(Issuable issuable) throws XMLStreamException {
IssueBuilder builder = issuable.newIssueBuilder();
String id = null;
String message = null;
while (stream.hasNext()) {
int next = stream.next();
if (next == XMLStreamConstants.END_ELEMENT && "Issue".equals(stream.getLocalName())) {
Preconditions.checkState(!"AnalyzerDriver".equals(id), "The analyzer failed, double check rule parameters or disable failing rules: " + message);
builder.ruleKey(RuleKey.of(CSharpPlugin.REPOSITORY_KEY, id));
builder.message(message);
issuable.addIssue(builder.build());
break;
} else if (next == XMLStreamConstants.START_ELEMENT) {
String tagName = stream.getLocalName();
if ("Id".equals(tagName)) {
id = stream.getElementText();
} else if ("Line".equals(tagName)) {
builder.line(Integer.parseInt(stream.getElementText()));
} else if ("Message".equals(tagName)) {
message = stream.getElementText();
}
}
}
}
}
private void appendLine(StringBuilder sb, String line) {
sb.append(line);
sb.append("\r\n");
}
private static class LogInfoStreamConsumer implements StreamConsumer {
@Override
public void consumeLine(String line) {
LOG.info(line);
}
}
private static class LogErrorStreamConsumer implements StreamConsumer {
@Override
public void consumeLine(String line) {
LOG.error(line);
}
}
private Iterable filesToAnalyze() {
return fs.files(fs.predicates().and(fs.predicates().hasType(Type.MAIN), fs.predicates().hasLanguage(CSharpPlugin.LANGUAGE_KEY)));
}
private File toolInput() {
return new File(fs.workDir(), "analysis-input.xml");
}
private File toolOutput() {
return toolOutput(fs);
}
public static File toolOutput(FileSystem fileSystem) {
return new File(fileSystem.workDir(), "analysis-output.xml");
}
}