Merge pull request #3189 from oowekyala:update-LinguisticNaming

[java] Update rule LinguisticNaming #3189
This commit is contained in:
Andreas Dangel committed 2021-04-10 16:06:27 +02:00
commit be94effc77
6 files changed
+83 -45

No files matched your search

+1 -1
View File
@@ -85,7 +85,7 @@
<rule ref="category/java/codestyle.xml/FormalParameterNamingConventions"/>
<rule ref="category/java/codestyle.xml/GenericsNaming"/>
<rule ref="category/java/codestyle.xml/IdenticalCatchBranches"/>
<!-- <rule ref="category/java/codestyle.xml/LinguisticNaming"/> -->
<rule ref="category/java/codestyle.xml/LinguisticNaming"/>
<rule ref="category/java/codestyle.xml/LocalHomeNamingConvention"/>
<rule ref="category/java/codestyle.xml/LocalInterfaceSessionNamingConvention"/>
<rule ref="category/java/codestyle.xml/LocalVariableCouldBeFinal"/>
@@ -70,6 +70,12 @@ public final class PrettyPrintingUtil {
}
}
public static String prettyPrintType(ASTType t) {
StringBuilder sb = new StringBuilder();
prettyPrintTypeNode(t, sb);
return sb.toString();
}
/**
* Returns a normalized method name. This just looks at the image of the types of the parameters.
*/
@@ -1,15 +1,14 @@
/**
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.codestyle;
import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.containsCamelCaseWord;
import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.startsWithCamelCaseWord;
import static net.sourceforge.pmd.properties.PropertyFactory.booleanProperty;
import static net.sourceforge.pmd.properties.PropertyFactory.stringListProperty;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -20,11 +19,18 @@ import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
import net.sourceforge.pmd.lang.java.rule.AbstractIgnoredAnnotationRule;
import net.sourceforge.pmd.lang.java.ast.Annotatable;
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
import net.sourceforge.pmd.lang.java.rule.internal.JavaPropertyUtil;
import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind;
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
import net.sourceforge.pmd.properties.PropertyDescriptor;
public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
public class LinguisticNamingRule extends AbstractJavaRulechainRule {
private static final PropertyDescriptor<List<String>> IGNORED_ANNOTS =
JavaPropertyUtil.ignoredAnnotationsDescriptor("java.lang.Override");
private static final PropertyDescriptor<Boolean> CHECK_BOOLEAN_METHODS =
booleanProperty("checkBooleanMethod").defaultValue(true).desc("Check method names and types for inconsistent naming.").build();
private static final PropertyDescriptor<Boolean> CHECK_GETTERS =
@@ -57,6 +63,8 @@ public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
.defaultValues("is", "has", "can", "have", "will", "should").build();
public LinguisticNamingRule() {
super(ASTMethodDeclaration.class, ASTFieldDeclaration.class, ASTLocalVariableDeclaration.class);
definePropertyDescriptor(IGNORED_ANNOTS);
definePropertyDescriptor(CHECK_BOOLEAN_METHODS);
definePropertyDescriptor(CHECK_GETTERS);
definePropertyDescriptor(CHECK_SETTERS);
@@ -67,14 +75,6 @@ public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
definePropertyDescriptor(CHECK_FIELDS);
definePropertyDescriptor(CHECK_VARIABLES);
definePropertyDescriptor(BOOLEAN_FIELD_PREFIXES_PROPERTY);
addRuleChainVisit(ASTMethodDeclaration.class);
addRuleChainVisit(ASTFieldDeclaration.class);
addRuleChainVisit(ASTLocalVariableDeclaration.class);
}
@Override
protected Collection<String> defaultSuppressionAnnotations() {
return Collections.checkedList(Arrays.asList("java.lang.Override"), String.class);
}
@Override
@@ -105,6 +105,10 @@ public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
return data;
}
private boolean hasIgnoredAnnotation(Annotatable node) {
return node.isAnyAnnotationPresent(getProperty(IGNORED_ANNOTS));
}
private void checkPrefixedTransformMethods(ASTMethodDeclaration node, Object data, String nameOfMethod) {
List<String> prefixes = getProperty(TRANSFORM_METHOD_NAMES_PROPERTY);
String[] splitMethodName = StringUtils.splitByCharacterTypeCamelCase(nameOfMethod);
@@ -117,9 +121,8 @@ public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
}
private void checkTransformMethods(ASTMethodDeclaration node, Object data, String nameOfMethod) {
List<String> infixes = getProperty(TRANSFORM_METHOD_NAMES_PROPERTY);
for (String infix : infixes) {
if (node.isVoid() && containsWord(nameOfMethod, StringUtils.capitalize(infix))) {
for (String infix : getProperty(TRANSFORM_METHOD_NAMES_PROPERTY)) {
if (node.isVoid() && containsCamelCaseWord(nameOfMethod, StringUtils.capitalize(infix))) {
// "To" or any other configured infix in the middle somewhere
addViolationWithMessage(data, node, "Linguistics Antipattern - The transform method ''{0}'' should not return void linguistically",
new Object[] { nameOfMethod });
@@ -130,21 +133,21 @@ public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
}
private void checkGetters(ASTMethodDeclaration node, Object data, String nameOfMethod) {
if (hasPrefix(nameOfMethod, "get") && node.isVoid()) {
if (startsWithCamelCaseWord(nameOfMethod, "get") && node.isVoid()) {
addViolationWithMessage(data, node, "Linguistics Antipattern - The getter ''{0}'' should not return void linguistically",
new Object[] { nameOfMethod });
}
}
private void checkSetters(ASTMethodDeclaration node, Object data, String nameOfMethod) {
if (hasPrefix(nameOfMethod, "set") && !node.isVoid()) {
if (startsWithCamelCaseWord(nameOfMethod, "set") && !node.isVoid()) {
addViolationWithMessage(data, node, "Linguistics Antipattern - The setter ''{0}'' should not return any type except void linguistically",
new Object[] { nameOfMethod });
}
}
private boolean isBooleanType(ASTType node) {
return "boolean".equalsIgnoreCase(node.getTypeImage())
return node.getTypeMirror().unbox().isPrimitive(PrimitiveTypeKind.BOOLEAN)
|| TypeTestUtil.isA("java.util.concurrent.atomic.AtomicBoolean", node)
|| TypeTestUtil.isA("java.util.function.Predicate", node);
}
@@ -153,9 +156,9 @@ public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
ASTType t = node.getResultTypeNode();
if (!t.isVoid()) {
for (String prefix : getProperty(BOOLEAN_METHOD_PREFIXES_PROPERTY)) {
if (hasPrefix(nameOfMethod, prefix) && !isBooleanType(t)) {
if (startsWithCamelCaseWord(nameOfMethod, prefix) && !isBooleanType(t)) {
addViolationWithMessage(data, node, "Linguistics Antipattern - The method ''{0}'' indicates linguistically it returns a boolean, but it returns ''{1}''",
new Object[] { nameOfMethod, t.getTypeImage() });
new Object[] {nameOfMethod, PrettyPrintingUtil.prettyPrintType(t) });
}
}
}
@@ -163,18 +166,18 @@ public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
private void checkField(ASTType typeNode, ASTVariableDeclarator node, Object data) {
for (String prefix : getProperty(BOOLEAN_FIELD_PREFIXES_PROPERTY)) {
if (hasPrefix(node.getName(), prefix) && !isBooleanType(typeNode)) {
if (startsWithCamelCaseWord(node.getName(), prefix) && !isBooleanType(typeNode)) {
addViolationWithMessage(data, node, "Linguistics Antipattern - The field ''{0}'' indicates linguistically it is a boolean, but it is ''{1}''",
new Object[] { node.getName(), typeNode.getTypeImage() });
new Object[] { node.getName(), PrettyPrintingUtil.prettyPrintType(typeNode) });
}
}
}
private void checkVariable(ASTType typeNode, ASTVariableDeclarator node, Object data) {
for (String prefix : getProperty(BOOLEAN_FIELD_PREFIXES_PROPERTY)) {
if (hasPrefix(node.getName(), prefix) && !isBooleanType(typeNode)) {
if (startsWithCamelCaseWord(node.getName(), prefix) && !isBooleanType(typeNode)) {
addViolationWithMessage(data, node, "Linguistics Antipattern - The variable ''{0}'' indicates linguistically it is a boolean, but it is ''{1}''",
new Object[] { node.getName(), typeNode.getTypeImage() });
new Object[] { node.getName(), PrettyPrintingUtil.prettyPrintType(typeNode) });
}
}
}
@@ -183,8 +186,7 @@ public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
public Object visit(ASTFieldDeclaration node, Object data) {
ASTType type = node.getTypeNode();
if (type != null && getProperty(CHECK_FIELDS)) {
List<ASTVariableDeclarator> fields = node.findChildrenOfType(ASTVariableDeclarator.class);
for (ASTVariableDeclarator field : fields) {
for (ASTVariableDeclarator field : node.children(ASTVariableDeclarator.class)) {
checkField(type, field, data);
}
}
@@ -195,24 +197,11 @@ public class LinguisticNamingRule extends AbstractIgnoredAnnotationRule {
public Object visit(ASTLocalVariableDeclaration node, Object data) {
ASTType type = node.getTypeNode();
if (type != null && getProperty(CHECK_VARIABLES)) {
List<ASTVariableDeclarator> variables = node.findChildrenOfType(ASTVariableDeclarator.class);
for (ASTVariableDeclarator variable : variables) {
for (ASTVariableDeclarator variable : node.children(ASTVariableDeclarator.class)) {
checkVariable(type, variable, data);
}
}
return data;
}
private static boolean hasPrefix(String name, String prefix) {
return name.startsWith(prefix) && name.length() > prefix.length()
&& Character.isUpperCase(name.charAt(prefix.length()));
}
private static boolean containsWord(String name, String word) {
int index = name.indexOf(word);
if (index >= 0 && name.length() > index + word.length()) {
return Character.isUpperCase(name.charAt(index + word.length()));
}
return false;
}
}
@@ -262,12 +262,42 @@ public final class JavaRuleUtil {
* @param camelCaseString A string
* @param prefixWord A prefix
*/
static boolean startsWithCamelCaseWord(String camelCaseString, String prefixWord) {
public static boolean startsWithCamelCaseWord(String camelCaseString, String prefixWord) {
return camelCaseString.startsWith(prefixWord)
&& camelCaseString.length() > prefixWord.length()
&& Character.isUpperCase(camelCaseString.charAt(prefixWord.length()));
}
/**
* Returns true if the string has the given word as a word, not at the start.
* There needs to be a camelcase word boundary after the prefix.
*
* <code>
* containsCamelCaseWord("isABoolean", "Bool") == false
* containsCamelCaseWord("isABoolean", "A") == true
* containsCamelCaseWord("isABoolean", "is") == error (not capitalized)
* </code>
*
* @param camelCaseString A string
* @param capitalizedWord A word, non-empty, capitalized
*
* @throws AssertionError If the word is empty or not capitalized
*/
public static boolean containsCamelCaseWord(String camelCaseString, String capitalizedWord) {
assert capitalizedWord.length() > 0 && Character.isUpperCase(capitalizedWord.charAt(0))
: "Not a capitalized string \"" + capitalizedWord + "\"";
int index = camelCaseString.indexOf(capitalizedWord);
if (index >= 0 && camelCaseString.length() > index + capitalizedWord.length()) {
return Character.isUpperCase(camelCaseString.charAt(index + capitalizedWord.length()));
}
if (index >= 0 && camelCaseString.length() == index + capitalizedWord.length()) {
return true;
}
return false;
}
public static boolean isGetterOrSetterCall(ASTMethodCall call) {
return call.getArguments().size() == 0
&& (startsWithCamelCaseWord(call.getMethodName(), "get")
@@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.codestyle;
import net.sourceforge.pmd.testframework.PmdRuleTst;
@org.junit.Ignore("Rule has not been updated yet")
public class LinguisticNamingTest extends PmdRuleTst {
// no additional unit tests
}
@@ -4,6 +4,7 @@
package net.sourceforge.pmd.lang.java.rule.internal;
import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.containsCamelCaseWord;
import static net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil.startsWithCamelCaseWord;
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -31,6 +32,19 @@ public class JavaRuleUtilTest extends BaseNonParserTest {
assertThrows(NullPointerException.class, () -> startsWithCamelCaseWord("fnei", null));
}
@Test
public void testContainsCamelCaseWords() {
assertFalse(containsCamelCaseWord("isABoolean", "Bool"), "no word boundary");
assertTrue(containsCamelCaseWord("isABoolean", "A"), "ok word in the middle");
assertTrue(containsCamelCaseWord("isABoolean", "Boolean"), "ok word at the end");
assertThrows(NullPointerException.class, () -> containsCamelCaseWord(null, "A"));
assertThrows(NullPointerException.class, () -> containsCamelCaseWord("fnei", null));
assertThrows(AssertionError.class, () -> containsCamelCaseWord("fnei", ""), "empty string");
assertThrows(AssertionError.class, () -> containsCamelCaseWord("fnei", "a"), "not capitalized");
}
@Test
public void testFlattenConcatOperands() {
ASTExpression e = parseExpr("s1+s2+s3");