[java] Add new rule UseEnumCollections

This commit is contained in:
Clément Fournier
2024-05-23 19:28:04 +02:00
parent f30b535f61
commit 05e7724185
4 changed files with 162 additions and 0 deletions

View File

@ -0,0 +1,50 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.bestpractices;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
import net.sourceforge.pmd.lang.java.types.JClassType;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
/**
* Detect cases where EnumSet and EnumMap can be used.
*
* @author Clément Fournier
*/
public class UseEnumCollectionsRule extends AbstractJavaRulechainRule {
public UseEnumCollectionsRule() {
super(ASTConstructorCall.class);
}
@Override
public Object visit(ASTConstructorCall call, Object data) {
JTypeMirror builtType = call.getTypeMirror();
if (!builtType.isRaw()) {
boolean isMap = TypeTestUtil.isExactlyA(HashMap.class, builtType);
if (isMap || TypeTestUtil.isExactlyA(HashSet.class, builtType)) {
List<JTypeMirror> typeArgs = ((JClassType) builtType).getTypeArgs();
JTypeDeclSymbol keySymbol = typeArgs.get(0).getSymbol();
if (keySymbol instanceof JClassSymbol && ((JClassSymbol) keySymbol).isEnum()) {
String enumCollectionReplacement = isMap ? "EnumMap" : "EnumSet";
asCtx(data).addViolation(call.getTypeNode(), enumCollectionReplacement);
}
}
}
return null;
}
}

View File

@ -1723,6 +1723,39 @@ public class Foo {
</example>
</rule>
<rule name="UseEnumCollections"
language="java"
since="7.3.0"
message="This collection could be an {0}"
class="net.sourceforge.pmd.lang.java.rule.bestpractices.UseEnumCollectionsRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_bestpractices.html#useenumcollections">
<description>
Wherever possible, use `EnumSet` or `EnumMap` instead of `HashSet` and `HashMap` when the keys
are of an enum type. This rule reports constructor expressions for hash sets or maps whose key
type is an enum type.
</description>
<priority>3</priority>
<example>
<![CDATA[
import java.util.EnumMap;
import java.util.HashSet;
enum Example {
A, B, C;
public static Set<Example> newSet() {
return new HashSet<>(); // Could be EnumSet.noneOf(Example.class)
}
public static <V> Map<Example, V> newMap() {
return new HashMap<>(); // Could be new EnumMap<>(Example.class)
}
}
]]>
</example>
</rule>
<rule name="UseStandardCharsets"
language="java"
since="6.34.0"

View File

@ -0,0 +1,11 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.bestpractices;
import net.sourceforge.pmd.test.PmdRuleTst;
class UseEnumCollectionsTest extends PmdRuleTst {
// no additional unit tests
}

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data
xmlns="http://pmd.sourceforge.net/rule-tests"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd">
<test-code>
<description>Use enumset</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>8</expected-linenumbers>
<expected-messages>
<message>This collection could be an EnumSet</message>
</expected-messages>
<code><![CDATA[
import java.util.*;
public class Foo {
enum E {A, B, C}
public static boolean bar() {
Set<E> set = new HashSet<>();
return set.contains(E.A);
}
}
]]></code>
</test-code>
<test-code>
<description>Use enummap</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>7</expected-linenumbers>
<expected-messages>
<message>This collection could be an EnumMap</message>
</expected-messages>
<code><![CDATA[
import java.util.*;
public class Foo {
public enum E {A, B, C}
public static Map<E, Boolean> bar() {
return new HashMap<>();
}
}
]]></code>
</test-code>
<test-code>
<description>Neg, LinkedHashSet or LinkedHashMap</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
import java.util.*;
public class Foo {
public enum E {A, B, C}
public static Map<E, Boolean> bar() {
Set<E> set = new LinkedHashSet<>();
return new LinkedHashMap<>();
}
}
]]></code>
</test-code>
</test-data>