@@ -1,3 +1,3 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<pmd version="4.2.5" timestamp="2010-09-15T13:59:35.931"> | |||
<pmd version="4.2.5" timestamp="2010-09-15T16:58:28.966"> | |||
</pmd> |
@@ -27,4 +27,7 @@ public final class PmdConstants { | |||
public static final String REPOSITORY_NAME = "PMD"; | |||
public static final String PLUGIN_NAME = "PMD"; | |||
public static final String PLUGIN_KEY = CoreProperties.PMD_PLUGIN; | |||
public static final String XPATH_CLASS ="net.sourceforge.pmd.rules.XPathRule"; | |||
public static final String XPATH_EXPRESSION_PARAM ="xpath"; | |||
public static final String XPATH_MESSAGE_PARAM ="message"; | |||
} |
@@ -30,7 +30,6 @@ import org.sonar.api.profiles.RulesProfile; | |||
import org.sonar.api.resources.Java; | |||
import org.sonar.api.rules.ActiveRule; | |||
import org.sonar.api.rules.ActiveRuleParam; | |||
import org.sonar.api.rules.RulePriority; | |||
import org.sonar.api.utils.SonarException; | |||
import org.sonar.plugins.pmd.xml.PmdProperty; | |||
import org.sonar.plugins.pmd.xml.PmdRule; | |||
@@ -49,15 +48,15 @@ public class PmdProfileExporter extends ProfileExporter { | |||
@Override | |||
public void exportProfile(RulesProfile profile, Writer writer) { | |||
try { | |||
PmdRuleset tree = buildModuleTree(profile.getActiveRulesByRepository(PmdConstants.REPOSITORY_KEY), profile.getName()); | |||
String xmlModules = buildXmlFromModuleTree(tree); | |||
PmdRuleset tree = createPmdRuleset(profile.getActiveRulesByRepository(PmdConstants.REPOSITORY_KEY), profile.getName()); | |||
String xmlModules = exportPmdRulesetToXml(tree); | |||
writer.append(xmlModules); | |||
} catch (IOException e) { | |||
throw new SonarException("Fail to export the profile " + profile, e); | |||
} | |||
} | |||
protected PmdRuleset buildModuleTree(List<ActiveRule> activeRules, String profileName) { | |||
protected PmdRuleset createPmdRuleset(List<ActiveRule> activeRules, String profileName) { | |||
PmdRuleset ruleset = new PmdRuleset(profileName); | |||
for (ActiveRule activeRule : activeRules) { | |||
if (activeRule.getRule().getPluginName().equals(CoreProperties.PMD_PLUGIN)) { | |||
@@ -72,12 +71,25 @@ public class PmdProfileExporter extends ProfileExporter { | |||
} | |||
rule.setProperties(properties); | |||
ruleset.addRule(rule); | |||
processXPathRule(activeRule.getRuleKey(), rule); | |||
} | |||
} | |||
return ruleset; | |||
} | |||
protected String buildXmlFromModuleTree(PmdRuleset tree) { | |||
protected void processXPathRule(String sonarRuleKey, PmdRule rule) { | |||
if (PmdConstants.XPATH_CLASS.equals(rule.getRef())) { | |||
rule.setRef(null); | |||
rule.setMessage(rule.getProperty(PmdConstants.XPATH_MESSAGE_PARAM).getValue()); | |||
rule.removeProperty(PmdConstants.XPATH_MESSAGE_PARAM); | |||
PmdProperty xpathExp = rule.getProperty(PmdConstants.XPATH_EXPRESSION_PARAM); | |||
xpathExp.setValue("" + xpathExp.getValue() + ""); | |||
rule.setClazz(PmdConstants.XPATH_CLASS); | |||
rule.setName(sonarRuleKey); | |||
} | |||
} | |||
protected String exportPmdRulesetToXml(PmdRuleset tree) { | |||
XStream xstream = new XStream(); | |||
xstream.setClassLoader(getClass().getClassLoader()); | |||
xstream.processAnnotations(PmdRuleset.class); |
@@ -43,4 +43,8 @@ public class PmdProperty { | |||
public String getValue() { | |||
return value; | |||
} | |||
public void setValue(String value) { | |||
this.value = value; | |||
} | |||
} |
@@ -19,13 +19,13 @@ | |||
*/ | |||
package org.sonar.plugins.pmd.xml; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import com.thoughtworks.xstream.annotations.XStreamAsAttribute; | |||
import com.thoughtworks.xstream.annotations.XStreamOmitField; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@XStreamAlias("rule") | |||
public class PmdRule implements Comparable<String> { | |||
@@ -34,20 +34,26 @@ public class PmdRule implements Comparable<String> { | |||
private String priority; | |||
@XStreamAsAttribute | |||
private String name; | |||
@XStreamAsAttribute | |||
private String message; | |||
private List<PmdProperty> properties = new ArrayList<PmdProperty>(); | |||
@XStreamOmitField | |||
private String description; //NOSONAR unused private field | |||
private String description; // NOSONAR unused private field | |||
@XStreamOmitField | |||
private String exclude;//NOSONAR unused private field | |||
private String exclude;// NOSONAR unused private field | |||
@XStreamOmitField | |||
private String example;//NOSONAR unused private field | |||
private String example;// NOSONAR unused private field | |||
@XStreamOmitField | |||
@XStreamAsAttribute | |||
@XStreamAlias(value = "class") | |||
private String clazz;//NOSONAR unused private field | |||
private String clazz;// NOSONAR unused private field | |||
public PmdRule(String ref) { | |||
this(ref, null); | |||
@@ -70,6 +76,15 @@ public class PmdRule implements Comparable<String> { | |||
return properties; | |||
} | |||
public PmdProperty getProperty(String propertyName) { | |||
for (PmdProperty prop : properties) { | |||
if (propertyName.equals(prop.getName())) { | |||
return prop; | |||
} | |||
} | |||
return null; | |||
} | |||
public int compareTo(String o) { | |||
return o.compareTo(ref); | |||
} | |||
@@ -89,4 +104,36 @@ public class PmdRule implements Comparable<String> { | |||
properties.add(property); | |||
} | |||
public void setName(String name) { | |||
this.name = name; | |||
} | |||
public void setMessage(String message) { | |||
this.message = message; | |||
} | |||
public String getMessage() { | |||
return message; | |||
} | |||
public String getClazz() { | |||
return clazz; | |||
} | |||
public void setRef(String ref) { | |||
this.ref = ref; | |||
} | |||
public void removeProperty(String propertyName) { | |||
PmdProperty prop = getProperty(propertyName); | |||
properties.remove(prop); | |||
} | |||
public void setClazz(String clazz) { | |||
this.clazz = clazz; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
} |
@@ -2133,4 +2133,38 @@ public abstract class ShouldBeAbstract | |||
</param> | |||
</rule> | |||
<rule key="XPathRule" priority="MAJOR"> | |||
<name><![CDATA[XPath rule template]]></name> | |||
<configKey><![CDATA[net.sourceforge.pmd.rules.XPathRule]]></configKey> | |||
<category name="Maintainability"/> | |||
<cardinality>MULTIPLE</cardinality> | |||
<description> | |||
<![CDATA[PMD provides a very handy method for creating new rules by writing an XPath query. When the XPath query finds a match, a violation is created. | |||
Let's take a simple example : assume we have a Factory class that must be always declared final. | |||
We'd like to report a violation each time a declaration of Factory is not declared final. Consider the following class: : | |||
<pre> | |||
public class a { | |||
Factory f1; | |||
void myMethod() { | |||
Factory f2; | |||
int a; | |||
} | |||
} | |||
</pre> | |||
The following expression does the magic we need: | |||
<pre> | |||
//VariableDeclarator | |||
[../Type/ReferenceType/ClassOrInterfaceType | |||
[@Image = 'Factory'] and ..[@Final='false']] | |||
</pre> | |||
See the <a href="http://pmd.sourceforge.net/xpathruletutorial.html">XPath rule tutorial</a> for more information.]]> | |||
</description> | |||
<param key="xpath" type="s"> | |||
<description><![CDATA[XPath expressions.]]></description> | |||
</param> | |||
<param key="message" type="s"> | |||
<description><![CDATA[Message to display when a violation occurs.]]></description> | |||
</param> | |||
</rule> | |||
</rules> |
@@ -1,21 +1,28 @@ | |||
package org.sonar.plugins.pmd; | |||
import static org.hamcrest.Matchers.is; | |||
import static org.hamcrest.Matchers.nullValue; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.Reader; | |||
import java.io.StringReader; | |||
import java.io.StringWriter; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.List; | |||
import org.apache.commons.io.IOUtils; | |||
import org.junit.Test; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.platform.ServerFileSystem; | |||
import org.sonar.api.profiles.RulesProfile; | |||
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.rules.RulePriority; | |||
import org.sonar.api.rules.RuleFinder; | |||
import org.sonar.api.rules.RuleQuery; | |||
import org.sonar.api.utils.ValidationMessages; | |||
import org.sonar.plugins.pmd.xml.PmdProperty; | |||
import org.sonar.plugins.pmd.xml.PmdRule; | |||
import org.sonar.test.TestUtils; | |||
import org.xml.sax.SAXException; | |||
@@ -25,57 +32,80 @@ public class PmdProfileExporterTest { | |||
@Test | |||
public void testExportProfile() throws IOException, SAXException { | |||
List<ActiveRule> activeRulesExpected = buildActiveRulesFixture(buildRulesFixture()); | |||
RulesProfile activeProfile = new RulesProfile(); | |||
activeProfile.setActiveRules(activeRulesExpected); | |||
activeProfile.setName("A test profile"); | |||
StringWriter xmlOutput = new StringWriter(); | |||
exporter.exportProfile(activeProfile, xmlOutput); | |||
assertXmlAreSimilar(xmlOutput.toString(), "test_xml_complete.xml"); | |||
} | |||
ServerFileSystem fileSystem = mock(ServerFileSystem.class); | |||
PmdRuleRepository repository = new PmdRuleRepository(fileSystem); | |||
List<Rule> rules = repository.createRules(); | |||
private List<org.sonar.api.rules.Rule> buildRulesFixture() { | |||
final Rule rule1 = new Rule("Coupling Between Objects", "CouplingBetweenObjects", | |||
"rulesets/coupling.xml/CouplingBetweenObjects", null, CoreProperties.PMD_PLUGIN, null); | |||
RuleParam ruleParam1 = new RuleParam(rule1, "threshold", null, "i"); | |||
rule1.setParams(Arrays.asList(ruleParam1)); | |||
RuleFinder ruleFinder = new PmdRuleFinder(rules); | |||
PmdProfileImporter importer = new PmdProfileImporter(ruleFinder); | |||
Reader reader = new StringReader(TestUtils.getResourceContent("/org/sonar/plugins/pmd/simple.xml")); | |||
RulesProfile rulesProfile = importer.importProfile(reader, ValidationMessages.create()); | |||
final Rule rule2 = new Rule("Excessive Imports", "ExcessiveImports", | |||
"rulesets/coupling.xml/ExcessiveImports", null, CoreProperties.PMD_PLUGIN, null); | |||
RuleParam ruleParam2 = new RuleParam(rule2, "max", null, "i"); | |||
rule2.setParams(Arrays.asList(ruleParam2)); | |||
final Rule rule3 = new Rule("Use Notify All Instead Of Notify", "UseNotifyAllInsteadOfNotify", | |||
"rulesets/design.xml/UseNotifyAllInsteadOfNotify", null, CoreProperties.PMD_PLUGIN, null); | |||
StringWriter xmlOutput = new StringWriter(); | |||
exporter.exportProfile(rulesProfile, xmlOutput); | |||
assertEquals(TestUtils.getResourceContent("/org/sonar/plugins/pmd/export_simple.xml"), xmlOutput.toString()); | |||
} | |||
final org.sonar.api.rules.Rule rule4 = new org.sonar.api.rules.Rule("Class names should always begin with an upper case character.", | |||
"ClassNamingConventions", | |||
"rulesets/naming.xml/ClassNamingConventions", null, CoreProperties.PMD_PLUGIN, null); | |||
@Test | |||
public void testExportXPathRule() { | |||
StringWriter xmlOutput = new StringWriter(); | |||
RulesProfile profile = RulesProfile.create(); | |||
Rule xpathTemplate = Rule.create(PmdConstants.REPOSITORY_KEY, "MyOwnRule", "This is my own xpath rule.") | |||
.setConfigKey(PmdConstants.XPATH_CLASS).setPluginName(PmdConstants.REPOSITORY_KEY); | |||
xpathTemplate.createParameter(PmdConstants.XPATH_EXPRESSION_PARAM); | |||
xpathTemplate.createParameter(PmdConstants.XPATH_MESSAGE_PARAM); | |||
ActiveRule xpath = profile.activateRule(xpathTemplate, null); | |||
xpath.setParameter(PmdConstants.XPATH_EXPRESSION_PARAM, "//FieldDeclaration"); | |||
xpath.setParameter(PmdConstants.XPATH_MESSAGE_PARAM, "This is bad"); | |||
exporter.exportProfile(profile, xmlOutput); | |||
System.out.println(xmlOutput.toString()); | |||
assertEquals(TestUtils.getResourceContent("/org/sonar/plugins/pmd/export_xpath_rules.xml"), xmlOutput.toString()); | |||
} | |||
return Arrays.asList(rule1, rule2, rule3, rule4); | |||
@Test | |||
public void testProcessingXPathRule() { | |||
String message = "This is bad"; | |||
String xpathExpression = "xpathExpression"; | |||
PmdRule rule = new PmdRule(PmdConstants.XPATH_CLASS); | |||
rule.addProperty(new PmdProperty(PmdConstants.XPATH_EXPRESSION_PARAM, xpathExpression)); | |||
rule.addProperty(new PmdProperty(PmdConstants.XPATH_MESSAGE_PARAM, message)); | |||
rule.setName("MyOwnRule"); | |||
exporter.processXPathRule("xpathKey", rule); | |||
assertThat(rule.getMessage(), is(message)); | |||
assertThat(rule.getRef(), is(nullValue())); | |||
assertThat(rule.getClazz(), is(PmdConstants.XPATH_CLASS)); | |||
assertThat(rule.getProperty(PmdConstants.XPATH_MESSAGE_PARAM), is(nullValue())); | |||
assertThat(rule.getName(), is("xpathKey")); | |||
assertThat(rule.getProperty(PmdConstants.XPATH_EXPRESSION_PARAM).getValue(), is(xpathExpression)); | |||
} | |||
private List<ActiveRule> buildActiveRulesFixture(List<org.sonar.api.rules.Rule> rules) { | |||
List<ActiveRule> activeRules = new ArrayList<ActiveRule>(); | |||
private static class PmdRuleFinder implements RuleFinder { | |||
ActiveRule activeRule1 = new ActiveRule(null, rules.get(0), RulePriority.CRITICAL); | |||
activeRule1.setActiveRuleParams(Arrays.asList(new ActiveRuleParam(activeRule1, rules.get(0).getParams().get(0), "20"))); | |||
activeRules.add(activeRule1); | |||
private List<Rule> rules; | |||
ActiveRule activeRule2 = new ActiveRule(null, rules.get(1), RulePriority.MAJOR); | |||
activeRule2.setActiveRuleParams(Arrays.asList(new ActiveRuleParam(activeRule2, rules.get(1).getParams().get(0), "30"))); | |||
activeRules.add(activeRule2); | |||
public PmdRuleFinder(List<Rule> rules) { | |||
this.rules = rules; | |||
} | |||
ActiveRule activeRule3 = new ActiveRule(null, rules.get(2), RulePriority.MINOR); | |||
activeRules.add(activeRule3); | |||
public Rule findByKey(String repositoryKey, String key) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
return activeRules; | |||
} | |||
public Collection<Rule> findAll(RuleQuery query) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
private void assertXmlAreSimilar(String xml, String xmlFileToFind) throws IOException, SAXException { | |||
InputStream input = getClass().getResourceAsStream("/org/sonar/plugins/pmd/" + xmlFileToFind); | |||
String xmlToFind = IOUtils.toString(input); | |||
TestUtils.assertSimilarXml(xmlToFind, xml); | |||
public Rule find(RuleQuery query) { | |||
for (Rule rule : rules) { | |||
if (query.getConfigKey().equals(rule.getConfigKey())) { | |||
rule.setPluginName(PmdConstants.REPOSITORY_KEY); | |||
return rule; | |||
} | |||
} | |||
return null; | |||
} | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
<ruleset> | |||
<rule ref="rulesets/coupling.xml/CouplingBetweenObjects"> | |||
<priority>2</priority> | |||
<properties> | |||
<property name="threshold" value="20"/> | |||
</properties> | |||
</rule> | |||
<rule ref="rulesets/coupling.xml/ExcessiveImports"> | |||
<priority>3</priority> | |||
</rule> | |||
<rule ref="rulesets/design.xml/UseNotifyAllInsteadOfNotify"> | |||
<priority>4</priority> | |||
</rule> | |||
</ruleset> |
@@ -0,0 +1,8 @@ | |||
<ruleset> | |||
<rule name="MyOwnRule" message="This is bad" class="net.sourceforge.pmd.rules.XPathRule"> | |||
<priority>3</priority> | |||
<properties> | |||
<property name="xpath" value="//FieldDeclaration"/> | |||
</properties> | |||
</rule> | |||
</ruleset> |