Skip to content

Commit

Permalink
SONARCS-552 Export a C# Quality Profile to SonarLint for VS2015
Browse files Browse the repository at this point in the history
  • Loading branch information
dbolkensteyn committed Oct 21, 2015
1 parent 7a5a71b commit 1bf4ea4
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/main/java/org/sonar/plugins/csharp/CSharpPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public List getExtensions() {
CSharpSourceCodeColorizer.class,
RuleRunnerExtractor.class,
CSharpSensor.class,
CSharpCPDMapping.class);
CSharpCPDMapping.class,
SonarLintProfileExporter.class);

builder.addAll(CSharpFxCopProvider.extensions());
builder.addAll(CSharpCodeCoverageProvider.extensions());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@
package org.sonar.plugins.csharp;

import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import org.sonar.api.BatchExtension;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
import org.sonar.squidbridge.rules.SqaleXmlLoader;

import java.io.InputStreamReader;
import java.util.Set;

public class CSharpSonarRulesDefinition implements RulesDefinition {
public class CSharpSonarRulesDefinition implements RulesDefinition, BatchExtension {

private Set<String> parameterlessRuleKeys = null;

@Override
public void define(Context context) {
Expand All @@ -38,7 +44,20 @@ public void define(Context context) {
loader.load(repository, new InputStreamReader(getClass().getResourceAsStream("/org/sonar/plugins/csharp/rules.xml"), Charsets.UTF_8));
SqaleXmlLoader.load(repository, "/org/sonar/plugins/csharp/sqale.xml");

ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (NewRule rule : repository.rules()) {
if (rule.params().isEmpty()) {
builder.add(rule.key());
}
}
parameterlessRuleKeys = builder.build();

repository.done();
}

public Set<String> parameterlessRuleKeys() {
Preconditions.checkNotNull(parameterlessRuleKeys);
return parameterlessRuleKeys;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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.Throwables;
import com.google.common.collect.Sets;
import org.sonar.api.profiles.ProfileExporter;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.Rule;

import java.io.IOException;
import java.io.Writer;
import java.util.Set;

public class SonarLintProfileExporter extends ProfileExporter {

private final CSharpSonarRulesDefinition csharpRulesDefinition;

public SonarLintProfileExporter(CSharpSonarRulesDefinition csharpRulesDefinition) {
super("sonarlint-vs-cs", "SonarLint for Visual Studio Rule Set");
setSupportedLanguages(CSharpPlugin.LANGUAGE_KEY);
this.csharpRulesDefinition = csharpRulesDefinition;
}

@Override
public void exportProfile(RulesProfile ruleProfile, Writer writer) {
Set<String> disabledRuleKeys = Sets.newHashSet();
disabledRuleKeys.addAll(csharpRulesDefinition.parameterlessRuleKeys());

appendLine(writer, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
appendLine(writer, "<RuleSet Name=\"Rules for SonarLint\" Description=\"This rule set was automatically generated from SonarQube.\" ToolsVersion=\"14.0\">");
appendLine(writer, " <Rules AnalyzerId=\"SonarLint\" RuleNamespace=\"SonarLint\">");

for (ActiveRule activeRule : ruleProfile.getActiveRulesByRepository(CSharpPlugin.REPOSITORY_KEY)) {
Rule rule = activeRule.getRule();
disabledRuleKeys.remove(rule.getKey());

if (rule.getParams().isEmpty() && rule.getTemplate() == null && !activeRule.getSeverity().equals(rule.getSeverity())) {
// Rule is from SonarLint and severity is non-default, explicitly enable
appendLine(writer, " <Rule Id=\"" + rule.getKey() + "\" Action=\"Warning\" />");
}
}

for (String disableRuleKey : disabledRuleKeys) {
appendLine(writer, " <Rule Id=\"" + disableRuleKey + "\" Action=\"None\" />");
}

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

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

}
3 changes: 2 additions & 1 deletion src/test/java/org/sonar/plugins/csharp/CSharpPluginTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public void getExtensions() {
CSharpSonarWayProfile.class,
RuleRunnerExtractor.class,
CSharpSensor.class,
CSharpCPDMapping.class
CSharpCPDMapping.class,
SonarLintProfileExporter.class
};

assertThat(nonProperties(extensions)).contains(expectedExtensions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.junit.Test;
import org.sonar.api.server.rule.RulesDefinition.Context;

import java.util.Set;

import static org.fest.assertions.Assertions.assertThat;

public class CSharpSonarRulesDefinitionTest {
Expand All @@ -31,10 +33,16 @@ public void test() {
Context context = new Context();
assertThat(context.repositories()).isEmpty();

new CSharpSonarRulesDefinition().define(context);
CSharpSonarRulesDefinition csharpRulesDefinition = new CSharpSonarRulesDefinition();
csharpRulesDefinition.define(context);

assertThat(context.repositories()).hasSize(1);
assertThat(context.repository("csharpsquid").rules()).isNotEmpty();

Set<String> sonarLintRules = csharpRulesDefinition.parameterlessRuleKeys();
assertThat(sonarLintRules.contains("S100")).isFalse();
assertThat(sonarLintRules.size()).isGreaterThan(50);
assertThat(sonarLintRules.size()).isLessThanOrEqualTo(context.repository("csharpsquid").rules().size());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleParam;
import org.sonar.api.rules.RulePriority;

import java.io.StringWriter;
import java.util.Set;

import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class SonarLintProfileExporterTest {

@Test
public void test() {
// S1000 has parameters and is enabled -> should not be in exported rule set
Rule ruleS1000 = mock(Rule.class);
when(ruleS1000.getKey()).thenReturn("S1000");
RuleParam ruleParam = mock(RuleParam.class);
when(ruleS1000.getParams()).thenReturn(ImmutableList.of(ruleParam));
when(ruleS1000.getTemplate()).thenReturn(null);
when(ruleS1000.getSeverity()).thenReturn(RulePriority.MAJOR);
org.sonar.api.rules.ActiveRule activeRuleS1000 = mock(ActiveRule.class);
when(activeRuleS1000.getRule()).thenReturn(ruleS1000);
when(activeRuleS1000.getSeverity()).thenReturn(RulePriority.BLOCKER);

// S1001 is a template rule and is enabled -> should not be in exported rule set
Rule ruleS1001 = mock(Rule.class);
when(ruleS1001.getKey()).thenReturn("S1001");
when(ruleS1001.getParams()).thenReturn(ImmutableList.<RuleParam>of());
Rule baseTemplateRule = mock(Rule.class);
when(ruleS1001.getTemplate()).thenReturn(baseTemplateRule);
when(ruleS1001.getSeverity()).thenReturn(RulePriority.MAJOR);
org.sonar.api.rules.ActiveRule activeRuleS1001 = mock(ActiveRule.class);
when(activeRuleS1001.getRule()).thenReturn(ruleS1001);
when(activeRuleS1001.getSeverity()).thenReturn(RulePriority.BLOCKER);

// S1002 is a SonarLint rule and disabled -> should be disabled in exported rule set
Rule ruleS1002 = mock(Rule.class);
when(ruleS1002.getKey()).thenReturn("S1002");
when(ruleS1002.getParams()).thenReturn(ImmutableList.<RuleParam>of());
when(ruleS1002.getTemplate()).thenReturn(null);
when(ruleS1002.getSeverity()).thenReturn(RulePriority.MAJOR);

// S1003 is a SonarLint rule and enabled at default severity -> should not be in exported rule set
Rule ruleS1003 = mock(Rule.class);
when(ruleS1003.getKey()).thenReturn("S1003");
when(ruleS1003.getParams()).thenReturn(ImmutableList.<RuleParam>of());
when(ruleS1003.getTemplate()).thenReturn(null);
when(ruleS1003.getSeverity()).thenReturn(RulePriority.MAJOR);
org.sonar.api.rules.ActiveRule activeRuleS1003 = mock(ActiveRule.class);
when(activeRuleS1003.getRule()).thenReturn(ruleS1003);
when(activeRuleS1003.getSeverity()).thenReturn(RulePriority.MAJOR);

// S1004 is a SonarLint rule and enabled at different severity -> should be in exported rule set
Rule ruleS1004 = mock(Rule.class);
when(ruleS1004.getKey()).thenReturn("S1004");
when(ruleS1004.getParams()).thenReturn(ImmutableList.<RuleParam>of());
when(ruleS1004.getTemplate()).thenReturn(null);
when(ruleS1004.getSeverity()).thenReturn(RulePriority.MAJOR);
org.sonar.api.rules.ActiveRule activeRuleS1004 = mock(ActiveRule.class);
when(activeRuleS1004.getRule()).thenReturn(ruleS1004);
when(activeRuleS1004.getSeverity()).thenReturn(RulePriority.BLOCKER);

Set<String> allRules = ImmutableSet.of(
ruleS1000.getKey(),
ruleS1001.getKey(),
ruleS1002.getKey(),
ruleS1003.getKey(),
ruleS1004.getKey());
CSharpSonarRulesDefinition csharpRulesDefinition = mock(CSharpSonarRulesDefinition.class);
when(csharpRulesDefinition.parameterlessRuleKeys()).thenReturn(allRules);

SonarLintProfileExporter exporter = new SonarLintProfileExporter(csharpRulesDefinition);
assertThat(exporter.getKey()).isEqualTo("sonarlint-vs-cs");
assertThat(exporter.getName()).isEqualTo("SonarLint for Visual Studio Rule Set");
assertThat(exporter.getSupportedLanguages()).containsOnly("cs");

StringWriter writer = new StringWriter();
RulesProfile rulesProfile = mock(RulesProfile.class);
when(rulesProfile.getActiveRulesByRepository(CSharpPlugin.REPOSITORY_KEY)).thenReturn(
ImmutableList.of(
activeRuleS1000,
activeRuleS1001,
activeRuleS1003,
activeRuleS1004));
exporter.exportProfile(rulesProfile, writer);
assertThat(writer.toString()).isEqualTo(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
"<RuleSet Name=\"Rules for SonarLint\" Description=\"This rule set was automatically generated from SonarQube.\" ToolsVersion=\"14.0\">\r\n" +
" <Rules AnalyzerId=\"SonarLint\" RuleNamespace=\"SonarLint\">\r\n" +
" <Rule Id=\"S1004\" Action=\"Warning\" />\r\n" +
" <Rule Id=\"S1002\" Action=\"None\" />\r\n" +
" </Rules>\r\n" +
"</RuleSet>\r\n");
}

}

0 comments on commit 1bf4ea4

Please sign in to comment.