diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 9e6789a0db..d7a2394228 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -14,6 +14,10 @@ This is a {{ site.pmd.release_type }} release. ### 🚀 New and noteworthy +#### ✨ New Rules +* The new Java rule {%rule java/bestpractices/UseEnumCollections %} reports usages for `HashSet` and `HashMap` + when the keys are of an enum type. The specialized enum collections are more space- and time-efficient. + ### 🐛 Fixed Issues * core * [#4992](https://github.com/pmd/pmd/pull/4992): \[core] CPD: Include processing errors in XML report @@ -26,6 +30,7 @@ This is a {{ site.pmd.release_type }} release. * java * [#5050](https://github.com/pmd/pmd/issues/5050): \[java] Problems with pattern variables in switch branches * java-bestpractices + * [#577](https://github.com/pmd/pmd/issues/577): \[java] New Rule: Check that Map is an EnumMap if K is an enum value * [#5047](https://github.com/pmd/pmd/issues/5047): \[java] UnusedPrivateMethod FP for Generics & Overloads * plsql * [#1934](https://github.com/pmd/pmd/issues/1934): \[plsql] ParseException with MERGE statement in anonymous block diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseEnumCollectionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseEnumCollectionsRule.java new file mode 100644 index 0000000000..a90b971544 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseEnumCollectionsRule.java @@ -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 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; + } +} diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml index 8db08d0343..a8a3169254 100644 --- a/pmd-java/src/main/resources/category/java/bestpractices.xml +++ b/pmd-java/src/main/resources/category/java/bestpractices.xml @@ -1723,6 +1723,40 @@ public class Foo { + + + + Wherever possible, use `EnumSet` or `EnumMap` instead of `HashSet` and `HashMap` when the keys + are of an enum type. The specialized enum collections are more space- and time-efficient. + This rule reports constructor expressions for hash sets or maps whose key + type is an enum type. + + 3 + + newSet() { + return new HashSet<>(); // Could be EnumSet.noneOf(Example.class) + } + + public static Map newMap() { + return new HashMap<>(); // Could be new EnumMap<>(Example.class) + } + } + ]]> + + + + diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseEnumCollectionsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseEnumCollectionsTest.java new file mode 100644 index 0000000000..5dcb333fc4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseEnumCollectionsTest.java @@ -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 +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseEnumCollections.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseEnumCollections.xml new file mode 100644 index 0000000000..c98b1d7124 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseEnumCollections.xml @@ -0,0 +1,68 @@ + + + + + Use enumset + 1 + 8 + + This collection could be an EnumSet + + set = new HashSet<>(); + return set.contains(E.A); + } + } + ]]> + + + + + + Use enummap + 1 + 7 + + This collection could be an EnumMap + + bar() { + return new HashMap<>(); + } + } + ]]> + + + + Neg, LinkedHashSet or LinkedHashMap + 0 + bar() { + Set set = new LinkedHashSet<>(); + return new LinkedHashMap<>(); + } + } + ]]> + + +