forked from phoedos/pmd
Merge branch 'pr-1121'
This commit is contained in:
@ -75,4 +75,11 @@ public abstract class PropertyDescriptorBuilder<E, T extends PropertyDescriptorB
|
||||
public abstract PropertyDescriptor<E> build();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of the property to be built.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import org.w3c.dom.Element;
|
||||
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.RulePriority;
|
||||
import net.sourceforge.pmd.RuleSetReference;
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
@ -20,7 +21,7 @@ import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
|
||||
/**
|
||||
* Builds a rule, validating its parameters throughout. The builder can define property descriptors, but not override
|
||||
* them. For that, use {@link RuleFactory#decorateRule(Rule, Element)}.
|
||||
* them. For that, use {@link RuleFactory#decorateRule(Rule, RuleSetReference, Element)}.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.0.0
|
||||
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.rule.codestyle;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaNode;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.RegexProperty;
|
||||
import net.sourceforge.pmd.properties.RegexProperty.RegexPBuilder;
|
||||
import net.sourceforge.pmd.util.StringUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for naming conventions rule. Not public API, but
|
||||
* used to uniformize eg property names between our rules.
|
||||
*
|
||||
* <p>Protected methods may leak API because concrete classes
|
||||
* are not final so they're package private instead
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.5.0
|
||||
*/
|
||||
abstract class AbstractNamingConventionRule<T extends JavaNode> extends AbstractJavaRule {
|
||||
|
||||
|
||||
/** The argument is interpreted as the display name, and is converted to camel case to get the property name. */
|
||||
RegexPBuilder defaultProp(String name) {
|
||||
return defaultProp(StringUtil.toCamelCase(name, true), name);
|
||||
}
|
||||
|
||||
/** Returns a pre-filled builder with the given name and display name (for the description). */
|
||||
RegexPBuilder defaultProp(String name, String displayName) {
|
||||
return RegexProperty.named(name + "Pattern")
|
||||
.desc("Regex which applies to " + displayName.trim() + " names")
|
||||
.defaultValue(defaultConvention());
|
||||
}
|
||||
|
||||
/** Default regex string for this kind of entities. */
|
||||
abstract String defaultConvention();
|
||||
|
||||
|
||||
/** Generic "kind" of node, eg "static method" or "utility class". */
|
||||
abstract String kindDisplayName(T node, PropertyDescriptor<Pattern> descriptor);
|
||||
|
||||
/** Extracts the name that should be pattern matched. */
|
||||
String nameExtractor(T node) {
|
||||
return node.getImage();
|
||||
}
|
||||
|
||||
|
||||
void checkMatches(T node, PropertyDescriptor<Pattern> regex, Object data) {
|
||||
String name = nameExtractor(node);
|
||||
if (!getProperty(regex).matcher(name).matches()) {
|
||||
addViolation(data, node, new Object[]{
|
||||
kindDisplayName(node, regex),
|
||||
name,
|
||||
getProperty(regex).toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -16,33 +16,30 @@ import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTInitializer;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.AccessNode;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.RegexProperty;
|
||||
import net.sourceforge.pmd.properties.RegexProperty.RegexPBuilder;
|
||||
import net.sourceforge.pmd.util.StringUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Configurable naming conventions for type declarations.
|
||||
*/
|
||||
public class ClassNamingConventionsRule extends AbstractJavaRule {
|
||||
public class ClassNamingConventionsRule extends AbstractNamingConventionRule<ASTAnyTypeDeclaration> {
|
||||
|
||||
private static final RegexProperty CLASS_REGEX = defaultProp("class").desc("Regex which applies to concrete class names").build();
|
||||
private static final RegexProperty ABSTRACT_CLASS_REGEX = defaultProp("abstract class").build();
|
||||
private static final RegexProperty INTERFACE_REGEX = defaultProp("interface").build();
|
||||
private static final RegexProperty ENUMERATION_REGEX = defaultProp("enum").build();
|
||||
private static final RegexProperty ANNOTATION_REGEX = defaultProp("annotation").build();
|
||||
private static final RegexProperty UTILITY_CLASS_REGEX = defaultProp("utility class").defaultValue("[A-Z][a-zA-Z0-9]+(Utils?|Helper)").build();
|
||||
private final RegexProperty classRegex = defaultProp("class", "concrete class").build();
|
||||
private final RegexProperty abstractClassRegex = defaultProp("abstract class").build();
|
||||
private final RegexProperty interfaceRegex = defaultProp("interface").build();
|
||||
private final RegexProperty enumerationRegex = defaultProp("enum").build();
|
||||
private final RegexProperty annotationRegex = defaultProp("annotation").build();
|
||||
private final RegexProperty utilityClassRegex = defaultProp("utility class").defaultValue("[A-Z][a-zA-Z0-9]+(Utils?|Helper)").build();
|
||||
|
||||
|
||||
public ClassNamingConventionsRule() {
|
||||
definePropertyDescriptor(CLASS_REGEX);
|
||||
definePropertyDescriptor(ABSTRACT_CLASS_REGEX);
|
||||
definePropertyDescriptor(INTERFACE_REGEX);
|
||||
definePropertyDescriptor(ENUMERATION_REGEX);
|
||||
definePropertyDescriptor(ANNOTATION_REGEX);
|
||||
definePropertyDescriptor(UTILITY_CLASS_REGEX);
|
||||
definePropertyDescriptor(classRegex);
|
||||
definePropertyDescriptor(abstractClassRegex);
|
||||
definePropertyDescriptor(interfaceRegex);
|
||||
definePropertyDescriptor(enumerationRegex);
|
||||
definePropertyDescriptor(annotationRegex);
|
||||
definePropertyDescriptor(utilityClassRegex);
|
||||
|
||||
addRuleChainVisit(ASTClassOrInterfaceDeclaration.class);
|
||||
addRuleChainVisit(ASTEnumDeclaration.class);
|
||||
@ -50,16 +47,6 @@ public class ClassNamingConventionsRule extends AbstractJavaRule {
|
||||
}
|
||||
|
||||
|
||||
private void checkMatches(ASTAnyTypeDeclaration node, PropertyDescriptor<Pattern> regex, Object data) {
|
||||
if (!getProperty(regex).matcher(node.getImage()).matches()) {
|
||||
addViolation(data, node, new Object[]{
|
||||
isUtilityClass(node) ? "utility class" : node.getTypeKind().getPrintableName(),
|
||||
node.getImage(),
|
||||
getProperty(regex).toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This could probably be moved to ClassOrInterfaceDeclaration
|
||||
// to share the implementation and be used from XPath
|
||||
private boolean isUtilityClass(ASTAnyTypeDeclaration node) {
|
||||
@ -126,13 +113,13 @@ public class ClassNamingConventionsRule extends AbstractJavaRule {
|
||||
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
|
||||
|
||||
if (node.isAbstract()) {
|
||||
checkMatches(node, ABSTRACT_CLASS_REGEX, data);
|
||||
checkMatches(node, abstractClassRegex, data);
|
||||
} else if (isUtilityClass(node)) {
|
||||
checkMatches(node, UTILITY_CLASS_REGEX, data);
|
||||
checkMatches(node, utilityClassRegex, data);
|
||||
} else if (node.isInterface()) {
|
||||
checkMatches(node, INTERFACE_REGEX, data);
|
||||
checkMatches(node, interfaceRegex, data);
|
||||
} else {
|
||||
checkMatches(node, CLASS_REGEX, data);
|
||||
checkMatches(node, classRegex, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -141,22 +128,26 @@ public class ClassNamingConventionsRule extends AbstractJavaRule {
|
||||
|
||||
@Override
|
||||
public Object visit(ASTEnumDeclaration node, Object data) {
|
||||
checkMatches(node, ENUMERATION_REGEX, data);
|
||||
checkMatches(node, enumerationRegex, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTAnnotationTypeDeclaration node, Object data) {
|
||||
checkMatches(node, ANNOTATION_REGEX, data);
|
||||
checkMatches(node, annotationRegex, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
private static RegexPBuilder defaultProp(String name) {
|
||||
return RegexProperty.named(StringUtil.toCamelCase(name) + "Pattern")
|
||||
.desc("Regex which applies to " + name.trim() + " names")
|
||||
.defaultValue("[A-Z][a-zA-Z0-9]+");
|
||||
@Override
|
||||
String defaultConvention() {
|
||||
return "[A-Z][a-zA-Z0-9]+";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
String kindDisplayName(ASTAnyTypeDeclaration node, PropertyDescriptor<Pattern> descriptor) {
|
||||
return isUtilityClass(node) ? "utility class" : node.getTypeKind().getPrintableName();
|
||||
}
|
||||
}
|
||||
|
@ -15,16 +15,14 @@ import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
|
||||
import net.sourceforge.pmd.properties.BooleanProperty;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.RegexProperty;
|
||||
import net.sourceforge.pmd.properties.RegexProperty.RegexPBuilder;
|
||||
import net.sourceforge.pmd.util.StringUtil;
|
||||
|
||||
|
||||
public class MethodNamingConventionsRule extends AbstractJavaRule {
|
||||
public class MethodNamingConventionsRule extends AbstractNamingConventionRule<ASTMethodDeclaration> {
|
||||
|
||||
private static final Map<String, String> DESCRIPTOR_TO_DISPLAY_NAME = new HashMap<>();
|
||||
|
||||
@ -32,34 +30,24 @@ public class MethodNamingConventionsRule extends AbstractJavaRule {
|
||||
private static final BooleanProperty CHECK_NATIVE_METHODS_DESCRIPTOR = new BooleanProperty("checkNativeMethods",
|
||||
"deprecated! Check native methods", true, 1.0f);
|
||||
|
||||
private static final RegexProperty INSTANCE_REGEX = defaultProp("method").desc("Regex which applies to instance method names").build();
|
||||
private static final RegexProperty STATIC_REGEX = defaultProp("static").build();
|
||||
private static final RegexProperty NATIVE_REGEX = defaultProp("native").build();
|
||||
private static final RegexProperty JUNIT3_REGEX = defaultProp("JUnit 3 test").defaultValue("test[A-Z0-9][a-zA-Z0-9]*").build();
|
||||
private static final RegexProperty JUNIT4_REGEX = defaultProp("JUnit 4 test").build();
|
||||
|
||||
private final RegexProperty instanceRegex = defaultProp("", "instance").build();
|
||||
private final RegexProperty staticRegex = defaultProp("static").build();
|
||||
private final RegexProperty nativeRegex = defaultProp("native").build();
|
||||
private final RegexProperty junit3Regex = defaultProp("JUnit 3 test").defaultValue("test[A-Z0-9][a-zA-Z0-9]*").build();
|
||||
private final RegexProperty junit4Regex = defaultProp("JUnit 4 test").build();
|
||||
|
||||
|
||||
public MethodNamingConventionsRule() {
|
||||
definePropertyDescriptor(CHECK_NATIVE_METHODS_DESCRIPTOR);
|
||||
|
||||
definePropertyDescriptor(INSTANCE_REGEX);
|
||||
definePropertyDescriptor(STATIC_REGEX);
|
||||
definePropertyDescriptor(NATIVE_REGEX);
|
||||
definePropertyDescriptor(JUNIT3_REGEX);
|
||||
definePropertyDescriptor(JUNIT4_REGEX);
|
||||
definePropertyDescriptor(instanceRegex);
|
||||
definePropertyDescriptor(staticRegex);
|
||||
definePropertyDescriptor(nativeRegex);
|
||||
definePropertyDescriptor(junit3Regex);
|
||||
definePropertyDescriptor(junit4Regex);
|
||||
}
|
||||
|
||||
private void checkMatches(ASTMethodDeclaration node, PropertyDescriptor<Pattern> regex, Object data) {
|
||||
if (!getProperty(regex).matcher(node.getMethodName()).matches()) {
|
||||
addViolation(data, node.getMethodDeclarator(), new Object[]{
|
||||
DESCRIPTOR_TO_DISPLAY_NAME.get(regex.name()) + " method",
|
||||
node.getMethodName(),
|
||||
getProperty(regex).toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isJunit4Test(ASTMethodDeclaration node) {
|
||||
return node.isAnnotationPresent("org.junit.Test");
|
||||
}
|
||||
@ -92,31 +80,48 @@ public class MethodNamingConventionsRule extends AbstractJavaRule {
|
||||
|
||||
if (node.isNative()) {
|
||||
if (getProperty(CHECK_NATIVE_METHODS_DESCRIPTOR)) {
|
||||
checkMatches(node, NATIVE_REGEX, data);
|
||||
checkMatches(node, nativeRegex, data);
|
||||
} else {
|
||||
return super.visit(node, data);
|
||||
}
|
||||
} else if (node.isStatic()) {
|
||||
checkMatches(node, STATIC_REGEX, data);
|
||||
checkMatches(node, staticRegex, data);
|
||||
} else if (isJunit4Test(node)) {
|
||||
checkMatches(node, JUNIT4_REGEX, data);
|
||||
checkMatches(node, junit4Regex, data);
|
||||
} else if (isJunit3Test(node)) {
|
||||
checkMatches(node, JUNIT3_REGEX, data);
|
||||
checkMatches(node, junit3Regex, data);
|
||||
} else {
|
||||
checkMatches(node, INSTANCE_REGEX, data);
|
||||
checkMatches(node, instanceRegex, data);
|
||||
}
|
||||
|
||||
return super.visit(node, data);
|
||||
}
|
||||
|
||||
|
||||
private static RegexPBuilder defaultProp(String displayName) {
|
||||
String propName = StringUtil.toCamelCase(displayName, true) + "Pattern";
|
||||
DESCRIPTOR_TO_DISPLAY_NAME.put(propName, displayName);
|
||||
@Override
|
||||
String defaultConvention() {
|
||||
return "[a-z][a-zA-Z0-9]+";
|
||||
}
|
||||
|
||||
return RegexProperty.named(propName)
|
||||
.desc("Regex which applies to " + displayName.trim() + " method names")
|
||||
.defaultValue("[a-z][a-zA-Z0-9]+");
|
||||
|
||||
@Override
|
||||
String nameExtractor(ASTMethodDeclaration node) {
|
||||
return node.getMethodName();
|
||||
}
|
||||
|
||||
@Override
|
||||
RegexPBuilder defaultProp(String name, String displayName) {
|
||||
String display = (displayName + " method").trim();
|
||||
RegexPBuilder prop = super.defaultProp(name.isEmpty() ? "method" : name, display);
|
||||
|
||||
DESCRIPTOR_TO_DISPLAY_NAME.put(prop.getName(), display);
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
String kindDisplayName(ASTMethodDeclaration node, PropertyDescriptor<Pattern> descriptor) {
|
||||
return DESCRIPTOR_TO_DISPLAY_NAME.get(descriptor.name());
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,9 @@ public class Foo {
|
||||
<test-code>
|
||||
<description>method names should not contain underscores</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-messages>
|
||||
<message>The instance method name 'bar_foo' doesn't match '[a-z][a-zA-Z0-9]+'</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
public class Foo {
|
||||
void bar_foo() {}
|
||||
@ -45,6 +48,9 @@ public class Foo {
|
||||
<rule-property name="checkNativeMethods">true</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>2</expected-linenumbers>
|
||||
<expected-messages>
|
||||
<message>The native method name '__surfunc__' doesn't match '[a-z][a-zA-Z0-9]+'</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
public class Foo {
|
||||
protected final native void __surfunc__(float[] data);
|
||||
@ -68,6 +74,9 @@ public class MethodNamingConventions implements SomeInterface {
|
||||
<rule-property name="staticPattern">st_[a-z][A-Za-z]*</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>2</expected-linenumbers>
|
||||
<expected-messages>
|
||||
<message>The static method name 'foo' doesn't match 'st_[a-z][A-Za-z]*'</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
class Foo {
|
||||
static void foo() {
|
||||
@ -92,6 +101,9 @@ public class MethodNamingConventions implements SomeInterface {
|
||||
<rule-property name="nativePattern">nt_[a-z][A-Za-z]*</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>2</expected-linenumbers>
|
||||
<expected-messages>
|
||||
<message>The native method name 'foo' doesn't match 'nt_[a-z][A-Za-z]*'</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
class Foo {
|
||||
native void foo() {
|
||||
@ -116,6 +128,9 @@ public class MethodNamingConventions implements SomeInterface {
|
||||
<rule-property name="junit3TestPattern">test_[a-z][A-Za-z]*</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>9</expected-linenumbers>
|
||||
<expected-messages>
|
||||
<message>The JUnit 3 test method name 'testGetBestTeam' doesn't match 'test_[a-z][A-Za-z]*'</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.TestCase;
|
||||
@ -140,6 +155,10 @@ public class MethodNamingConventions implements SomeInterface {
|
||||
<rule-property name="junit4TestPattern">[a-z][A-Za-z]*Test</rule-property>
|
||||
<expected-problems>2</expected-problems>
|
||||
<expected-linenumbers>12,16</expected-linenumbers>
|
||||
<expected-messages>
|
||||
<message>The JUnit 4 test method name 'testGetBestTeam' doesn't match '[a-z][A-Za-z]*Test'</message>
|
||||
<message>The JUnit 4 test method name 'getBestTeam' doesn't match '[a-z][A-Za-z]*Test'</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.TestCase;
|
||||
@ -168,4 +187,25 @@ public class MethodNamingConventions implements SomeInterface {
|
||||
</code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>Instance method custom convention</description>
|
||||
<rule-property name="methodPattern">m_[a-z][A-Za-z]*</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-linenumbers>3</expected-linenumbers>
|
||||
<expected-messages>
|
||||
<message>The instance method name 'fooBar' doesn't match 'm_[a-z][A-Za-z]*'</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
public class Foo {
|
||||
|
||||
public void fooBar() {
|
||||
}
|
||||
|
||||
public void m_fooBar() {
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</code>
|
||||
</test-code>
|
||||
|
||||
</test-data>
|
||||
|
Reference in New Issue
Block a user