Merge pull request #3177 from oowekyala:java-update-LooseCoupling
[java] Update rule LooseCoupling #3177
This commit is contained in:
commit
43feee9416
6 files changed
+229
-25
No files matched your search
@@ -36,7 +36,7 @@
|
||||
<rule ref="category/java/bestpractices.xml/JUnitTestsShouldIncludeAssert"/>
|
||||
<rule ref="category/java/bestpractices.xml/JUnitUseExpected"/>
|
||||
<rule ref="category/java/bestpractices.xml/LiteralsFirstInComparisons"/>
|
||||
<!-- <rule ref="category/java/bestpractices.xml/LooseCoupling"/> -->
|
||||
<rule ref="category/java/bestpractices.xml/LooseCoupling"/>
|
||||
<rule ref="category/java/bestpractices.xml/MethodReturnsInternalArray"/>
|
||||
<rule ref="category/java/bestpractices.xml/MissingOverride"/>
|
||||
<!-- <rule ref="category/java/bestpractices.xml/OneDeclarationPerLine"/> -->
|
||||
|
||||
@@ -83,6 +83,7 @@ The default version is always ES6.
|
||||
not necessary are allowed, if they separate expressions of different precedence.
|
||||
The other property `ignoreBalancing` (default: true) is similar, in that it allows parentheses that help
|
||||
reading and understanding the expressions.
|
||||
* The rule {% rule "java/bestpractices/LooseCoupling" %} has a new property to allow some types to be coupled to (`allowedTypes`).
|
||||
|
||||
#### Removed Rules
|
||||
|
||||
@@ -133,10 +134,12 @@ The following previously deprecated rules have been finally removed:
|
||||
* [#1998](https://github.com/pmd/pmd/issues/1998): \[java] AccessorClassGeneration false-negative: subclass calls private constructor
|
||||
* [#2130](https://github.com/pmd/pmd/issues/2130): \[java] UnusedLocalVariable: false-negative with array
|
||||
* [#2147](https://github.com/pmd/pmd/issues/2147): \[java] JUnitTestsShouldIncludeAssert - false positives with lambdas and static methods
|
||||
* [#2464](https://github.com/pmd/pmd/issues/2464): \[java] LooseCoupling must ignore class literals: ArrayList.class
|
||||
* [#2542](https://github.com/pmd/pmd/issues/2542): \[java] UseCollectionIsEmpty can not detect the case `foo.bar().size()`
|
||||
* [#2796](https://github.com/pmd/pmd/issue/2796): \[java] UnusedAssignment false positive with call chains
|
||||
* [#2797](https://github.com/pmd/pmd/issues/2797): \[java] MissingOverride long-standing issues
|
||||
* [#2806](https://github.com/pmd/pmd/issues/2806): \[java] SwitchStmtsShouldHaveDefault false-positive with Java 14 switch non-fallthrough branches
|
||||
* [#2822](https://github.com/pmd/pmd/issues/2822): \[java] LooseCoupling rule: Extend to cover user defined implementations and interfaces
|
||||
* [#2883](https://github.com/pmd/pmd/issues/2883): \[java] JUnitAssertionsShouldIncludeMessage false positive with method call
|
||||
* [#2890](https://github.com/pmd/pmd/issues/2890): \[java] UnusedPrivateMethod false positive with generics
|
||||
* java-codestyle
|
||||
|
||||
+68
-20
@@ -5,38 +5,86 @@
|
||||
package net.sourceforge.pmd.lang.java.rule.bestpractices;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.ast.NodeStream;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTArrayAllocation;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTArrayType;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTBlock;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTClassLiteral;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTExtendsList;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTResultType;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTSuperExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTThisExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTTypeExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaNode;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
|
||||
public class LooseCouplingRule extends AbstractJavaRule {
|
||||
public class LooseCouplingRule extends AbstractJavaRulechainRule {
|
||||
|
||||
private static final PropertyDescriptor<List<String>> ALLOWED_TYPES =
|
||||
PropertyFactory.stringListProperty("allowedTypes")
|
||||
.desc("Exceptions to the rule")
|
||||
.defaultValues("java.util.Properties")
|
||||
.build();
|
||||
|
||||
public LooseCouplingRule() {
|
||||
super(ASTClassOrInterfaceType.class);
|
||||
definePropertyDescriptor(ALLOWED_TYPES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTClassOrInterfaceType node, Object data) {
|
||||
if (methodHasOverride(node)) {
|
||||
return data;
|
||||
if (isConcreteCollectionType(node)
|
||||
&& !isInOverriddenMethodSignature(node)
|
||||
&& !isInAllowedSyntacticCtx(node)
|
||||
&& !isAllowedType(node)) {
|
||||
addViolation(data, node, node.getSimpleName());
|
||||
}
|
||||
Node parent = node.getNthParent(3);
|
||||
boolean isType = (TypeTestUtil.isA(Collection.class, node) || TypeTestUtil.isA(Map.class, node))
|
||||
&& !(node.getType() != null && node.getType().isInterface());
|
||||
|
||||
if (isType && (parent instanceof ASTFieldDeclaration || parent instanceof ASTFormalParameter
|
||||
|| parent instanceof ASTResultType)) {
|
||||
addViolation(data, node, node.getImage());
|
||||
}
|
||||
return data;
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean methodHasOverride(JavaNode node) {
|
||||
ASTMethodDeclaration method = node.ancestors(ASTMethodDeclaration.class).first();
|
||||
return method != null && method.isAnnotationPresent(Override.class);
|
||||
private boolean isInAllowedSyntacticCtx(ASTClassOrInterfaceType node) {
|
||||
JavaNode parent = node.getParent();
|
||||
return parent instanceof ASTConstructorCall // new ArrayList<>()
|
||||
|| parent instanceof ASTTypeExpression // instanceof, method reference
|
||||
|| parent instanceof ASTCastExpression // if we allow instanceof, we should allow cast
|
||||
|| parent instanceof ASTClassLiteral // ArrayList.class
|
||||
|| parent instanceof ASTClassOrInterfaceType // AbstractMap.SimpleEntry
|
||||
|| parent instanceof ASTExtendsList // extends AbstractMap<...>
|
||||
|| parent instanceof ASTThisExpression // Enclosing.this
|
||||
|| parent instanceof ASTSuperExpression // Enclosing.super
|
||||
|| parent instanceof ASTArrayType && parent.getParent() instanceof ASTArrayAllocation;
|
||||
}
|
||||
|
||||
private boolean isAllowedType(ASTClassOrInterfaceType node) {
|
||||
for (String allowed : getProperty(ALLOWED_TYPES)) {
|
||||
if (TypeTestUtil.isA(allowed, node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private boolean isConcreteCollectionType(ASTClassOrInterfaceType node) {
|
||||
return (TypeTestUtil.isA(Collection.class, node) || TypeTestUtil.isA(Map.class, node))
|
||||
&& !node.getTypeMirror().isInterface();
|
||||
}
|
||||
|
||||
private static boolean isInOverriddenMethodSignature(JavaNode node) {
|
||||
JavaNode ancestor = node.ancestors().map(NodeStream.asInstanceOf(ASTMethodDeclaration.class, ASTBlock.class)).first();
|
||||
if (ancestor instanceof ASTMethodDeclaration) {
|
||||
// then it's in a signature and not the body
|
||||
return ((ASTMethodDeclaration) ancestor).isOverridden();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -939,9 +939,12 @@ class Foo {
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.LooseCouplingRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_bestpractices.html#loosecoupling">
|
||||
<description>
|
||||
The use of implementation types (i.e., HashSet) as object references limits your ability to use alternate
|
||||
implementations in the future as requirements change. Whenever available, referencing objects
|
||||
by their interface types (i.e, Set) provides much more flexibility.
|
||||
Excessive coupling to implementation types (e.g., `HashSet`) limits your ability to use alternate
|
||||
implementations in the future as requirements change. Whenever available, declare variables
|
||||
and parameters using a more general type (e.g, `Set`).
|
||||
|
||||
This rule reports uses of concrete collection types. User-defined types that should be treated
|
||||
the same as interfaces can be configured with the property `allowedTypes`.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
|
||||
-1
@@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices;
|
||||
|
||||
import net.sourceforge.pmd.testframework.PmdRuleTst;
|
||||
|
||||
@org.junit.Ignore("Rule has not been updated yet")
|
||||
public class LooseCouplingTest extends PmdRuleTst {
|
||||
// no additional unit tests
|
||||
}
|
||||
+151
@@ -135,6 +135,157 @@ import java.util.LinkedHashMap;
|
||||
public class Test {
|
||||
@Override
|
||||
public LinkedHashMap findGetters() {}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>FP with method reference</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.HashMap;
|
||||
|
||||
public class Test {
|
||||
private static final ThreadLocal TREE_CACHE =
|
||||
ThreadLocal.withInitial(HashMap::new);
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>FP with instanceof and cast</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.HashMap;
|
||||
|
||||
public class Test {
|
||||
boolean m(Map m) {
|
||||
if (m instanceof HashMap) {
|
||||
return ((HashMap) m).isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>FP with static method call</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.HashMap;
|
||||
|
||||
public class MyMap implements Map {
|
||||
static MyMap create() { return null; }
|
||||
}
|
||||
|
||||
class Foo {
|
||||
final Map map =
|
||||
MyMap.create();
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>FP with array creation</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.HashMap;
|
||||
|
||||
public class MyMap implements Map {
|
||||
static MyMap create() { return null; }
|
||||
}
|
||||
|
||||
class Foo {
|
||||
final Map[] map = new MyMap[5]; // ok
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>FP with j.util.Properties</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.Properties;
|
||||
|
||||
class Foo {
|
||||
final Properties map = new Properties();
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>Exception list, no exception</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<code><![CDATA[
|
||||
package mine;
|
||||
import java.util.List;
|
||||
|
||||
class O implements List {
|
||||
final O map = new O();
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>Exception list</description>
|
||||
<rule-property name="allowedTypes">mine.O</rule-property>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
package mine;
|
||||
import java.util.List;
|
||||
|
||||
class O implements List {
|
||||
final O map = new O();
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>#2464 class literals</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.ArrayList;
|
||||
|
||||
class Foo {
|
||||
final Object o = ArrayList.class;
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>Inner class selection</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.AbstractMap;
|
||||
|
||||
class Foo {
|
||||
final Object o =
|
||||
new AbstractMap.SimpleEntry<>("", "");
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>Extends clause</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.AbstractMap;
|
||||
|
||||
class Foo extends AbstractMap<String, String> {
|
||||
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>This/super qualifier</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import java.util.ArrayList;
|
||||
|
||||
class Foo extends ArrayList {
|
||||
|
||||
{
|
||||
Foo.super.clear();
|
||||
}
|
||||
|
||||
class Inner {
|
||||
{
|
||||
Foo.this.clear();
|
||||
Foo.super.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
Reference in new issue
Block a user